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(); using var rentedConnection = await ConnectionPool.RentConnectionAsync();
using var query = rentedConnection.Object.UseStoredProcedure("insert_flag"); await rentedConnection.Object.UseStoredProcedure("insert_flag")
.SetParam("@flag", flag.Name)
.ExecuteNonQueryAsync();
}
flags.ForEach(async f => public async Task RenameFlagAsync(Flag flag)
await query.SetParam("@flag", f.Name) {
.ExecuteNonQueryAsync(reuse: true)); using var rentedConnection = await ConnectionPool.RentConnectionAsync();
await rentedConnection.Object.UseStoredProcedure("rename_flag")
.SetParam("@old", flag.OldName)
.SetParam("@new", flag.Name)
.ExecuteNonQueryAsync();
}
public async Task DeleteFlagAsync(Flag flag)
{
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
{
Delete,
Rename,
Add
}
public class Flag
{ {
public string Name { get; set; } public string Name { get; set; }
public string OldName { get; set; }
public bool IsChecked { get; set; } public bool IsChecked { get; set; }
public Method FormMethod { get; set; } public Method FlagMethod { get; set; }
public Flag()
{
} }
public class RenameFlag : FormFlag private Flag(string name, Method method)
{ {
public string NewName { get; set; } Name = name;
FlagMethod = method;
} }
public enum Method private Flag(string name, string oldName, Method method)
{ {
Delete, Name = name;
OldName = oldName;
FlagMethod = method;
}
Rename, public static Result<Flag> CreateFromDelete(string name)
=> Result<Flag>.Pass(new Flag(name, Method.Delete)); // We don't need any validation for deleted flags.
Add 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);
}
} }
} }

@ -11,7 +11,7 @@
<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>

@ -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>
<form method="post" asp-page-handler="Unstage">
<input type="text" name="password" /> <input type="text" name="password" />
<br /> <br />
<br /> <br />
<button type="submit" onclick="window.confirm('Really unstage the selected flags?')">unstage</button> <button type="submit">Remove from staging</button>
@if (Model.staging.AddedFlags.Any()) <h3>Deleted Flags</h3>
@* TODO: There has to be a better way to handle this*@
@for (int i = 0; i < Model.StagedFlags.Flags.Count(); i++)
{ {
<h3>New Flags</h3> if (Model.StagedFlags.Flags[i].FlagMethod == Method.Delete)
<div class="flag-container">
@foreach (FormFlag flag in Model.staging.AddedFlags)
{ {
<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>Renamed Flags</h3>
@for (int i = 0; i < Model.StagedFlags.Flags.Count(); i++)
{ {
<h3>Deleted Flags</h3> if (Model.StagedFlags.Flags[i].FlagMethod == Method.Rename)
<div class="flag-container">
@foreach (FormFlag flag in Model.staging.DeletedFlags)
{ {
<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> if (Model.StagedFlags.Flags[i].FlagMethod == Method.Add)
<div class="flag-container">
@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?
{
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 =>
_ = x.FormMethod switch
{ {
// Using an enum seems kinda redundant here. var stagingFlag = Flag.CreateFromDelete(flag).Value;
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. StagedFlags.Flags.Add(stagingFlag);
StagedFlags.Names.Remove(stagingFlag.Name);
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();
Message = $"Successfully unstaged flags";
}
catch
{
Message = "Something went very wrong";
}
Message = $"{stagingFlag.Name} deleted.";
return Page(); return Page();
} }
public async Task<IActionResult> OnPostCommitAsync(string password) public IActionResult OnPostRename(string flag, string newName)
{ {
if (password != staging.Password) var stagingFlag = Flag.CreateFromRename(flag, newName, 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 StagedFlags.Flags.Add(stagingFlag.Value);
.ForEach(flag => System.IO.File.Copy(Path.Combine(FlagsPath, flag.Name + ".png"), Path.Combine(FlagsPath, "dead/", flag.Name + ".png"))); StagedFlags.Names.Remove(stagingFlag.Value.OldName);
}
if (staging.AddedFlags.Any()) Message = $"{stagingFlag.Value.OldName} renamed to {stagingFlag.Value.Name}.";
{ return Page();
await Database.InsertFlagsAsync(staging.AddedFlags);
staging.AddedFlags
.ForEach(flag => System.IO.File.Copy(Path.Combine(FlagsPath, "staging/", flag.Name + ".png"), Path.Combine(FlagsPath, flag.Name + ".png")));
} }
if (staging.RenamedFlags.Any()) public async Task<IActionResult> OnPostAddAsync(IFormFile upload, bool gloss)
{ {
await Database.RenameFlagsAsync(staging.RenamedFlags); var stagingFlag = await Flag.CreateFromFile(upload, AllNames);
staging.RenamedFlags
.ForEach(flag => System.IO.File.Copy(Path.Combine(FlagsPath, flag.Name + ".png"), Path.Combine(FlagsPath, flag.NewName + ".png")));
}
await Database.UpdateKnownFlags(); if (stagingFlag.Failed)
staging.Flags = await Database.GetFlags();
staging.Clear();
Message = "Changes Commited successfully";
}
catch (Exception e)
{ {
Message = "Something went bang\n" + e.Message; Message = stagingFlag.ErrorMessage;
}
return Page(); return Page();
} }
public IActionResult OnPostDelete(string flag) using var memoryStream = new MemoryStream();
{ await upload.CopyToAsync(memoryStream);
staging.DeletedFlags.Add(new FormFlag
{
Name = flag
});
staging.Flags.Remove(flag); memoryStream.Position = 0;
return Page(); // 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);
public IActionResult OnPostRename(string flag, string newName) if (gloss)
{
if (!(FileNameIsValid(newName)))
{ {
Message = "Invalid Filename."; using var glossImage = new MagickImage(WebRoot + "/gloss.png");
return Page(); glossImage.Composite(image, new PointD(0, 0), CompositeOperator.Over);
} }
staging.RenamedFlags.Add(new RenameFlag image.Write(WebRoot + "/flags/staging/" + upload.FileName);
{
Name = flag,
NewName = newName
});
staging.Flags.Remove(flag); StagedFlags.Flags.Add(stagingFlag.Value);
Message = $"{stagingFlag.Value.Name} uploaded";
return Page(); return Page();
} }
public async Task<IActionResult> OnPostAddAsync() public IActionResult OnPostUnstage(Flag[] flags, string password)
{
try
{ {
if (!(await ValidateImageAsync())) if (password != StagedFlags.Password)
{ {
Message = "Invalid Image."; Message = "Incorrect Password";
return Page(); return Page();
} }
// TODO: maybe there's something no releasing memory here. - can't Directory.Move(). for (int i = flags.Length - 1; i >= 0; i--)
using var memoryStream = new MemoryStream();
await Upload.CopyToAsync(memoryStream);
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"); if (flags[i].IsChecked != true)
{
gloss.Composite(image, new PointD(0, 0), CompositeOperator.Over); continue;
} }
image.Write(FlagsPath + "staging/" + Upload.FileName); StagedFlags.Flags.RemoveAt(i);
staging.AddedFlags.Add(new FormFlag var flag = flags[i];
switch (flag.FlagMethod)
{ {
Name = Path.GetFileNameWithoutExtension(Upload.FileName) case Method.Add:
}); System.IO.File.Delete(WebRoot + "/flags/staging/" + flag.Name);
StagedFlags.Names.Remove(flag.Name);
break;
Message = "Flag uploaded successfully!"; case Method.Delete:
StagedFlags.Names.Add(flag.Name);
break;
return Page(); case Method.Rename:
StagedFlags.Names.Add(flag.OldName);
break;
default:
throw new Exception();
}
} }
catch (Exception e)
{
Message = $"Something went bang.\n\n\n{e.Message}";
Message = "Removed flags from staging";
return Page(); 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;
}
using (var reader = new BinaryReader(memoryStream)) case Method.Delete:
{ await Database.DeleteFlagAsync(flag);
reader.BaseStream.Position = 0; Directory.Move(WebRoot + "/flags/" + flagname, WebRoot + "/flags/dead/" + flagname);
break;
return reader.ReadBytes(PNGHeader.Length).SequenceEqual(PNGHeader); case Method.Rename:
} await Database.RenameFlagAsync(flag);
Directory.Move(WebRoot + "/flags/" + flag.OldName + ".png", WebRoot + "/flags/" + flagname);
break;
default:
throw new Exception();
} }
} }
/// <summary> await Database.UpdateKnownFlags();
/// Matches bad things we don't want in filenames. StagedFlags.Names = Database.KnownFlags;
/// Inverts the result - returns false on a match. StagedFlags.Clear();
/// </summary>
/// <param name="fileName">The name of the file to validate.</param> Message = "Changes committed successfully";
/// <returns></returns> return Page();
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