Add basic manifest checking in uploaded archives
parent
91b9328afc
commit
f5b136720a
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
|
@ -59,8 +59,37 @@
|
||||||
© 2022 - LANCommander - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
|
© 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>
|
||||||
|
|
|
@ -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
|
|
@ -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"}
|
|
@ -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');
|
|
@ -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;
|
||||||
|
|
|
@ -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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"}
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue