Rewrite the upload page.

dotnetflags
C-xC-c 5 years ago
parent 4f1fceadcd
commit dc88b5ccf1

@ -15,6 +15,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.0" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.0" />
<PackageReference Include="MySql.Data" Version="8.0.18" /> <PackageReference Include="MySql.Data" Version="8.0.18" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />

@ -32,27 +32,6 @@ namespace BantFlags.Data.Database
KnownFlags = flags.ToHashSet(); KnownFlags = flags.ToHashSet();
} }
public async Task DeleteFlagsAsync(List<FormFlag> flags)
{
using var rentedConnection = await ConnectionPool.RentConnectionAsync();
using var query = rentedConnection.Object.UseStoredProcedure("delete_flag");
flags.ForEach(async f =>
await query.SetParam("@flag", f.Name)
.ExecuteNonQueryAsync(reuse: true));
}
public async Task RenameFlagsAsync(List<RenameFlag> flags)
{
using var rentedConnection = await ConnectionPool.RentConnectionAsync();
using var query = rentedConnection.Object.UseStoredProcedure("rename_flag");
flags.ForEach(async flag =>
await query.SetParam("@old", flag.Name)
.SetParam("@new", flag.NewName)
.ExecuteNonQueryAsync(reuse: true));
}
public async Task InsertPost(PostModel post) public async Task InsertPost(PostModel post)
{ {
using (var rentedConnection = await ConnectionPool.RentConnectionAsync()) using (var rentedConnection = await ConnectionPool.RentConnectionAsync())
@ -88,14 +67,29 @@ namespace BantFlags.Data.Database
.ToList(); .ToList();
} }
public async Task InsertFlagsAsync(List<FormFlag> flags) public async Task InsertFlagAsync(Flag flag)
{
using var rentedConnection = await ConnectionPool.RentConnectionAsync();
await rentedConnection.Object.UseStoredProcedure("insert_flag")
.SetParam("@flag", flag.Name)
.ExecuteNonQueryAsync();
}
public async Task RenameFlagAsync(Flag flag)
{ {
using var rentedConnection = await ConnectionPool.RentConnectionAsync(); using var rentedConnection = await ConnectionPool.RentConnectionAsync();
using var query = rentedConnection.Object.UseStoredProcedure("insert_flag"); await rentedConnection.Object.UseStoredProcedure("rename_flag")
.SetParam("@old", flag.OldName)
.SetParam("@new", flag.Name)
.ExecuteNonQueryAsync();
}
flags.ForEach(async f => public async Task DeleteFlagAsync(Flag flag)
await query.SetParam("@flag", f.Name) {
.ExecuteNonQueryAsync(reuse: true)); using var rentedConnection = await ConnectionPool.RentConnectionAsync();
await rentedConnection.Object.UseStoredProcedure("delete_flag")
.SetParam("@flag", flag.Name)
.ExecuteNonQueryAsync();
} }
} }

@ -1,63 +1,139 @@
using System.Collections.Generic; using ImageMagick;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace BantFlags.Data namespace BantFlags.Data
{ {
/// <summary>
/// Singleton class comprised of objects used in/Upload.
/// It's a fucking mess and I hate it so much.
/// </summary>
public class Staging public class Staging
{ {
public List<RenameFlag> RenamedFlags { get; set; } public List<Flag> Flags { get; set; }
public List<FormFlag> DeletedFlags { get; set; }
public List<FormFlag> AddedFlags { get; set; }
/// <summary>
/// The current list of resolved flags including changes currently in Staging.
/// Exists here since it's a singleton.
/// </summary>
public List<string> Flags { get; set; }
/// <summary>
/// Used for commiting and unstaging staged flags.
/// </summary>
public string Password { get; } public string Password { get; }
public HashSet<string> Names { get; set; }
public Staging(string password) public Staging(string password)
{ {
RenamedFlags = new List<RenameFlag>(); Flags = new List<Flag>();
DeletedFlags = new List<FormFlag>();
AddedFlags = new List<FormFlag>();
Password = password; Password = password;
} }
public void Clear() public void Clear()
{ {
RenamedFlags = new List<RenameFlag>(); Flags = new List<Flag>();
DeletedFlags = new List<FormFlag>();
AddedFlags = new List<FormFlag>();
} }
} }
public class FormFlag public enum Method
{ {
public string Name { get; set; } Delete,
public bool IsChecked { get; set; }
public Method FormMethod { get; set; } Rename,
}
public class RenameFlag : FormFlag Add
{
public string NewName { get; set; }
} }
public enum Method public class Flag
{ {
Delete, public string Name { get; set; }
Rename, public string OldName { get; set; }
Add public bool IsChecked { get; set; }
public Method FlagMethod { get; set; }
public Flag()
{
}
private Flag(string name, Method method)
{
Name = name;
FlagMethod = method;
}
private Flag(string name, string oldName, Method method)
{
Name = name;
OldName = oldName;
FlagMethod = method;
}
public static Result<Flag> CreateFromDelete(string name)
=> Result<Flag>.Pass(new Flag(name, Method.Delete)); // We don't need any validation for deleted flags.
public static Result<Flag> CreateFromRename(string oldName, string newName, HashSet<string> names)
{
Result<string> fileName = ValidateFileName(newName, names);
if (fileName.Failed)
return Result<Flag>.Fail(fileName.ErrorMessage);
return Result<Flag>.Pass(new Flag(newName, oldName, Method.Rename));
}
public static async Task<Result<Flag>> CreateFromFile(IFormFile upload, HashSet<string> names)
{
byte[] PNGHeader = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
if (upload.ContentType.ToLower() != "image/png")
return Result<Flag>.Fail("Image must be a png.");
if (upload.Length > 15 * 1024)
return Result<Flag>.Fail("File too big. Max size is 15kb.");
var name = Path.GetFileNameWithoutExtension(upload.FileName);
Result<string> fileName = ValidateFileName(name, names);
if (fileName.Failed)
return Result<Flag>.Fail(fileName.ErrorMessage);
using (var memoryStream = new MemoryStream())
{
await upload.CopyToAsync(memoryStream);
memoryStream.Position = 0;
using (var image = new MagickImage(memoryStream))
{
if (image.Width != 16 || image.Height != 11)
return Result<Flag>.Fail("Invalid image dimensions. Flags should be 16px by 11px.");
}
using (var reader = new BinaryReader(memoryStream))
{
reader.BaseStream.Position = 0;
if (!reader.ReadBytes(PNGHeader.Length).SequenceEqual(PNGHeader))
return Result<Flag>.Fail("Invalid png header.");
}
}
return Result<Flag>.Pass(new Flag(name, Method.Add));
}
private static Result<string> ValidateFileName(string name, HashSet<string> names)
{
if (string.IsNullOrWhiteSpace(name))
return Result<string>.Fail("Flag name can't be empty.");
if (name.Length > 100)
return Result<string>.Fail("Flag name too long.");
if (name == "empty, or there were errors. Re - set your flags.")
return Result<string>.Fail("Invalid flag name.");
if (name.Contains("||") || name.Contains(","))
return Result<string>.Fail("Flag name contains invalid characters. You can't use \"||\" or \",\".");
if (names.Contains(name))
return Result<string>.Fail("A flag with that name already exists.");
return Result<string>.Pass(name);
}
} }
} }

@ -10,8 +10,8 @@
<a href="~/bantflags.user.js">Install Bantflags</a> <a href="~/bantflags.user.js">Install Bantflags</a>
<br /> <br />
<br /> <br />
<a href="https://nineball.party/srsbsn/3521">Official Thread</a> <a href="https://nineball.party/srsbsn/3521#bottom">Official Thread</a>
<br /> <br />
<br /> <br />
<a asp-page="Upload">Upload Flags</a> <a asp-page="Upload">Upload Flags</a>

@ -1,5 +1,5 @@
@page @page
@using BantFlags.Data @using BantFlags.Data
@model BantFlags.UploadModel @model BantFlags.UploadModel
@{ @{
ViewData["Title"] = "Upload"; ViewData["Title"] = "Upload";
@ -8,27 +8,24 @@
<h1>Upload</h1> <h1>Upload</h1>
<img src="~/montage.png" /> <img src="~/montage.png" />
@Model.Message <h2 id="message">@Model.Message</h2>
<h2>Add a Flag</h2> <h2>Add a Flag</h2>
<form method="post" asp-page-handler="Add" enctype="multipart/form-data"> <form method="post" asp-page-handler="Add" enctype="multipart/form-data">
<input type="file" asp-for="Upload" /> <input type="file" name="upload" />
@Html.CheckBoxFor(model => model.ShouldGloss)<span>Apply Gloss?</span> <input type="checkbox" name="gloss" value="true" /><span>Apply Gloss?</span>
<br /> <br />
<br /> <br />
<input type="submit" value="Upload Flag" /> <input type="submit" value="Upload Flag" />
</form> </form>
<h2>Manage Existing Flags</h2> <h2>Manage Existing Flags</h2>
<form method="post" enctype="multipart/form-data"> <form method="post">
<label>Flag:</label>
<select name="flag"> <select name="flag">
@foreach (string s in Model.staging.Flags) @foreach (string s in Model.StagedFlags.Names)
{ {
<option>@s</option> <option>@s</option>
} }
</select> </select>
<label>New Name:</label>
<input type="text" name="newName" /> <input type="text" name="newName" />
<br /> <br />
<br /> <br />
@ -45,77 +42,65 @@
<input type="submit" value="Commit Changes" /> <input type="submit" value="Commit Changes" />
</form> </form>
<h2>Pending Changes</h2> @if (Model.StagedFlags.Flags.Any())
<form method="post" asp-page-handler="Unstage"> {
<label>Password:</label> <h2>Pending Changes</h2>
<input type="text" name="password" /> <form method="post" asp-page-handler="Unstage">
<br /> <input type="text" name="password" />
<br /> <br />
<button type="submit" onclick="window.confirm('Really unstage the selected flags?')">unstage</button> <br />
@if (Model.staging.AddedFlags.Any()) <button type="submit">Remove from staging</button>
{ <h3>Deleted Flags</h3>
<h3>New Flags</h3> @* TODO: There has to be a better way to handle this*@
<div class="flag-container"> @for (int i = 0; i < Model.StagedFlags.Flags.Count(); i++)
{
@foreach (FormFlag flag in Model.staging.AddedFlags) if (Model.StagedFlags.Flags[i].FlagMethod == Method.Delete)
{ {
<div class="flag"> <div class="flag">
<img src="~/flags/staging/@(flag.Name).png" />
<br /> <label>@(Model.StagedFlags.Flags[i].Name)</label>
<span>@flag.Name</span> <img src="~/flags/@(Model.StagedFlags.Flags[i].Name).png" />
<input type="hidden" name="addedAndDeletedFlags.Index" value="@flag.Name" /> <input type="hidden" name="flags[@i].Name" value="@Model.StagedFlags.Flags[i].Name" />
<input type="hidden" name="addedAndDeletedFlags[@flag.Name].Name" value="@flag.Name" /> <input type="hidden" name="flags[@i].FormMethod" value="@Model.StagedFlags.Flags[i].FlagMethod" />
<input type="hidden" name="addedAndDeletedFlags[@flag.Name].Method" value="@Method.Add" /> <input type="checkbox" name="flags[@i].IsChecked" value="true" />
<br />
<input type="checkbox" name="addedAndDeletedFlags[@flag.Name].IsChecked" value="true" />
</div> </div>
} }
</div>
}
@if (Model.staging.DeletedFlags.Any()) }
{
<h3>Deleted Flags</h3> <h3>Renamed Flags</h3>
<div class="flag-container"> @for (int i = 0; i < Model.StagedFlags.Flags.Count(); i++)
@foreach (FormFlag flag in Model.staging.DeletedFlags) {
if (Model.StagedFlags.Flags[i].FlagMethod == Method.Rename)
{ {
<div class="flag"> <div class="flag">
<img src="~/flags/@(flag.Name).png" /> <label>@(Model.StagedFlags.Flags[i].Name)</label>
<br /> <img src="~/flags/@(Model.StagedFlags.Flags[i].OldName).png" />
<span>@flag.Name</span> <input type="hidden" name="flags[@i].Name" value="@Model.StagedFlags.Flags[i].Name" />
<input type="hidden" name="addedAndDeletedFlags.Index" value="@flag.Name" /> <input type="hidden" name="flags[@i].FormMethod" value="@Model.StagedFlags.Flags[i].FlagMethod" />
<input type="hidden" name="addedAndDeletedFlags[@flag.Name].Name" value="@flag.Name" /> <input type="checkbox" name="flags[@i].IsChecked" value="true" />
<input type="hidden" name="addedAndDeletedFlags[@flag.Name].Method" value="@Method.Delete" />
<br />
<input type="checkbox" name="addedAndDeletedFlags[@flag.Name].IsChecked" value="true" />
</div> </div>
} }
</div>
} }
@if (Model.staging.RenamedFlags.Any()) <h3>Added Flags</h3>
{ @for (int i = 0; i < Model.StagedFlags.Flags.Count(); i++)
<h3>Renamed Flags</h3> {
<div class="flag-container"> if (Model.StagedFlags.Flags[i].FlagMethod == Method.Add)
@foreach (RenameFlag flag in Model.staging.RenamedFlags)
{ {
<div class="flag"> <div class="flag">
<img src="~/flags/@(flag.Name).png" /> <label>@(Model.StagedFlags.Flags[i].Name)</label>
<br /> <img src="~/flags/staging/@(Model.StagedFlags.Flags[i].Name).png" />
<span>@flag.NewName</span> <input type="hidden" name="flags[@i].Name" value="@Model.StagedFlags.Flags[i].Name" />
<input type="hidden" name="renamedFlags.Index" value="@flag.Name" /> <input type="hidden" name="flags[@i].FormMethod" value="@Model.StagedFlags.Flags[i].FlagMethod" />
<input type="hidden" name="renamedFlags[@flag.Name].Name" value="@flag.Name" /> <input type="checkbox" name="flags[@i].IsChecked" value="true" />
<input type="hidden" name="renamedFlags[@flag.Name].Method" value="@Method.Rename" />
<input type="hidden" name="renamedFlags[@flag.Name].NewName" value="@flag.NewName" />
<br />
<input type="checkbox" name="renamedFlags[@flag.Name].IsChecked" value="true" />
</div> </div>
} }
</div>
} }
</form> </form>
}
@section Head { @section Head {
<link rel="stylesheet" href="~/upload.css" /> <link rel="stylesheet" href="~/upload.css" />
@ -128,7 +113,7 @@
let x = document.getElementsByTagName('select')[0].children let x = document.getElementsByTagName('select')[0].children
Array.prototype.slice.call(x).forEach(function (y) { Array.prototype.slice.call(x).forEach(function (y) {
var name = y.innerHTML; var name = y.innerHTML;
y.innerHTML = "<img src=\"https://flags.plum.moe/flags/" + name + ".png\">" + name y.innerHTML = "<img src=\"flags/" + name + ".png\">" + name
}); });
}, { once: true }); }, { once: true });
</script> </script>

