parent
da60543c72
commit
a080bf1c6c
@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Core.Authentication
|
||||
{
|
||||
public interface IProtectionService
|
||||
{
|
||||
string Protect(string plainText);
|
||||
string UnProtect(string plainText);
|
||||
}
|
||||
|
||||
public class ProtectionService : IProtectionService
|
||||
{
|
||||
private readonly IConfigFileProvider _configService;
|
||||
|
||||
public ProtectionService(IConfigFileProvider configService)
|
||||
{
|
||||
_configService = configService;
|
||||
}
|
||||
|
||||
public string Protect(string text)
|
||||
{
|
||||
var key = Encoding.UTF8.GetBytes(_configService.ApiKey);
|
||||
|
||||
using (var aesAlg = Aes.Create())
|
||||
{
|
||||
using (var encryptor = aesAlg.CreateEncryptor(key, aesAlg.IV))
|
||||
{
|
||||
using (var msEncrypt = new MemoryStream())
|
||||
{
|
||||
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
|
||||
using (var swEncrypt = new StreamWriter(csEncrypt))
|
||||
{
|
||||
swEncrypt.Write(text);
|
||||
}
|
||||
|
||||
var iv = aesAlg.IV;
|
||||
|
||||
var decryptedContent = msEncrypt.ToArray();
|
||||
|
||||
var result = new byte[iv.Length + decryptedContent.Length];
|
||||
|
||||
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
|
||||
Buffer.BlockCopy(decryptedContent, 0, result, iv.Length, decryptedContent.Length);
|
||||
|
||||
return Convert.ToBase64String(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string UnProtect(string value)
|
||||
{
|
||||
if (value.IsNullOrWhiteSpace())
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
value = value.Replace(" ", "+");
|
||||
var fullCipher = Convert.FromBase64String(value);
|
||||
|
||||
var iv = new byte[16];
|
||||
var cipher = new byte[fullCipher.Length - iv.Length];
|
||||
|
||||
Buffer.BlockCopy(fullCipher, 0, iv, 0, iv.Length);
|
||||
Buffer.BlockCopy(fullCipher, iv.Length, cipher, 0, fullCipher.Length - iv.Length);
|
||||
var key = Encoding.UTF8.GetBytes(_configService.ApiKey);
|
||||
|
||||
using (var aesAlg = Aes.Create())
|
||||
{
|
||||
using (var decryptor = aesAlg.CreateDecryptor(key, iv))
|
||||
{
|
||||
string result;
|
||||
using (var msDecrypt = new MemoryStream(cipher))
|
||||
{
|
||||
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
|
||||
{
|
||||
using (var srDecrypt = new StreamReader(csDecrypt))
|
||||
{
|
||||
result = srDecrypt.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public interface IDownloadMappingService
|
||||
{
|
||||
Uri ConvertToProxyLink(Uri link, string serverUrl, int indexerId, string file = "t");
|
||||
string ConvertToNormalLink(string link);
|
||||
}
|
||||
|
||||
public class DownloadMappingService : IDownloadMappingService
|
||||
{
|
||||
private readonly IProtectionService _protectionService;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
|
||||
public DownloadMappingService(IProtectionService protectionService, IConfigFileProvider configFileProvider)
|
||||
{
|
||||
_protectionService = protectionService;
|
||||
_configFileProvider = configFileProvider;
|
||||
}
|
||||
|
||||
public Uri ConvertToProxyLink(Uri link, string serverUrl, int indexerId, string file = "t")
|
||||
{
|
||||
var urlBase = _configFileProvider.UrlBase;
|
||||
|
||||
if (urlBase.IsNotNullOrWhiteSpace() && !urlBase.StartsWith("/"))
|
||||
{
|
||||
urlBase = "/" + urlBase;
|
||||
}
|
||||
|
||||
var encryptedLink = _protectionService.Protect(link.ToString());
|
||||
var encodedLink = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(encryptedLink));
|
||||
var urlEncodedFile = WebUtility.UrlEncode(file);
|
||||
var proxyLink = $"{serverUrl}{urlBase}/api/v1/indexer/{indexerId}/download?apikey={_configFileProvider.ApiKey}&link={encodedLink}&file={urlEncodedFile}";
|
||||
return new Uri(proxyLink);
|
||||
}
|
||||
|
||||
public string ConvertToNormalLink(string link)
|
||||
{
|
||||
var encodedLink = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(link));
|
||||
var decryptedLink = _protectionService.UnProtect(encodedLink);
|
||||
|
||||
return decryptedLink;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.TPL;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public interface IDownloadService
|
||||
{
|
||||
byte[] DownloadReport(string link, int indexerId);
|
||||
}
|
||||
|
||||
public class DownloadService : IDownloadService
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
private readonly IIndexerStatusService _indexerStatusService;
|
||||
private readonly IRateLimitService _rateLimitService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DownloadService(IIndexerFactory indexerFactory,
|
||||
IIndexerStatusService indexerStatusService,
|
||||
IRateLimitService rateLimitService,
|
||||
IEventAggregator eventAggregator,
|
||||
Logger logger)
|
||||
{
|
||||
_indexerFactory = indexerFactory;
|
||||
_indexerStatusService = indexerStatusService;
|
||||
_rateLimitService = rateLimitService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public byte[] DownloadReport(string link, int indexerId)
|
||||
{
|
||||
var url = new HttpUri(link);
|
||||
|
||||
// Limit grabs to 2 per second.
|
||||
if (link.IsNotNullOrWhiteSpace() && !link.StartsWith("magnet:"))
|
||||
{
|
||||
_rateLimitService.WaitAndPulse(url.Host, TimeSpan.FromSeconds(2));
|
||||
}
|
||||
|
||||
var indexer = _indexerFactory.GetInstance(_indexerFactory.Get(indexerId));
|
||||
bool success;
|
||||
var downloadedBytes = Array.Empty<byte>();
|
||||
|
||||
try
|
||||
{
|
||||
downloadedBytes = indexer.Download(url);
|
||||
_indexerStatusService.RecordSuccess(indexerId);
|
||||
success = true;
|
||||
}
|
||||
catch (ReleaseUnavailableException)
|
||||
{
|
||||
_logger.Trace("Release {0} no longer available on indexer.", link);
|
||||
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, false));
|
||||
throw;
|
||||
}
|
||||
catch (ReleaseDownloadException ex)
|
||||
{
|
||||
var http429 = ex.InnerException as TooManyRequestsException;
|
||||
if (http429 != null)
|
||||
{
|
||||
_indexerStatusService.RecordFailure(indexerId, http429.RetryAfter);
|
||||
}
|
||||
else
|
||||
{
|
||||
_indexerStatusService.RecordFailure(indexerId);
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, false));
|
||||
throw;
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success));
|
||||
return downloadedBytes;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NzbDrone.Common.Messaging;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Events
|
||||
{
|
||||
public class IndexerDownloadEvent : IEvent
|
||||
{
|
||||
public int IndexerId { get; set; }
|
||||
public bool Successful { get; set; }
|
||||
|
||||
public IndexerDownloadEvent(int indexerId, bool successful)
|
||||
{
|
||||
IndexerId = indexerId;
|
||||
Successful = successful;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
namespace NzbDrone.Core.Indexers.Events
|
||||
{
|
||||
public class IndexerQueryEvent : IEvent
|
||||
{
|
@ -0,0 +1,29 @@
|
||||
using System.IO;
|
||||
using Nancy;
|
||||
|
||||
namespace NzbDrone.Http.Extensions
|
||||
{
|
||||
public static class ResponseExtensions
|
||||
{
|
||||
public static Response FromByteArray(this IResponseFormatter formatter, byte[] body, string contentType = null)
|
||||
{
|
||||
return new ByteArrayResponse(body, contentType);
|
||||
}
|
||||
}
|
||||
|
||||
public class ByteArrayResponse : Response
|
||||
{
|
||||
public ByteArrayResponse(byte[] body, string contentType = null)
|
||||
{
|
||||
this.ContentType = contentType ?? "application/octet-stream";
|
||||
|
||||
this.Contents = stream =>
|
||||
{
|
||||
using (var writer = new BinaryWriter(stream))
|
||||
{
|
||||
writer.Write(body);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue