mirror of https://github.com/Jackett/Jackett
Remove Jackett Owin web server
Dead code since upgrade to Jackett.Server
This commit is contained in:
parent
795c896abe
commit
81b40df6ee
|
@ -2,8 +2,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27004.2008
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jackett", "Jackett\Jackett.csproj", "{E636D5F8-68B4-4903-B4ED-CCFD9C9E899F}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BE7B0C8A-6144-47CD-821E-B09BA1B7BADE}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
..\appveyor.yml = ..\appveyor.yml
|
||||
|
@ -43,10 +41,6 @@ Global
|
|||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E636D5F8-68B4-4903-B4ED-CCFD9C9E899F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E636D5F8-68B4-4903-B4ED-CCFD9C9E899F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E636D5F8-68B4-4903-B4ED-CCFD9C9E899F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E636D5F8-68B4-4903-B4ED-CCFD9C9E899F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BF611F7B-4658-4CB8-AA9E-0736FADAA3BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BF611F7B-4658-4CB8-AA9E-0736FADAA3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BF611F7B-4658-4CB8-AA9E-0736FADAA3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
@ -80,7 +74,6 @@ Global
|
|||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{E636D5F8-68B4-4903-B4ED-CCFD9C9E899F} = {FF8B9A1B-AE7E-4F14-9C37-DA861D034738}
|
||||
{BF611F7B-4658-4CB8-AA9E-0736FADAA3BA} = {FF8B9A1B-AE7E-4F14-9C37-DA861D034738}
|
||||
{FF9025B1-EC14-4AA9-8081-9F69C5E35B63} = {FF8B9A1B-AE7E-4F14-9C37-DA861D034738}
|
||||
{A61E311A-6F8B-4497-B5E4-2EA8994C7BD8} = {FF8B9A1B-AE7E-4F14-9C37-DA861D034738}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
|
||||
</startup>
|
||||
|
||||
</configuration>
|
|
@ -1,21 +0,0 @@
|
|||
using System.Web.Http.Filters;
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
public class JackettAPINoCacheAttribute : System.Web.Http.Filters.ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
|
||||
{
|
||||
if(null!= actionExecutedContext &&
|
||||
null!= actionExecutedContext.Response &&
|
||||
null!= actionExecutedContext.Response.Headers)
|
||||
actionExecutedContext.Response.Headers.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue()
|
||||
{
|
||||
NoStore = true,
|
||||
Private = true
|
||||
};
|
||||
|
||||
base.OnActionExecuted(actionExecutedContext);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using Jackett.Common.Models.Config;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using Jackett.Common.Utils;
|
||||
|
||||
namespace Jackett.Controllers
|
||||
{
|
||||
[AllowAnonymous]
|
||||
[JackettAPINoCache]
|
||||
public class BlackholeController : ApiController
|
||||
{
|
||||
private Logger logger;
|
||||
private IIndexerManagerService indexerService;
|
||||
private readonly ServerConfig serverConfig;
|
||||
IProtectionService protectionService;
|
||||
|
||||
public BlackholeController(IIndexerManagerService i, Logger l, ServerConfig config, IProtectionService ps)
|
||||
{
|
||||
logger = l;
|
||||
indexerService = i;
|
||||
serverConfig = config;
|
||||
|
||||
protectionService = ps;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IHttpActionResult> Blackhole(string indexerID, string path, string jackett_apikey, string file)
|
||||
{
|
||||
|
||||
var jsonReply = new JObject();
|
||||
try
|
||||
{
|
||||
var indexer = indexerService.GetWebIndexer(indexerID);
|
||||
if (!indexer.IsConfigured)
|
||||
{
|
||||
logger.Warn(string.Format("Rejected a request to {0} which is unconfigured.", indexer.DisplayName));
|
||||
throw new Exception("This indexer is not configured.");
|
||||
}
|
||||
|
||||
if (serverConfig.APIKey != jackett_apikey)
|
||||
throw new Exception("Incorrect API key");
|
||||
|
||||
path = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(path));
|
||||
path = protectionService.UnProtect(path);
|
||||
var remoteFile = new Uri(path, UriKind.RelativeOrAbsolute);
|
||||
var fileExtension = ".torrent";
|
||||
var downloadBytes = await indexer.Download(remoteFile);
|
||||
|
||||
// handle magnet URLs
|
||||
if (downloadBytes.Length >= 7
|
||||
&& downloadBytes[0] == 0x6d // m
|
||||
&& downloadBytes[1] == 0x61 // a
|
||||
&& downloadBytes[2] == 0x67 // g
|
||||
&& downloadBytes[3] == 0x6e // n
|
||||
&& downloadBytes[4] == 0x65 // e
|
||||
&& downloadBytes[5] == 0x74 // t
|
||||
&& downloadBytes[6] == 0x3a // :
|
||||
)
|
||||
{
|
||||
fileExtension = ".magnet";
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(serverConfig.BlackholeDir))
|
||||
{
|
||||
throw new Exception("Blackhole directory not set!");
|
||||
}
|
||||
|
||||
if (!Directory.Exists(serverConfig.BlackholeDir))
|
||||
{
|
||||
throw new Exception("Blackhole directory does not exist: " + serverConfig.BlackholeDir);
|
||||
}
|
||||
|
||||
var fileName = DateTime.Now.Ticks.ToString() + "-" + StringUtil.MakeValidFileName(indexer.DisplayName, '_', false);
|
||||
if (string.IsNullOrWhiteSpace(file))
|
||||
fileName += fileExtension;
|
||||
else
|
||||
fileName += "-"+StringUtil.MakeValidFileName(file + fileExtension, '_', false); // call MakeValidFileName() again to avoid any possibility of path traversal attacks
|
||||
|
||||
File.WriteAllBytes(Path.Combine(serverConfig.BlackholeDir, fileName), downloadBytes);
|
||||
jsonReply["result"] = "success";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex, "Error downloading to blackhole " + indexerID + " " + path);
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
}
|
||||
|
||||
return Json(jsonReply);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using BencodeNET.Parsing;
|
||||
using Jackett.Common.Models.Config;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using Jackett.Common.Utils;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Controllers
|
||||
{
|
||||
[AllowAnonymous]
|
||||
[JackettAPINoCache]
|
||||
public class DownloadController : ApiController
|
||||
{
|
||||
private ServerConfig config;
|
||||
private Logger logger;
|
||||
private IIndexerManagerService indexerService;
|
||||
private IProtectionService protectionService;
|
||||
|
||||
public DownloadController(IIndexerManagerService i, Logger l, IProtectionService ps, ServerConfig serverConfig)
|
||||
{
|
||||
config = serverConfig;
|
||||
logger = l;
|
||||
indexerService = i;
|
||||
protectionService = ps;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<HttpResponseMessage> Download(string indexerID, string path, string jackett_apikey, string file)
|
||||
{
|
||||
try
|
||||
{
|
||||
var indexer = indexerService.GetWebIndexer(indexerID);
|
||||
|
||||
if (!indexer.IsConfigured)
|
||||
{
|
||||
logger.Warn(string.Format("Rejected a request to {0} which is unconfigured.", indexer.DisplayName));
|
||||
return Request.CreateResponse(HttpStatusCode.Forbidden, "This indexer is not configured.");
|
||||
}
|
||||
|
||||
path = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(path));
|
||||
path = protectionService.UnProtect(path);
|
||||
|
||||
if (config.APIKey != jackett_apikey)
|
||||
return new HttpResponseMessage(HttpStatusCode.Unauthorized);
|
||||
|
||||
var target = new Uri(path, UriKind.RelativeOrAbsolute);
|
||||
var downloadBytes = await indexer.Download(target);
|
||||
|
||||
// handle magnet URLs
|
||||
if (downloadBytes.Length >= 7
|
||||
&& downloadBytes[0] == 0x6d // m
|
||||
&& downloadBytes[1] == 0x61 // a
|
||||
&& downloadBytes[2] == 0x67 // g
|
||||
&& downloadBytes[3] == 0x6e // n
|
||||
&& downloadBytes[4] == 0x65 // e
|
||||
&& downloadBytes[5] == 0x74 // t
|
||||
&& downloadBytes[6] == 0x3a // :
|
||||
)
|
||||
{
|
||||
var magneturi = Encoding.UTF8.GetString(downloadBytes);
|
||||
var response = Request.CreateResponse(HttpStatusCode.Moved);
|
||||
response.Headers.Location = new Uri(magneturi);
|
||||
return response;
|
||||
}
|
||||
|
||||
// This will fix torrents where the keys are not sorted, and thereby not supported by Sonarr.
|
||||
byte[] sortedDownloadBytes = null;
|
||||
try
|
||||
{
|
||||
var parser = new BencodeParser();
|
||||
var torrentDictionary = parser.Parse(downloadBytes);
|
||||
sortedDownloadBytes = torrentDictionary.EncodeAsBytes();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var content = indexer.Encoding.GetString(downloadBytes);
|
||||
logger.Error(content);
|
||||
throw new Exception("BencodeParser failed", e);
|
||||
}
|
||||
|
||||
var result = new HttpResponseMessage(HttpStatusCode.OK);
|
||||
result.Content = new ByteArrayContent(sortedDownloadBytes);
|
||||
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-bittorrent");
|
||||
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
|
||||
{
|
||||
FileName = StringUtil.MakeValidFileName(file, '_', false) + ".torrent" // call MakeValidFileName again to avoid any kind of injection attack
|
||||
};
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error(e, "Error downloading " + indexerID + " " + path);
|
||||
return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
using Jackett.Common;
|
||||
using Jackett.Common.Indexers;
|
||||
using Jackett.Common.Models;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using Jackett.Common.Utils;
|
||||
using Jackett.Utils;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Controllers
|
||||
{
|
||||
public interface IIndexerController
|
||||
{
|
||||
IIndexerManagerService IndexerService { get; }
|
||||
IIndexer CurrentIndexer { get; set; }
|
||||
}
|
||||
|
||||
public class RequiresIndexerAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
base.OnActionExecuting(actionContext);
|
||||
|
||||
var controller = actionContext.ControllerContext.Controller;
|
||||
if (!(controller is IIndexerController))
|
||||
return;
|
||||
|
||||
var indexerController = controller as IIndexerController;
|
||||
|
||||
var parameters = actionContext.RequestContext.RouteData.Values;
|
||||
|
||||
if (!parameters.ContainsKey("indexerId"))
|
||||
{
|
||||
indexerController.CurrentIndexer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var indexerId = parameters["indexerId"] as string;
|
||||
if (indexerId.IsNullOrEmptyOrWhitespace())
|
||||
return;
|
||||
|
||||
var indexerService = indexerController.IndexerService;
|
||||
var indexer = indexerService.GetIndexer(indexerId);
|
||||
indexerController.CurrentIndexer = indexer;
|
||||
}
|
||||
}
|
||||
|
||||
[RoutePrefix("api/v2.0/indexers")]
|
||||
[JackettAuthorized]
|
||||
[JackettAPINoCache]
|
||||
public class IndexerApiController : ApiController, IIndexerController
|
||||
{
|
||||
public IIndexerManagerService IndexerService { get; private set; }
|
||||
public IIndexer CurrentIndexer { get; set; }
|
||||
|
||||
public IndexerApiController(IIndexerManagerService indexerManagerService, IServerService ss, ICacheService c, Logger logger)
|
||||
{
|
||||
IndexerService = indexerManagerService;
|
||||
serverService = ss;
|
||||
cacheService = c;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[RequiresIndexer]
|
||||
public async Task<IHttpActionResult> Config()
|
||||
{
|
||||
var config = await CurrentIndexer.GetConfigurationForSetup();
|
||||
return Ok(config.ToJson(null));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ActionName("Config")]
|
||||
[RequiresIndexer]
|
||||
public async Task UpdateConfig([FromBody]Common.Models.DTO.ConfigItem[] config)
|
||||
{
|
||||
try
|
||||
{
|
||||
// HACK
|
||||
var jsonString = JsonConvert.SerializeObject(config);
|
||||
var json = JToken.Parse(jsonString);
|
||||
|
||||
var configurationResult = await CurrentIndexer.ApplyConfiguration(json);
|
||||
|
||||
if (configurationResult == IndexerConfigurationStatus.RequiresTesting)
|
||||
await IndexerService.TestIndexer(CurrentIndexer.ID);
|
||||
}
|
||||
catch
|
||||
{
|
||||
var baseIndexer = CurrentIndexer as BaseIndexer;
|
||||
if (null != baseIndexer)
|
||||
baseIndexer.ResetBaseConfig();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("")]
|
||||
public IEnumerable<Common.Models.DTO.Indexer> Indexers()
|
||||
{
|
||||
var dto = IndexerService.GetAllIndexers().Select(i => new Common.Models.DTO.Indexer(i));
|
||||
return dto;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[RequiresIndexer]
|
||||
public async Task Test()
|
||||
{
|
||||
JToken jsonReply = new JObject();
|
||||
try
|
||||
{
|
||||
await IndexerService.TestIndexer(CurrentIndexer.ID);
|
||||
CurrentIndexer.LastError = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
if (ex.InnerException != null)
|
||||
msg += ": " + ex.InnerException.Message;
|
||||
|
||||
if (CurrentIndexer != null)
|
||||
CurrentIndexer.LastError = msg;
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
[RequiresIndexer]
|
||||
[Route("{indexerId}")]
|
||||
public void Delete()
|
||||
{
|
||||
IndexerService.DeleteIndexer(CurrentIndexer.ID);
|
||||
}
|
||||
|
||||
// TODO
|
||||
// This should go to ServerConfigurationController
|
||||
[Route("Cache")]
|
||||
[HttpGet]
|
||||
public List<TrackerCacheResult> Cache()
|
||||
{
|
||||
var results = cacheService.GetCachedResults();
|
||||
ConfigureCacheResults(results);
|
||||
return results;
|
||||
}
|
||||
|
||||
private void ConfigureCacheResults(IEnumerable<TrackerCacheResult> results)
|
||||
{
|
||||
var serverUrl = serverService.GetServerUrl(Request);
|
||||
foreach (var result in results)
|
||||
{
|
||||
var link = result.Link;
|
||||
var file = StringUtil.MakeValidFileName(result.Title, '_', false);
|
||||
result.Link = serverService.ConvertToProxyLink(link, serverUrl, result.TrackerId, "dl", file);
|
||||
if (result.Link != null && result.Link.Scheme != "magnet" && !string.IsNullOrWhiteSpace(Engine.ServerConfig.BlackholeDir))
|
||||
result.BlackholeLink = serverService.ConvertToProxyLink(link, serverUrl, result.TrackerId, "bh", file);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private Logger logger;
|
||||
private IServerService serverService;
|
||||
private ICacheService cacheService;
|
||||
}
|
||||
}
|
|
@ -1,487 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
using System.Xml.Linq;
|
||||
using Jackett.Common;
|
||||
using Jackett.Common.Indexers;
|
||||
using Jackett.Common.Indexers.Meta;
|
||||
using Jackett.Common.Models;
|
||||
using Jackett.Common.Models.DTO;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using Jackett.Common.Utils;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Controllers
|
||||
{
|
||||
public class RequiresApiKeyAttribute : AuthorizationFilterAttribute
|
||||
{
|
||||
public override void OnAuthorization(HttpActionContext actionContext)
|
||||
{
|
||||
var validApiKey = Engine.ServerConfig.APIKey;
|
||||
var queryParams = actionContext.Request.GetQueryNameValuePairs();
|
||||
var queryApiKey = queryParams.Where(x => x.Key == "apikey" || x.Key == "passkey").Select(x => x.Value).FirstOrDefault();
|
||||
|
||||
#if DEBUG
|
||||
if (Debugger.IsAttached)
|
||||
return;
|
||||
#endif
|
||||
if (queryApiKey != validApiKey)
|
||||
actionContext.Response = ResultsController.GetErrorHttpResponseMessage(actionContext, HttpStatusCode.Unauthorized, 100, $"Invalid API Key");
|
||||
}
|
||||
}
|
||||
|
||||
public class RequiresConfiguredIndexerAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
var controller = actionContext.ControllerContext.Controller;
|
||||
if (!(controller is IIndexerController))
|
||||
return;
|
||||
|
||||
var indexerController = controller as IIndexerController;
|
||||
|
||||
var parameters = actionContext.RequestContext.RouteData.Values;
|
||||
|
||||
if (!parameters.ContainsKey("indexerId"))
|
||||
{
|
||||
indexerController.CurrentIndexer = null;
|
||||
actionContext.Response = ResultsController.GetErrorHttpResponseMessage(actionContext, HttpStatusCode.NotFound, 200, $"indexer is not specified");
|
||||
return;
|
||||
}
|
||||
|
||||
var indexerId = parameters["indexerId"] as string;
|
||||
if (indexerId.IsNullOrEmptyOrWhitespace())
|
||||
{
|
||||
indexerController.CurrentIndexer = null;
|
||||
actionContext.Response = ResultsController.GetErrorHttpResponseMessage(actionContext, HttpStatusCode.NotFound, 201, $"Indexer is not specified (empty value)");
|
||||
return;
|
||||
}
|
||||
|
||||
var indexerService = indexerController.IndexerService;
|
||||
var indexer = indexerService.GetIndexer(indexerId);
|
||||
|
||||
if (indexer == null)
|
||||
{
|
||||
indexerController.CurrentIndexer = null;
|
||||
actionContext.Response = ResultsController.GetErrorHttpResponseMessage(actionContext, HttpStatusCode.NotFound, 201, $"Indexer is not supported");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!indexer.IsConfigured)
|
||||
{
|
||||
indexerController.CurrentIndexer = null;
|
||||
actionContext.Response = ResultsController.GetErrorHttpResponseMessage(actionContext, HttpStatusCode.NotFound, 201, $"Indexer is not configured");
|
||||
return;
|
||||
}
|
||||
|
||||
indexerController.CurrentIndexer = indexer;
|
||||
}
|
||||
}
|
||||
|
||||
public class RequiresValidQueryAttribute : RequiresConfiguredIndexerAttribute
|
||||
{
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
base.OnActionExecuting(actionContext);
|
||||
if (actionContext.Response != null)
|
||||
return;
|
||||
|
||||
var controller = actionContext.ControllerContext.Controller;
|
||||
if (!(controller is IResultController))
|
||||
return;
|
||||
|
||||
var resultController = controller as IResultController;
|
||||
|
||||
var query = actionContext.ActionArguments.First().Value;
|
||||
var queryType = query.GetType();
|
||||
var converter = queryType.GetMethod("ToTorznabQuery", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
|
||||
if (converter == null)
|
||||
actionContext.Response = ResultsController.GetErrorHttpResponseMessage(actionContext, HttpStatusCode.BadRequest, 900, $"ToTorznabQuery() not found");
|
||||
var converted = converter.Invoke(null, new object[] { query });
|
||||
var torznabQuery = converted as TorznabQuery;
|
||||
resultController.CurrentQuery = torznabQuery;
|
||||
|
||||
if (queryType == typeof(ApiSearch)) // Skip CanHandleQuery() check for manual search (CurrentIndexer isn't used during manul search)
|
||||
return;
|
||||
|
||||
if (!resultController.CurrentIndexer.CanHandleQuery(resultController.CurrentQuery))
|
||||
actionContext.Response = ResultsController.GetErrorHttpResponseMessage(actionContext, HttpStatusCode.NotImplemented, 201, $"{resultController.CurrentIndexer.ID} does not support the requested query. Please check the capabilities (t=caps) and make sure the search mode and categories are supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public class JsonResponseAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
|
||||
{
|
||||
base.OnActionExecuted(actionExecutedContext);
|
||||
|
||||
if (actionExecutedContext.Exception != null)
|
||||
throw new Exception("Error while executing request", actionExecutedContext.Exception);
|
||||
|
||||
var content = actionExecutedContext.Response.Content as ObjectContent;
|
||||
actionExecutedContext.Response.Content = new JsonContent(content.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IResultController : IIndexerController
|
||||
{
|
||||
TorznabQuery CurrentQuery { get; set; }
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[JackettAPINoCache]
|
||||
[RoutePrefix("api/v2.0/indexers")]
|
||||
[RequiresApiKey]
|
||||
[RequiresValidQuery]
|
||||
public class ResultsController : ApiController, IResultController
|
||||
{
|
||||
public IIndexerManagerService IndexerService { get; private set; }
|
||||
public IIndexer CurrentIndexer { get; set; }
|
||||
public TorznabQuery CurrentQuery { get; set; }
|
||||
|
||||
public ResultsController(IIndexerManagerService indexerManagerService, IServerService ss, ICacheService c, Logger logger)
|
||||
{
|
||||
IndexerService = indexerManagerService;
|
||||
serverService = ss;
|
||||
cacheService = c;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ManualSearchResult> Results([FromUri]ApiSearch request)
|
||||
{
|
||||
var manualResult = new ManualSearchResult();
|
||||
var trackers = IndexerService.GetAllIndexers().Where(t => t.IsConfigured);
|
||||
if (request.Tracker != null)
|
||||
trackers = trackers.Where(t => request.Tracker.Contains(t.ID));
|
||||
trackers = trackers.Where(t => t.CanHandleQuery(CurrentQuery));
|
||||
|
||||
var tasks = trackers.ToList().Select(t => t.ResultsForQuery(CurrentQuery)).ToList();
|
||||
try
|
||||
{
|
||||
var aggregateTask = Task.WhenAll(tasks);
|
||||
await aggregateTask;
|
||||
}
|
||||
catch (AggregateException aex)
|
||||
{
|
||||
foreach (var ex in aex.InnerExceptions)
|
||||
{
|
||||
logger.Error(ex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex);
|
||||
}
|
||||
|
||||
manualResult.Indexers = tasks.Select(t =>
|
||||
{
|
||||
var resultIndexer = new ManualSearchResultIndexer();
|
||||
IIndexer indexer = null;
|
||||
if (t.Status == TaskStatus.RanToCompletion)
|
||||
{
|
||||
resultIndexer.Status = ManualSearchResultIndexerStatus.OK;
|
||||
resultIndexer.Results = t.Result.Releases.Count();
|
||||
resultIndexer.Error = null;
|
||||
indexer = t.Result.Indexer;
|
||||
}
|
||||
else if (t.Exception.InnerException is IndexerException)
|
||||
{
|
||||
resultIndexer.Status = ManualSearchResultIndexerStatus.Error;
|
||||
resultIndexer.Results = 0;
|
||||
resultIndexer.Error = ((IndexerException)t.Exception.InnerException).ToString();
|
||||
indexer = ((IndexerException)t.Exception.InnerException).Indexer;
|
||||
}
|
||||
else
|
||||
{
|
||||
resultIndexer.Status = ManualSearchResultIndexerStatus.Unknown;
|
||||
resultIndexer.Results = 0;
|
||||
resultIndexer.Error = null;
|
||||
}
|
||||
|
||||
if (indexer != null)
|
||||
{
|
||||
resultIndexer.ID = indexer.ID;
|
||||
resultIndexer.Name = indexer.DisplayName;
|
||||
}
|
||||
return resultIndexer;
|
||||
}).ToList();
|
||||
|
||||
manualResult.Results = tasks.Where(t => t.Status == TaskStatus.RanToCompletion).Where(t => t.Result.Releases.Count() > 0).SelectMany(t =>
|
||||
{
|
||||
var searchResults = t.Result.Releases;
|
||||
var indexer = t.Result.Indexer;
|
||||
cacheService.CacheRssResults(indexer, searchResults);
|
||||
|
||||
return searchResults.Select(result =>
|
||||
{
|
||||
var item = AutoMapper.Mapper.Map<TrackerCacheResult>(result);
|
||||
item.Tracker = indexer.DisplayName;
|
||||
item.TrackerId = indexer.ID;
|
||||
item.Peers = item.Peers - item.Seeders; // Use peers as leechers
|
||||
|
||||
return item;
|
||||
});
|
||||
}).OrderByDescending(d => d.PublishDate).ToList();
|
||||
|
||||
ConfigureCacheResults(manualResult.Results);
|
||||
|
||||
logger.Info(string.Format("Manual search for \"{0}\" on {1} with {2} results.", CurrentQuery.SanitizedSearchTerm, string.Join(", ", manualResult.Indexers.Select(i => i.ID)), manualResult.Results.Count()));
|
||||
return manualResult;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IHttpActionResult> Torznab([FromUri]Common.Models.DTO.TorznabRequest request)
|
||||
{
|
||||
if (string.Equals(CurrentQuery.QueryType, "caps", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return ResponseMessage(new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(CurrentIndexer.TorznabCaps.ToXml(), Encoding.UTF8, "application/xml")
|
||||
});
|
||||
}
|
||||
|
||||
// indexers - returns a list of all included indexers (meta indexers only)
|
||||
if (string.Equals(CurrentQuery.QueryType, "indexers", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
if (!(CurrentIndexer is BaseMetaIndexer)) // shouldn't be needed because CanHandleQuery should return false
|
||||
{
|
||||
logger.Warn($"A search request with t=indexers from {Request.GetOwinContext().Request.RemoteIpAddress} was made but the indexer {CurrentIndexer.DisplayName} isn't a meta indexer.");
|
||||
return GetErrorXML(203, "Function Not Available: this isn't a meta indexer");
|
||||
}
|
||||
var CurrentBaseMetaIndexer = (BaseMetaIndexer)CurrentIndexer;
|
||||
var indexers = CurrentBaseMetaIndexer.Indexers;
|
||||
if (string.Equals(request.configured, "true", StringComparison.InvariantCultureIgnoreCase))
|
||||
indexers = indexers.Where(i => i.IsConfigured);
|
||||
else if (string.Equals(request.configured, "false", StringComparison.InvariantCultureIgnoreCase))
|
||||
indexers = indexers.Where(i => !i.IsConfigured);
|
||||
|
||||
var xdoc = new XDocument(
|
||||
new XDeclaration("1.0", "UTF-8", null),
|
||||
new XElement("indexers",
|
||||
from i in indexers
|
||||
select new XElement("indexer",
|
||||
new XAttribute("id", i.ID),
|
||||
new XAttribute("configured", i.IsConfigured),
|
||||
new XElement("title", i.DisplayName),
|
||||
new XElement("description", i.DisplayDescription),
|
||||
new XElement("link", i.SiteLink),
|
||||
new XElement("language", i.Language),
|
||||
new XElement("type", i.Type),
|
||||
i.TorznabCaps.GetXDocument().FirstNode
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return ResponseMessage(new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(xdoc.Declaration.ToString() + Environment.NewLine + xdoc.ToString(), Encoding.UTF8, "application/xml")
|
||||
});
|
||||
}
|
||||
|
||||
if (CurrentQuery.ImdbID != null)
|
||||
{
|
||||
/* We should allow this (helpful in case of aggregate idnexers)
|
||||
if (!string.IsNullOrEmpty(CurrentQuery.SearchTerm))
|
||||
{
|
||||
logger.Warn($"A search request from {Request.GetOwinContext().Request.RemoteIpAddress} was made containing q and imdbid.");
|
||||
return GetErrorXML(201, "Incorrect parameter: please specify either imdbid or q");
|
||||
}
|
||||
*/
|
||||
|
||||
CurrentQuery.ImdbID = ParseUtil.GetFullImdbID(CurrentQuery.ImdbID); // normalize ImdbID
|
||||
if (CurrentQuery.ImdbID == null)
|
||||
{
|
||||
logger.Warn($"A search request from {Request.GetOwinContext().Request.RemoteIpAddress} was made with an invalid imdbid.");
|
||||
return GetErrorXML(201, "Incorrect parameter: invalid imdbid format");
|
||||
}
|
||||
|
||||
if (!CurrentIndexer.TorznabCaps.SupportsImdbSearch)
|
||||
{
|
||||
logger.Warn($"A search request with imdbid from {Request.GetOwinContext().Request.RemoteIpAddress} was made but the indexer {CurrentIndexer.DisplayName} doesn't support it.");
|
||||
return GetErrorXML(203, "Function Not Available: imdbid is not supported by this indexer");
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await CurrentIndexer.ResultsForQuery(CurrentQuery);
|
||||
|
||||
// Some trackers do not support multiple category filtering so filter the releases that match manually.
|
||||
int? newItemCount = null;
|
||||
|
||||
// Cache non query results
|
||||
if (string.IsNullOrEmpty(CurrentQuery.SanitizedSearchTerm))
|
||||
{
|
||||
newItemCount = cacheService.GetNewItemCount(CurrentIndexer, result.Releases);
|
||||
cacheService.CacheRssResults(CurrentIndexer, result.Releases);
|
||||
}
|
||||
|
||||
// Log info
|
||||
var logBuilder = new StringBuilder();
|
||||
if (newItemCount != null)
|
||||
{
|
||||
logBuilder.AppendFormat("Found {0} ({1} new) releases from {2}", result.Releases.Count(), newItemCount, CurrentIndexer.DisplayName);
|
||||
}
|
||||
else
|
||||
{
|
||||
logBuilder.AppendFormat("Found {0} releases from {1}", result.Releases.Count(), CurrentIndexer.DisplayName);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(CurrentQuery.SanitizedSearchTerm))
|
||||
{
|
||||
logBuilder.AppendFormat(" for: {0}", CurrentQuery.GetQueryString());
|
||||
}
|
||||
|
||||
logger.Info(logBuilder.ToString());
|
||||
|
||||
var serverUrl = serverService.GetServerUrl(Request);
|
||||
var resultPage = new ResultPage(new ChannelInfo
|
||||
{
|
||||
Title = CurrentIndexer.DisplayName,
|
||||
Description = CurrentIndexer.DisplayDescription,
|
||||
Link = new Uri(CurrentIndexer.SiteLink),
|
||||
ImageUrl = new Uri(serverUrl + "logos/" + CurrentIndexer.ID + ".png"),
|
||||
ImageTitle = CurrentIndexer.DisplayName,
|
||||
ImageLink = new Uri(CurrentIndexer.SiteLink),
|
||||
ImageDescription = CurrentIndexer.DisplayName
|
||||
});
|
||||
|
||||
var proxiedReleases = result.Releases.Select(r => AutoMapper.Mapper.Map<ReleaseInfo>(r)).Select(r =>
|
||||
{
|
||||
r.Link = serverService.ConvertToProxyLink(r.Link, serverUrl, r.Origin.ID, "dl", r.Title);
|
||||
return r;
|
||||
});
|
||||
|
||||
resultPage.Releases = proxiedReleases.ToList();
|
||||
|
||||
var xml = resultPage.ToXml(new Uri(serverUrl));
|
||||
// Force the return as XML
|
||||
return ResponseMessage(new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(xml, Encoding.UTF8, "application/rss+xml")
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Engine.Logger.Error(e);
|
||||
return GetErrorXML(900, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
static public HttpResponseMessage GetErrorHttpResponseMessage(HttpActionContext actionContext, HttpStatusCode status, int torznabCode, string description)
|
||||
{
|
||||
var parameters = actionContext.RequestContext.RouteData.Values;
|
||||
var action = parameters["action"] as string;
|
||||
|
||||
if (action == "torznab")
|
||||
{
|
||||
return GetErrorXMLHttpResponseMessage(torznabCode, description);
|
||||
}
|
||||
else
|
||||
{
|
||||
return actionContext.Request.CreateErrorResponse(status, description);
|
||||
}
|
||||
}
|
||||
|
||||
static public HttpResponseMessage GetErrorXMLHttpResponseMessage(int code, string description)
|
||||
{
|
||||
var xdoc = new XDocument(
|
||||
new XDeclaration("1.0", "UTF-8", null),
|
||||
new XElement("error",
|
||||
new XAttribute("code", code.ToString()),
|
||||
new XAttribute("description", description)
|
||||
)
|
||||
);
|
||||
|
||||
var xml = xdoc.Declaration.ToString() + Environment.NewLine + xdoc.ToString();
|
||||
|
||||
return new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(xml, Encoding.UTF8, "application/xml")
|
||||
};
|
||||
}
|
||||
|
||||
public IHttpActionResult GetErrorXML(int code, string description)
|
||||
{
|
||||
return ResponseMessage(GetErrorXMLHttpResponseMessage(code, description));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[JsonResponse]
|
||||
public async Task<TorrentPotatoResponse> Potato([FromUri]TorrentPotatoRequest request)
|
||||
{
|
||||
var result = await CurrentIndexer.ResultsForQuery(CurrentQuery);
|
||||
|
||||
// Cache non query results
|
||||
if (string.IsNullOrEmpty(CurrentQuery.SanitizedSearchTerm))
|
||||
cacheService.CacheRssResults(CurrentIndexer, result.Releases);
|
||||
|
||||
// Log info
|
||||
if (string.IsNullOrWhiteSpace(CurrentQuery.SanitizedSearchTerm))
|
||||
logger.Info($"Found {result.Releases.Count()} torrentpotato releases from {CurrentIndexer.DisplayName}");
|
||||
else
|
||||
logger.Info($"Found {result.Releases.Count()} torrentpotato releases from {CurrentIndexer.DisplayName} for: {CurrentQuery.GetQueryString()}");
|
||||
|
||||
var serverUrl = serverService.GetServerUrl(Request);
|
||||
var potatoReleases = result.Releases.Where(r => r.Link != null || r.MagnetUri != null).Select(r =>
|
||||
{
|
||||
var release = AutoMapper.Mapper.Map<ReleaseInfo>(r);
|
||||
release.Link = serverService.ConvertToProxyLink(release.Link, serverUrl, CurrentIndexer.ID, "dl", release.Title);
|
||||
var item = new TorrentPotatoResponseItem()
|
||||
{
|
||||
release_name = release.Title + "[" + CurrentIndexer.DisplayName + "]", // Suffix the indexer so we can see which tracker we are using in CPS as it just says torrentpotato >.>
|
||||
torrent_id = release.Guid.ToString(),
|
||||
details_url = release.Comments.ToString(),
|
||||
download_url = (release.Link != null ? release.Link.ToString() : release.MagnetUri.ToString()),
|
||||
imdb_id = release.Imdb.HasValue ? ParseUtil.GetFullImdbID("tt" + release.Imdb) : null,
|
||||
freeleech = (release.DownloadVolumeFactor == 0 ? true : false),
|
||||
type = "movie",
|
||||
size = (long)release.Size / (1024 * 1024), // This is in MB
|
||||
leechers = (release.Peers ?? -1) - (release.Seeders ?? 0),
|
||||
seeders = release.Seeders ?? -1,
|
||||
publish_date = r.PublishDate == DateTime.MinValue ? null : release.PublishDate.ToUniversalTime().ToString("s")
|
||||
};
|
||||
return item;
|
||||
});
|
||||
|
||||
var potatoResponse = new TorrentPotatoResponse()
|
||||
{
|
||||
results = potatoReleases.ToList()
|
||||
};
|
||||
|
||||
return potatoResponse;
|
||||
}
|
||||
|
||||
private void ConfigureCacheResults(IEnumerable<TrackerCacheResult> results)
|
||||
{
|
||||
var serverUrl = serverService.GetServerUrl(Request);
|
||||
foreach (var result in results)
|
||||
{
|
||||
var link = result.Link;
|
||||
var file = StringUtil.MakeValidFileName(result.Title, '_', false);
|
||||
result.Link = serverService.ConvertToProxyLink(link, serverUrl, result.TrackerId, "dl", file);
|
||||
if (!string.IsNullOrWhiteSpace(Engine.ServerConfig.BlackholeDir))
|
||||
{
|
||||
if (result.Link != null)
|
||||
result.BlackholeLink = serverService.ConvertToProxyLink(link, serverUrl, result.TrackerId, "bh", file);
|
||||
else if (result.MagnetUri != null)
|
||||
result.BlackholeLink = serverService.ConvertToProxyLink(result.MagnetUri, serverUrl, result.TrackerId, "bh", file);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private Logger logger;
|
||||
private IServerService serverService;
|
||||
private ICacheService cacheService;
|
||||
}
|
||||
}
|
|
@ -1,204 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Web.Http;
|
||||
using Jackett.Common;
|
||||
using Jackett.Common.Models;
|
||||
using Jackett.Common.Models.Config;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using Jackett.Common.Utils;
|
||||
using Jackett.Utils;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Controllers
|
||||
{
|
||||
[RoutePrefix("api/v2.0/server")]
|
||||
[JackettAuthorized]
|
||||
[JackettAPINoCache]
|
||||
public class ServerConfigurationController : ApiController
|
||||
{
|
||||
private readonly IConfigurationService configService;
|
||||
private ServerConfig serverConfig;
|
||||
private IServerService serverService;
|
||||
private IProcessService processService;
|
||||
private IIndexerManagerService indexerService;
|
||||
private ISecuityService securityService;
|
||||
private IUpdateService updater;
|
||||
private ILogCacheService logCache;
|
||||
private Logger logger;
|
||||
|
||||
public ServerConfigurationController(IConfigurationService c, IServerService s, IProcessService p, IIndexerManagerService i, ISecuityService ss, IUpdateService u, ILogCacheService lc, Logger l, ServerConfig sc)
|
||||
{
|
||||
configService = c;
|
||||
serverConfig = sc;
|
||||
serverService = s;
|
||||
processService = p;
|
||||
indexerService = i;
|
||||
securityService = ss;
|
||||
updater = u;
|
||||
logCache = lc;
|
||||
logger = l;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public void AdminPassword([FromBody]string password)
|
||||
{
|
||||
var oldPassword = serverConfig.AdminPassword;
|
||||
if (string.IsNullOrEmpty(password))
|
||||
password = null;
|
||||
|
||||
if (oldPassword != password)
|
||||
{
|
||||
serverConfig.AdminPassword = securityService.HashPassword(password);
|
||||
configService.SaveConfig(serverConfig);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public void Update()
|
||||
{
|
||||
updater.CheckForUpdatesNow();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public Common.Models.DTO.ServerConfig Config()
|
||||
{
|
||||
|
||||
var dto = new Common.Models.DTO.ServerConfig(serverService.notices, serverConfig, configService.GetVersion());
|
||||
return dto;
|
||||
}
|
||||
|
||||
[ActionName("Config")]
|
||||
[HttpPost]
|
||||
public void UpdateConfig([FromBody]Common.Models.DTO.ServerConfig config)
|
||||
{
|
||||
var originalPort = serverConfig.Port;
|
||||
var originalAllowExternal = serverConfig.AllowExternal;
|
||||
int port = config.port;
|
||||
bool external = config.external;
|
||||
string saveDir = config.blackholedir;
|
||||
bool updateDisabled = config.updatedisabled;
|
||||
bool preRelease = config.prerelease;
|
||||
bool logging = config.logging;
|
||||
string basePathOverride = config.basepathoverride;
|
||||
if (basePathOverride != null)
|
||||
{
|
||||
basePathOverride = basePathOverride.TrimEnd('/');
|
||||
if (!string.IsNullOrWhiteSpace(basePathOverride) && !basePathOverride.StartsWith("/"))
|
||||
throw new Exception("The Base Path Override must start with a /");
|
||||
}
|
||||
|
||||
string omdbApiKey = config.omdbkey;
|
||||
string omdbApiUrl = config.omdburl;
|
||||
|
||||
serverConfig.UpdateDisabled = updateDisabled;
|
||||
serverConfig.UpdatePrerelease = preRelease;
|
||||
serverConfig.BasePathOverride = basePathOverride;
|
||||
serverConfig.RuntimeSettings.BasePath = Engine.Server.BasePath();
|
||||
configService.SaveConfig(serverConfig);
|
||||
|
||||
Engine.SetLogLevel(logging ? LogLevel.Debug : LogLevel.Info);
|
||||
serverConfig.RuntimeSettings.TracingEnabled = logging;
|
||||
|
||||
if (omdbApiKey != serverConfig.OmdbApiKey || omdbApiUrl != serverConfig.OmdbApiUrl)
|
||||
{
|
||||
serverConfig.OmdbApiKey = omdbApiKey;
|
||||
serverConfig.OmdbApiUrl = omdbApiUrl.TrimEnd('/');
|
||||
configService.SaveConfig(serverConfig);
|
||||
// HACK
|
||||
indexerService.InitAggregateIndexer();
|
||||
}
|
||||
|
||||
if (config.proxy_type != serverConfig.ProxyType ||
|
||||
config.proxy_url != serverConfig.ProxyUrl ||
|
||||
config.proxy_port != serverConfig.ProxyPort ||
|
||||
config.proxy_username != serverConfig.ProxyUsername ||
|
||||
config.proxy_password != serverConfig.ProxyPassword)
|
||||
{
|
||||
if (config.proxy_port < 1 || config.proxy_port > 65535)
|
||||
throw new Exception("The port you have selected is invalid, it must be below 65535.");
|
||||
|
||||
serverConfig.ProxyUrl = config.proxy_url;
|
||||
serverConfig.ProxyType = config.proxy_type;
|
||||
serverConfig.ProxyPort = config.proxy_port;
|
||||
serverConfig.ProxyUsername = config.proxy_username;
|
||||
serverConfig.ProxyPassword = config.proxy_password;
|
||||
configService.SaveConfig(serverConfig);
|
||||
}
|
||||
|
||||
if (port != serverConfig.Port || external != serverConfig.AllowExternal)
|
||||
{
|
||||
|
||||
if (ServerUtil.RestrictedPorts.Contains(port))
|
||||
throw new Exception("The port you have selected is restricted, try a different one.");
|
||||
|
||||
if (port < 1 || port > 65535)
|
||||
throw new Exception("The port you have selected is invalid, it must be below 65535.");
|
||||
|
||||
// Save port to the config so it can be picked up by the if needed when running as admin below.
|
||||
serverConfig.AllowExternal = external;
|
||||
serverConfig.Port = port;
|
||||
configService.SaveConfig(serverConfig);
|
||||
|
||||
// On Windows change the url reservations
|
||||
if (System.Environment.OSVersion.Platform != PlatformID.Unix)
|
||||
{
|
||||
if (!ServerUtil.IsUserAdministrator())
|
||||
{
|
||||
try
|
||||
{
|
||||
processService.StartProcessAndLog(System.Windows.Forms.Application.ExecutablePath, "--ReserveUrls", true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
serverConfig.Port = originalPort;
|
||||
serverConfig.AllowExternal = originalAllowExternal;
|
||||
configService.SaveConfig(serverConfig);
|
||||
|
||||
throw new Exception("Failed to acquire admin permissions to reserve the new port.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
serverService.ReserveUrls(true);
|
||||
}
|
||||
}
|
||||
|
||||
(new Thread(() =>
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
serverService.Stop();
|
||||
Engine.BuildContainer(serverConfig.RuntimeSettings, new WebApi2Module());
|
||||
Engine.Server.Initalize();
|
||||
Engine.Server.Start();
|
||||
})).Start();
|
||||
}
|
||||
|
||||
if (saveDir != serverConfig.BlackholeDir)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(saveDir))
|
||||
{
|
||||
if (!Directory.Exists(saveDir))
|
||||
{
|
||||
throw new Exception("Blackhole directory does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
serverConfig.BlackholeDir = saveDir;
|
||||
configService.SaveConfig(serverConfig);
|
||||
}
|
||||
|
||||
serverConfig.ConfigChanged();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public List<CachedLog> Logs()
|
||||
{
|
||||
return logCache.Logs;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using Jackett.Common.Models.Config;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using Jackett.Utils;
|
||||
using MimeMapping;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Controllers
|
||||
{
|
||||
[RoutePrefix("UI")]
|
||||
[JackettAuthorized]
|
||||
[JackettAPINoCache]
|
||||
public class WebUIController : ApiController
|
||||
{
|
||||
private IConfigurationService config;
|
||||
private ServerConfig serverConfig;
|
||||
private ISecuityService securityService;
|
||||
private Logger logger;
|
||||
|
||||
public WebUIController(IConfigurationService config, ISecuityService ss, ServerConfig s, Logger l)
|
||||
{
|
||||
this.config = config;
|
||||
serverConfig = s;
|
||||
securityService = ss;
|
||||
logger = l;
|
||||
}
|
||||
|
||||
private HttpResponseMessage GetFile(string path)
|
||||
{
|
||||
var result = new HttpResponseMessage(HttpStatusCode.OK);
|
||||
var mappedPath = Path.Combine(config.GetContentFolder(), path);
|
||||
var stream = new FileStream(mappedPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
result.Content = new StreamContent(stream);
|
||||
result.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeUtility.GetMimeMapping(mappedPath));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public IHttpActionResult Logout()
|
||||
{
|
||||
var ctx = Request.GetOwinContext();
|
||||
var authManager = ctx.Authentication;
|
||||
authManager.SignOut("ApplicationCookie");
|
||||
return Redirect("UI/Dashboard");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
public async Task<HttpResponseMessage> Dashboard()
|
||||
{
|
||||
if (Request.RequestUri.Query != null && Request.RequestUri.Query.Contains("logout"))
|
||||
{
|
||||
var file = GetFile("login.html");
|
||||
securityService.Logout(file);
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
if (securityService.CheckAuthorised(Request))
|
||||
{
|
||||
return GetFile("index.html");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var formData = await Request.Content.ReadAsFormDataAsync();
|
||||
|
||||
if (formData != null && securityService.HashPassword(formData["password"]) == serverConfig.AdminPassword)
|
||||
{
|
||||
var file = GetFile("index.html");
|
||||
securityService.Login(file);
|
||||
return file;
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetFile("login.html");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{E636D5F8-68B4-4903-B4ED-CCFD9C9E899F}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Jackett</RootNamespace>
|
||||
<AssemblyName>Jackett</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
<UpdateEnabled>false</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateInterval>7</UpdateInterval>
|
||||
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||
<UpdatePeriodically>false</UpdatePeriodically>
|
||||
<UpdateRequired>false</UpdateRequired>
|
||||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Configuration.Install" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Net.Http.WebRequest" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Security" />
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
<Reference Include="System.Transactions" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CacheControlAttribute.cs" />
|
||||
<Compile Include="Controllers\BlackholeController.cs" />
|
||||
<Compile Include="Controllers\DownloadController.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Services\ServiceConfigService.cs" />
|
||||
<Compile Include="Services\ProtectionService.cs" />
|
||||
<Compile Include="Services\SecuityService.cs" />
|
||||
<Compile Include="Startup.cs" />
|
||||
<Compile Include="Controllers\IndexerApiController.cs" />
|
||||
<Compile Include="Controllers\UIController.cs" />
|
||||
<Compile Include="Controllers\ServerConfigurationController.cs" />
|
||||
<Compile Include="Controllers\ResultsController.cs" />
|
||||
<Compile Include="Utils\JackettAuthorizedAttribute.cs" />
|
||||
<Compile Include="Utils\RedirectMiddleware.cs" />
|
||||
<Compile Include="Services\ServerService.cs" />
|
||||
<Compile Include="Utils\WebAPIRequestLogger.cs" />
|
||||
<Compile Include="Utils\WebAPIToNLogTracer.cs" />
|
||||
<Compile Include="WebApi2Module.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include=".NETFramework,Version=v4.5.1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Microsoft .NET Framework 4.5.1 %28x86 and x64%29</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac.WebApi2">
|
||||
<Version>4.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.OwinSelfHost">
|
||||
<Version>5.2.4</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Tracing">
|
||||
<Version>5.2.4</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Owin.StaticFiles">
|
||||
<Version>4.0.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Jackett.Common\Jackett.Common.csproj">
|
||||
<Project>{6b854a1b-9a90-49c0-bc37-9a35c75bca73}</Project>
|
||||
<Name>Jackett.Common</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
<ProjectExtensions>
|
||||
<MonoDevelop>
|
||||
<Properties>
|
||||
<Policies>
|
||||
<TextStylePolicy TabWidth="4" IndentWidth="4" RemoveTrailingWhitespace="True" NoTabsAfterNonTabs="False" EolMarker="Native" FileWidth="80" TabsToSpaces="True" scope="text/plain" />
|
||||
</Policies>
|
||||
</Properties>
|
||||
</MonoDevelop>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
|
@ -1,35 +0,0 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Jackett")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Jackett")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("5881fb69-3cb2-42b7-a744-2c1e537176bd")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("0.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("0.0.0.0")]
|
|
@ -1,63 +0,0 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Jackett.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Jackett.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
|
@ -1,194 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Jackett.Common;
|
||||
using Jackett.Common.Models.Config;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using Jackett.Common.Utils;
|
||||
|
||||
namespace Jackett.Services
|
||||
{
|
||||
|
||||
public class ProtectionService : IProtectionService
|
||||
{
|
||||
DataProtectionScope PROTECTION_SCOPE = DataProtectionScope.LocalMachine;
|
||||
private const string JACKETT_KEY = "JACKETT_KEY";
|
||||
const string APPLICATION_KEY = "Dvz66r3n8vhTGip2/quiw5ISyM37f7L2iOdupzdKmzkvXGhAgQiWK+6F+4qpxjPVNks1qO7LdWuVqRlzgLzeW8mChC6JnBMUS1Fin4N2nS9lh4XPuCZ1che75xO92Nk2vyXUo9KSFG1hvEszAuLfG2Mcg1r0sVyVXd2gQDU/TbY=";
|
||||
private byte[] _instanceKey;
|
||||
|
||||
public ProtectionService(ServerConfig config)
|
||||
{
|
||||
if (System.Environment.OSVersion.Platform == PlatformID.Unix)
|
||||
{
|
||||
// We should not be running as root and will only have access to the local store.
|
||||
PROTECTION_SCOPE = DataProtectionScope.CurrentUser;
|
||||
}
|
||||
_instanceKey = Encoding.UTF8.GetBytes(config.InstanceId);
|
||||
}
|
||||
|
||||
public string Protect(string plainText)
|
||||
{
|
||||
var jackettKey = Environment.GetEnvironmentVariable(JACKETT_KEY);
|
||||
|
||||
if (jackettKey == null)
|
||||
{
|
||||
return ProtectDefaultMethod(plainText);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ProtectUsingKey(plainText, jackettKey);
|
||||
}
|
||||
}
|
||||
|
||||
public string UnProtect(string plainText)
|
||||
{
|
||||
var jackettKey = Environment.GetEnvironmentVariable(JACKETT_KEY);
|
||||
|
||||
if (jackettKey == null)
|
||||
{
|
||||
return UnProtectDefaultMethod(plainText);
|
||||
}
|
||||
else
|
||||
{
|
||||
return UnProtectUsingKey(plainText, jackettKey);
|
||||
}
|
||||
}
|
||||
|
||||
public string LegacyProtect(string plainText)
|
||||
{
|
||||
return Protect(plainText);
|
||||
}
|
||||
|
||||
public string LegacyUnProtect(string plainText)
|
||||
{
|
||||
return UnProtect(plainText);
|
||||
}
|
||||
|
||||
private string ProtectDefaultMethod(string plainText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(plainText))
|
||||
return string.Empty;
|
||||
|
||||
var plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
var appKey = Convert.FromBase64String(APPLICATION_KEY);
|
||||
var instanceKey = _instanceKey;
|
||||
var entropy = new byte[appKey.Length + instanceKey.Length];
|
||||
Buffer.BlockCopy(instanceKey, 0, entropy, 0, instanceKey.Length);
|
||||
Buffer.BlockCopy(appKey, 0, entropy, instanceKey.Length, appKey.Length);
|
||||
|
||||
var protectedBytes = ProtectedData.Protect(plainBytes, entropy, PROTECTION_SCOPE);
|
||||
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
using (RijndaelManaged AES = new RijndaelManaged())
|
||||
{
|
||||
AES.KeySize = 256;
|
||||
AES.BlockSize = 128;
|
||||
|
||||
var key = new Rfc2898DeriveBytes(instanceKey, instanceKey.Reverse().ToArray(), 64);
|
||||
AES.Key = key.GetBytes(AES.KeySize / 8);
|
||||
AES.IV = key.GetBytes(AES.BlockSize / 8);
|
||||
|
||||
AES.Mode = CipherMode.CBC;
|
||||
|
||||
using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
|
||||
{
|
||||
cs.Write(protectedBytes, 0, protectedBytes.Length);
|
||||
cs.Close();
|
||||
}
|
||||
protectedBytes = ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
return Convert.ToBase64String(protectedBytes);
|
||||
}
|
||||
|
||||
private string UnProtectDefaultMethod(string plainText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(plainText))
|
||||
return string.Empty;
|
||||
|
||||
var protectedBytes = Convert.FromBase64String(plainText);
|
||||
var instanceKey = _instanceKey;
|
||||
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
using (RijndaelManaged AES = new RijndaelManaged())
|
||||
{
|
||||
AES.KeySize = 256;
|
||||
AES.BlockSize = 128;
|
||||
|
||||
var key = new Rfc2898DeriveBytes(instanceKey, instanceKey.Reverse().ToArray(), 64);
|
||||
AES.Key = key.GetBytes(AES.KeySize / 8);
|
||||
AES.IV = key.GetBytes(AES.BlockSize / 8);
|
||||
|
||||
AES.Mode = CipherMode.CBC;
|
||||
|
||||
using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
|
||||
{
|
||||
cs.Write(protectedBytes, 0, protectedBytes.Length);
|
||||
cs.Close();
|
||||
}
|
||||
protectedBytes = ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
var appKey = Convert.FromBase64String(APPLICATION_KEY);
|
||||
var entropy = new byte[appKey.Length + instanceKey.Length];
|
||||
Buffer.BlockCopy(instanceKey, 0, entropy, 0, instanceKey.Length);
|
||||
Buffer.BlockCopy(appKey, 0, entropy, instanceKey.Length, appKey.Length);
|
||||
|
||||
var unprotectedBytes = ProtectedData.Unprotect(protectedBytes, entropy, PROTECTION_SCOPE);
|
||||
return Encoding.UTF8.GetString(unprotectedBytes);
|
||||
}
|
||||
|
||||
private string ProtectUsingKey(string plainText, string key)
|
||||
{
|
||||
return StringCipher.Encrypt(plainText, key);
|
||||
}
|
||||
|
||||
private string UnProtectUsingKey(string plainText, string key)
|
||||
{
|
||||
return StringCipher.Decrypt(plainText, key);
|
||||
}
|
||||
|
||||
public void Protect<T>(T obj)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
|
||||
foreach (var property in type.GetProperties(BindingFlags.SetProperty | BindingFlags.GetProperty | BindingFlags.Public))
|
||||
{
|
||||
if (property.GetCustomAttributes(typeof(JackettProtectedAttribute), false).Count() > 0)
|
||||
{
|
||||
var value = property.GetValue(obj);
|
||||
if (value is string)
|
||||
{
|
||||
var protectedString = Protect(value as string);
|
||||
property.SetValue(obj, protectedString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UnProtect<T>(T obj)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
|
||||
foreach (var property in type.GetProperties(BindingFlags.SetProperty | BindingFlags.GetProperty | BindingFlags.Public))
|
||||
{
|
||||
if (property.GetCustomAttributes(typeof(JackettProtectedAttribute), false).Count() > 0)
|
||||
{
|
||||
var value = property.GetValue(obj);
|
||||
if (value is string)
|
||||
{
|
||||
var unprotectedString = UnProtect(value as string);
|
||||
property.SetValue(obj, unprotectedString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Jackett.Common.Models.Config;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
|
||||
namespace Jackett.Services
|
||||
{
|
||||
|
||||
class SecuityService : ISecuityService
|
||||
{
|
||||
private const string COOKIENAME = "JACKETT";
|
||||
private ServerConfig _serverConfig;
|
||||
|
||||
public SecuityService(ServerConfig sc)
|
||||
{
|
||||
_serverConfig = sc;
|
||||
}
|
||||
|
||||
public string HashPassword(string input)
|
||||
{
|
||||
if (input == null)
|
||||
return null;
|
||||
// Append key as salt
|
||||
input += _serverConfig.APIKey;
|
||||
|
||||
UnicodeEncoding UE = new UnicodeEncoding();
|
||||
byte[] hashValue;
|
||||
byte[] message = UE.GetBytes(input);
|
||||
|
||||
SHA512Managed hashString = new SHA512Managed();
|
||||
string hex = "";
|
||||
|
||||
hashValue = hashString.ComputeHash(message);
|
||||
foreach (byte x in hashValue)
|
||||
{
|
||||
hex += String.Format("{0:x2}", x);
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
public void Login(HttpResponseMessage response)
|
||||
{
|
||||
// Login
|
||||
response.Headers.Add("Set-Cookie", COOKIENAME + "=" + _serverConfig.AdminPassword + "; path=/");
|
||||
}
|
||||
|
||||
public void Logout(HttpResponseMessage response)
|
||||
{
|
||||
// Logout
|
||||
response.Headers.Add("Set-Cookie", COOKIENAME + "=; path=/");
|
||||
}
|
||||
|
||||
public bool CheckAuthorised(HttpRequestMessage request)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_serverConfig.AdminPassword))
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
var cookie = request.Headers.GetCookies(COOKIENAME).FirstOrDefault();
|
||||
if (cookie != null)
|
||||
{
|
||||
return cookie[COOKIENAME].Value == _serverConfig.AdminPassword;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,378 +0,0 @@
|
|||
using Microsoft.Owin.Hosting;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Collections;
|
||||
using System.Text.RegularExpressions;
|
||||
using Jackett.Common;
|
||||
using Jackett.Common.Models.Config;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using Jackett.Common.Utils.Clients;
|
||||
|
||||
namespace Jackett.Services
|
||||
{
|
||||
|
||||
public class ServerService : IServerService
|
||||
{
|
||||
private IDisposable _server = null;
|
||||
|
||||
private IIndexerManagerService indexerService;
|
||||
private IProcessService processService;
|
||||
private ISerializeService serializeService;
|
||||
private IConfigurationService configService;
|
||||
private Logger logger;
|
||||
private Common.Utils.Clients.WebClient client;
|
||||
private IUpdateService updater;
|
||||
private List<string> _notices = new List<string>();
|
||||
private ServerConfig config;
|
||||
IProtectionService _protectionService;
|
||||
|
||||
public ServerService(IIndexerManagerService i, IProcessService p, ISerializeService s, IConfigurationService c, Logger l, Common.Utils.Clients.WebClient w, IUpdateService u, IProtectionService protectionService, ServerConfig serverConfig)
|
||||
{
|
||||
indexerService = i;
|
||||
processService = p;
|
||||
serializeService = s;
|
||||
configService = c;
|
||||
logger = l;
|
||||
client = w;
|
||||
updater = u;
|
||||
config = serverConfig;
|
||||
_protectionService = protectionService;
|
||||
}
|
||||
|
||||
public List<string> notices
|
||||
{
|
||||
get
|
||||
{
|
||||
return _notices;
|
||||
}
|
||||
}
|
||||
|
||||
public Uri ConvertToProxyLink(Uri link, string serverUrl, string indexerId, string action = "dl", string file = "t")
|
||||
{
|
||||
if (link == null || (link.IsAbsoluteUri && link.Scheme == "magnet" && action != "bh")) // no need to convert a magnet link to a proxy link unless it's a blackhole link
|
||||
return link;
|
||||
|
||||
var encryptedLink = _protectionService.Protect(link.ToString());
|
||||
var encodedLink = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(encryptedLink));
|
||||
string urlEncodedFile = WebUtility.UrlEncode(file);
|
||||
var proxyLink = string.Format("{0}{1}/{2}/?jackett_apikey={3}&path={4}&file={5}", serverUrl, action, indexerId, config.APIKey, encodedLink, urlEncodedFile);
|
||||
return new Uri(proxyLink);
|
||||
}
|
||||
|
||||
public string BasePath()
|
||||
{
|
||||
if (config.BasePathOverride == null || config.BasePathOverride == "")
|
||||
{
|
||||
return "";
|
||||
}
|
||||
var path = config.BasePathOverride;
|
||||
if (path.EndsWith("/"))
|
||||
{
|
||||
path = path.TrimEnd('/');
|
||||
}
|
||||
if (!path.StartsWith("/"))
|
||||
{
|
||||
path = "/" + path;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public void Initalize()
|
||||
{
|
||||
logger.Info("Starting Jackett " + configService.GetVersion());
|
||||
try
|
||||
{
|
||||
var x = Environment.OSVersion;
|
||||
var runtimedir = RuntimeEnvironment.GetRuntimeDirectory();
|
||||
logger.Info("Environment version: " + Environment.Version.ToString() + " (" + runtimedir + ")");
|
||||
logger.Info("OS version: " + Environment.OSVersion.ToString() + (Environment.Is64BitOperatingSystem ? " (64bit OS)" : "") + (Environment.Is64BitProcess ? " (64bit process)" : ""));
|
||||
|
||||
try
|
||||
{
|
||||
int workerThreads;
|
||||
int completionPortThreads;
|
||||
ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
|
||||
logger.Info("ThreadPool MaxThreads: " + workerThreads + " workerThreads, " + completionPortThreads + " completionPortThreads");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Error while getting MaxThreads details: " + e);
|
||||
}
|
||||
|
||||
logger.Info("App config/log directory: " + configService.GetAppDataFolder());
|
||||
|
||||
try
|
||||
{
|
||||
var issuefile = "/etc/issue";
|
||||
if (File.Exists(issuefile))
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(issuefile))
|
||||
{
|
||||
string firstLine;
|
||||
firstLine = reader.ReadLine();
|
||||
if (firstLine != null)
|
||||
logger.Info("issue: " + firstLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error(e, "Error while reading the issue file");
|
||||
}
|
||||
|
||||
Type monotype = Type.GetType("Mono.Runtime");
|
||||
if (monotype != null)
|
||||
{
|
||||
MethodInfo displayName = monotype.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
var monoVersion = "unknown";
|
||||
if (displayName != null)
|
||||
monoVersion = displayName.Invoke(null, null).ToString();
|
||||
logger.Info("mono version: " + monoVersion);
|
||||
|
||||
var monoVersionO = new Version(monoVersion.Split(' ')[0]);
|
||||
|
||||
if (monoVersionO.Major < 5 || (monoVersionO.Major == 5 && monoVersionO.Minor < 8))
|
||||
{
|
||||
string notice = "A minimum Mono version of 5.8 is required. Please update to the latest version from http://www.mono-project.com/download/";
|
||||
_notices.Add(notice);
|
||||
logger.Error(notice);
|
||||
Engine.Exit(1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Check for mono-devel
|
||||
// Is there any better way which doesn't involve a hard cashes?
|
||||
var mono_devel_file = Path.Combine(runtimedir, "mono-api-info.exe");
|
||||
if (!File.Exists(mono_devel_file))
|
||||
{
|
||||
var notice = "It looks like the mono-devel package is not installed, please make sure it's installed to avoid crashes.";
|
||||
_notices.Add(notice);
|
||||
logger.Error(notice);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error(e, "Error while checking for mono-devel");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Check for ca-certificates-mono
|
||||
var mono_cert_file = Path.Combine(runtimedir, "cert-sync.exe");
|
||||
if (!File.Exists(mono_cert_file))
|
||||
{
|
||||
if ((monoVersionO.Major >= 4 && monoVersionO.Minor >= 8) || monoVersionO.Major >= 5)
|
||||
{
|
||||
var notice = "The ca-certificates-mono package is not installed, HTTPS trackers won't work. Please install it.";
|
||||
_notices.Add(notice);
|
||||
logger.Error(notice);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info("The ca-certificates-mono package is not installed, it will become mandatory once mono >= 4.8 is used.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error(e, "Error while checking for ca-certificates-mono");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Encoding.GetEncoding("windows-1255");
|
||||
}
|
||||
catch (NotSupportedException e)
|
||||
{
|
||||
logger.Debug(e);
|
||||
logger.Error(e.Message + " Most likely the mono-locale-extras package is not installed.");
|
||||
Engine.Exit(2);
|
||||
}
|
||||
|
||||
if (Engine.WebClientType == typeof(HttpWebClient) || Engine.WebClientType == typeof(HttpWebClient2))
|
||||
{
|
||||
// check if the certificate store was initialized using Mono.Security.X509.X509StoreManager.TrustedRootCertificates.Count
|
||||
try
|
||||
{
|
||||
var monoSecurity = Assembly.Load("Mono.Security");
|
||||
Type monoX509StoreManager = monoSecurity.GetType("Mono.Security.X509.X509StoreManager");
|
||||
if (monoX509StoreManager != null)
|
||||
{
|
||||
var TrustedRootCertificatesProperty = monoX509StoreManager.GetProperty("TrustedRootCertificates");
|
||||
var TrustedRootCertificates = (ICollection)TrustedRootCertificatesProperty.GetValue(null);
|
||||
|
||||
logger.Info("TrustedRootCertificates count: " + TrustedRootCertificates.Count);
|
||||
|
||||
if (TrustedRootCertificates.Count == 0)
|
||||
{
|
||||
var CACertificatesFiles = new string[] {
|
||||
"/etc/ssl/certs/ca-certificates.crt", // Debian based
|
||||
"/etc/pki/tls/certs/ca-bundle.c", // RedHat based
|
||||
"/etc/ssl/ca-bundle.pem", // SUSE
|
||||
};
|
||||
|
||||
var notice = "The mono certificate store is not initialized.<br/>\n";
|
||||
var logSpacer = " ";
|
||||
var CACertificatesFile = CACertificatesFiles.Where(f => File.Exists(f)).FirstOrDefault();
|
||||
var CommandRoot = "curl -sS https://curl.haxx.se/ca/cacert.pem | cert-sync /dev/stdin";
|
||||
var CommandUser = "curl -sS https://curl.haxx.se/ca/cacert.pem | cert-sync --user /dev/stdin";
|
||||
if (CACertificatesFile != null)
|
||||
{
|
||||
CommandRoot = "cert-sync " + CACertificatesFile;
|
||||
CommandUser = "cert-sync --user " + CACertificatesFile;
|
||||
}
|
||||
notice += logSpacer + "Please run the following command as root:<br/>\n";
|
||||
notice += logSpacer + "<pre>" + CommandRoot + "</pre><br/>\n";
|
||||
notice += logSpacer + "If you don't have root access or you're running MacOS, please run the following command as the jackett user (" + Environment.UserName + "):<br/>\n";
|
||||
notice += logSpacer + "<pre>" + CommandUser + "</pre>";
|
||||
_notices.Add(notice);
|
||||
logger.Error(Regex.Replace(notice, "<.*?>", String.Empty));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error(e, "Error while chekcing the mono certificate store");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Error while getting environment details: " + e);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (Environment.UserName == "root")
|
||||
{
|
||||
var notice = "Jackett is running with root privileges. You should run Jackett as an unprivileged user.";
|
||||
_notices.Add(notice);
|
||||
logger.Error(notice);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error(e, "Error while checking the username");
|
||||
}
|
||||
|
||||
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
|
||||
// Load indexers
|
||||
indexerService.InitIndexers(configService.GetCardigannDefinitionsFolders());
|
||||
client.Init();
|
||||
updater.CleanupTempDir();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// Start the server
|
||||
logger.Info("Starting web server at " + config.GetListenAddresses()[0]);
|
||||
var startOptions = new StartOptions();
|
||||
config.GetListenAddresses().ToList().ForEach(u => startOptions.Urls.Add(u));
|
||||
config.RuntimeSettings.BasePath = BasePath();
|
||||
try
|
||||
{
|
||||
_server = WebApp.Start<Startup>(startOptions);
|
||||
}
|
||||
catch (TargetInvocationException e)
|
||||
{
|
||||
var inner = e.InnerException;
|
||||
if (inner is SocketException && ((SocketException)inner).SocketErrorCode == SocketError.AddressAlreadyInUse) // Linux (mono)
|
||||
{
|
||||
logger.Error("Address already in use: Most likely Jackett is already running.");
|
||||
Engine.Exit(1);
|
||||
}
|
||||
else if (inner is HttpListenerException && ((HttpListenerException)inner).ErrorCode == 183) // Windows
|
||||
{
|
||||
logger.Error(inner.Message + " Most likely Jackett is already running.");
|
||||
Engine.Exit(1);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
logger.Debug("Web server started");
|
||||
updater.StartUpdateChecker();
|
||||
}
|
||||
|
||||
public void ReserveUrls(bool doInstall = true)
|
||||
{
|
||||
logger.Debug("Unreserving Urls");
|
||||
config.GetListenAddresses(false).ToList().ForEach(u => RunNetSh(string.Format("http delete urlacl {0}", u)));
|
||||
config.GetListenAddresses(true).ToList().ForEach(u => RunNetSh(string.Format("http delete urlacl {0}", u)));
|
||||
if (doInstall)
|
||||
{
|
||||
logger.Debug("Reserving Urls");
|
||||
config.GetListenAddresses(config.AllowExternal).ToList().ForEach(u => RunNetSh(string.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", u)));
|
||||
logger.Debug("Urls reserved");
|
||||
}
|
||||
}
|
||||
|
||||
private void RunNetSh(string args)
|
||||
{
|
||||
processService.StartProcessAndLog("netsh.exe", args);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_server != null)
|
||||
{
|
||||
_server.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public string GetServerUrl(Object obj)
|
||||
{
|
||||
string serverUrl = "";
|
||||
|
||||
if (obj is HttpRequestMessage request)
|
||||
{
|
||||
var scheme = request.RequestUri.Scheme;
|
||||
var port = request.RequestUri.Port;
|
||||
|
||||
// Check for protocol headers added by reverse proxys
|
||||
// X-Forwarded-Proto: A de facto standard for identifying the originating protocol of an HTTP request
|
||||
var X_Forwarded_Proto = request.Headers.Where(x => x.Key == "X-Forwarded-Proto").Select(x => x.Value).FirstOrDefault();
|
||||
if (X_Forwarded_Proto != null)
|
||||
{
|
||||
scheme = X_Forwarded_Proto.First();
|
||||
}
|
||||
// Front-End-Https: Non-standard header field used by Microsoft applications and load-balancers
|
||||
else if (request.Headers.Where(x => x.Key == "Front-End-Https" && x.Value.FirstOrDefault() == "on").Any())
|
||||
{
|
||||
scheme = "https";
|
||||
}
|
||||
|
||||
// default to 443 if the Host header doesn't contain the port (needed for reverse proxy setups)
|
||||
if (scheme == "https" && !request.RequestUri.Authority.Contains(":"))
|
||||
port = 443;
|
||||
|
||||
serverUrl = string.Format("{0}://{1}:{2}{3}/", scheme, request.RequestUri.Host, port, BasePath());
|
||||
}
|
||||
|
||||
return serverUrl;
|
||||
}
|
||||
|
||||
public string GetBlackholeDirectory()
|
||||
{
|
||||
return config.BlackholeDir;
|
||||
}
|
||||
|
||||
public string GetApiKey()
|
||||
{
|
||||
return config.APIKey;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Configuration.Install;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.ServiceProcess;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
|
||||
namespace Jackett.Services
|
||||
{
|
||||
|
||||
public class ServiceConfigService : IServiceConfigService
|
||||
{
|
||||
private const string NAME = "Jackett";
|
||||
private const string DESCRIPTION = "Additional indexers for Sonarr";
|
||||
private const string SERVICEEXE = "JackettService.exe";
|
||||
|
||||
private IConfigurationService configService;
|
||||
private Logger logger;
|
||||
|
||||
public ServiceConfigService(IConfigurationService c, Logger l)
|
||||
{
|
||||
configService = c;
|
||||
logger = l;
|
||||
}
|
||||
|
||||
public bool ServiceExists()
|
||||
{
|
||||
return GetService(NAME) != null;
|
||||
}
|
||||
|
||||
public bool ServiceRunning()
|
||||
{
|
||||
var service = GetService(NAME);
|
||||
if (service == null)
|
||||
return false;
|
||||
return service.Status == ServiceControllerStatus.Running;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
|
||||
var service = GetService(NAME);
|
||||
service.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
var service = GetService(NAME);
|
||||
service.Stop();
|
||||
}
|
||||
|
||||
public ServiceController GetService(string serviceName)
|
||||
{
|
||||
return ServiceController.GetServices().FirstOrDefault(c => String.Equals(c.ServiceName, serviceName, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
public void Install()
|
||||
{
|
||||
if (ServiceExists())
|
||||
{
|
||||
logger.Warn("The service is already installed!");
|
||||
}
|
||||
else
|
||||
{
|
||||
var installer = new ServiceProcessInstaller
|
||||
{
|
||||
Account = ServiceAccount.LocalSystem
|
||||
};
|
||||
|
||||
var serviceInstaller = new ServiceInstaller();
|
||||
|
||||
var exePath = Path.Combine(configService.ApplicationFolder(), SERVICEEXE);
|
||||
if (!File.Exists(exePath) && Debugger.IsAttached)
|
||||
{
|
||||
exePath = Path.Combine(configService.ApplicationFolder(), "..\\..\\..\\Jackett.Service\\bin\\Debug", SERVICEEXE);
|
||||
}
|
||||
|
||||
string[] cmdline = { @"/assemblypath=" + exePath};
|
||||
|
||||
var context = new InstallContext("jackettservice_install.log", cmdline);
|
||||
serviceInstaller.Context = context;
|
||||
serviceInstaller.DisplayName = NAME;
|
||||
serviceInstaller.ServiceName = NAME;
|
||||
serviceInstaller.Description = DESCRIPTION;
|
||||
serviceInstaller.StartType = ServiceStartMode.Automatic;
|
||||
serviceInstaller.Parent = installer;
|
||||
|
||||
serviceInstaller.Install(new ListDictionary());
|
||||
}
|
||||
}
|
||||
|
||||
public void Uninstall()
|
||||
{
|
||||
RemoveService();
|
||||
|
||||
var serviceInstaller = new ServiceInstaller();
|
||||
var context = new InstallContext("jackettservice_uninstall.log", null);
|
||||
serviceInstaller.Context = context;
|
||||
serviceInstaller.ServiceName = NAME;
|
||||
serviceInstaller.Uninstall(null);
|
||||
|
||||
logger.Info("The service was uninstalled.");
|
||||
}
|
||||
|
||||
public void RemoveService()
|
||||
{
|
||||
var service = GetService(NAME);
|
||||
if(service == null)
|
||||
{
|
||||
logger.Warn("The service is already uninstalled");
|
||||
return;
|
||||
}
|
||||
if (service.Status != ServiceControllerStatus.Stopped)
|
||||
{
|
||||
service.Stop();
|
||||
service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(60));
|
||||
|
||||
service.Refresh();
|
||||
if (service.Status == ServiceControllerStatus.Stopped)
|
||||
logger.Info("Service stopped.");
|
||||
else
|
||||
logger.Error("Failed to stop the service");
|
||||
}
|
||||
else
|
||||
logger.Warn("The service was already stopped");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,209 +0,0 @@
|
|||
using Owin;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using Microsoft.Owin;
|
||||
using Jackett;
|
||||
using Microsoft.Owin.StaticFiles;
|
||||
using Microsoft.Owin.FileSystems;
|
||||
using System.Web.Http.Tracing;
|
||||
using Jackett.Utils;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Web.Http.Filters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Autofac.Integration.WebApi;
|
||||
using Jackett.Common;
|
||||
using Jackett.Common.Utils;
|
||||
|
||||
[assembly: OwinStartup(typeof(Startup))]
|
||||
namespace Jackett
|
||||
{
|
||||
class ApiExceptionHandler : IExceptionFilter
|
||||
{
|
||||
public bool AllowMultiple
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Task ExecuteExceptionFilterAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
|
||||
{
|
||||
string msg = "";
|
||||
var json = new JObject();
|
||||
var exception = actionExecutedContext.Exception;
|
||||
|
||||
Engine.Logger.Error(exception);
|
||||
|
||||
var message = exception.Message;
|
||||
if (exception.InnerException != null)
|
||||
message += ": " + exception.InnerException.Message;
|
||||
msg = message;
|
||||
|
||||
if (exception is ExceptionWithConfigData)
|
||||
json["config"] = ((ExceptionWithConfigData)exception).ConfigData.ToJson(null, false);
|
||||
|
||||
json["result"] = "error";
|
||||
json["error"] = msg;
|
||||
json["stacktrace"] = exception.StackTrace;
|
||||
if (exception.InnerException != null)
|
||||
json["innerstacktrace"] = exception.InnerException.StackTrace;
|
||||
|
||||
var response = actionExecutedContext.Request.CreateResponse();
|
||||
response.Content = new JsonContent(json);
|
||||
response.StatusCode = HttpStatusCode.InternalServerError;
|
||||
|
||||
actionExecutedContext.Response = response;
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
|
||||
static class JackettRouteExtensions
|
||||
{
|
||||
public static void ConfigureLegacyRoutes(this HttpRouteCollection routeCollection)
|
||||
{
|
||||
// Sonarr appends /api by default to all Torznab indexers, so we need that "ignored"
|
||||
// parameter to catch these as well.
|
||||
|
||||
// Legacy fallback for Torznab results
|
||||
routeCollection.MapHttpRoute(
|
||||
name: "LegacyTorznab",
|
||||
routeTemplate: "torznab/{indexerId}/{ignored}",
|
||||
defaults: new
|
||||
{
|
||||
controller = "Results",
|
||||
action = "Torznab",
|
||||
ignored = RouteParameter.Optional,
|
||||
}
|
||||
);
|
||||
|
||||
// Legacy fallback for Potato results
|
||||
routeCollection.MapHttpRoute(
|
||||
name: "LegacyPotato",
|
||||
routeTemplate: "potato/{indexerId}/{ignored}",
|
||||
defaults: new
|
||||
{
|
||||
controller = "Results",
|
||||
action = "Potato",
|
||||
ignored = RouteParameter.Optional,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class Startup
|
||||
{
|
||||
public void Configuration(IAppBuilder appBuilder)
|
||||
{
|
||||
// Configure Web API for self-host.
|
||||
var config = new HttpConfiguration();
|
||||
var jackettServerConfig = Engine.ServerConfig;
|
||||
|
||||
// try to fix SocketException crashes
|
||||
// based on http://stackoverflow.com/questions/23368885/signalr-owin-self-host-on-linux-mono-socketexception-when-clients-lose-connectio/30583109#30583109
|
||||
try
|
||||
{
|
||||
if (appBuilder.Properties.TryGetValue(typeof(HttpListener).FullName, out object httpListener) && httpListener is HttpListener)
|
||||
{
|
||||
// HttpListener should not return exceptions that occur when sending the response to the client
|
||||
((HttpListener)httpListener).IgnoreWriteExceptions = true;
|
||||
//Engine.Logger.Info("set HttpListener.IgnoreWriteExceptions = true");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Engine.Logger.Error(e, "Error while setting HttpListener.IgnoreWriteExceptions = true");
|
||||
}
|
||||
appBuilder.Use<WebApiRootRedirectMiddleware>();
|
||||
appBuilder.Use<LegacyApiRedirectMiddleware>();
|
||||
|
||||
// register exception handler
|
||||
config.Filters.Add(new ApiExceptionHandler());
|
||||
|
||||
;
|
||||
|
||||
// Setup tracing if enabled
|
||||
if (jackettServerConfig.RuntimeSettings.TracingEnabled)
|
||||
{
|
||||
config.EnableSystemDiagnosticsTracing();
|
||||
config.Services.Replace(typeof(ITraceWriter), new WebAPIToNLogTracer(jackettServerConfig));
|
||||
}
|
||||
// Add request logging if enabled
|
||||
if (jackettServerConfig.RuntimeSettings.LogRequests)
|
||||
config.MessageHandlers.Add(new WebAPIRequestLogger());
|
||||
|
||||
config.DependencyResolver = new AutofacWebApiDependencyResolver(Engine.GetContainer());
|
||||
|
||||
|
||||
config.MapHttpAttributeRoutes();
|
||||
|
||||
// Sonarr appends /api by default to all Torznab indexers, so we need that "ignored"
|
||||
// parameter to catch these as well.
|
||||
// (I'd rather not duplicate the whole route.)
|
||||
config.Routes.MapHttpRoute(
|
||||
name: "IndexerResultsAPI",
|
||||
routeTemplate: "api/v2.0/indexers/{indexerId}/results/{action}/{ignored}",
|
||||
defaults: new
|
||||
{
|
||||
controller = "Results",
|
||||
action = "Results",
|
||||
ignored = RouteParameter.Optional,
|
||||
}
|
||||
);
|
||||
|
||||
config.Routes.MapHttpRoute(
|
||||
name: "IndexerAPI",
|
||||
routeTemplate: "api/v2.0/indexers/{indexerId}/{action}",
|
||||
defaults: new
|
||||
{
|
||||
controller = "IndexerApi",
|
||||
indexerId = ""
|
||||
}
|
||||
);
|
||||
|
||||
config.Routes.MapHttpRoute(
|
||||
name: "ServerConfiguration",
|
||||
routeTemplate: "api/v2.0/server/{action}",
|
||||
defaults: new
|
||||
{
|
||||
controller = "ServerConfiguration"
|
||||
}
|
||||
);
|
||||
|
||||
config.Routes.MapHttpRoute(
|
||||
name: "WebUI",
|
||||
routeTemplate: "UI/{action}",
|
||||
defaults: new { controller = "WebUI" }
|
||||
);
|
||||
|
||||
config.Routes.MapHttpRoute(
|
||||
name: "download",
|
||||
routeTemplate: "dl/{indexerID}",
|
||||
defaults: new { controller = "Download", action = "Download" }
|
||||
);
|
||||
|
||||
config.Routes.MapHttpRoute(
|
||||
name: "blackhole",
|
||||
routeTemplate: "bh/{indexerID}",
|
||||
defaults: new { controller = "Blackhole", action = "Blackhole" }
|
||||
);
|
||||
|
||||
config.Routes.ConfigureLegacyRoutes();
|
||||
|
||||
appBuilder.UseWebApi(config);
|
||||
|
||||
|
||||
appBuilder.UseFileServer(new FileServerOptions
|
||||
{
|
||||
RequestPath = new PathString(string.Empty),
|
||||
FileSystem = new PhysicalFileSystem(Engine.ConfigService.GetContentFolder()),
|
||||
EnableDirectoryBrowsing = false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
using Jackett.Common;
|
||||
|
||||
namespace Jackett.Utils
|
||||
{
|
||||
public class JackettAuthorizedAttribute : AuthorizationFilterAttribute
|
||||
{
|
||||
public override void OnAuthorization(HttpActionContext actionContext)
|
||||
{
|
||||
// Skip authorisation on blank passwords
|
||||
if (string.IsNullOrEmpty(Engine.ServerConfig.AdminPassword))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Engine.SecurityService.CheckAuthorised(actionContext.Request))
|
||||
{
|
||||
if (actionContext.ControllerContext.ControllerDescriptor.ControllerType.GetCustomAttributes(true).Where(a => a.GetType() == typeof(AllowAnonymousAttribute)).Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (actionContext.ControllerContext.ControllerDescriptor.ControllerType.GetMethod(actionContext.ActionDescriptor.ActionName).GetCustomAttributes(true).Where(a => a.GetType() == typeof(AllowAnonymousAttribute)).Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode
|
||||
.Unauthorized);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
using Microsoft.Owin;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Jackett.Common;
|
||||
|
||||
namespace Jackett.Utils
|
||||
{
|
||||
public class WebApiRootRedirectMiddleware : OwinMiddleware
|
||||
{
|
||||
public WebApiRootRedirectMiddleware(OwinMiddleware next)
|
||||
: base(next)
|
||||
{
|
||||
//Ideally we'd dependency inject the server config into the middleware but AutoFac's Owin package has not been updated to support Autofac > 5
|
||||
}
|
||||
|
||||
public async override Task Invoke(IOwinContext context)
|
||||
{
|
||||
if (context.Request.Path != null && context.Request.Path.HasValue && context.Request.Path.Value.StartsWith(Engine.ServerConfig.RuntimeSettings.BasePath, StringComparison.Ordinal))
|
||||
{
|
||||
context.Request.Path = new PathString(context.Request.Path.Value.Substring(Engine.ServerConfig.RuntimeSettings.BasePath.Length));
|
||||
}
|
||||
|
||||
if (context.Request.Path == null || string.IsNullOrWhiteSpace(context.Request.Path.ToString()) || context.Request.Path.ToString() == "/")
|
||||
{
|
||||
// 301 is the status code of permanent redirect
|
||||
context.Response.StatusCode = 302;
|
||||
var redir = Engine.ServerConfig.RuntimeSettings.BasePath + "/UI/Dashboard";
|
||||
Engine.Logger.Info("redirecting to " + redir);
|
||||
context.Response.Headers.Set("Location", redir);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Next.Invoke(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LegacyApiRedirectMiddleware : OwinMiddleware
|
||||
{
|
||||
public LegacyApiRedirectMiddleware(OwinMiddleware next)
|
||||
: base(next)
|
||||
{
|
||||
}
|
||||
|
||||
public async override Task Invoke(IOwinContext context)
|
||||
{
|
||||
if (context.Request.Path == null || string.IsNullOrWhiteSpace(context.Request.Path.ToString()) || context.Request.Path.Value.StartsWith("/Admin", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// 301 is the status code of permanent redirect
|
||||
context.Response.StatusCode = 302;
|
||||
var redir = context.Request.Path.Value.Replace("/Admin", "/UI");
|
||||
Engine.Logger.Info("redirecting to " + redir);
|
||||
context.Response.Headers.Set("Location", redir);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Next.Invoke(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jackett.Common;
|
||||
|
||||
namespace Jackett.Utils
|
||||
{
|
||||
class WebAPIRequestLogger : DelegatingHandler
|
||||
{
|
||||
protected override async Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
//logging request body
|
||||
string requestBody = await request.Content.ReadAsStringAsync();
|
||||
Trace.WriteLine(requestBody);
|
||||
Engine.Logger.Debug(request.Method + ": " + request.RequestUri);
|
||||
Engine.Logger.Debug("Body: " + requestBody);
|
||||
|
||||
//let other handlers process the request
|
||||
return await base.SendAsync(request, cancellationToken)
|
||||
.ContinueWith(task =>
|
||||
{
|
||||
if (null != task.Result.Content)
|
||||
{
|
||||
//once response is ready, log it
|
||||
var responseBody = task.Result.Content.ReadAsStringAsync().Result;
|
||||
Trace.WriteLine(responseBody);
|
||||
Engine.Logger.Debug("Response: " + responseBody);
|
||||
}
|
||||
return task.Result;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http.Tracing;
|
||||
using Jackett.Common;
|
||||
using Jackett.Common.Models.Config;
|
||||
|
||||
namespace Jackett.Utils
|
||||
{
|
||||
public class WebAPIToNLogTracer : ITraceWriter
|
||||
{
|
||||
private ServerConfig _serverConfig;
|
||||
|
||||
public WebAPIToNLogTracer(ServerConfig serverConfig)
|
||||
{
|
||||
_serverConfig = serverConfig;
|
||||
}
|
||||
|
||||
public void Trace(HttpRequestMessage request, string category, TraceLevel level,
|
||||
Action<TraceRecord> traceAction)
|
||||
{
|
||||
if (_serverConfig.RuntimeSettings.TracingEnabled)
|
||||
{
|
||||
TraceRecord rec = new TraceRecord(request, category, level);
|
||||
traceAction(rec);
|
||||
WriteTrace(rec);
|
||||
}
|
||||
}
|
||||
|
||||
protected void WriteTrace(TraceRecord rec)
|
||||
{
|
||||
var message = string.Format("{0} {1} {2}", rec.Operator, rec.Operation, rec.Message);
|
||||
switch (rec.Level)
|
||||
{
|
||||
case TraceLevel.Debug:
|
||||
Engine.Logger.Debug(message);
|
||||
break;
|
||||
case TraceLevel.Error:
|
||||
Engine.Logger.Error(message);
|
||||
break;
|
||||
case TraceLevel.Fatal:
|
||||
Engine.Logger.Fatal(message);
|
||||
break;
|
||||
case TraceLevel.Info:
|
||||
Engine.Logger.Info(message);
|
||||
break;
|
||||
case TraceLevel.Off:
|
||||
// Do nothing?
|
||||
break;
|
||||
case TraceLevel.Warn:
|
||||
Engine.Logger.Warn(message);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
System.Diagnostics.Trace.WriteLine(message, rec.Category);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
using Autofac;
|
||||
using Autofac.Integration.WebApi;
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
public class WebApi2Module : Module
|
||||
{
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
builder.RegisterAssemblyTypes(typeof(WebApi2Module).Assembly).AsImplementedInterfaces().SingleInstance();
|
||||
builder.RegisterApiControllers(typeof(WebApi2Module).Assembly).InstancePerRequest();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue