|
|
|
@ -0,0 +1,275 @@
|
|
|
|
|
using BantFlags.Data;
|
|
|
|
|
using BantFlags.Data.Database;
|
|
|
|
|
using ImageMagick;
|
|
|
|
|
using Microsoft.AspNetCore.Hosting;
|
|
|
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Data;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
namespace BantFlags
|
|
|
|
|
{
|
|
|
|
|
[RequestFormLimits(ValueCountLimit = 5000)]
|
|
|
|
|
[IgnoreAntiforgeryToken(Order = 2000)]
|
|
|
|
|
public class UploadModel : PageModel
|
|
|
|
|
{
|
|
|
|
|
private IWebHostEnvironment Env { get; }
|
|
|
|
|
private DatabaseService Database { get; set; }
|
|
|
|
|
|
|
|
|
|
private readonly byte[] pngHeader = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
|
|
|
|
|
|
|
|
|
|
private string FlagsPath { get; set; }
|
|
|
|
|
|
|
|
|
|
public Staging staging { get; set; }
|
|
|
|
|
|
|
|
|
|
public UploadModel(IWebHostEnvironment env, DatabaseService db, Staging s)
|
|
|
|
|
{
|
|
|
|
|
Env = env;
|
|
|
|
|
Database = db;
|
|
|
|
|
|
|
|
|
|
staging = s;
|
|
|
|
|
|
|
|
|
|
FlagsPath = Env.WebRootPath + "/flags/";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string Message { get; private set; }
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
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.
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
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";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Page();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<IActionResult> OnPostCommitAsync(string password)
|
|
|
|
|
{
|
|
|
|
|
if (password != staging.Password)
|
|
|
|
|
{
|
|
|
|
|
Message = "Wrong Password";
|
|
|
|
|
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())
|
|
|
|
|
{
|
|
|
|
|
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())
|
|
|
|
|
{
|
|
|
|
|
await Database.RenameFlagsAsync(staging.RenamedFlags);
|
|
|
|
|
|
|
|
|
|
staging.RenamedFlags
|
|
|
|
|
.ForEach(flag => System.IO.File.Copy(Path.Combine(FlagsPath, flag.Name + ".png"), Path.Combine(FlagsPath, flag.NewName + ".png")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await Database.UpdateKnownFlags();
|
|
|
|
|
staging.Flags = await Database.GetFlags();
|
|
|
|
|
staging.Clear();
|
|
|
|
|
|
|
|
|
|
Message = "Changes Commited successfully";
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Message = "Something went bang\n" + e.Message;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Page();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IActionResult OnPostDelete(string flag)
|
|
|
|
|
{
|
|
|
|
|
staging.DeletedFlags.Add(new FormFlag
|
|
|
|
|
{
|
|
|
|
|
Name = flag
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
staging.Flags.Remove(flag);
|
|
|
|
|
|
|
|
|
|
return Page();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IActionResult OnPostRename(string flag, string newName)
|
|
|
|
|
{
|
|
|
|
|
if (!(FileNameIsValid(newName)))
|
|
|
|
|
{
|
|
|
|
|
Message = "Invalid Filename.";
|
|
|
|
|
|
|
|
|
|
return Page();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
staging.RenamedFlags.Add(new RenameFlag
|
|
|
|
|
{
|
|
|
|
|
Name = flag,
|
|
|
|
|
NewName = newName
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
staging.Flags.Remove(flag);
|
|
|
|
|
|
|
|
|
|
return Page();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<IActionResult> OnPostAddAsync()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (!(await ValidateImageAsync()))
|
|
|
|
|
{
|
|
|
|
|
Message = "Invalid Image.";
|
|
|
|
|
return Page();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: maybe there's something no releasing memory here.
|
|
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
|
|
gloss.Composite(image, new PointD(0, 0), CompositeOperator.Over);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
image.Write(FlagsPath + "staging/" + Upload.FileName);
|
|
|
|
|
|
|
|
|
|
staging.AddedFlags.Add(new FormFlag
|
|
|
|
|
{
|
|
|
|
|
Name = Path.GetFileNameWithoutExtension(Upload.FileName)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Message = "Flag uploaded successfully!";
|
|
|
|
|
|
|
|
|
|
return Page();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Message = $"Something went bang.\n{e.Message}";
|
|
|
|
|
|
|
|
|
|
return Page();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: hash images and check against for duplicates.
|
|
|
|
|
// 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 (!(FileNameIsValid(fileName))
|
|
|
|
|
|| Upload.Length > 15 * 1024 // 15KB
|
|
|
|
|
|| Upload.ContentType.ToLower() != "image/png")
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using (var reader = new BinaryReader(memoryStream))
|
|
|
|
|
{
|
|
|
|
|
reader.BaseStream.Position = 0;
|
|
|
|
|
|
|
|
|
|
return reader.ReadBytes(pngHeader.Length).SequenceEqual(pngHeader);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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("||")
|
|
|
|
|
|| Database.KnownFlags().Contains(fileName)
|
|
|
|
|
|| staging.AddedFlags.Select(x => x.Name).Contains(fileName)
|
|
|
|
|
|| fileName.Length > 100);
|
|
|
|
|
}
|
|
|
|
|
}
|