diff options
-rw-r--r-- | src/SMAPI.Web/Controllers/LogParserController.cs | 82 | ||||
-rw-r--r-- | src/SMAPI.Web/Framework/Compression/GzipHelper.cs | 89 | ||||
-rw-r--r-- | src/SMAPI.Web/Framework/Compression/IGzipHelper.cs | 17 | ||||
-rw-r--r-- | src/SMAPI.Web/Startup.cs | 4 |
4 files changed, 118 insertions, 74 deletions
diff --git a/src/SMAPI.Web/Controllers/LogParserController.cs b/src/SMAPI.Web/Controllers/LogParserController.cs index 21e4a56f..dc5895b0 100644 --- a/src/SMAPI.Web/Controllers/LogParserController.cs +++ b/src/SMAPI.Web/Controllers/LogParserController.cs @@ -1,13 +1,11 @@ using System; -using System.IO; -using System.IO.Compression; using System.Linq; -using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using StardewModdingAPI.Web.Framework; using StardewModdingAPI.Web.Framework.Clients.Pastebin; +using StardewModdingAPI.Web.Framework.Compression; using StardewModdingAPI.Web.Framework.ConfigModels; using StardewModdingAPI.Web.Framework.LogParsing; using StardewModdingAPI.Web.Framework.LogParsing.Models; @@ -27,9 +25,8 @@ namespace StardewModdingAPI.Web.Controllers /// <summary>The underlying Pastebin client.</summary> private readonly IPastebinClient Pastebin; - /// <summary>The first bytes in a valid zip file.</summary> - /// <remarks>See <a href="https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers"/>.</remarks> - private const uint GzipLeadBytes = 0x8b1f; + /// <summary>The underlying text compression helper.</summary> + private readonly IGzipHelper GzipHelper; /********* @@ -41,10 +38,12 @@ namespace StardewModdingAPI.Web.Controllers /// <summary>Construct an instance.</summary> /// <param name="siteConfig">The context config settings.</param> /// <param name="pastebin">The Pastebin API client.</param> - public LogParserController(IOptions<SiteConfig> siteConfig, IPastebinClient pastebin) + /// <param name="gzipHelper">The underlying text compression helper.</param> + public LogParserController(IOptions<SiteConfig> siteConfig, IPastebinClient pastebin, IGzipHelper gzipHelper) { this.Config = siteConfig.Value; this.Pastebin = pastebin; + this.GzipHelper = gzipHelper; } /*** @@ -84,7 +83,7 @@ namespace StardewModdingAPI.Web.Controllers return this.View("Index", new LogParserModel(this.Config.LogParserUrl, null) { UploadError = "The log file seems to be empty." }); // upload log - input = this.CompressString(input); + input = this.GzipHelper.CompressString(input); SavePasteResult result = await this.Pastebin.PostAsync(input); // handle errors @@ -106,75 +105,10 @@ namespace StardewModdingAPI.Web.Controllers private async Task<PasteInfo> GetAsync(string id) { PasteInfo response = await this.Pastebin.GetAsync(id); - response.Content = this.DecompressString(response.Content); + response.Content = this.GzipHelper.DecompressString(response.Content); return response; } - /// <summary>Compress a string.</summary> - /// <param name="text">The text to compress.</param> - /// <remarks>Derived from <a href="https://stackoverflow.com/a/17993002/262123"/>.</remarks> - private string CompressString(string text) - { - // get raw bytes - byte[] buffer = Encoding.UTF8.GetBytes(text); - - // compressed - byte[] compressedData; - using (MemoryStream stream = new MemoryStream()) - { - using (GZipStream zipStream = new GZipStream(stream, CompressionLevel.Optimal, leaveOpen: true)) - zipStream.Write(buffer, 0, buffer.Length); - - stream.Position = 0; - compressedData = new byte[stream.Length]; - stream.Read(compressedData, 0, compressedData.Length); - } - - // prefix length - byte[] zipBuffer = new byte[compressedData.Length + 4]; - Buffer.BlockCopy(compressedData, 0, zipBuffer, 4, compressedData.Length); - Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, zipBuffer, 0, 4); - - // return string representation - return Convert.ToBase64String(zipBuffer); - } - /// <summary>Decompress a string.</summary> - /// <param name="rawText">The compressed text.</param> - /// <remarks>Derived from <a href="https://stackoverflow.com/a/17993002/262123"/>.</remarks> - private string DecompressString(string rawText) - { - // get raw bytes - byte[] zipBuffer; - try - { - zipBuffer = Convert.FromBase64String(rawText); - } - catch - { - return rawText; // not valid base64, wasn't compressed by the log parser - } - - // skip if not gzip - if (BitConverter.ToUInt16(zipBuffer, 4) != LogParserController.GzipLeadBytes) - return rawText; - - // decompress - using (MemoryStream memoryStream = new MemoryStream()) - { - // read length prefix - int dataLength = BitConverter.ToInt32(zipBuffer, 0); - memoryStream.Write(zipBuffer, 4, zipBuffer.Length - 4); - - // read data - byte[] buffer = new byte[dataLength]; - memoryStream.Position = 0; - using (GZipStream gZipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) - gZipStream.Read(buffer, 0, buffer.Length); - - // return original string - return Encoding.UTF8.GetString(buffer); - } - } } } diff --git a/src/SMAPI.Web/Framework/Compression/GzipHelper.cs b/src/SMAPI.Web/Framework/Compression/GzipHelper.cs new file mode 100644 index 00000000..cc8f4737 --- /dev/null +++ b/src/SMAPI.Web/Framework/Compression/GzipHelper.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Text; + +namespace StardewModdingAPI.Web.Framework.Compression +{ + /// <summary>Handles GZip compression logic.</summary> + internal class GzipHelper : IGzipHelper + { + /********* + ** Fields + *********/ + /// <summary>The first bytes in a valid zip file.</summary> + /// <remarks>See <a href="https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers"/>.</remarks> + private const uint GzipLeadBytes = 0x8b1f; + + + /********* + ** Public methods + *********/ + /// <summary>Compress a string.</summary> + /// <param name="text">The text to compress.</param> + /// <remarks>Derived from <a href="https://stackoverflow.com/a/17993002/262123"/>.</remarks> + public string CompressString(string text) + { + // get raw bytes + byte[] buffer = Encoding.UTF8.GetBytes(text); + + // compressed + byte[] compressedData; + using (MemoryStream stream = new MemoryStream()) + { + using (GZipStream zipStream = new GZipStream(stream, CompressionLevel.Optimal, leaveOpen: true)) + zipStream.Write(buffer, 0, buffer.Length); + + stream.Position = 0; + compressedData = new byte[stream.Length]; + stream.Read(compressedData, 0, compressedData.Length); + } + + // prefix length + byte[] zipBuffer = new byte[compressedData.Length + 4]; + Buffer.BlockCopy(compressedData, 0, zipBuffer, 4, compressedData.Length); + Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, zipBuffer, 0, 4); + + // return string representation + return Convert.ToBase64String(zipBuffer); + } + + /// <summary>Decompress a string.</summary> + /// <param name="rawText">The compressed text.</param> + /// <remarks>Derived from <a href="https://stackoverflow.com/a/17993002/262123"/>.</remarks> + public string DecompressString(string rawText) + { + // get raw bytes + byte[] zipBuffer; + try + { + zipBuffer = Convert.FromBase64String(rawText); + } + catch + { + return rawText; // not valid base64, wasn't compressed by the log parser + } + + // skip if not gzip + if (BitConverter.ToUInt16(zipBuffer, 4) != GzipHelper.GzipLeadBytes) + return rawText; + + // decompress + using (MemoryStream memoryStream = new MemoryStream()) + { + // read length prefix + int dataLength = BitConverter.ToInt32(zipBuffer, 0); + memoryStream.Write(zipBuffer, 4, zipBuffer.Length - 4); + + // read data + byte[] buffer = new byte[dataLength]; + memoryStream.Position = 0; + using (GZipStream gZipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) + gZipStream.Read(buffer, 0, buffer.Length); + + // return original string + return Encoding.UTF8.GetString(buffer); + } + } + } +} diff --git a/src/SMAPI.Web/Framework/Compression/IGzipHelper.cs b/src/SMAPI.Web/Framework/Compression/IGzipHelper.cs new file mode 100644 index 00000000..a000865e --- /dev/null +++ b/src/SMAPI.Web/Framework/Compression/IGzipHelper.cs @@ -0,0 +1,17 @@ +namespace StardewModdingAPI.Web.Framework.Compression +{ + /// <summary>Handles GZip compression logic.</summary> + internal interface IGzipHelper + { + /********* + ** Methods + *********/ + /// <summary>Compress a string.</summary> + /// <param name="text">The text to compress.</param> + string CompressString(string text); + + /// <summary>Decompress a string.</summary> + /// <param name="rawText">The compressed text.</param> + string DecompressString(string rawText); + } +} diff --git a/src/SMAPI.Web/Startup.cs b/src/SMAPI.Web/Startup.cs index 33737235..bb43f5a5 100644 --- a/src/SMAPI.Web/Startup.cs +++ b/src/SMAPI.Web/Startup.cs @@ -20,6 +20,7 @@ using StardewModdingAPI.Web.Framework.Clients.GitHub; using StardewModdingAPI.Web.Framework.Clients.ModDrop; using StardewModdingAPI.Web.Framework.Clients.Nexus; using StardewModdingAPI.Web.Framework.Clients.Pastebin; +using StardewModdingAPI.Web.Framework.Compression; using StardewModdingAPI.Web.Framework.ConfigModels; using StardewModdingAPI.Web.Framework.RewriteRules; @@ -149,6 +150,9 @@ namespace StardewModdingAPI.Web devKey: api.PastebinDevKey )); } + + // init helpers + services.AddSingleton<IGzipHelper>(new GzipHelper()); } /// <summary>The method called by the runtime to configure the HTTP request pipeline.</summary> |