@ -7,274 +7,181 @@ 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
{
// I don't know if I need these anymore.
[RequestFormLimits(ValueCountLimit = 5000)]
[IgnoreAntiforgeryToken(Order = 2000)]
public class UploadModel : PageModel
{
private IWebHostEnvironment Env { get ; }
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 FlagsPath { get ; set ; }
private string WebRoot { get ; }
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 db s , Staging n s, IWebHostEnvironment env )
{
Env = env ;
Database = db ;
Database = dbs ;
staging = s;
StagedFlags = n s;
FlagsPath = Env . WebRootPath + "/flags/" ;
WebRoot = env . WebRootPath ;
}
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 ( )
public void OnGet ( )
{
if ( staging . Flags = = null )
{
staging . Flags = await Database . GetFlags ( ) ; // Because we can't populate Flags in the constructor.
}
StagedFlags . Names = StagedFlags . Names ? ? Database . KnownFlags ;
}
public IActionResult OnPost Unstage( 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 ) ;
var stagingFlag = Flag . CreateFromDelete ( flag ) . Value ;
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 ( )
} ) ;
StagedFlags . Flags . Add ( stagingFlag ) ;
StagedFlags . Names . Remove ( stagingFlag . Name ) ;
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 ) ) ;
Message = $"{stagingFlag.Name} deleted." ;
return Page ( ) ;
}
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" ;
}
catch
if ( stagingFlag . Failed )
{
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 ( ) ;
}
public async Task < IActionResult > OnPostCommitAsync ( string password )
public async Task < IActionResult > OnPost AddAsync( 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 ( ) ;
}
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 ) ;
using var memoryStream = new MemoryStream ( ) ;
await upload . CopyToAsync ( memoryStream ) ;
staging . AddedFlags
. ForEach ( flag = > System . IO . File . Copy ( Path . Combine ( FlagsPath , "staging/" , flag . Name + ".png" ) , Path . Combine ( FlagsPath , flag . Name + ".png" ) ) ) ;
}
memoryStream . Position = 0 ;
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" ) ) ) ;
}
// 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 ) ;
await Database . UpdateKnownFlags ( ) ;
staging . Flags = await Database . GetFlags ( ) ;
staging . Clear ( ) ;
Message = "Changes Commited successfully" ;
}
catch ( Exception e )
if ( gloss )
{
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 )
{
staging . DeletedFlags . Add ( new FormFlag
{
Name = flag
} ) ;
image . Write ( WebRoot + "/flags/staging/" + upload . FileName ) ;
staging. Flags . Remove ( flag ) ;
StagedFlags . Flags . Add ( stagingFlag . Value ) ;
Message = $"{stagingFlag.Value.Name} uploaded" ;
return Page ( ) ;
}
public IActionResult OnPost Rename( string flag , string newName )
public IActionResult OnPost Unstage( Flag [ ] flags , string password )
{
if ( ! ( FileNameIsValid ( newName ) ) )
if ( password ! = StagedFlags . Password )
{
Message = "Invalid Filename." ;
Message = "Incorrect Password" ;
return Page ( ) ;
}
staging . RenamedFlags . Add ( new RenameFlag
{
Name = flag ,
NewName = newName
} ) ;
staging . Flags . Remove ( flag ) ;
return Page ( ) ;
}
public async Task < IActionResult > OnPostAddAsync ( )
{
try
for ( int i = flags . Length - 1 ; i > = 0 ; i - - )
{
if ( ! ( await ValidateImageAsync ( ) ) )
if ( flags [ i ] . IsChecked ! = true )
{
Message = "Invalid Image." ;
return Page ( ) ;
continue ;
}
// TODO: maybe there's something no releasing memory here. - can't Directory.Move().
StagedFlags . Flags . RemoveAt ( 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 )
var flag = flags [ i ] ;
switch ( flag . FlagMethod )
{
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
{
Name = Path . GetFileNameWithoutExtension ( Upload . FileName )
} ) ;
Message = "Flag uploaded successfully!" ;
return Page ( ) ;
default :
throw new Exception ( ) ;
}
}
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.
// or is that going too far?
/// <summary>
/// Rigorously validates an image to ensure it's a flag.
/// </summary>
public async Task < bool > ValidateImageAsync ( )
public async Task < IActionResult > OnPostCommit ( string password )
{
var fileName = Path . GetFileNameWithoutExtension ( Upload . FileName ) ;
if ( ! ( FileNameIsValid ( fileName ) )
| | Upload . Length > 15 * 1024 // 15KB
| | Upload . ContentType . ToLower ( ) ! = "image/png" )
if ( password ! = StagedFlags . Password )
{
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 ;
using ( var image = new MagickImage ( memoryStream ) )
switch ( flag . FlagMethod )
{
if ( image . Width ! = 16 | | image . Height ! = 11 )
{
return false ;
}
case Method . Add :
await Database . InsertFlagAsync ( flag ) ;
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 ) )
{
reader . BaseStream . Position = 0 ;
await Database . UpdateKnownFlags ( ) ;
StagedFlags . Names = Database . KnownFlags ;
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 ) ;
}
}