From e326736021be8ff5af4208d16f59d5e3e4f22b3e Mon Sep 17 00:00:00 2001 From: vnugent Date: Wed, 13 Mar 2024 16:19:50 -0400 Subject: Squashed commit of the following: commit 1e08c6d2112459dc02a0ab873123c4a363b01d21 Author: vnugent Date: Wed Mar 13 16:17:58 2024 -0400 ci: verified container build ready for next release commit 85a1e5b7cc5c99e97a2d4e99bbceb0d2139742ff Author: vnugent Date: Tue Mar 12 22:05:16 2024 -0400 ci: exciting bare-metal build process, os support, smaller packages commit 748cdbf4880d830fd794e92856e8c35a46e4f884 Author: vnugent Date: Mon Mar 11 21:21:18 2024 -0400 feat(app): #1 update libs & add curl support --- back-end/src/Endpoints/SiteLookupEndpoint.cs | 156 +++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 back-end/src/Endpoints/SiteLookupEndpoint.cs (limited to 'back-end/src/Endpoints/SiteLookupEndpoint.cs') diff --git a/back-end/src/Endpoints/SiteLookupEndpoint.cs b/back-end/src/Endpoints/SiteLookupEndpoint.cs new file mode 100644 index 0000000..effe6aa --- /dev/null +++ b/back-end/src/Endpoints/SiteLookupEndpoint.cs @@ -0,0 +1,156 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; +using System.Net; +using System.Text; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; + +using VNLib.Utils; +using VNLib.Utils.Memory; +using VNLib.Plugins; +using VNLib.Plugins.Essentials; +using VNLib.Plugins.Essentials.Endpoints; +using VNLib.Plugins.Essentials.Extensions; +using VNLib.Plugins.Extensions.Loading; +using VNLib.Plugins.Extensions.Validation; + +using SimpleBookmark.PlatformFeatures.Curl; + +namespace SimpleBookmark.Endpoints +{ + [ConfigurationName("curl")] + internal sealed class SiteLookupEndpoint : ProtectedWebEndpoint + { + const string DefaultCurlExecName = "curl"; + const int MaxTimeoutValue = 30000; + + private readonly SystemCurlApp _curl; + private readonly IAsyncLazy _isSupported; + + public SiteLookupEndpoint(PluginBase plugin, IConfigScope config) + { + string path = config.GetRequiredProperty("path", p => p.GetString()!); + InitPathAndLog(path, plugin.Log); + + string exePath = config.GetValueOrDefault("exe_path", p => p.GetString(), DefaultCurlExecName); + bool httspOnly = config.GetValueOrDefault("https_only", p => p.GetBoolean(), false); + + //Optional extra arguments + string[] extrArgs = config.GetValueOrDefault( + "extra_args", + p => p.EnumerateArray().Select(s => s.GetString()!).ToArray(), + Array.Empty() + ); + + _curl = new SystemCurlApp(exePath, httspOnly, extrArgs); + + //Immediately check if curl is supported + _isSupported = _curl.TestIsAvailable(plugin.UnloadToken).AsLazy(); + } + + protected override async ValueTask GetAsync(HttpEntity entity) + { + WebMessage webm = new(); + + bool isEnabled = await _isSupported; + + //Allow site to cache if curl is supported on the platform + if (entity.QueryArgs.ContainsKey("support")) + { + webm.Success = isEnabled; + return VirtualOk(entity, webm); + } + + //Assert supported value as curl is required for a normal url lookup + if(webm.Assert(isEnabled, "Curl is not supported on the current platform")) + { + return VirtualClose(entity, webm, HttpStatusCode.NotImplemented); + } + + string? url = entity.QueryArgs.GetValueOrDefault("url"); + + if(webm.Assert(!string.IsNullOrWhiteSpace(url), "No url provided")) + { + return VirtualClose(entity, webm, HttpStatusCode.BadRequest); + } + + if(webm.Assert(UrlFromBase64Url(url!, out Uri? uri), "Invalid url provided")) + { + return VirtualClose(entity, webm, HttpStatusCode.UnprocessableEntity); + } + + int? timeoutMs = null; + + //Allow clients to specify a timeout for the request + string? timeoutMsS = entity.QueryArgs.GetValueOrDefault("timeout"); + if (timeoutMsS is not null && int.TryParse(timeoutMsS, out int _timeoutMs)) + { + //Miniumum timeout must be greater than 1 second because curl is timed in seconds + timeoutMs = Math.Clamp(_timeoutMs, 1000, MaxTimeoutValue); + } + + try + { + //Exec curl on the url + CurlResult result = await _curl.ExecLookupAsync(uri!, timeoutMs, entity.EventCancellation); + + if(webm.Assert(result.IsError == false, result.ErrorMessage!)) + { + return VirtualClose(entity, webm, HttpStatusCode.InternalServerError); + } + + webm.Success = true; + webm.Result = result.Result; //Set curl lookup result as the response + + return VirtualOk(entity, webm); + } + catch (TimeoutException) + { + webm.Result = "Request timed out"; + return VirtualClose(entity, webm, HttpStatusCode.InternalServerError); + } + catch (OperationCanceledException) + { + webm.Result = "Request timed out"; + return VirtualClose(entity, webm, HttpStatusCode.InternalServerError); + } + } + + /* + * Reads in a base64url encoded string which is the user's search url and + * attempts to parse it into a uri. If the url is invalid, the function + */ + private static bool UrlFromBase64Url(string base64Url, out Uri? uri) + { + uri = null; + + //Alloc output buffer for decoded data + using UnsafeMemoryHandle output = MemoryUtil.UnsafeAllocNearestPage(base64Url.Length, true); + + ERRNO decoded = VnEncoding.Base64UrlDecode(base64Url, output.Span, Encoding.UTF8); + if(decoded < 1) + { + return false; + } + + //Recover the url string from its binary representation and try to parse it into a uri + string urlstring = Encoding.UTF8.GetString(output.Span[..(int)decoded]); + return Uri.TryCreate(urlstring, UriKind.Absolute, out uri); + } + } +} -- cgit