public static async Task <KeyValuePair <string, string> > HandleFormDispositionSection(MultipartSection section, ContentDispositionHeaderValue contentDisposition) { FormMultipartSection formDataSection = new FormMultipartSection(section, contentDisposition); // Content-Disposition: form-data; name="key" // // value // Do not limit the key name length here because the multipart headers length limit is already in effect. string key = formDataSection.Name; string value = await formDataSection.GetValueAsync(); return(new KeyValuePair <string, string>(key, value)); }
private async Task <IFormCollection> InnerReadFormAsync(CancellationToken cancellationToken) { if (!HasFormContentType) { throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType); } cancellationToken.ThrowIfCancellationRequested(); //if (_options.BufferBody) //{ // _request.EnableRewind(_options.MemoryBufferThreshold, _options.BufferBodyLengthLimit); //} FormCollection formFields = null; FormFileCollection files = null; // Some of these code paths use StreamReader which does not support cancellation tokens. using (cancellationToken.Register((state) => ((HttpContext)state).Abort(), _request.HttpContext)) { var contentType = ContentType; // Check the content-type if (HasApplicationFormContentType(contentType)) { var encoding = FilterEncoding(contentType.Encoding); using (var formReader = new FormReader(_request.Body, encoding) { ValueCountLimit = _options.ValueCountLimit, KeyLengthLimit = _options.KeyLengthLimit, ValueLengthLimit = _options.ValueLengthLimit, }) { formFields = new FormCollection(await formReader.ReadFormAsync(cancellationToken)); } } else if (HasMultipartFormContentType(contentType)) { var formAccumulator = new KeyValueAccumulator(); var boundary = GetBoundary(contentType, _options.MultipartBoundaryLengthLimit); var multipartReader = new MultipartReader(boundary, _request.Body) { HeadersCountLimit = _options.MultipartHeadersCountLimit, HeadersLengthLimit = _options.MultipartHeadersLengthLimit, BodyLengthLimit = _options.MultipartBodyLengthLimit, }; var section = await multipartReader.ReadNextSectionAsync(cancellationToken); while (section != null) { // Parse the content disposition here and pass it further to avoid reparsings ContentDispositionHeaderValue contentDisposition; ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition); if (contentDisposition.IsFileDisposition()) { var fileSection = new FileMultipartSection(section, contentDisposition); FormFile file; if (section.Body.CanSeek) { // Find the end await section.Body.ConsumeAsync(cancellationToken); var name = fileSection.Name; var fileName = fileSection.FileName; if (section.BaseStreamOffset.HasValue) { // Relative reference to buffered request body file = new StreamFormFile(_request.Body, section.BaseStreamOffset.Value, section.Body.Length, name, fileName); } else { // Individually buffered file body file = new StreamFormFile(section.Body, 0, section.Body.Length, name, fileName); } file.Headers = new HeaderDictionary(section.Headers); } else { //// Enable buffering for the file if not already done for the full body //section.EnableRewind( // _request.HttpContext.Response.RegisterForDispose, // _options.MemoryBufferThreshold, _options.MultipartBodyLengthLimit); //read all bytes. var bytes = await section.Body.ReadAllBytesAsync(cancellationToken); var name = fileSection.Name; var fileName = fileSection.FileName; if (section.BaseStreamOffset.HasValue) { // Relative reference to buffered request body file = new BufferedFormFile(new ArraySegment <byte>(bytes, (int)section.BaseStreamOffset.Value, (int)section.Body.Length), name, fileName); } else { // Individually buffered file body file = new BufferedFormFile(new ArraySegment <byte>(bytes, 0, (int)section.Body.Length), name, fileName); } file.Headers = new HeaderDictionary(section.Headers); } if (files == null) { files = new FormFileCollection(); } if (files.Count >= _options.ValueCountLimit) { throw new InvalidDataException($"Form value count limit {_options.ValueCountLimit} exceeded."); } files.Add(file); } else if (contentDisposition.IsFormDisposition()) { var formDataSection = new FormMultipartSection(section, contentDisposition); // Content-Disposition: form-data; name="key" // // value // Do not limit the key name length here because the mulipart headers length limit is already in effect. var key = formDataSection.Name; var value = await formDataSection.GetValueAsync(); formAccumulator.Append(key, value); if (formAccumulator.ValueCount > _options.ValueCountLimit) { throw new InvalidDataException($"Form value count limit {_options.ValueCountLimit} exceeded."); } } else { System.Diagnostics.Debug.Assert(false, "Unrecognized content-disposition for this section: " + section.ContentDisposition); } section = await multipartReader.ReadNextSectionAsync(cancellationToken); } if (formAccumulator.HasValues) { formFields = new FormCollection(formAccumulator.GetResults(), files); } } } // Rewind so later readers don't have to. if (_request.Body.CanSeek) { _request.Body.Seek(0, SeekOrigin.Begin); } if (formFields != null) { Form = formFields; } else if (files != null) { Form = new FormCollection(null, files); } else { Form = FormCollection.Empty; } return(Form); }
public async Task <IActionResult> UploadFile([FromRoute] string username, [FromRoute] string directoryPath) { //string requestContentType = Request.ContentType; //bool hasFormContentType = Request.HasFormContentType; // Accumulate all form key-value pairs in the request (in case we want to do something with other form-fields that are not only files) KeyValueAccumulator formAccumulator = new KeyValueAccumulator(); Dictionary <string, string> sectionDictionary = new Dictionary <string, string>(); // get off the boundary appended by the form-post string boundary = GetBoundary(Request.ContentType); try // FIX THIS HUGE TRY BLOCK { var reader = new MultipartReader(boundary, Request.Body); MultipartSection section = null; section = await reader.ReadNextSectionAsync(); while (section != null) { // Get the conten header disposition for checking if we are handling a file-form-field or just any other type of field ContentDispositionHeaderValue contentDisposition; var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition); if (hasContentDispositionHeader) { if (contentDisposition.IsFileDisposition()) { // Fetch data from header (Delete this after we implement authentication) var appId = Request.Headers["AppId"]; var phonenumber = Request.Headers["OwnerPhoneNumber"]; var firstname = Request.Headers["OwnerFirstName"]; var lastname = Request.Headers["OwnerLastName"]; // Here we handle file fields string fileName = contentDisposition.FileName.ToString().Replace(" ", ""); string UploadsDir = Path.Combine(appEnv.WebRootPath, $"dataspace/{username}"); // If the BASE dir doesn't exist, create it Directory.CreateDirectory(UploadsDir); string physicalPath; string url; // Check for subdir if (String.IsNullOrWhiteSpace(directoryPath)) { physicalPath = Path.Combine(UploadsDir, fileName); // Prepare file name and path for writing url = $"{Request.Scheme}://{Request.Host}/dataspace/{username}/files/{fileName}"; directoryPath = ""; } else { physicalPath = Path.Combine(UploadsDir, directoryPath, fileName); // Prepare file name and path for writing url = $"{Request.Scheme}://{Request.Host}/dataspace/{username}/files/{directoryPath}/{fileName}"; } //// We can also use a temp location for now eg. AppData on Windows //var targetFilePath = Path.GetTempFileName(); // Createa a stream and write the request (section-field) body/data using (var targetStream = System.IO.File.Create(physicalPath)) { // Copy file to disk await section.Body.CopyToAsync(targetStream); // TODO: Generate metadata and save the path into a FileMicroservice with SignalR (db handler) FileDto newFile = new FileDto { Name = fileName, Path = directoryPath, Url = url, MimeType = section.ContentType, FileSizeInKB = (int)(targetStream.Length / 1024) // Test }; dataSpaceSignalRClient.SaveFileMetadata(phonenumber, newFile); // rename stuff like this to nodePath } } else if (contentDisposition.IsFormDisposition()) { // Remove this (?) // Here we handle other form fields StringSegment fieldName = HeaderUtilities.RemoveQuotes(contentDisposition.Name); FormMultipartSection formMultipartSection = section.AsFormDataSection(); string fieldValue = await formMultipartSection.GetValueAsync().ConfigureAwait(false); sectionDictionary.Add(formMultipartSection.Name, fieldValue); using (var streamReader = new StreamReader(section.Body)) { if (String.Equals(fieldValue, "undefined", StringComparison.OrdinalIgnoreCase)) { fieldValue = String.Empty; } formAccumulator.Append(fieldName.ToString(), fieldValue); } } } // Read next section/field section = await reader.ReadNextSectionAsync(); } } catch (Exception e) { // handle any unread or errors on errors while streaming and writing received data return(BadRequest()); } //// Remove this (?) //// Handle all the non-file fields either here, or above while reading the streams already eg. chosen parent directory //var result = sectionDictionary; //var frmResults = formAccumulator.GetResults(); return(Ok()); }
public async Task <ParseBodyAndSavePacketsResult> ParseBodyAndSavePackets(MessageHeaders messageHeaders, HttpRequest request) { var cancellationToken = request.HttpContext.RequestAborted; cancellationToken.ThrowIfCancellationRequested(); var format = messageHeaders.Format ?? TransportConstants.RequestFormDataFormat; string boundary = null; if (format == TransportConstants.RequestFormDataFormat) { int fileSectionBufferSize = 81920; // если данные formdata упорядочены в потоке запроса (бинарные данные должны идти после большинства метаданных о пакете) if (messageHeaders.Hints?.Contains(MessageHints.OrderedFormData) == true) { var indexToPacketDataItem = new Dictionary <int, PacketFormDataItem>(); var indexToPacketBytes = new Dictionary <int, byte[]>(); var indexToConfigurationItem = new Dictionary <int, ConfigurationRequestDataItem>(); if (MediaTypeHeaderValue.TryParse(request.ContentType, out var contentType)) { boundary = HeaderUtilities.GetBoundary(contentType, 70); } var multipartReader = new MultipartReader(boundary, request.Body) { //ValueCountLimit = _options.ValueCountLimit, //KeyLengthLimit = _options.KeyLengthLimit, //ValueLengthLimit = _options.ValueLengthLimit, HeadersCountLimit = int.MaxValue, HeadersLengthLimit = int.MaxValue, BodyLengthLimit = long.MaxValue, }; //PacketFormDataItem current = null; //byte[] currentBytes = null; var agentId = messageHeaders.GetAgentIdData(); var section = await multipartReader.ReadNextSectionAsync(cancellationToken); while (section != null) { // Parse the content disposition here and pass it further to avoid reparsings if (!ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition)) { throw new InvalidDataException("Form section has invalid Content-Disposition value: " + section.ContentDisposition); } if (contentDisposition.IsFileDisposition()) { var fileSection = new FileMultipartSection(section, contentDisposition); var name = fileSection.Name; var fileName = fileSection.FileName; var packetId = fileSection.FileName; var result = GetFormPathDataFromEntry(name); if (result != null && result.Parts.Count == 3 && result.Parts[0] == TransportConstants.FormDataPacketsProp) { if (int.TryParse(result.Parts[1], out var index)) { var item = indexToPacketDataItem[index]; var providerKey = item.ProviderKey; var packetItem = indexToPacketDataItem[index]; if (packetItem.PacketId != packetId) { throw new InvalidDataException($"Incorrect format for form-data message. Section {name} has invalid FileName."); } var bytes = await ReadToEnd(fileSection.FileStream, fileSectionBufferSize, cancellationToken); indexToPacketBytes.Add(index, bytes); } else { throw new InvalidDataException($"Incorrect format for form-data message. Section {name} does not have index suffix."); } } else { throw new InvalidDataException($"Incorrect format for form-data message. Section {name} incorrect."); } } else if (contentDisposition.IsFormDisposition()) { var formDataSection = new FormMultipartSection(section, contentDisposition); // Content-Disposition: form-data; name="key" // // value // Do not limit the key name length here because the multipart headers length limit is already in effect. var key = formDataSection.Name; var value = await formDataSection.GetValueAsync(); var result = GetFormPathDataFromEntry(key); if (result != null && result.Parts.Count == 3 && result.Parts[0] == TransportConstants.FormDataPacketsProp) { if (int.TryParse(result.Parts[1], out var index)) { if (!indexToPacketDataItem.TryGetValue(index, out var dataItem)) { //// сохраняем предыдущий //if (current != null) //{ // await this.packetsStore.AddIfNotExistsPacketPartAsync(agentId, this.CreateAddPacketRequest(current, currentBytes)); //} dataItem = new PacketFormDataItem(); indexToPacketDataItem.Add(index, dataItem); } if (!dataItem.FillProperty(result.Parts[2], value)) { throw new InvalidDataException($"Incorrect format for form-data message. Section {key} incorrect."); } } else { throw new InvalidDataException($"Incorrect format for form-data message. Section {key} does not have index suffix."); } } else if (result != null && result.Parts.Count == 3 && result.Parts[0] == TransportConstants.FormDataConfigurationProp) { if (int.TryParse(result.Parts[1], out var index)) { if (!indexToConfigurationItem.TryGetValue(index, out var dataItem)) { dataItem = new ConfigurationRequestDataItem(); indexToConfigurationItem.Add(index, dataItem); } if (!dataItem.FillProperty(result.Parts[2], value)) { throw new InvalidDataException($"Incorrect format for form-data message. Section {key} incorrect."); } } else { throw new InvalidDataException($"Incorrect format for form-data message. Section {key} does not have index suffix."); } } else { // ignore or throw? } //if (formAccumulator.ValueCount > _options.ValueCountLimit) //{ // throw new InvalidDataException($"Form value count limit {_options.ValueCountLimit} exceeded."); //} } else { System.Diagnostics.Debug.Assert(false, "Unrecognized content-disposition for this section: " + section.ContentDisposition); } section = await multipartReader.ReadNextSectionAsync(cancellationToken); } // сохраняем все var addResult = indexToPacketDataItem.Any() ? await this.packetsStore.AddIfNotExistsPacketsPartsAsync(agentId, indexToPacketDataItem.Select(x => { var bytes = indexToPacketBytes[x.Key]; return(this.CreateAddPacketRequest(agentId, x.Value, bytes)); }).ToList()) : AddAddPacketsPartsResult.EmptyResult(); return(new ParseBodyAndSavePacketsResult { TransferedPackets = indexToPacketDataItem.Values .Select(x => { var fromAddResult = addResult.Results.Single(r => r.Request.PacketId == x.PacketId); return new TransferedPacketResponse { PacketId = x.PacketId, ProviderKey = x.ProviderKey, //AgentIdData = agentId, Result = fromAddResult.Success ? TransferedProcessingResult.Saved : TransferedProcessingResult.Error, StorageToken = fromAddResult.StorageToken, Id = fromAddResult.Id, }; }).ToList(), ConfigurationsStats = indexToConfigurationItem.Values, }); } else { var indexToPacketDataItem = new Dictionary <string, PacketFormDataItem>(); var indexToPacketBytes = new Dictionary <string, byte[]>(); var indexToConfigurationItem = new Dictionary <string, ConfigurationRequestDataItem>(); var agentId = messageHeaders.GetAgentIdData(); foreach (var item in request.Form) { var key = item.Key; var value = item.Value; var result = GetFormPathDataFromEntry(key); if (result != null && result.Parts.Count == 3 && result.Parts[0] == TransportConstants.FormDataPacketsProp) { var index = result.Parts[1]; if (!indexToPacketDataItem.TryGetValue(index, out var dataItem)) { dataItem = new PacketFormDataItem(); indexToPacketDataItem.Add(index, dataItem); } if (!dataItem.FillProperty(result.Parts[2], value)) { throw new InvalidDataException($"Incorrect format for form-data message. Section {key} incorrect."); } } if (result != null && result.Parts.Count == 3 && result.Parts[0] == TransportConstants.FormDataConfigurationProp) { var index = result.Parts[1]; if (!indexToConfigurationItem.TryGetValue(index, out var dataItem)) { dataItem = new ConfigurationRequestDataItem(); indexToConfigurationItem.Add(index, dataItem); } if (!dataItem.FillProperty(result.Parts[2], value)) { throw new InvalidDataException($"Incorrect format for form-data message. Section {key} incorrect."); } } } foreach (var file in request.Form.Files) { var pair = indexToPacketDataItem .FirstOrDefault(x => x.Value.PacketId == file.FileName); if (default(KeyValuePair <string, PacketFormDataItem>).Equals(pair)) { var item = pair.Value; var providerKey = item.ProviderKey; var packetId = item.PacketId; using (var fileStream = file.OpenReadStream()) { var currentBytes = await ReadToEnd(fileStream, fileSectionBufferSize, cancellationToken); indexToPacketBytes.Add(pair.Key, currentBytes); } } } // сохраняем все var addResult = indexToPacketDataItem.Any() ? await this.packetsStore.AddIfNotExistsPacketsPartsAsync(agentId, indexToPacketDataItem.Select(x => { var bytes = indexToPacketBytes[x.Key]; return(this.CreateAddPacketRequest(agentId, x.Value, bytes)); }).ToList()) : AddAddPacketsPartsResult.EmptyResult(); return(new ParseBodyAndSavePacketsResult { TransferedPackets = indexToPacketDataItem.Values .Select(x => { var fromAddResult = addResult.Results .Single(r => r.Request.PacketId == x.PacketId); return new TransferedPacketResponse { PacketId = x.PacketId, ProviderKey = x.ProviderKey, //AgentIdData = agentId, Result = fromAddResult.Success ? TransferedProcessingResult.Saved : TransferedProcessingResult.Error, StorageToken = fromAddResult.StorageToken, Id = fromAddResult.Id, }; }).ToList(), ConfigurationsStats = indexToConfigurationItem.Values, }); } } else { throw new NotSupportedException($"format {format} is not supported"); } }
private async Task <FormCollection> GetMultipartFormCollection(ModificationContext modificationContext) { var formAccumulator = new KeyValueAccumulator(); var multipartReader = new MultipartReader(_originalBoundary, modificationContext.ContentStream) { HeadersCountLimit = _options.Value.FormOptions.MultipartHeadersCountLimit, HeadersLengthLimit = _options.Value.FormOptions.MultipartHeadersLengthLimit, BodyLengthLimit = _options.Value.FormOptions.MultipartBodyLengthLimit, }; var section = await multipartReader.ReadNextSectionAsync(); while (section != null) { ContentDispositionHeaderValue contentDisposition; ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition); if (contentDisposition.IsFileDisposition()) { var fileSection = new FileMultipartSection(section, contentDisposition); await section.Body.DrainAsync(new System.Threading.CancellationToken()); FormFile file; if (contentDisposition.FileName == "\"\"") { file = new FormFile( section.Body, 0, section.Body.Length, fileSection.Name, fileSection.FileName ); } else { file = new FormFile( section.BaseStreamOffset.HasValue ? modificationContext.ContentStream : section.Body, section.BaseStreamOffset.HasValue ? section.BaseStreamOffset.Value : 0, section.Body.Length, fileSection.Name, fileSection.FileName ); } file.Headers = new HeaderDictionary(section.Headers); if (_formFiles == null) { _formFiles = new FormFileCollection(); } if (_formFiles.Count >= _options.Value.FormOptions.ValueCountLimit) { throw new InvalidDataException($"Form value count limit {_options.Value.FormOptions.ValueCountLimit} exceeded."); } _formFiles.Add(file); } else if (contentDisposition.IsFormDisposition()) { var formDataSection = new FormMultipartSection(section, contentDisposition); var value = await formDataSection.GetValueAsync(); formAccumulator.Append(formDataSection.Name, value); if (formAccumulator.ValueCount > _options.Value.FormOptions.ValueCountLimit) { throw new InvalidDataException($"Form value count limit {_options.Value.FormOptions.ValueCountLimit} exceeded."); } } else { throw new InvalidDataException($"Unrecognized content-disposition for this section: {section.ContentDisposition}"); } section = await multipartReader.ReadNextSectionAsync(); } if (formAccumulator.HasValues || _formFiles != null) { return(new FormCollection(formAccumulator.HasValues ? formAccumulator.GetResults() : null, _formFiles)); } else { return(null); } }
public async Task <IActionResult> Upload() { string multipartBoundary = Request.GetMultipartBoundary(); if (string.IsNullOrEmpty(multipartBoundary)) { Response.StatusCode = 400; return(Json(new UploadResult { Succeeded = false, Description = $"Expected a multipart request, but got '{Request.ContentType}'." })); } var formAccumulator = new KeyValueAccumulator(); var reader = new MultipartReader(multipartBoundary, HttpContext.Request.Body); MultipartSection section = await reader.ReadNextSectionAsync(); string fileGuid = Guid.NewGuid().ToString(); string userId = _userManager.GetUserId(HttpContext.User); string fileExtension = string.Empty; while (section != null) { FileMultipartSection fileSection = section.AsFileSection(); if (fileSection != null) { string fileName = fileSection.FileName; fileExtension = Path.GetExtension(fileName); string targetFolderPath = Path.Combine(_hostingEnvironment.WebRootPath, _appSettings.UploadsFolder, userId); string targetFilePath = Path.Combine(_hostingEnvironment.WebRootPath, _appSettings.UploadsFolder, userId, $"{fileGuid}{fileExtension}"); if (!Directory.Exists(targetFolderPath)) { Directory.CreateDirectory(targetFolderPath); } using (var targetStream = System.IO.File.Create(targetFilePath)) { await fileSection.FileStream.CopyToAsync(targetStream); _logger.LogInformation($"Copied the uploaded file '{fileName}' to '{targetFilePath}'."); } } else { FormMultipartSection formSection = section.AsFormDataSection(); if (formSection != null) { string name = formSection.Name; string value = await formSection.GetValueAsync(); formAccumulator.Append(name, value); if (formAccumulator.ValueCount > FormReader.DefaultValueCountLimit) { throw new InvalidDataException($"Form key count limit {FormReader.DefaultValueCountLimit} exceeded."); } } } section = await reader.ReadNextSectionAsync(); } var uploadResult = new UploadResult { Succeeded = true, Description = "File was uploaded successfully", FileUrl = $"{_appSettings.Domain}/{_appSettings.UploadsFolder}/{userId}/{fileGuid}{fileExtension}", FileName = $"{fileGuid}{fileExtension}" }; return(Json(uploadResult)); }