@ -7,274 +7,181 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace BantFlags namespace BantFlags
{ {
// I don't know if I need these anymore.
[RequestFormLimits(ValueCountLimit = 5000)]
[IgnoreAntiforgeryToken(Order = 2000)]
public class UploadModel : PageModel public class UploadModel : PageModel
{ {
private IWebHostEnvironment Env { get; }
private DatabaseService Database { get; set; } private DatabaseService Database { get; set; }
public Staging StagedFlags { get; set; }
public string Message { get; private set; }
private readonly byte[] PNGHeader = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; private string WebRoot { get; }
private string FlagsPath { get; set; }
public Staging staging { get; set; } public HashSet<string> AllNames => StagedFlags.Names.Concat(StagedFlags.Flags.Select(x => x.Name)).ToHashSet();
public UploadModel(IWebHostEnvironment env, DatabaseService db, Staging s) public UploadModel(DatabaseService dbs, Staging ns, IWebHostEnvironment env)
{ {
Env = env; Database = dbs;
Database = db;
staging = s; StagedFlags = ns;
FlagsPath = Env.WebRootPath + "/flags/"; WebRoot = env.WebRootPath;
} }
public string Message { get; private set; } public void OnGet()
// TODO: These bound properties should be inlined.
[BindProperty]
public IFormFile Upload { get; set; }
[BindProperty]
public bool ShouldGloss { get; set; }
public async void OnGet()
{ {
if (staging.Flags == null) StagedFlags.Names = StagedFlags.Names ?? Database.KnownFlags;
{
staging.Flags = await Database.GetFlags(); // Because we can't populate Flags in the constructor.
}
} }
public IActionResult OnPostUnstage(List<FormFlag> addedAndDeletedFlags, List<RenameFlag> renamedFlags, string password) public IActionResult OnPostDelete(string flag)
{ {
if (password != staging.Password) // TODO: Maybe we should hash this? var stagingFlag = Flag.CreateFromDelete(flag).Value;
{
Message = "Wrong Password";
return Page();
}
try // Haha I can't program
{
var addedAndDeleted = addedAndDeletedFlags.Where(x => x.IsChecked);
var renamed = renamedFlags.Where(x => x.IsChecked);
addedAndDeleted.ForEach(x => StagedFlags.Flags.Add(stagingFlag);
_ = x.FormMethod switch StagedFlags.Names.Remove(stagingFlag.Name);
{
// Using an enum seems kinda redundant here.
Method.Delete => staging.DeletedFlags.Remove(staging.DeletedFlags.First(y => y.Name == x.Name)),
Method.Add => staging.AddedFlags.Remove(staging.AddedFlags.First(y => y.Name == x.Name)),
_ => throw new Exception()
});
renamed.ForEach(x => staging.RenamedFlags.Remove(staging.RenamedFlags.First(y => y.Name == x.Name))); // These can be reworked to use asp-for and then we can work off the original objects. Message = $"{stagingFlag.Name} deleted.";
return Page();
addedAndDeleted.ForEach(x => staging.Flags.Add(x.Name)); }
renamed.ForEach(x => staging.Flags.Add(x.Name));
staging.Flags = staging.Flags.Distinct().OrderBy(x => x).ToList(); public IActionResult OnPostRename(string flag, string newName)
{
var stagingFlag = Flag.CreateFromRename(flag, newName, AllNames);
Message = $"Successfully unstaged flags"; if (stagingFlag.Failed)
}
catch
{ {
Message = "Something went very wrong"; Message = stagingFlag.ErrorMessage;
return Page();
} }
StagedFlags.Flags.Add(stagingFlag.Value);
StagedFlags.Names.Remove(stagingFlag.Value.OldName);
Message = $"{stagingFlag.Value.OldName} renamed to {stagingFlag.Value.Name}.";
return Page(); return Page();
} }
public async Task<IActionResult> OnPostCommitAsync(string password) public async Task<IActionResult> OnPostAddAsync(IFormFile upload, bool gloss)
{ {
if (password != staging.Password) var stagingFlag = await Flag.CreateFromFile(upload, AllNames);
if (stagingFlag.Failed)
{ {
Message = "Wrong Password"; Message = stagingFlag.ErrorMessage;
return Page(); return Page();
} }
try
{
// TODO: This needs to be rewritten / burnt / both
if (staging.DeletedFlags.Any())
{
await Database.DeleteFlagsAsync(staging.DeletedFlags);
staging.DeletedFlags
.ForEach(flag => System.IO.File.Copy(Path.Combine(FlagsPath, flag.Name + ".png"), Path.Combine(FlagsPath, "dead/", flag.Name + ".png")));
}
if (staging.AddedFlags.Any()) using var memoryStream = new MemoryStream();
{ await upload.CopyToAsync(memoryStream);
await Database.InsertFlagsAsync(staging.AddedFlags);
staging.AddedFlags memoryStream.Position = 0;
.ForEach(flag => System.IO.File.Copy(Path.Combine(FlagsPath, "staging/", flag.Name + ".png"), Path.Combine(FlagsPath, flag.Name + ".png")));
}
if (staging.RenamedFlags.Any()) // Magic.NET is a huge dependency to be used like this
{ // Maybe we should switch to a Process and expect to have
await Database.RenameFlagsAsync(staging.RenamedFlags); // ImageMagick installed on the target machine.
using var image = new MagickImage(memoryStream);
staging.RenamedFlags
.ForEach(flag => System.IO.File.Copy(Path.Combine(FlagsPath, flag.Name + ".png"), Path.Combine(FlagsPath, flag.NewName + ".png")));
}
await Database.UpdateKnownFlags(); if (gloss)
staging.Flags = await Database.GetFlags();
staging.Clear();
Message = "Changes Commited successfully";
}
catch (Exception e)
{ {
Message = "Something went bang\n" + e.Message; using var glossImage = new MagickImage(WebRoot + "/gloss.png");
}
return Page(); glossImage.Composite(image, new PointD(0, 0), CompositeOperator.Over);
} }
public IActionResult OnPostDelete(string flag) image.Write(WebRoot + "/flags/staging/" + upload.FileName);
{
staging.DeletedFlags.Add(new FormFlag
{
Name = flag
});
staging.Flags.Remove(flag); StagedFlags.Flags.Add(stagingFlag.Value);
Message = $"{stagingFlag.Value.Name} uploaded";
return Page(); return Page();
} }
public IActionResult OnPostRename(string flag, string newName) public IActionResult OnPostUnstage(Flag[] flags, string password)
{ {
if (!(FileNameIsValid(newName))) if (password != StagedFlags.Password)
{ {
Message = "Invalid Filename."; Message = "Incorrect Password";
return Page(); return Page();
} }
staging.RenamedFlags.Add(new RenameFlag for (int i = flags.Length - 1; i >= 0; i--)
{
Name = flag,
NewName = newName
});
staging.Flags.Remove(flag);
return Page();
}
public async Task<IActionResult> OnPostAddAsync()
{
try
{ {
if (!(await ValidateImageAsync())) if (flags[i].IsChecked != true)
{ {
Message = "Invalid Image."; continue;
return Page();
} }
// TODO: maybe there's something no releasing memory here. - can't Directory.Move(). StagedFlags.Flags.RemoveAt(i);
using var memoryStream = new MemoryStream(); var flag = flags[i];
await Upload.CopyToAsync(memoryStream); switch (flag.FlagMethod)
memoryStream.Position = 0;
// Magic.NET is a huge dependency to be used like this
// Maybe we should switch to a Process and expect to have
// ImageMagick installed on the target machine.
using var image = new MagickImage(memoryStream);
if (ShouldGloss)
{ {
using var gloss = new MagickImage(Env.WebRootPath + "/gloss.png"); case Method.Add:
System.IO.File.Delete(WebRoot + "/flags/staging/" + flag.Name);
StagedFlags.Names.Remove(flag.Name);
break;
gloss.Composite(image, new PointD(0, 0), CompositeOperator.Over); case Method.Delete:
} StagedFlags.Names.Add(flag.Name);
break;
image.Write(FlagsPath + "staging/" + Upload.FileName); case Method.Rename:
StagedFlags.Names.Add(flag.OldName);
break;
staging.AddedFlags.Add(new FormFlag default:
{ throw new Exception();
Name = Path.GetFileNameWithoutExtension(Upload.FileName) }
});
Message = "Flag uploaded successfully!";
return Page();
} }
catch (Exception e)
{
Message = $"Something went bang.\n\n\n{e.Message}";
return Page(); Message = "Removed flags from staging";
} return Page();
} }
// TODO: hash images and check against for duplicates. public async Task<IActionResult> OnPostCommit(string password)
// or is that going too far?
/// <summary>
/// Rigorously validates an image to ensure it's a flag.
/// </summary>
public async Task<bool> ValidateImageAsync()
{ {
var fileName = Path.GetFileNameWithoutExtension(Upload.FileName); if (password != StagedFlags.Password)
if (!(FileNameIsValid(fileName))
|| Upload.Length > 15 * 1024 // 15KB
|| Upload.ContentType.ToLower() != "image/png")
{ {
return false; Message = "Incorrect Password";
return Page();
} }
using (var memoryStream = new MemoryStream()) foreach (var flag in StagedFlags.Flags)
{ {
await Upload.CopyToAsync(memoryStream); string flagname = flag.Name + ".png";
memoryStream.Position = 0; switch (flag.FlagMethod)
using (var image = new MagickImage(memoryStream))
{ {
if (image.Width != 16 || image.Height != 11) case Method.Add:
{ await Database.InsertFlagAsync(flag);
return false; Directory.Move(WebRoot + "/flags/staging/" + flagname, WebRoot + "/flags/" + flagname);
} break;
case Method.Delete:
await Database.DeleteFlagAsync(flag);
Directory.Move(WebRoot + "/flags/" + flagname, WebRoot + "/flags/dead/" + flagname);
break;
case Method.Rename:
await Database.RenameFlagAsync(flag);
Directory.Move(WebRoot + "/flags/" + flag.OldName + ".png", WebRoot + "/flags/" + flagname);
break;
default:
throw new Exception();
} }
}
using (var reader = new BinaryReader(memoryStream)) await Database.UpdateKnownFlags();
{ StagedFlags.Names = Database.KnownFlags;
reader.BaseStream.Position = 0; StagedFlags.Clear();
return reader.ReadBytes(PNGHeader.Length).SequenceEqual(PNGHeader); Message = "Changes committed successfully";
} return Page();
}
} }
/// <summary>
/// Matches bad things we don't want in filenames.
/// Inverts the result - returns false on a match.
/// </summary>
/// <param name="fileName">The name of the file to validate.</param>
/// <returns></returns>
private bool FileNameIsValid(string fileName) =>
!(fileName == null
|| fileName.Contains("||")
|| fileName.Contains(",")
|| Database.KnownFlags.Contains(fileName)
|| staging.AddedFlags.Select(x => x.Name).Contains(fileName)
|| staging.DeletedFlags.Select(x => x.Name).Contains(fileName)
|| staging.RenamedFlags.Select(x => x.Name).Contains(fileName)
|| staging.RenamedFlags.Select(x => x.NewName).Contains(fileName)
|| fileName.Length > 100);
} }
} }

@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;

@ -22,3 +22,7 @@ h3 {
display: inline-block; display: inline-block;
padding-bottom: 10px; padding-bottom: 10px;
} }
#message {
color: #ee0;
}
Loading…
Cancel
Save