From f5b136720a8d8725e1b6bb494ce5ad9ae911388b Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Fri, 6 Jan 2023 00:47:30 -0600 Subject: [PATCH] Add basic manifest checking in uploaded archives --- .../Controllers/ArchivesController.cs | 67 +++++++++++++++++++ LANCommander/LANCommander.csproj | 1 + LANCommander/Models/GameManifest.cs | 36 ++++++++++ LANCommander/Views/Archives/Add.cshtml | 2 +- LANCommander/Views/Shared/_Layout.cshtml | 29 ++++++++ LANCommander/wwwroot/js/Modal.js | 16 +++++ LANCommander/wwwroot/js/Modal.js.map | 1 + LANCommander/wwwroot/js/Modal.ts | 22 ++++++ LANCommander/wwwroot/js/Upload.js | 52 +++++++++----- LANCommander/wwwroot/js/Upload.js.map | 2 +- LANCommander/wwwroot/js/Upload.ts | 64 ++++++++++++------ 11 files changed, 255 insertions(+), 37 deletions(-) create mode 100644 LANCommander/Models/GameManifest.cs create mode 100644 LANCommander/wwwroot/js/Modal.js create mode 100644 LANCommander/wwwroot/js/Modal.js.map create mode 100644 LANCommander/wwwroot/js/Modal.ts diff --git a/LANCommander/Controllers/ArchivesController.cs b/LANCommander/Controllers/ArchivesController.cs index 8bac380..8640dc3 100644 --- a/LANCommander/Controllers/ArchivesController.cs +++ b/LANCommander/Controllers/ArchivesController.cs @@ -1,8 +1,12 @@ using LANCommander.Data; using LANCommander.Data.Models; using LANCommander.Extensions; +using LANCommander.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using System.IO.Compression; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; namespace LANCommander.Controllers { @@ -98,5 +102,68 @@ namespace LANCommander.Controllers return RedirectToAction("Edit", "Games", new { id = gameId }); } + + public async Task ValidateManifest(Guid id) + { + var path = Path.Combine("Upload", id.ToString()); + + string manifestContents = String.Empty; + + if (!System.IO.File.Exists(path)) + return BadRequest("Specified object does not exist"); + + try + { + using (ZipArchive archive = ZipFile.OpenRead(path)) + { + var manifest = archive.Entries.FirstOrDefault(e => e.FullName == "_manifest.yml"); + + if (manifest == null) + throw new FileNotFoundException("Manifest file not found. Add a _manifest.yml file to your archive and try again."); + + foreach (ZipArchiveEntry entry in archive.Entries) + { + if (entry.FullName == "_manifest.yml") + { + using (StreamReader sr = new StreamReader(entry.Open())) + { + manifestContents = await sr.ReadToEndAsync(); + } + } + } + } + } + catch (InvalidDataException ex) + { + System.IO.File.Delete(path); + return BadRequest("Uploaded archive is corrupt or not a .zip file."); + } + catch (FileNotFoundException ex) + { + System.IO.File.Delete(path); + return BadRequest(ex.Message); + } + catch + { + System.IO.File.Delete(path); + return BadRequest("An unknown error occurred."); + } + + var deserializer = new DeserializerBuilder() + .WithNamingConvention(PascalCaseNamingConvention.Instance) + .Build(); + + try + { + var manifest = deserializer.Deserialize(manifestContents); + } + catch + { + System.IO.File.Delete(path); + return BadRequest("The manifest file is invalid or corrupt."); + } + + return Ok(); + } } } diff --git a/LANCommander/LANCommander.csproj b/LANCommander/LANCommander.csproj index 595c9ee..4cf90e2 100644 --- a/LANCommander/LANCommander.csproj +++ b/LANCommander/LANCommander.csproj @@ -38,6 +38,7 @@ + diff --git a/LANCommander/Models/GameManifest.cs b/LANCommander/Models/GameManifest.cs new file mode 100644 index 0000000..0e0d3ec --- /dev/null +++ b/LANCommander/Models/GameManifest.cs @@ -0,0 +1,36 @@ +namespace LANCommander.Models +{ + public class GameManifest + { + public string Title { get; set; } + public string SortTitle { get; set; } + public string Description { get; set; } + public DateTime ReleasedOn { get; set; } + public string[] Genre { get; set; } + public string[] Tags { get; set; } + public string[] Publishers { get; set; } + public string[] Developers { get; set; } + public string Version { get; set; } + public string Icon { get; set; } + public GameAction[] Actions { get; set; } + public bool Singleplayer { get; set; } + public MultiplayerInfo LocalMultiplayer { get; set; } + public MultiplayerInfo LanMultiplayer { get; set; } + public MultiplayerInfo OnlineMultiplayer { get; set; } + } + + public class GameAction + { + public string Name { get; set; } + public string Arguments { get; set; } + public string Path { get; set; } + public string WorkingDirectory { get; set; } + public bool IsPrimaryAction { get; set; } + } + + public class MultiplayerInfo + { + public int MinPlayers { get; set; } + public int MaxPlayers { get; set; } + } +} diff --git a/LANCommander/Views/Archives/Add.cshtml b/LANCommander/Views/Archives/Add.cshtml index ab1b5be..f22fa1a 100644 --- a/LANCommander/Views/Archives/Add.cshtml +++ b/LANCommander/Views/Archives/Add.cshtml @@ -122,7 +122,7 @@ .removeClass('progress-bar-striped') .removeClass('progress-bar-animated') .addClass('bg-danger') - .text('Error!'); + .text('Upload Error!'); $('#UploadButton').show(); $('#SaveButton').hide(); diff --git a/LANCommander/Views/Shared/_Layout.cshtml b/LANCommander/Views/Shared/_Layout.cshtml index 7c1e9e1..a360b8f 100644 --- a/LANCommander/Views/Shared/_Layout.cshtml +++ b/LANCommander/Views/Shared/_Layout.cshtml @@ -59,8 +59,37 @@ © 2022 - LANCommander - Privacy + + + + + @await RenderSectionAsync("Scripts", required: false) diff --git a/LANCommander/wwwroot/js/Modal.js b/LANCommander/wwwroot/js/Modal.js new file mode 100644 index 0000000..fe80049 --- /dev/null +++ b/LANCommander/wwwroot/js/Modal.js @@ -0,0 +1,16 @@ +class Modal { + constructor(id) { + this.ElementId = id; + // @ts-ignore + this.Instance = new bootstrap.Modal(`#${this.ElementId}`, { + keyboard: false + }); + } + Show(header, message) { + document.getElementById(`${this.ElementId}Header`).innerText = header; + document.getElementById(`${this.ElementId}Message`).innerText = message; + this.Instance.show(); + } +} +const ErrorModal = new Modal('ErrorModal'); +//# sourceMappingURL=Modal.js.map \ No newline at end of file diff --git a/LANCommander/wwwroot/js/Modal.js.map b/LANCommander/wwwroot/js/Modal.js.map new file mode 100644 index 0000000..74e6593 --- /dev/null +++ b/LANCommander/wwwroot/js/Modal.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Modal.js","sourceRoot":"","sources":["Modal.ts"],"names":[],"mappings":"AAAA,MAAM,KAAK;IAIP,YAAY,EAAU;QAClB,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;QAEpB,aAAa;QACb,IAAI,CAAC,QAAQ,GAAG,IAAI,SAAS,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,EAAE;YACtD,QAAQ,EAAE,KAAK;SAClB,CAAC,CAAC;IACP,CAAC;IAED,IAAI,CAAC,MAAc,EAAE,OAAe;QAChC,QAAQ,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC,SAAS,QAAQ,CAAC,CAAC,SAAS,GAAG,MAAM,CAAC;QACtE,QAAQ,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC,SAAS,SAAS,CAAC,CAAC,SAAS,GAAG,OAAO,CAAC;QAExE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;CACJ;AAED,MAAM,UAAU,GAAG,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC"} \ No newline at end of file diff --git a/LANCommander/wwwroot/js/Modal.ts b/LANCommander/wwwroot/js/Modal.ts new file mode 100644 index 0000000..ec1a0b5 --- /dev/null +++ b/LANCommander/wwwroot/js/Modal.ts @@ -0,0 +1,22 @@ +class Modal { + Instance: any; + ElementId: string; + + constructor(id: string) { + this.ElementId = id; + + // @ts-ignore + this.Instance = new bootstrap.Modal(`#${this.ElementId}`, { + keyboard: false + }); + } + + Show(header: string, message: string) { + document.getElementById(`${this.ElementId}Header`).innerText = header; + document.getElementById(`${this.ElementId}Message`).innerText = message; + + this.Instance.show(); + } +} + +const ErrorModal = new Modal('ErrorModal'); \ No newline at end of file diff --git a/LANCommander/wwwroot/js/Upload.js b/LANCommander/wwwroot/js/Upload.js index b3ce983..005a303 100644 --- a/LANCommander/wwwroot/js/Upload.js +++ b/LANCommander/wwwroot/js/Upload.js @@ -18,6 +18,7 @@ class Uploader { constructor() { this.InitRoute = "/Upload/Init"; this.ChunkRoute = "/Upload/Chunk"; + this.ValidateManifestRoute = "/Archives/ValidateManifest"; this.MaxChunkSize = 1024 * 1024 * 25; } Init(fileInputId, uploadButtonId) { @@ -44,22 +45,13 @@ class Uploader { this.GetChunks(); try { for (let chunk of this.Chunks) { - let formData = new FormData(); - formData.append('file', this.File.slice(chunk.Start, chunk.End + 1)); - formData.append('start', chunk.Start.toString()); - formData.append('end', chunk.End.toString()); - formData.append('key', this.Key); - formData.append('total', this.File.size.toString()); - console.info(`Uploading chunk ${chunk.Index}/${this.TotalChunks}...`); - let chunkResponse = yield fetch(this.ChunkRoute, { - method: "POST", - body: formData - }); - if (!chunkResponse) - throw `Error uploading chunk ${chunk.Index}/${this.TotalChunks}`; - this.OnProgress(chunk.Index / this.TotalChunks); + yield this.UploadChunk(chunk); } - this.OnComplete(this.Key); + var isValid = yield this.ValidateManifest(); + if (isValid) + this.OnComplete(this.Key); + else + this.OnError(); } catch (_a) { this.OnError(); @@ -67,6 +59,36 @@ class Uploader { } }); } + UploadChunk(chunk) { + return __awaiter(this, void 0, void 0, function* () { + let formData = new FormData(); + formData.append('file', this.File.slice(chunk.Start, chunk.End + 1)); + formData.append('start', chunk.Start.toString()); + formData.append('end', chunk.End.toString()); + formData.append('key', this.Key); + formData.append('total', this.File.size.toString()); + console.info(`Uploading chunk ${chunk.Index}/${this.TotalChunks}...`); + let chunkResponse = yield fetch(this.ChunkRoute, { + method: "POST", + body: formData + }); + if (!chunkResponse) + throw `Error uploading chunk ${chunk.Index}/${this.TotalChunks}`; + this.OnProgress(chunk.Index / this.TotalChunks); + }); + } + ValidateManifest() { + return __awaiter(this, void 0, void 0, function* () { + let validationResponse = yield fetch(`${this.ValidateManifestRoute}/${this.Key}`, { + method: "GET" + }); + if (!validationResponse.ok) { + ErrorModal.Show("Archive Invalid", yield validationResponse.text()); + return false; + } + return true; + }); + } GetChunks() { for (let currentChunk = 1; currentChunk <= this.TotalChunks; currentChunk++) { let start = (currentChunk - 1) * this.MaxChunkSize; diff --git a/LANCommander/wwwroot/js/Upload.js.map b/LANCommander/wwwroot/js/Upload.js.map index c7bfd53..8fa3db5 100644 --- a/LANCommander/wwwroot/js/Upload.js.map +++ b/LANCommander/wwwroot/js/Upload.js.map @@ -1 +1 @@ -{"version":3,"file":"Upload.js","sourceRoot":"","sources":["Upload.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"Upload.js","sourceRoot":"","sources":["Upload.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/LANCommander/wwwroot/js/Upload.ts b/LANCommander/wwwroot/js/Upload.ts index b83459a..d62c415 100644 --- a/LANCommander/wwwroot/js/Upload.ts +++ b/LANCommander/wwwroot/js/Upload.ts @@ -18,6 +18,7 @@ class Uploader { InitRoute: string = "/Upload/Init"; ChunkRoute: string = "/Upload/Chunk"; + ValidateManifestRoute: string = "/Archives/ValidateManifest"; MaxChunkSize: number = 1024 * 1024 * 25; TotalChunks: number; @@ -59,28 +60,15 @@ class Uploader { try { for (let chunk of this.Chunks) { - let formData = new FormData(); - - formData.append('file', this.File.slice(chunk.Start, chunk.End + 1)); - formData.append('start', chunk.Start.toString()); - formData.append('end', chunk.End.toString()); - formData.append('key', this.Key); - formData.append('total', this.File.size.toString()); - - console.info(`Uploading chunk ${chunk.Index}/${this.TotalChunks}...`); - - let chunkResponse = await fetch(this.ChunkRoute, { - method: "POST", - body: formData - }); - - if (!chunkResponse) - throw `Error uploading chunk ${chunk.Index}/${this.TotalChunks}`; - - this.OnProgress(chunk.Index / this.TotalChunks); + await this.UploadChunk(chunk); } - this.OnComplete(this.Key); + var isValid = await this.ValidateManifest(); + + if (isValid) + this.OnComplete(this.Key); + else + this.OnError(); } catch { this.OnError(); @@ -88,6 +76,42 @@ class Uploader { } } + async UploadChunk(chunk: Chunk) { + let formData = new FormData(); + + formData.append('file', this.File.slice(chunk.Start, chunk.End + 1)); + formData.append('start', chunk.Start.toString()); + formData.append('end', chunk.End.toString()); + formData.append('key', this.Key); + formData.append('total', this.File.size.toString()); + + console.info(`Uploading chunk ${chunk.Index}/${this.TotalChunks}...`); + + let chunkResponse = await fetch(this.ChunkRoute, { + method: "POST", + body: formData + }); + + if (!chunkResponse) + throw `Error uploading chunk ${chunk.Index}/${this.TotalChunks}`; + + this.OnProgress(chunk.Index / this.TotalChunks); + } + + async ValidateManifest(): Promise { + let validationResponse = await fetch(`${this.ValidateManifestRoute}/${this.Key}`, { + method: "GET" + }); + + if (!validationResponse.ok) { + ErrorModal.Show("Archive Invalid", await validationResponse.text()) + + return false; + } + + return true; + } + GetChunks() { for (let currentChunk = 1; currentChunk <= this.TotalChunks; currentChunk++) { let start = (currentChunk - 1) * this.MaxChunkSize;