Add basic manifest checking in uploaded archives

dashboard
Pat Hartl 2023-01-06 00:47:30 -06:00
parent 91b9328afc
commit f5b136720a
11 changed files with 255 additions and 37 deletions

View File

@ -1,8 +1,12 @@
using LANCommander.Data; using LANCommander.Data;
using LANCommander.Data.Models; using LANCommander.Data.Models;
using LANCommander.Extensions; using LANCommander.Extensions;
using LANCommander.Models;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.IO.Compression;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace LANCommander.Controllers namespace LANCommander.Controllers
{ {
@ -98,5 +102,68 @@ namespace LANCommander.Controllers
return RedirectToAction("Edit", "Games", new { id = gameId }); return RedirectToAction("Edit", "Games", new { id = gameId });
} }
public async Task<IActionResult> 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<GameManifest>(manifestContents);
}
catch
{
System.IO.File.Delete(path);
return BadRequest("The manifest file is invalid or corrupt.");
}
return Ok();
}
} }
} }

View File

@ -38,6 +38,7 @@
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.11" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.11" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.0" />
<PackageReference Include="YamlDotNet" Version="12.3.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -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; }
}
}

View File

@ -122,7 +122,7 @@
.removeClass('progress-bar-striped') .removeClass('progress-bar-striped')
.removeClass('progress-bar-animated') .removeClass('progress-bar-animated')
.addClass('bg-danger') .addClass('bg-danger')
.text('Error!'); .text('Upload Error!');
$('#UploadButton').show(); $('#UploadButton').show();
$('#SaveButton').hide(); $('#SaveButton').hide();

View File

@ -59,8 +59,37 @@
&copy; 2022 - LANCommander - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a> &copy; 2022 - LANCommander - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div> </div>
</footer> </footer>
<div class="modal modal-blur fade" id="ErrorModal" tabindex="-1">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<div class="modal-status bg-danger"></div>
<div class="modal-body text-center py-4">
<!-- Download SVG icon from http://tabler-icons.io/i/alert-triangle -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon mb-2 text-danger icon-lg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M12 9v2m0 4v.01" /><path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75" /></svg>
<h3 id="ErrorModalHeader"></h3>
<div class="text-muted" id="ErrorModalMessage"></div>
</div>
<div class="modal-footer">
<div class="w-100">
<div class="row">
<div class="col">
<a href="#" class="btn btn-danger w-100" data-bs-dismiss="modal">
Close
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="~/lib/jquery/dist/jquery.min.js"></script> <script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/js/tabler.min.js"></script> <script src="~/js/tabler.min.js"></script>
<script src="~/js/Modal.js"></script>
@await RenderSectionAsync("Scripts", required: false) @await RenderSectionAsync("Scripts", required: false)
</body> </body>
</html> </html>

View File

@ -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

View File

@ -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"}

View File

@ -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');

View File

@ -18,6 +18,7 @@ class Uploader {
constructor() { constructor() {
this.InitRoute = "/Upload/Init"; this.InitRoute = "/Upload/Init";
this.ChunkRoute = "/Upload/Chunk"; this.ChunkRoute = "/Upload/Chunk";
this.ValidateManifestRoute = "/Archives/ValidateManifest";
this.MaxChunkSize = 1024 * 1024 * 25; this.MaxChunkSize = 1024 * 1024 * 25;
} }
Init(fileInputId, uploadButtonId) { Init(fileInputId, uploadButtonId) {
@ -44,22 +45,13 @@ class Uploader {
this.GetChunks(); this.GetChunks();
try { try {
for (let chunk of this.Chunks) { for (let chunk of this.Chunks) {
let formData = new FormData(); yield this.UploadChunk(chunk);
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);
} }
this.OnComplete(this.Key); var isValid = yield this.ValidateManifest();
if (isValid)
this.OnComplete(this.Key);
else
this.OnError();
} }
catch (_a) { catch (_a) {
this.OnError(); 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() { GetChunks() {
for (let currentChunk = 1; currentChunk <= this.TotalChunks; currentChunk++) { for (let currentChunk = 1; currentChunk <= this.TotalChunks; currentChunk++) {
let start = (currentChunk - 1) * this.MaxChunkSize; let start = (currentChunk - 1) * this.MaxChunkSize;

View File

@ -1 +1 @@
{"version":3,"file":"Upload.js","sourceRoot":"","sources":["Upload.ts"],"names":[],"mappings":"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"} {"version":3,"file":"Upload.js","sourceRoot":"","sources":["Upload.ts"],"names":[],"mappings":"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"}

View File

@ -18,6 +18,7 @@ class Uploader {
InitRoute: string = "/Upload/Init"; InitRoute: string = "/Upload/Init";
ChunkRoute: string = "/Upload/Chunk"; ChunkRoute: string = "/Upload/Chunk";
ValidateManifestRoute: string = "/Archives/ValidateManifest";
MaxChunkSize: number = 1024 * 1024 * 25; MaxChunkSize: number = 1024 * 1024 * 25;
TotalChunks: number; TotalChunks: number;
@ -59,28 +60,15 @@ class Uploader {
try { try {
for (let chunk of this.Chunks) { for (let chunk of this.Chunks) {
let formData = new FormData(); await this.UploadChunk(chunk);
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);
} }
this.OnComplete(this.Key); var isValid = await this.ValidateManifest();
if (isValid)
this.OnComplete(this.Key);
else
this.OnError();
} }
catch { catch {
this.OnError(); 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<boolean> {
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() { GetChunks() {
for (let currentChunk = 1; currentChunk <= this.TotalChunks; currentChunk++) { for (let currentChunk = 1; currentChunk <= this.TotalChunks; currentChunk++) {
let start = (currentChunk - 1) * this.MaxChunkSize; let start = (currentChunk - 1) * this.MaxChunkSize;