Skip to content

Commit

Permalink
Merge pull request #1 from erossini/Using-EasyMDE
Browse files Browse the repository at this point in the history
Rewrite the Markdown Editor with EasyMDE
  • Loading branch information
erossini authored Jan 11, 2022
2 parents 745b9a7 + 3e25f54 commit b75b7ca
Show file tree
Hide file tree
Showing 77 changed files with 27,125 additions and 201 deletions.
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mono_crash.*
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
Expand Down Expand Up @@ -61,6 +62,9 @@ project.lock.json
project.fragment.lock.json
artifacts/

# ASP.NET Scaffolding
ScaffoldingReadMe.txt

# StyleCop
StyleCopReport.xml

Expand Down Expand Up @@ -137,6 +141,11 @@ _TeamCity*
.axoCover/*
!.axoCover/settings.json

# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info

# Visual Studio code coverage results
*.coverage
*.coveragexml
Expand Down Expand Up @@ -348,3 +357,6 @@ MigrationBackup/

# Ionide (cross platform F# VS Code tools) working folder
.ionide/

# Fody - auto-generated XML schema
FodyWeavers.xsd
15 changes: 13 additions & 2 deletions MarkdownEditor.sln
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31729.503
# Visual Studio Version 17
VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PSC.Blazor.Components.MarkdownEditor", "PSC.Blazor.Components.MarkdownEditor\PSC.Blazor.Components.MarkdownEditor.csproj", "{6F56954A-A45C-432C-9F63-699D15042667}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarkdownEditorDemo", "MarkdownEditorDemo\MarkdownEditorDemo.csproj", "{4A71D48E-5A73-49F6-B813-2919DB84DC29}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarkdownEditorDemo.Api", "MarkdownEditorDemo.Api\MarkdownEditorDemo.Api.csproj", "{9771B329-8344-4839-81D5-78B9F9AA79F4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Other files", "Other files", "{E62D6583-788F-4BDE-833F-6EE650453874}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -21,6 +28,10 @@ Global
{4A71D48E-5A73-49F6-B813-2919DB84DC29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A71D48E-5A73-49F6-B813-2919DB84DC29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A71D48E-5A73-49F6-B813-2919DB84DC29}.Release|Any CPU.Build.0 = Release|Any CPU
{9771B329-8344-4839-81D5-78B9F9AA79F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9771B329-8344-4839-81D5-78B9F9AA79F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9771B329-8344-4839-81D5-78B9F9AA79F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9771B329-8344-4839-81D5-78B9F9AA79F4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
112 changes: 112 additions & 0 deletions MarkdownEditorDemo.Api/Controllers/FilesController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
namespace MarkdownEditorDemo.Api.Controllers;

/// <summary>
/// Files controller
/// </summary>
[ApiController]
[Route("api/[Controller]")]
public class FilesController : ControllerBase
{
/// <summary>
/// The logger
/// </summary>
private readonly ILogger<FilesController> _logger;
/// <summary>
/// The HTTP context accessor
/// </summary>
private readonly IHttpContextAccessor _httpContextAccessor;
/// <summary>
/// The file size limit
/// </summary>
private readonly long _fileSizeLimit;
/// <summary>
/// The permitted extensions
/// </summary>
private readonly string[] _permittedExtensions = { ".gif", ".png", ".jpg", ".jpeg" };
/// <summary>
/// The target folder path
/// </summary>
private readonly string _targetFolderPath;

/// <summary>
/// Initializes a new instance of the <see cref="FilesController"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="config">The configuration.</param>
public FilesController(ILogger<FilesController> logger, IConfiguration config, IHttpContextAccessor httpContextAccessor)
{
_logger = logger;
_httpContextAccessor = httpContextAccessor;

_fileSizeLimit = config.GetValue<long>("FileSizeLimit");
_targetFolderPath = (string)System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
_targetFolderPath = Path.Combine(_targetFolderPath, "Uploads").Replace("file:\\", "");

Directory.CreateDirectory(_targetFolderPath);
}

/// <summary>
/// Uploads a file.
/// </summary>
/// <returns></returns>
[HttpPost]
[DisableFormValueModelBinding]
public async Task<IActionResult> Upload()
{
if (!Request.ContentType.IsMultipartContentType())
{
ModelState.AddModelError("File", "The request couldn't be processed (Error 1).");
_logger.LogWarning($"The request content type [{Request.ContentType}] is invalid.");
return BadRequest(ModelState);
}

var boundary = MediaTypeHeaderValue.Parse(Request.ContentType).GetBoundary(new FormOptions().MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();

string trustedFileNameForFileStorage = String.Empty;

while (section != null)
{
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition,
out var contentDisposition);

if (hasContentDispositionHeader)
{
if (contentDisposition.IsFileDisposition())
{
// Don't trust the file name sent by the client. To display the file name, HTML-encode the value.
var trustedFileNameForDisplay = WebUtility.HtmlEncode(contentDisposition.FileName.Value);
trustedFileNameForFileStorage = Path.GetFileNameWithoutExtension(Path.GetRandomFileName()) +
Path.GetExtension(trustedFileNameForDisplay);

var streamedFileContent = await FileHelpers.ProcessStreamedFile(section, contentDisposition,
ModelState, _permittedExtensions, _fileSizeLimit);

if (!ModelState.IsValid)
return BadRequest(ModelState);

var trustedFilePath = Path.Combine(_targetFolderPath, trustedFileNameForFileStorage);
using (var targetStream = System.IO.File.Create(trustedFilePath))
{
await targetStream.WriteAsync(streamedFileContent);
_logger.LogInformation($"Uploaded file '{trustedFileNameForDisplay}' saved to '{_targetFolderPath}' " +
$"as {trustedFileNameForFileStorage}");
}
}
}

// Drain any remaining section body that hasn't been consumed and read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}

if (!string.IsNullOrEmpty(trustedFileNameForFileStorage))
{
string host = $"{_httpContextAccessor.HttpContext.Request.Scheme}://{_httpContextAccessor.HttpContext.Request.Host.Value}";
int index = FileHelpers.GetExtensionId(Path.GetExtension(trustedFileNameForFileStorage));
return Ok($"{host}/{index}/{Path.GetFileName(trustedFileNameForFileStorage)}");
}
else
return BadRequest("It wasn't possible to upload the file");
}
}
56 changes: 56 additions & 0 deletions MarkdownEditorDemo.Api/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
namespace MarkdownEditorDemo.Api.Controllers
{
public class HomeController : Controller
{
/// <summary>
/// The logger
/// </summary>
private readonly ILogger<HomeController> _logger;
/// <summary>
/// The target folder path
/// </summary>
private readonly string _targetFolderPath;

/// <summary>
/// Initializes a new instance of the <see cref="HomeController"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="config">The configuration.</param>
public HomeController(ILogger<HomeController> logger, IConfiguration config)
{
_logger = logger;

_targetFolderPath = (string)System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
_targetFolderPath = Path.Combine(_targetFolderPath, "Uploads");
}

[HttpGet]
[Route("{fileType}/{fileName}")]
public async Task<IActionResult> Download(int fileType, string fileName)
{
string ext = FileHelpers.GetExtensionById(fileType);
if (ext == null)
{
_logger.LogInformation($"Extension not found.");
return BadRequest($"Extension not found.");
}

if(ext != Path.GetExtension(fileName))
{
_logger.LogInformation($"File not valid.");
return BadRequest($"File not valid.");
}

var trustedFilePath = Path.Combine(_targetFolderPath, fileName).Replace("file:\\", "");
if (!System.IO.File.Exists(trustedFilePath))
{
_logger.LogInformation($"File {trustedFilePath} not exists");
return NotFound($"File {trustedFilePath} not exists");
}

_logger.LogInformation($"Downloading file [{trustedFilePath}].");
var bytes = await System.IO.File.ReadAllBytesAsync(trustedFilePath);
return File(bytes, ext.GetMimeTypes(), trustedFilePath);
}
}
}
59 changes: 59 additions & 0 deletions MarkdownEditorDemo.Api/Controllers/ImageController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using Microsoft.AspNetCore.Mvc;

namespace MarkdownEditorDemo.Api.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class ImageController : Controller
{
[HttpPost]
public async Task<IActionResult> Upload(IFormFile files)
{
long size = files.Length;

var filePath = Path.GetTempFileName();

using (var stream = System.IO.File.Create(filePath))
{
await files.CopyToAsync(stream);
}

// Process uploaded files
// Don't rely on or trust the FileName property without validation.

return Ok();

//// Check if thefile is there
//if (file == null)
// return BadRequest("File is required");

//// Get the file name
//var fileName = file.FileName;

//// Get the extension
//var extension = Path.GetExtension(fileName);

//// Validate the extension based on your business needs

//// Generate a new file to avoid dublicates = (FileName withoutExtension - GUId.extension)
//var newFileName = $"{Path.GetFileNameWithoutExtension(fileName)}-{Guid.NewGuid().ToString()}{extension}";

//// Create the full path of the file including the directory (For this demo we will save the file insidea folder called Data within wwwroot)
//var directoryPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Data");
//var fullPath = Path.Combine(directoryPath, newFileName);

//// Maek sure the directory is ther bycreating it if it's not exist
//Directory.CreateDirectory(directoryPath);

//// Create a new file stream where you want to put your file and copy the content from the current file stream to the new one
//using (var fileStream = new FileStream(fullPath, FileMode.Create, FileAccess.Write))
//{
// // Copy the content to the new stream
// await file.CopyToAsync(fileStream);
//}

//// You are done return the new URL which is (yourapplication url/data/newfilename)
//return Ok($"https://localhost:44302/Data/{newFileName}");
}
}
}
25 changes: 25 additions & 0 deletions MarkdownEditorDemo.Api/Extensions/MultipartRequestExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace MarkdownEditorDemo.Api.Extensions;

public static class MultipartRequestExtensions
{
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
// The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
public static string GetBoundary(this MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;

if (string.IsNullOrWhiteSpace(boundary))
throw new InvalidDataException("Missing content-type boundary.");

if (boundary.Length > lengthLimit)
throw new InvalidDataException($"Multipart boundary length limit {lengthLimit} exceeded.");

return boundary;
}

public static bool IsMultipartContentType(this string contentType)
{
return !string.IsNullOrEmpty(contentType)
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}
}
31 changes: 31 additions & 0 deletions MarkdownEditorDemo.Api/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace MarkdownEditorDemo.Api.Extensions
{
public static class StringExtensions
{
/// <summary>
/// Gets the MIME types.
/// </summary>
/// <param name="ext">The ext.</param>
/// <returns></returns>
public static string GetMimeTypes(this string ext)
{
switch (ext)
{
case ".txt": return "text/plain";
case ".csv": return "text/csv";
case ".pdf": return "application/pdf";
case ".doc": return "application/vnd.ms-word";
case ".xls": return "application/vnd.ms-excel";
case ".ppt": return "application/vnd.ms-powerpoint";
case ".docx": return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
case ".xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
case ".pptx": return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
case ".png": return "image/png";
case ".jpg": return "image/jpeg";
case ".jpeg": return "image/jpeg";
case ".gif": return "image/gif";
default: return "application/octet-stream";
}
}
}
}
21 changes: 21 additions & 0 deletions MarkdownEditorDemo.Api/Filters/ModelBinding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace MarkdownEditorDemo.Api.Filters
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}

public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
}
8 changes: 8 additions & 0 deletions MarkdownEditorDemo.Api/GlobalUsing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
global using MarkdownEditorDemo.Api.Extensions;
global using MarkdownEditorDemo.Api.Filters;
global using MarkdownEditorDemo.Api.Utilities;
global using Microsoft.AspNetCore.Http.Features;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.AspNetCore.WebUtilities;
global using Microsoft.Net.Http.Headers;
global using System.Net;
Loading

0 comments on commit b75b7ca

Please sign in to comment.