From 6f5fb7e0bb747c6980b93d2354f1d53af1a29d66 Mon Sep 17 00:00:00 2001 From: Dity <125827669+db-2001@users.noreply.github.com> Date: Sun, 7 Apr 2024 18:20:28 -0400 Subject: [PATCH] API rewrite to parse JSON body for POST and DELETE --- Tranga/Server.cs | 160 ++++++++++++++++++++++++++++------------------- 1 file changed, 94 insertions(+), 66 deletions(-) diff --git a/Tranga/Server.cs b/Tranga/Server.cs index cf2d15a..8adaecc 100644 --- a/Tranga/Server.cs +++ b/Tranga/Server.cs @@ -12,17 +12,17 @@ namespace Tranga; public class Server : GlobalBase { - private readonly HttpListener _listener = new (); + private readonly HttpListener _listener = new(); private readonly Tranga _parent; - + public Server(Tranga parent) : base(parent) { this._parent = parent; - if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) this._listener.Prefixes.Add($"http://*:{settings.apiPortNumber}/"); else this._listener.Prefixes.Add($"http://localhost:{settings.apiPortNumber}/"); - Thread listenThread = new (Listen); + Thread listenThread = new(Listen); listenThread.Start(); Thread watchThread = new(WatchRunning); watchThread.Start(); @@ -30,7 +30,7 @@ public class Server : GlobalBase private void WatchRunning() { - while(_parent.keepRunning) + while (_parent.keepRunning) Thread.Sleep(1000); this._listener.Close(); } @@ -38,7 +38,7 @@ public class Server : GlobalBase private void Listen() { this._listener.Start(); - foreach(string prefix in this._listener.Prefixes) + foreach (string prefix in this._listener.Prefixes) Log($"Listening on {prefix}"); while (this._listener.IsListening && _parent.keepRunning) { @@ -54,7 +54,7 @@ public class Server : GlobalBase } catch (HttpListenerException) { - + } } } @@ -63,9 +63,9 @@ public class Server : GlobalBase { HttpListenerRequest request = context.Request; HttpListenerResponse response = context.Response; - if(request.HttpMethod == "OPTIONS") + if (request.HttpMethod == "OPTIONS") SendResponse(HttpStatusCode.OK, context.Response); - if(request.Url!.LocalPath.Contains("favicon")) + if (request.Url!.LocalPath.Contains("favicon")) SendResponse(HttpStatusCode.NoContent, response); switch (request.HttpMethod) @@ -79,16 +79,16 @@ public class Server : GlobalBase case "DELETE": HandleDelete(request, response); break; - default: + default: SendResponse(HttpStatusCode.BadRequest, response); break; } } - + private Dictionary GetRequestVariables(string query) { Dictionary ret = new(); - Regex queryRex = new (@"\?{1}&?([A-z0-9-=]+=[A-z0-9-=]+)+(&[A-z0-9-=]+=[A-z0-9-=]+)*"); + Regex queryRex = new(@"\?{1}&?([A-z0-9-=]+=[A-z0-9-=]+)+(&[A-z0-9-=]+=[A-z0-9-=]+)*"); if (!queryRex.IsMatch(query)) return ret; query = query.Substring(1); @@ -102,6 +102,22 @@ public class Server : GlobalBase return ret; } + private Dictionary GetRequestBody(HttpListenerRequest request) + { + if (!request.HasEntityBody) + { + Log("No request body"); + Dictionary emptyBody = new(); + return emptyBody; + } + Stream body = request.InputStream; + Encoding encoding = request.ContentEncoding; + StreamReader reader = new StreamReader(body, encoding); + string s = reader.ReadToEnd(); + Dictionary requestBody = JsonConvert.DeserializeObject>(s); + return requestBody; + } + private void HandleGet(HttpListenerRequest request, HttpListenerResponse response) { Dictionary requestVariables = GetRequestVariables(request.Url!.Query); @@ -265,7 +281,13 @@ public class Server : GlobalBase private void HandlePost(HttpListenerRequest request, HttpListenerResponse response) { - Dictionary requestVariables = GetRequestVariables(request.Url!.Query); + Dictionary requestVariables = GetRequestVariables(request.Url!.Query); //Variables in the URI + Dictionary requestBody = GetRequestBody(request); //Variables in the JSON body + Dictionary requestParams = new(); //The actual variable used for the API + + //Concatenate the two dictionaries for compatibility with older versions of front-ends + requestParams = requestVariables.Concat(requestBody).ToDictionary(x => x.Key, x => x.Value); + string? connectorName, internalId, jobId, chapterNumStr, customFolderName, translatedLanguage, notificationConnectorStr, libraryConnectorStr; MangaConnector? connector; Manga? tmpManga; @@ -277,7 +299,7 @@ public class Server : GlobalBase switch (path) { case "Manga": - if(!requestVariables.TryGetValue("internalId", out internalId) || + if(!requestParams.TryGetValue("internalId", out internalId) || !_parent.TryGetPublicationById(internalId, out tmpManga)) { SendResponse(HttpStatusCode.BadRequest, response); @@ -287,9 +309,9 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.OK, response, manga); break; case "Jobs/MonitorManga": - if(!requestVariables.TryGetValue("connector", out connectorName) || - !requestVariables.TryGetValue("internalId", out internalId) || - !requestVariables.TryGetValue("interval", out string? intervalStr) || + if(!requestParams.TryGetValue("connector", out connectorName) || + !requestParams.TryGetValue("internalId", out internalId) || + !requestParams.TryGetValue("interval", out string? intervalStr) || !_parent.TryGetConnector(connectorName, out connector)|| !_parent.TryGetPublicationById(internalId, out tmpManga) || !TimeSpan.TryParse(intervalStr, out TimeSpan interval)) @@ -300,7 +322,7 @@ public class Server : GlobalBase manga = (Manga)tmpManga!; - if (requestVariables.TryGetValue("ignoreBelowChapterNum", out chapterNumStr)) + if (requestParams.TryGetValue("ignoreBelowChapterNum", out chapterNumStr)) { if (!float.TryParse(chapterNumStr, numberFormatDecimalPoint, out float chapterNum)) { @@ -310,16 +332,16 @@ public class Server : GlobalBase manga.ignoreChaptersBelow = chapterNum; } - if (requestVariables.TryGetValue("customFolderName", out customFolderName)) + if (requestParams.TryGetValue("customFolderName", out customFolderName)) manga.MovePublicationFolder(settings.downloadLocation, customFolderName); - requestVariables.TryGetValue("translatedLanguage", out translatedLanguage); + requestParams.TryGetValue("translatedLanguage", out translatedLanguage); _parent.jobBoss.AddJob(new DownloadNewChapters(this, connector!, manga, true, interval, translatedLanguage: translatedLanguage??"en")); SendResponse(HttpStatusCode.Accepted, response); break; case "Jobs/DownloadNewChapters": - if(!requestVariables.TryGetValue("connector", out connectorName) || - !requestVariables.TryGetValue("internalId", out internalId) || + if(!requestParams.TryGetValue("connector", out connectorName) || + !requestParams.TryGetValue("internalId", out internalId) || !_parent.TryGetConnector(connectorName, out connector)|| !_parent.TryGetPublicationById(internalId, out tmpManga)) { @@ -329,7 +351,7 @@ public class Server : GlobalBase manga = (Manga)tmpManga!; - if (requestVariables.TryGetValue("ignoreBelowChapterNum", out chapterNumStr)) + if (requestParams.TryGetValue("ignoreBelowChapterNum", out chapterNumStr)) { if (!float.TryParse(chapterNumStr, numberFormatDecimalPoint, out float chapterNum)) { @@ -339,15 +361,15 @@ public class Server : GlobalBase manga.ignoreChaptersBelow = chapterNum; } - if (requestVariables.TryGetValue("customFolderName", out customFolderName)) + if (requestParams.TryGetValue("customFolderName", out customFolderName)) manga.MovePublicationFolder(settings.downloadLocation, customFolderName); - requestVariables.TryGetValue("translatedLanguage", out translatedLanguage); + requestParams.TryGetValue("translatedLanguage", out translatedLanguage); _parent.jobBoss.AddJob(new DownloadNewChapters(this, connector!, manga, false, translatedLanguage: translatedLanguage??"en")); SendResponse(HttpStatusCode.Accepted, response); break; case "Jobs/UpdateMetadata": - if (!requestVariables.TryGetValue("internalId", out internalId)) + if (!requestParams.TryGetValue("internalId", out internalId)) { foreach (Job pJob in _parent.jobBoss.jobs.Where(possibleDncJob => possibleDncJob.jobType is Job.JobType.DownloadNewChaptersJob).ToArray())//ToArray to avoid modyifying while adding new jobs @@ -375,7 +397,7 @@ public class Server : GlobalBase } break; case "Jobs/StartNow": - if (!requestVariables.TryGetValue("jobId", out jobId) || + if (!requestParams.TryGetValue("jobId", out jobId) || !_parent.jobBoss.TryGetJobById(jobId, out job)) { SendResponse(HttpStatusCode.BadRequest, response); @@ -385,7 +407,7 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.Accepted, response); break; case "Jobs/Cancel": - if (!requestVariables.TryGetValue("jobId", out jobId) || + if (!requestParams.TryGetValue("jobId", out jobId) || !_parent.jobBoss.TryGetJobById(jobId, out job)) { SendResponse(HttpStatusCode.BadRequest, response); @@ -395,8 +417,8 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.Accepted, response); break; case "Settings/UpdateDownloadLocation": - if (!requestVariables.TryGetValue("downloadLocation", out string? downloadLocation) || - !requestVariables.TryGetValue("moveFiles", out string? moveFilesStr) || + if (!requestParams.TryGetValue("downloadLocation", out string? downloadLocation) || + !requestParams.TryGetValue("moveFiles", out string? moveFilesStr) || !Boolean.TryParse(moveFilesStr, out bool moveFiles)) { SendResponse(HttpStatusCode.BadRequest, response); @@ -406,7 +428,7 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.Accepted, response); break; /*case "Settings/UpdateWorkingDirectory": - if (!requestVariables.TryGetValue("workingDirectory", out string? workingDirectory)) + if (!requestParams.TryGetValue("workingDirectory", out string? workingDirectory)) { SendResponse(HttpStatusCode.BadRequest, response); break; @@ -415,7 +437,7 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.Accepted, response); break;*/ case "Settings/userAgent": - if(!requestVariables.TryGetValue("userAgent", out string? customUserAgent)) + if(!requestParams.TryGetValue("userAgent", out string? customUserAgent)) { SendResponse(HttpStatusCode.BadRequest, response); break; @@ -428,8 +450,8 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.Accepted, response); break; case "Settings/customRequestLimit": - if (!requestVariables.TryGetValue("requestType", out string? requestTypeStr) || - !requestVariables.TryGetValue("requestsPerMinute", out string? requestsPerMinuteStr) || + if (!requestParams.TryGetValue("requestType", out string? requestTypeStr) || + !requestParams.TryGetValue("requestsPerMinute", out string? requestsPerMinuteStr) || !Enum.TryParse(requestTypeStr, out RequestType requestType) || !int.TryParse(requestsPerMinuteStr, out int requestsPerMinute)) { @@ -450,7 +472,7 @@ public class Server : GlobalBase settings.ExportSettings(); break; case "NotificationConnectors/Update": - if (!requestVariables.TryGetValue("notificationConnector", out notificationConnectorStr) || + if (!requestParams.TryGetValue("notificationConnector", out notificationConnectorStr) || !Enum.TryParse(notificationConnectorStr, out notificationConnectorType)) { SendResponse(HttpStatusCode.BadRequest, response); @@ -459,8 +481,8 @@ public class Server : GlobalBase if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Gotify) { - if (!requestVariables.TryGetValue("gotifyUrl", out string? gotifyUrl) || - !requestVariables.TryGetValue("gotifyAppToken", out string? gotifyAppToken)) + if (!requestParams.TryGetValue("gotifyUrl", out string? gotifyUrl) || + !requestParams.TryGetValue("gotifyAppToken", out string? gotifyAppToken)) { SendResponse(HttpStatusCode.BadRequest, response); break; @@ -469,7 +491,7 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.Accepted, response); }else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.LunaSea) { - if (!requestVariables.TryGetValue("lunaseaWebhook", out string? lunaseaWebhook)) + if (!requestParams.TryGetValue("lunaseaWebhook", out string? lunaseaWebhook)) { SendResponse(HttpStatusCode.BadRequest, response); break; @@ -478,8 +500,8 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.Accepted, response); }else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Ntfy) { - if (!requestVariables.TryGetValue("ntfyUrl", out string? ntfyUrl) || - !requestVariables.TryGetValue("ntfyAuth", out string? ntfyAuth)) + if (!requestParams.TryGetValue("ntfyUrl", out string? ntfyUrl) || + !requestParams.TryGetValue("ntfyAuth", out string? ntfyAuth)) { SendResponse(HttpStatusCode.BadRequest, response); break; @@ -494,7 +516,7 @@ public class Server : GlobalBase break; case "NotificationConnectors/Test": NotificationConnector notificationConnector; - if (!requestVariables.TryGetValue("notificationConnector", out notificationConnectorStr) || + if (!requestParams.TryGetValue("notificationConnector", out notificationConnectorStr) || !Enum.TryParse(notificationConnectorStr, out notificationConnectorType)) { SendResponse(HttpStatusCode.BadRequest, response); @@ -503,8 +525,8 @@ public class Server : GlobalBase if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Gotify) { - if (!requestVariables.TryGetValue("gotifyUrl", out string? gotifyUrl) || - !requestVariables.TryGetValue("gotifyAppToken", out string? gotifyAppToken)) + if (!requestParams.TryGetValue("gotifyUrl", out string? gotifyUrl) || + !requestParams.TryGetValue("gotifyAppToken", out string? gotifyAppToken)) { SendResponse(HttpStatusCode.BadRequest, response); break; @@ -512,7 +534,7 @@ public class Server : GlobalBase notificationConnector = new Gotify(this, gotifyUrl, gotifyAppToken); }else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.LunaSea) { - if (!requestVariables.TryGetValue("lunaseaWebhook", out string? lunaseaWebhook)) + if (!requestParams.TryGetValue("lunaseaWebhook", out string? lunaseaWebhook)) { SendResponse(HttpStatusCode.BadRequest, response); break; @@ -520,8 +542,8 @@ public class Server : GlobalBase notificationConnector = new LunaSea(this, lunaseaWebhook); }else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Ntfy) { - if (!requestVariables.TryGetValue("ntfyUrl", out string? ntfyUrl) || - !requestVariables.TryGetValue("ntfyAuth", out string? ntfyAuth)) + if (!requestParams.TryGetValue("ntfyUrl", out string? ntfyUrl) || + !requestParams.TryGetValue("ntfyAuth", out string? ntfyAuth)) { SendResponse(HttpStatusCode.BadRequest, response); break; @@ -538,7 +560,7 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.Accepted, response); break; case "NotificationConnectors/Reset": - if (!requestVariables.TryGetValue("notificationConnector", out notificationConnectorStr) || + if (!requestParams.TryGetValue("notificationConnector", out notificationConnectorStr) || !Enum.TryParse(notificationConnectorStr, out notificationConnectorType)) { SendResponse(HttpStatusCode.BadRequest, response); @@ -548,7 +570,7 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.Accepted, response); break; case "LibraryConnectors/Update": - if (!requestVariables.TryGetValue("libraryConnector", out libraryConnectorStr) || + if (!requestParams.TryGetValue("libraryConnector", out libraryConnectorStr) || !Enum.TryParse(libraryConnectorStr, out libraryConnectorType)) { SendResponse(HttpStatusCode.BadRequest, response); @@ -557,9 +579,9 @@ public class Server : GlobalBase if (libraryConnectorType is LibraryConnector.LibraryType.Kavita) { - if (!requestVariables.TryGetValue("kavitaUrl", out string? kavitaUrl) || - !requestVariables.TryGetValue("kavitaUsername", out string? kavitaUsername) || - !requestVariables.TryGetValue("kavitaPassword", out string? kavitaPassword)) + if (!requestParams.TryGetValue("kavitaUrl", out string? kavitaUrl) || + !requestParams.TryGetValue("kavitaUsername", out string? kavitaUsername) || + !requestParams.TryGetValue("kavitaPassword", out string? kavitaPassword)) { SendResponse(HttpStatusCode.BadRequest, response); break; @@ -568,8 +590,8 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.Accepted, response); }else if (libraryConnectorType is LibraryConnector.LibraryType.Komga) { - if (!requestVariables.TryGetValue("komgaUrl", out string? komgaUrl) || - !requestVariables.TryGetValue("komgaAuth", out string? komgaAuth)) + if (!requestParams.TryGetValue("komgaUrl", out string? komgaUrl) || + !requestParams.TryGetValue("komgaAuth", out string? komgaAuth)) { SendResponse(HttpStatusCode.BadRequest, response); break; @@ -584,7 +606,7 @@ public class Server : GlobalBase break; case "LibraryConnectors/Test": LibraryConnector libraryConnector; - if (!requestVariables.TryGetValue("libraryConnector", out libraryConnectorStr) || + if (!requestParams.TryGetValue("libraryConnector", out libraryConnectorStr) || !Enum.TryParse(libraryConnectorStr, out libraryConnectorType)) { SendResponse(HttpStatusCode.BadRequest, response); @@ -593,9 +615,9 @@ public class Server : GlobalBase if (libraryConnectorType is LibraryConnector.LibraryType.Kavita) { - if (!requestVariables.TryGetValue("kavitaUrl", out string? kavitaUrl) || - !requestVariables.TryGetValue("kavitaUsername", out string? kavitaUsername) || - !requestVariables.TryGetValue("kavitaPassword", out string? kavitaPassword)) + if (!requestParams.TryGetValue("kavitaUrl", out string? kavitaUrl) || + !requestParams.TryGetValue("kavitaUsername", out string? kavitaUsername) || + !requestParams.TryGetValue("kavitaPassword", out string? kavitaPassword)) { SendResponse(HttpStatusCode.BadRequest, response); break; @@ -603,8 +625,8 @@ public class Server : GlobalBase libraryConnector = new Kavita(this, kavitaUrl, kavitaUsername, kavitaPassword); }else if (libraryConnectorType is LibraryConnector.LibraryType.Komga) { - if (!requestVariables.TryGetValue("komgaUrl", out string? komgaUrl) || - !requestVariables.TryGetValue("komgaAuth", out string? komgaAuth)) + if (!requestParams.TryGetValue("komgaUrl", out string? komgaUrl) || + !requestParams.TryGetValue("komgaAuth", out string? komgaAuth)) { SendResponse(HttpStatusCode.BadRequest, response); break; @@ -620,7 +642,7 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.Accepted, response); break; case "LibraryConnectors/Reset": - if (!requestVariables.TryGetValue("libraryConnector", out libraryConnectorStr) || + if (!requestParams.TryGetValue("libraryConnector", out libraryConnectorStr) || !Enum.TryParse(libraryConnectorStr, out libraryConnectorType)) { SendResponse(HttpStatusCode.BadRequest, response); @@ -637,7 +659,13 @@ public class Server : GlobalBase private void HandleDelete(HttpListenerRequest request, HttpListenerResponse response) { - Dictionary requestVariables = GetRequestVariables(request.Url!.Query); + Dictionary requestVariables = GetRequestVariables(request.Url!.Query); //Variables in the URI + Dictionary requestBody = GetRequestBody(request); //Variables in the JSON body + Dictionary requestParams = new(); //The actual variable used for the API + + //Concatenate the two dictionaries for compatibility with older versions of front-ends + requestParams = requestVariables.Concat(requestBody).ToDictionary(x => x.Key, x => x.Value); + string? connectorName, internalId; MangaConnector connector; Manga manga; @@ -645,7 +673,7 @@ public class Server : GlobalBase switch (path) { case "Jobs": - if (!requestVariables.TryGetValue("jobId", out string? jobId) || + if (!requestParams.TryGetValue("jobId", out string? jobId) || !_parent.jobBoss.TryGetJobById(jobId, out Job? job)) { SendResponse(HttpStatusCode.BadRequest, response); @@ -655,8 +683,8 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.Accepted, response); break; case "Jobs/DownloadNewChapters": - if(!requestVariables.TryGetValue("connector", out connectorName) || - !requestVariables.TryGetValue("internalId", out internalId) || + if(!requestParams.TryGetValue("connector", out connectorName) || + !requestParams.TryGetValue("internalId", out internalId) || _parent.GetConnector(connectorName) is null || _parent.GetPublicationById(internalId) is null) { @@ -669,7 +697,7 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.Accepted, response); break; case "NotificationConnectors": - if (!requestVariables.TryGetValue("notificationConnector", out string? notificationConnectorStr) || + if (!requestParams.TryGetValue("notificationConnector", out string? notificationConnectorStr) || !Enum.TryParse(notificationConnectorStr, out NotificationConnector.NotificationConnectorType notificationConnectorType)) { SendResponse(HttpStatusCode.BadRequest, response); @@ -679,7 +707,7 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.Accepted, response); break; case "LibraryConnectors": - if (!requestVariables.TryGetValue("libraryConnectors", out string? libraryConnectorStr) || + if (!requestParams.TryGetValue("libraryConnector", out string? libraryConnectorStr) || !Enum.TryParse(libraryConnectorStr, out LibraryConnector.LibraryType libraryConnectoryType)) {