summaryrefslogtreecommitdiff
path: root/src/SMAPI.Web/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI.Web/Framework')
-rw-r--r--src/SMAPI.Web/Framework/AllowLargePostsAttribute.cs52
-rw-r--r--src/SMAPI.Web/Framework/ConfigModels/LogParserConfig.cs18
-rw-r--r--src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs2
-rw-r--r--src/SMAPI.Web/Framework/LogParser/GetPasteResponse.cs15
-rw-r--r--src/SMAPI.Web/Framework/LogParser/PastebinClient.cs110
-rw-r--r--src/SMAPI.Web/Framework/LogParser/SavePasteResponse.cs15
6 files changed, 211 insertions, 1 deletions
diff --git a/src/SMAPI.Web/Framework/AllowLargePostsAttribute.cs b/src/SMAPI.Web/Framework/AllowLargePostsAttribute.cs
new file mode 100644
index 00000000..68ead3c2
--- /dev/null
+++ b/src/SMAPI.Web/Framework/AllowLargePostsAttribute.cs
@@ -0,0 +1,52 @@
+using System;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Mvc.Filters;
+
+namespace StardewModdingAPI.Web.Framework
+{
+ /// <summary>A filter which increases the maximum request size for an endpoint.</summary>
+ /// <remarks>Derived from <a href="https://stackoverflow.com/a/38360093/262123"/>.</remarks>
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+ public class AllowLargePostsAttribute : Attribute, IAuthorizationFilter, IOrderedFilter
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The underlying form options.</summary>
+ private readonly FormOptions FormOptions;
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The attribute order.</summary>
+ public int Order { get; set; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ public AllowLargePostsAttribute()
+ {
+ this.FormOptions = new FormOptions
+ {
+ ValueLengthLimit = 200 * 1024 * 1024 // 200MB
+ };
+ }
+
+ /// <summary>Called early in the filter pipeline to confirm request is authorized.</summary>
+ /// <param name="context">The authorisation filter context.</param>
+ public void OnAuthorization(AuthorizationFilterContext context)
+ {
+ IFeatureCollection features = context.HttpContext.Features;
+ IFormFeature formFeature = features.Get<IFormFeature>();
+
+ if (formFeature?.Form == null)
+ {
+ // Request form has not been read yet, so set the limits
+ features.Set<IFormFeature>(new FormFeature(context.HttpContext.Request, this.FormOptions));
+ }
+ }
+ }
+}
diff --git a/src/SMAPI.Web/Framework/ConfigModels/LogParserConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/LogParserConfig.cs
new file mode 100644
index 00000000..5cb0cf95
--- /dev/null
+++ b/src/SMAPI.Web/Framework/ConfigModels/LogParserConfig.cs
@@ -0,0 +1,18 @@
+namespace StardewModdingAPI.Web.Framework.ConfigModels
+{
+ /// <summary>The config settings for the log parser.</summary>
+ internal class LogParserConfig
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The base URL for the Pastebin API.</summary>
+ public string PastebinBaseUrl { get; set; }
+
+ /// <summary>The user agent for the Pastebin API client, where {0} is the SMAPI version.</summary>
+ public string PastebinUserAgent { get; set; }
+
+ /// <summary>The developer key used to authenticate with the Pastebin API.</summary>
+ public string PastebinDevKey { get; set; }
+ }
+}
diff --git a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs
index 03de639e..2fb5b97e 100644
--- a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs
+++ b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs
@@ -1,7 +1,7 @@
namespace StardewModdingAPI.Web.Framework.ConfigModels
{
/// <summary>The config settings for mod update checks.</summary>
- public class ModUpdateCheckConfig
+ internal class ModUpdateCheckConfig
{
/*********
** Accessors
diff --git a/src/SMAPI.Web/Framework/LogParser/GetPasteResponse.cs b/src/SMAPI.Web/Framework/LogParser/GetPasteResponse.cs
new file mode 100644
index 00000000..4f8794db
--- /dev/null
+++ b/src/SMAPI.Web/Framework/LogParser/GetPasteResponse.cs
@@ -0,0 +1,15 @@
+namespace StardewModdingAPI.Web.Framework.LogParser
+{
+ /// <summary>The response for a get-paste request.</summary>
+ internal class GetPasteResponse
+ {
+ /// <summary>Whether the log was successfully fetched.</summary>
+ public bool Success { get; set; }
+
+ /// <summary>The fetched paste content (if <see cref="Success"/> is <c>true</c>).</summary>
+ public string Content { get; set; }
+
+ /// <summary>The error message (if saving failed).</summary>
+ public string Error { get; set; }
+ }
+}
diff --git a/src/SMAPI.Web/Framework/LogParser/PastebinClient.cs b/src/SMAPI.Web/Framework/LogParser/PastebinClient.cs
new file mode 100644
index 00000000..8536f249
--- /dev/null
+++ b/src/SMAPI.Web/Framework/LogParser/PastebinClient.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Pathoschild.Http.Client;
+
+namespace StardewModdingAPI.Web.Framework.LogParser
+{
+ /// <summary>An API client for Pastebin.</summary>
+ internal class PastebinClient : IDisposable
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The underlying HTTP client.</summary>
+ private readonly IClient Client;
+
+ /// <summary>The developer key used to authenticate with the Pastebin API.</summary>
+ private readonly string DevKey;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="baseUrl">The base URL for the Pastebin API.</param>
+ /// <param name="userAgent">The user agent for the API client.</param>
+ /// <param name="devKey">The developer key used to authenticate with the Pastebin API.</param>
+ public PastebinClient(string baseUrl, string userAgent, string devKey)
+ {
+ this.Client = new FluentClient(baseUrl).SetUserAgent(userAgent);
+ this.DevKey = devKey;
+ }
+
+ /// <summary>Fetch a saved paste.</summary>
+ /// <param name="id">The paste ID.</param>
+ public async Task<GetPasteResponse> GetAsync(string id)
+ {
+ try
+ {
+ // get from API
+ string content = await this.Client
+ .GetAsync($"raw/{id}")
+ .AsString();
+
+ // handle Pastebin errors
+ if (string.IsNullOrWhiteSpace(content))
+ return new GetPasteResponse { Error = "Received an empty response from Pastebin." };
+ if (content.StartsWith("<!DOCTYPE"))
+ return new GetPasteResponse { Error = $"Received a captcha challenge from Pastebin. Please visit https://pastebin.com/{id} in a new window to solve it." };
+ return new GetPasteResponse { Success = true, Content = content };
+ }
+ catch (ApiException ex) when (ex.Status == HttpStatusCode.NotFound)
+ {
+ return new GetPasteResponse { Error = "There's no log with that ID." };
+ }
+ catch (Exception ex)
+ {
+ return new GetPasteResponse { Error = ex.ToString() };
+ }
+ }
+
+ public async Task<SavePasteResponse> PostAsync(string content)
+ {
+ try
+ {
+ // validate
+ if (string.IsNullOrWhiteSpace(content))
+ return new SavePasteResponse { Error = "The log content can't be empty." };
+
+ // post to API
+ string response = await this.Client
+ .PostAsync("api/api_post.php")
+ .WithBodyContent(new FormUrlEncodedContent(new Dictionary<string, string>
+ {
+ ["api_dev_key"] = "b8219d942109d1e60ebb14fbb45f06f9",
+ ["api_option"] = "paste",
+ ["api_paste_private"] = "1",
+ ["api_paste_code"] = content,
+ ["api_paste_expire_date"] = "1W"
+ }))
+ .AsString();
+
+ // handle Pastebin errors
+ if (string.IsNullOrWhiteSpace(response))
+ return new SavePasteResponse { Error = "Received an empty response from Pastebin." };
+ if (response.StartsWith("Bad API request"))
+ return new SavePasteResponse { Error = response };
+ if (!response.Contains("/"))
+ return new SavePasteResponse { Error = $"Received an unknown response: {response}" };
+
+ // return paste ID
+ string pastebinID = response.Split("/").Last();
+ return new SavePasteResponse { Success = true, ID = pastebinID };
+ }
+ catch (Exception ex)
+ {
+ return new SavePasteResponse { Success = false, Error = ex.ToString() };
+ }
+ }
+
+ /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
+ public void Dispose()
+ {
+ this.Client.Dispose();
+ }
+ }
+}
diff --git a/src/SMAPI.Web/Framework/LogParser/SavePasteResponse.cs b/src/SMAPI.Web/Framework/LogParser/SavePasteResponse.cs
new file mode 100644
index 00000000..1c0960a4
--- /dev/null
+++ b/src/SMAPI.Web/Framework/LogParser/SavePasteResponse.cs
@@ -0,0 +1,15 @@
+namespace StardewModdingAPI.Web.Framework.LogParser
+{
+ /// <summary>The response for a save-log request.</summary>
+ internal class SavePasteResponse
+ {
+ /// <summary>Whether the log was successfully saved.</summary>
+ public bool Success { get; set; }
+
+ /// <summary>The saved paste ID (if <see cref="Success"/> is <c>true</c>).</summary>
+ public string ID { get; set; }
+
+ /// <summary>The error message (if saving failed).</summary>
+ public string Error { get; set; }
+ }
+}