namespace HaWeb.Controllers; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Net.Http.Headers; using HaWeb.Filters; using HaWeb.FileHelpers; using HaWeb.XMLParser; using System.Text.Json.Serialization; using System.Text.Json; using HaDocument.Interfaces; using HaXMLReader.Interfaces; using Microsoft.FeatureManagement.Mvc; using System.Runtime.InteropServices; using Microsoft.AspNetCore.Http.Features; // Controlling all the API-Endpoints public class APIController : Controller { // DI private ILibrary _lib; private IReaderService _readerService; private readonly long _fileSizeLimit; private readonly string _targetFilePath; private readonly IXMLService _xmlService; // Options private static readonly string[] _permittedExtensions = { ".xml" }; private static readonly FormOptions _defaultFormOptions = new FormOptions(); public APIController(ILibrary lib, IReaderService readerService, IXMLService xmlService, IConfiguration config) { _lib = lib; _readerService = readerService; _xmlService = xmlService; _fileSizeLimit = config.GetValue("FileSizeLimit"); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { _targetFilePath = config.GetValue("StoredFilePathWindows"); } else { _targetFilePath = config.GetValue("StoredFilePathLinux"); } } [HttpGet] [Route("API/Syntaxcheck/{id}")] [DisableFormValueModelBinding] [ValidateAntiForgeryToken] [FeatureGate(Features.AdminService)] public async Task SyntaxCheck(string id) { return Ok(); } //// UPLOAD //// [HttpPost] [Route("API/Upload")] [DisableFormValueModelBinding] [ValidateAntiForgeryToken] [FeatureGate(Features.UploadService)] public async Task Upload(string? id) { List? docs = null; //// 1. Stage: Check Request format and request spec // Checks the Content-Type Field (must be multipart + Boundary) if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) { ModelState.AddModelError("Error", $"Wrong / No Content Type on the Request"); return BadRequest(ModelState); } // Divides the multipart document into it's sections and sets up a reader var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit); var reader = new MultipartReader(boundary, HttpContext.Request.Body); MultipartSection? section = null; try { section = await reader.ReadNextSectionAsync(); } catch (Exception ex) { ModelState.AddModelError("Error", "The Request is bad: " + ex.Message); } while (section != null) { // Multipart document content disposition header read for a section: // Starts with boundary, contains field name, content-dispo, filename, content-type var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition); if (contentDisposition != null && contentDisposition.Name == "__RequestVerificationToken") { try { section = await reader.ReadNextSectionAsync(); } catch (Exception ex) { ModelState.AddModelError("Error", "The Request is bad: " + ex.Message); } continue; } if (hasContentDispositionHeader && contentDisposition != null) { // Checks if it is a section with content-disposition, name, filename if (!MultipartRequestHelper.HasFileContentDisposition(contentDisposition)) { ModelState.AddModelError("Error", $"Wrong Content-Dispostion Headers in Multipart Document"); return BadRequest(ModelState); } //// 2. Stage: Check File. Sanity checks on the file on a byte level, extension checking, is it empty etc. var streamedFileContent = await XMLFileHelpers.ProcessStreamedFile( section, contentDisposition, ModelState, _permittedExtensions, _fileSizeLimit); if (!ModelState.IsValid || streamedFileContent == null) return BadRequest(ModelState); //// 3. Stage: Valid XML checking using a simple XDocument.Load() var xdocument = await XDocumentFileHelper.ProcessStreamedFile(streamedFileContent, ModelState); if (!ModelState.IsValid || xdocument == null) return UnprocessableEntity(ModelState); //// 4. Stage: Is it a Hamann-Document? What kind? var retdocs = await _xmlService.ProbeHamannFile(xdocument, ModelState); if (!ModelState.IsValid || retdocs == null || !retdocs.Any()) return UnprocessableEntity(ModelState); //// 5. Stage: Saving the File(s) foreach (var doc in retdocs) { await _xmlService.UpdateAvailableFiles(doc, _targetFilePath, ModelState); if (!ModelState.IsValid) return StatusCode(500, ModelState); if (docs == null) docs = new List(); docs.Add(doc); } } try { section = await reader.ReadNextSectionAsync(); } catch (Exception ex) { ModelState.AddModelError("Error", "The Request is bad: " + ex.Message); } } // 6. Stage: Success! Returning Ok, and redirecting JsonSerializerOptions options = new() { ReferenceHandler = ReferenceHandler.Preserve, Converters = { new IdentificationStringJSONConverter() } }; string json = JsonSerializer.Serialize(docs); return Created(nameof(UploadController), json); } }