mirror of
https://github.com/Jackett/Jackett.git
synced 2025-09-17 17:34:09 +02:00
Refactor
This commit is contained in:
409
src/Jackett/WebServer.cs
Normal file
409
src/Jackett/WebServer.cs
Normal file
@@ -0,0 +1,409 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Targets;
|
||||
using NLog.Windows.Forms;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
public class WebServer
|
||||
{
|
||||
public static bool IsWindows { get { return Environment.OSVersion.Platform == PlatformID.Win32NT; } }
|
||||
}
|
||||
/*
|
||||
public const int DefaultPort = 9117;
|
||||
public static int Port = DefaultPort;
|
||||
public static bool ListenPublic = true;
|
||||
|
||||
public static Server ServerInstance { get; private set; }
|
||||
public static bool IsFirstRun { get; private set; }
|
||||
public static Logger LoggerInstance { get; private set; }
|
||||
public static bool IsWindows { get { return Environment.OSVersion.Platform == PlatformID.Win32NT; } }
|
||||
public static string AppConfigDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Jackett");
|
||||
|
||||
HttpListener listener;
|
||||
IndexerManager indexerManager;
|
||||
WebApi webApi;
|
||||
SonarrApi sonarrApi;
|
||||
|
||||
|
||||
public WebServer()
|
||||
{
|
||||
// Allow all SSL.. sucks I know but mono on linux is having problems without it..
|
||||
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
|
||||
|
||||
ReadServerSettingsFile();
|
||||
LoadApiKey();
|
||||
|
||||
|
||||
indexerManager = new IndexerManager();
|
||||
sonarrApi = new SonarrApi();
|
||||
webApi = new WebApi(indexerManager, sonarrApi);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static void SetupLogging()
|
||||
{
|
||||
var logConfig = new LoggingConfiguration();
|
||||
|
||||
var logFile = new FileTarget();
|
||||
logConfig.AddTarget("file", logFile);
|
||||
logFile.Layout = "${longdate} ${level} ${message} \n ${exception:format=ToString}\n";
|
||||
logFile.FileName = Path.Combine(AppConfigDirectory, "log.txt");
|
||||
logFile.ArchiveFileName = "log.{#####}.txt";
|
||||
logFile.ArchiveAboveSize = 500000;
|
||||
logFile.MaxArchiveFiles = 1;
|
||||
logFile.KeepFileOpen = false;
|
||||
logFile.ArchiveNumbering = ArchiveNumberingMode.DateAndSequence;
|
||||
var logFileRule = new LoggingRule("*", LogLevel.Debug, logFile);
|
||||
logConfig.LoggingRules.Add(logFileRule);
|
||||
|
||||
if (WebServer.IsWindows)
|
||||
{
|
||||
#if !__MonoCS__
|
||||
var logAlert = new MessageBoxTarget();
|
||||
logConfig.AddTarget("alert", logAlert);
|
||||
logAlert.Layout = "${message}";
|
||||
logAlert.Caption = "Alert";
|
||||
var logAlertRule = new LoggingRule("*", LogLevel.Fatal, logAlert);
|
||||
logConfig.LoggingRules.Add(logAlertRule);
|
||||
#endif
|
||||
}
|
||||
|
||||
var logConsole = new ConsoleTarget();
|
||||
logConfig.AddTarget("console", logConsole);
|
||||
logConsole.Layout = "${longdate} ${level} ${message} ${exception:format=ToString}";
|
||||
var logConsoleRule = new LoggingRule("*", LogLevel.Debug, logConsole);
|
||||
logConfig.LoggingRules.Add(logConsoleRule);
|
||||
|
||||
LogManager.Configuration = logConfig;
|
||||
LoggerInstance = LogManager.GetCurrentClassLogger();
|
||||
}
|
||||
|
||||
void LoadApiKey()
|
||||
{
|
||||
var apiKeyFile = Path.Combine(WebServer.AppConfigDirectory, "api_key.txt");
|
||||
if (File.Exists(apiKeyFile))
|
||||
ApiKey.CurrentKey = File.ReadAllText(apiKeyFile).Trim();
|
||||
else
|
||||
{
|
||||
ApiKey.CurrentKey = ApiKey.Generate();
|
||||
File.WriteAllText(apiKeyFile, ApiKey.CurrentKey);
|
||||
}
|
||||
}
|
||||
|
||||
static void CreateOrMigrateSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(AppConfigDirectory))
|
||||
{
|
||||
IsFirstRun = true;
|
||||
Directory.CreateDirectory(AppConfigDirectory);
|
||||
}
|
||||
Console.WriteLine("App config/log directory: " + AppConfigDirectory);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("Could not create settings directory. " + ex.Message);
|
||||
Application.Exit();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string oldDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Jackett");
|
||||
if (Directory.Exists(oldDir) && !Directory.Exists(AppConfigDirectory))
|
||||
{
|
||||
Directory.Move(oldDir, AppConfigDirectory);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("ERROR could not migrate settings directory " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task Start()
|
||||
{
|
||||
CreateOrMigrateSettings();
|
||||
WebServer.LoggerInstance.Info("Starting HTTP server on port " + Port + " listening " + (ListenPublic ? "publicly" : "privately"));
|
||||
|
||||
try
|
||||
{
|
||||
listener = new HttpListener();
|
||||
|
||||
if (ListenPublic)
|
||||
{
|
||||
listener.Prefixes.Add(string.Format("http://*:{0}/", Port));
|
||||
}
|
||||
else
|
||||
{
|
||||
listener.Prefixes.Add(string.Format("http://127.0.0.1:{0}/", Port));
|
||||
}
|
||||
|
||||
listener.Start();
|
||||
webApi.server = this;
|
||||
}
|
||||
catch (HttpListenerException ex)
|
||||
{
|
||||
if (ex.ErrorCode == 5)
|
||||
{
|
||||
var errorStr = "App must be ran as admin for permission to use port "
|
||||
+ Port + Environment.NewLine + "Restart app with admin privileges?";
|
||||
if (WebServer.IsWindows)
|
||||
{
|
||||
var dialogResult = MessageBox.Show(errorStr, "Error", MessageBoxButtons.YesNo);
|
||||
if (dialogResult == DialogResult.No)
|
||||
{
|
||||
Application.Exit();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// WebServer.RestartAsAdmin();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WebServer.LoggerInstance.Fatal("Failed to start HTTP WebServer. " + ex.Message, ex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WebServer.LoggerInstance.Error(ex, "Error starting HTTP server: " + ex.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
WebServer.LoggerInstance.Info("Server started on port " + Port);
|
||||
WebServer.LoggerInstance.Info("Accepting only requests from local system: " + (!ListenPublic));
|
||||
|
||||
while (true)
|
||||
{
|
||||
Exception error = null;
|
||||
try
|
||||
{
|
||||
error = null;
|
||||
var context = await listener.GetContextAsync();
|
||||
ProcessHttpRequest(context);
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
WebServer.LoggerInstance.Error(ex, "Critical error, HTTP listener was destroyed");
|
||||
Process.GetCurrentProcess().Kill();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex;
|
||||
WebServer.LoggerInstance.Error(ex, "Error processing HTTP request");
|
||||
}
|
||||
|
||||
if (error != null)
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void ReadSettingsFile()
|
||||
{
|
||||
var path = Path.Combine(AppConfigDirectory, "config.json");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
JObject f = new JObject();
|
||||
f.Add("port", WebServer.DefaultPort);
|
||||
f.Add("public", true);
|
||||
File.WriteAllText(path, f.ToString());
|
||||
}
|
||||
|
||||
var configJson = JObject.Parse(File.ReadAllText(path));
|
||||
int port = (int)configJson.GetValue("port");
|
||||
WebServer.Port = port;
|
||||
|
||||
WebServer.ListenPublic = (bool)configJson.GetValue("public");
|
||||
|
||||
Console.WriteLine("Config file path: " + path);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
listener.Stop();
|
||||
listener.Abort();
|
||||
}
|
||||
|
||||
async void ProcessHttpRequest(HttpListenerContext context)
|
||||
{
|
||||
WebServer.LoggerInstance.Trace("Received request: " + context.Request.Url.ToString());
|
||||
Exception exception = null;
|
||||
try
|
||||
{
|
||||
if (await webApi.HandleRequest(context))
|
||||
{
|
||||
|
||||
}
|
||||
else if (context.Request.Url.AbsolutePath.StartsWith("/api/"))
|
||||
{
|
||||
await ProcessTorznab(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
var responseBytes = Encoding.UTF8.GetBytes("Invalid request");
|
||||
await context.Response.OutputStream.WriteAsync(responseBytes, 0, responseBytes.Length);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
WebServer.LoggerInstance.Error(ex, ex.Message + ex.ToString());
|
||||
}
|
||||
|
||||
if (exception != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var errorBytes = Encoding.UTF8.GetBytes(exception.Message);
|
||||
await context.Response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
context.Response.Close();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async Task ProcessTorznab(HttpListenerContext context)
|
||||
{
|
||||
|
||||
var query = HttpUtility.ParseQueryString(context.Request.Url.Query);
|
||||
var inputStream = context.Request.InputStream;
|
||||
|
||||
var indexerId = context.Request.Url.Segments[2].TrimEnd('/').ToLower();
|
||||
var indexer = indexerManager.GetIndexer(indexerId);
|
||||
|
||||
if (context.Request.Url.Segments.Length > 4 && context.Request.Url.Segments[3] == "download/")
|
||||
{
|
||||
var downloadSegment = HttpServerUtility.UrlTokenDecode(context.Request.Url.Segments[4].TrimEnd('/'));
|
||||
var downloadLink = Encoding.UTF8.GetString(downloadSegment);
|
||||
var downloadBytes = await indexer.Download(new Uri(downloadLink));
|
||||
await context.Response.OutputStream.WriteAsync(downloadBytes, 0, downloadBytes.Length);
|
||||
return;
|
||||
}
|
||||
|
||||
var torznabQuery = TorznabQuery.FromHttpQuery(query);
|
||||
|
||||
if (torznabQuery.RageID != 0)
|
||||
torznabQuery.ShowTitles = await sonarrApi.GetShowTitle(torznabQuery.RageID);
|
||||
else if (!string.IsNullOrEmpty(torznabQuery.SearchTerm))
|
||||
torznabQuery.ShowTitles = new string[] { torznabQuery.SearchTerm };
|
||||
|
||||
var releases = await indexer.PerformQuery(torznabQuery);
|
||||
|
||||
WebServer.LoggerInstance.Debug(string.Format("Found {0} releases from {1}", releases.Length, indexer.DisplayName));
|
||||
|
||||
var severUrl = string.Format("{0}://{1}:{2}/", context.Request.Url.Scheme, context.Request.Url.Host, context.Request.Url.Port);
|
||||
|
||||
var resultPage = new ResultPage(new ChannelInfo
|
||||
{
|
||||
Title = indexer.DisplayName,
|
||||
Description = indexer.DisplayDescription,
|
||||
Link = indexer.SiteLink,
|
||||
ImageUrl = new Uri(severUrl + "logos/" + indexerId + ".png"),
|
||||
ImageTitle = indexer.DisplayName,
|
||||
ImageLink = indexer.SiteLink,
|
||||
ImageDescription = indexer.DisplayName
|
||||
});
|
||||
|
||||
// add Jackett proxy to download links...
|
||||
foreach (var release in releases)
|
||||
{
|
||||
if (release.Link == null || release.Link.Scheme == "magnet")
|
||||
continue;
|
||||
var originalLink = release.Link;
|
||||
var encodedLink = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(originalLink.ToString())) + "/download.torrent";
|
||||
var proxyLink = string.Format("{0}api/{1}/download/{2}", severUrl, indexerId, encodedLink);
|
||||
release.Link = new Uri(proxyLink);
|
||||
}
|
||||
|
||||
resultPage.Releases.AddRange(releases);
|
||||
|
||||
var xml = resultPage.ToXml(new Uri(severUrl));
|
||||
|
||||
var responseBytes = Encoding.UTF8.GetBytes(xml);
|
||||
context.Response.ContentEncoding = Encoding.UTF8;
|
||||
context.Response.ContentLength64 = responseBytes.LongLength;
|
||||
context.Response.ContentType = "application/rss+xml";
|
||||
await context.Response.OutputStream.WriteAsync(responseBytes, 0, responseBytes.Length);
|
||||
|
||||
}
|
||||
|
||||
public static JObject ReadServerSettingsFile()
|
||||
{
|
||||
var path = ServerConfigFile;
|
||||
JObject jsonReply = new JObject();
|
||||
if (File.Exists(path))
|
||||
{
|
||||
jsonReply = JObject.Parse(File.ReadAllText(path));
|
||||
Port = (int)jsonReply["port"];
|
||||
ListenPublic = (bool)jsonReply["public"];
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonReply["port"] = Port;
|
||||
jsonReply["public"] = ListenPublic;
|
||||
}
|
||||
return jsonReply;
|
||||
}
|
||||
|
||||
public static Task<int> ApplyPortConfiguration(JToken json)
|
||||
{
|
||||
JObject jsonObject = (JObject)json;
|
||||
JToken jJackettPort = jsonObject.GetValue("port");
|
||||
int jackettPort;
|
||||
if (!ServerUtil.IsPort(jJackettPort.ToString()))
|
||||
throw new CustomException("The value entered is not a valid port");
|
||||
else
|
||||
jackettPort = int.Parse(jJackettPort.ToString());
|
||||
|
||||
if (jackettPort == Port)
|
||||
throw new CustomException("The current port is the same as the one being used now.");
|
||||
else if (ServerUtil.RestrictedPorts.Contains(jackettPort))
|
||||
throw new CustomException("This port is not allowed due to it not being safe.");
|
||||
SaveSettings(jackettPort);
|
||||
|
||||
return Task.FromResult(jackettPort);
|
||||
}
|
||||
|
||||
private static string ServerConfigFile = Path.Combine(WebServer.AppConfigDirectory, "config.json");
|
||||
|
||||
private static void SaveSettings(int jacketPort)
|
||||
{
|
||||
JObject json = new JObject();
|
||||
json["port"] = jacketPort;
|
||||
json["public"] = ListenPublic;
|
||||
File.WriteAllText(ServerConfigFile, json.ToString());
|
||||
}
|
||||
|
||||
}*/
|
||||
}
|
Reference in New Issue
Block a user