diff options
Diffstat (limited to 'src/SMAPI.Web/Controllers/LogParserController.cs')
-rw-r--r-- | src/SMAPI.Web/Controllers/LogParserController.cs | 84 |
1 files changed, 83 insertions, 1 deletions
diff --git a/src/SMAPI.Web/Controllers/LogParserController.cs b/src/SMAPI.Web/Controllers/LogParserController.cs index 8e986196..f143bc5c 100644 --- a/src/SMAPI.Web/Controllers/LogParserController.cs +++ b/src/SMAPI.Web/Controllers/LogParserController.cs @@ -1,3 +1,7 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; @@ -20,6 +24,10 @@ namespace StardewModdingAPI.Web.Controllers /// <summary>The underlying Pastebin client.</summary> private readonly PastebinClient PastebinClient; + /// <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 @@ -60,7 +68,9 @@ namespace StardewModdingAPI.Web.Controllers [Route("log/fetch/{id}")] public async Task<GetPasteResponse> GetAsync(string id) { - return await this.PastebinClient.GetAsync(id); + GetPasteResponse response = await this.PastebinClient.GetAsync(id); + response.Content = this.DecompressString(response.Content); + return response; } /// <summary>Save raw log data.</summary> @@ -69,7 +79,79 @@ namespace StardewModdingAPI.Web.Controllers [Route("log/save")] public async Task<SavePasteResponse> PostAsync([FromBody] string content) { + content = this.CompressString(content); return await this.PastebinClient.PostAsync(content); } + + + /********* + ** Private 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> + 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 + var 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 + var 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); + } + } } } |