async Task <List <AttachmentInfo> > ReceiveByFormFileAsync(HttpContext context, string serviceName, string objectName, string systemID, string entityInfo, string objectID, bool isShared, bool isTracked, bool isTemporary, CancellationToken cancellationToken) { var attachments = new List <AttachmentInfo>(); await context.Request.Form.Files.Where(file => file != null && file.Length > 0).ForEachAsync(async(file, token) => { using (var uploadStream = file.OpenReadStream()) { // prepare var attachment = new AttachmentInfo { ID = context.GetParameter("x-attachment-id") ?? UtilityService.NewUUID, ServiceName = serviceName, ObjectName = objectName, SystemID = systemID, EntityInfo = entityInfo, ObjectID = objectID, Size = file.Length, Filename = file.FileName, ContentType = file.ContentType, IsShared = isShared, IsTracked = isTracked, IsTemporary = isTemporary, Title = file.FileName, Description = "", IsThumbnail = false }; // save file into disc using (var fileStream = new FileStream(attachment.GetFilePath(true), FileMode.Create, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, AspNetCoreUtilityService.BufferSize, true)) { var buffer = new byte[AspNetCoreUtilityService.BufferSize]; var read = 0; do { read = await uploadStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); if (read > 0) { await fileStream.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false); await fileStream.FlushAsync(cancellationToken).ConfigureAwait(false); } } while (read > 0); } // update attachment info attachments.Add(attachment); } }, cancellationToken, true, false).ConfigureAwait(false); return(attachments); }
async Task ReceiveAsync(HttpContext context, CancellationToken cancellationToken) { // prepare var stopwatch = Stopwatch.StartNew(); var serviceName = context.GetParameter("x-service-name"); var objectName = context.GetParameter("x-object-name"); var systemID = context.GetParameter("x-system-id"); var entityInfo = context.GetParameter("x-entity"); var objectID = context.GetParameter("x-object-id"); var isTemporary = "true".IsEquals(context.GetParameter("x-temporary")); if (string.IsNullOrWhiteSpace(objectID)) { throw new InvalidRequestException("Invalid object identity"); } // check permissions var gotRights = isTemporary ? await context.CanContributeAsync(serviceName, objectName, systemID, entityInfo, "", cancellationToken).ConfigureAwait(false) : await context.CanEditAsync(serviceName, objectName, systemID, entityInfo, objectID, cancellationToken).ConfigureAwait(false); if (!gotRights) { throw new AccessDeniedException(); } // limit size - default is 512 KB if (!Int32.TryParse(UtilityService.GetAppSetting("Limits:Thumbnail"), out var limitSize)) { limitSize = 512; } // read upload file var thumbnails = new List <byte[]>(); var asBase64 = context.GetParameter("x-as-base64") != null; if (asBase64) { var base64Data = (await context.ReadTextAsync(cancellationToken).ConfigureAwait(false)).ToJson()["Data"]; if (base64Data is JArray) { (base64Data as JArray).Take(7).ForEach(data => { var thumbnail = (data as JValue).Value.ToString().ToArray().Last().Base64ToBytes(); if (thumbnail != null && thumbnail.Length <= limitSize * 1024) { thumbnails.Add(thumbnail); } else { thumbnails.Add(null); } }); } else { thumbnails.Add((base64Data as JValue).Value.ToString().ToArray().Last().Base64ToBytes()); } } else { for (var index = 0; index < context.Request.Form.Files.Count && index < 7; index++) { thumbnails.Add(null); } await context.Request.Form.Files.Take(7).ForEachAsync(async(file, index, _) => { if (file != null && file.ContentType.IsStartsWith("image/") && file.Length > 0 && file.Length <= limitSize * 1024) { using (var stream = file.OpenReadStream()) { var thumbnail = new byte[file.Length]; await stream.ReadAsync(thumbnail, 0, (int)file.Length, cancellationToken).ConfigureAwait(false); thumbnails[index] = thumbnail; } } }, cancellationToken, true, false).ConfigureAwait(false); } // save uploaded files & create meta info var attachments = new List <AttachmentInfo>(); var useCache = "true".IsEquals(UtilityService.GetAppSetting("Files:Cache:Thumbnails", "true")) && Global.Cache != null; try { // save uploaded files into disc var title = ""; try { title = context.GetParameter("x-object-title")?.Url64Decode()?.GetANSIUri() ?? UtilityService.NewUUID; } catch { title = UtilityService.NewUUID; } await thumbnails.ForEachAsync(async (thumbnail, index, _) => { if (thumbnail != null) { // prepare var attachment = new AttachmentInfo { ID = context.GetParameter("x-attachment-id") ?? UtilityService.NewUUID, ServiceName = serviceName, ObjectName = objectName, SystemID = systemID, EntityInfo = entityInfo, ObjectID = objectID, Size = thumbnail.Length, Filename = $"{objectID}{(index > 0 ? $"-{index}" : "")}.jpg", ContentType = "image/jpeg", IsShared = false, IsTracked = false, IsTemporary = isTemporary, Title = title, Description = "", IsThumbnail = true }; // save file into temporary directory await UtilityService.WriteBinaryFileAsync(attachment.GetFilePath(true), thumbnail, false, cancellationToken).ConfigureAwait(false); // update attachment info attachments.Add(attachment); } }, cancellationToken, true, false).ConfigureAwait(false); // create meta info var response = new JArray(); var cacheKeys = new List <string>(); await attachments.ForEachAsync(async (attachment, token) => { response.Add(await context.CreateAsync(attachment, token).ConfigureAwait(false)); if (useCache) { var keys = await Global.Cache.GetSetMembersAsync($"{attachment.ObjectID}:Thumbnails", cancellationToken).ConfigureAwait(false); if (keys != null && keys.Count > 0) { cacheKeys = cacheKeys.Concat(new[] { $"{attachment.ObjectID}:Thumbnails" }).Concat(keys).ToList(); } } }, cancellationToken, true, false).ConfigureAwait(false); // clear cache if (useCache && cacheKeys.Count > 0) { await Global.Cache.RemoveAsync(cacheKeys.Distinct(StringComparer.OrdinalIgnoreCase).ToList(), cancellationToken).ConfigureAwait(false); } // move files from temporary directory to official directory attachments.ForEach(attachment => attachment.PrepareDirectories().MoveFile(this.Logger, "Http.Uploads", true)); // response await context.WriteAsync(response, cancellationToken).ConfigureAwait(false); stopwatch.Stop(); if (Global.IsDebugLogEnabled) { await context.WriteLogsAsync(this.Logger, "Http.Uploads", $"{thumbnails.Count(thumbnail => thumbnail != null)} thumbnail image(s) has been uploaded - Mode: {(asBase64 ? "base64" : "file")} - Execution times: {stopwatch.GetElapsedTimes()}").ConfigureAwait(false); } } catch (Exception) { attachments.ForEach(attachment => attachment.DeleteFile(true, this.Logger, "Http.Uploads")); throw; } }
async Task <List <AttachmentInfo> > ReceiveByFormDataAsync(HttpContext context, string serviceName, string objectName, string systemID, string entityInfo, string objectID, bool isShared, bool isTracked, bool isTemporary, CancellationToken cancellationToken) { // check var attachments = new List <AttachmentInfo>(); if (string.IsNullOrWhiteSpace(context.Request.ContentType) || context.Request.ContentType.PositionOf("multipart/") < 0) { return(attachments); } // prepare the reader var boundary = context.Request.ContentType.ToArray(' ').Where(entry => entry.StartsWith("boundary=")).First().Substring(9); if (boundary.Length >= 2 && boundary[0] == '"' && boundary[boundary.Length - 1] == '"') { boundary = boundary.Substring(1, boundary.Length - 2); } var reader = new MultipartReader(boundary, context.Request.Body); // save all files into temporary directory MultipartSection section = null; do { // read the section section = await reader.ReadNextSectionAsync(cancellationToken).ConfigureAwait(false); if (section == null) { break; } // prepare filename var filename = ""; try { filename = section.ContentDisposition.ToArray(';').First(part => part.Contains("filename")).ToArray('=').Last().Trim('"'); } catch { } if (string.IsNullOrWhiteSpace(filename)) { continue; } // prepare info var attachment = new AttachmentInfo { ID = context.GetParameter("x-attachment-id") ?? UtilityService.NewUUID, ServiceName = serviceName, ObjectName = objectName, SystemID = systemID, EntityInfo = entityInfo, ObjectID = objectID, Filename = filename, ContentType = section.ContentType, IsShared = isShared, IsTracked = isTracked, IsTemporary = isTemporary, Title = filename, Description = "", IsThumbnail = false }; // save file into disc using (var fileStream = new FileStream(attachment.GetFilePath(true), FileMode.Create, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, AspNetCoreUtilityService.BufferSize, true)) { var buffer = new byte[AspNetCoreUtilityService.BufferSize]; var read = 0; do { read = await section.Body.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); if (read > 0) { await fileStream.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false); await fileStream.FlushAsync(cancellationToken).ConfigureAwait(false); } } while (read > 0); attachment.Size = fileStream.Length; } // update attachment info attachments.Add(attachment); } while (section == null); // return info of all uploaded files return(attachments); }
async Task FlushAsync(HttpContext context, CancellationToken cancellationToken) { // prepare var requestUri = context.GetRequestUri(); var pathSegments = requestUri.GetRequestPathSegments(); var attachment = new AttachmentInfo { ID = pathSegments.Length > 3 && pathSegments[3].IsValidUUID() ? pathSegments[3].ToLower() : "", ServiceName = pathSegments.Length > 1 && !pathSegments[1].IsValidUUID() ? pathSegments[1] : "", SystemID = pathSegments.Length > 1 && pathSegments[1].IsValidUUID() ? pathSegments[1].ToLower() : "", ContentType = pathSegments.Length > 2 ? pathSegments[2].Replace("=", "/") : "", Filename = pathSegments.Length > 4 && pathSegments[3].IsValidUUID() ? pathSegments[4].UrlDecode() : "", IsThumbnail = false }; if (string.IsNullOrWhiteSpace(attachment.ID) || string.IsNullOrWhiteSpace(attachment.Filename)) { throw new InvalidRequestException(); } // check "If-Modified-Since" request to reduce traffict var eTag = "file#" + attachment.ID; var noneMatch = context.GetHeaderParameter("If-None-Match"); var modifiedSince = context.GetHeaderParameter("If-Modified-Since") ?? context.GetHeaderParameter("If-Unmodified-Since"); if (eTag.IsEquals(noneMatch) && modifiedSince != null) { context.SetResponseHeaders((int)HttpStatusCode.NotModified, eTag, modifiedSince.FromHttpDateTime().ToUnixTimestamp(), "public", context.GetCorrelationID()); await context.FlushAsync(cancellationToken).ConfigureAwait(false); if (Global.IsDebugLogEnabled) { context.WriteLogs(this.Logger, "Http.Downloads", $"Response to request with status code 304 to reduce traffic ({requestUri})"); } return; } // get info & check permissions attachment = await context.GetAsync(attachment.ID, cancellationToken).ConfigureAwait(false); if (!await context.CanDownloadAsync(attachment, cancellationToken).ConfigureAwait(false)) { throw new AccessDeniedException(); } // check existed var cacheKey = attachment.ContentType.IsStartsWith("image/") && "true".IsEquals(UtilityService.GetAppSetting("Files:Cache:Images", "true")) && Global.Cache != null ? $"Image#{attachment.Filename.ToLower().GenerateUUID()}" : null; var hasCached = cacheKey != null && await Global.Cache.ExistsAsync(cacheKey, cancellationToken).ConfigureAwait(false); FileInfo fileInfo = null; if (!hasCached) { fileInfo = new FileInfo(attachment.GetFilePath()); if (!fileInfo.Exists) { if (Global.IsDebugLogEnabled) { context.WriteLogs(this.Logger, "Http.Downloads", $"Not found: {requestUri} => {fileInfo.FullName}"); } context.ShowHttpError((int)HttpStatusCode.NotFound, "Not Found", "FileNotFoundException", context.GetCorrelationID()); return; } } // flush the file to output stream, update counter & logs if (hasCached) { var lastModified = await Global.Cache.GetAsync <long>($"{cacheKey}:time", cancellationToken).ConfigureAwait(false); var bytes = await Global.Cache.GetAsync <byte[]>(cacheKey, cancellationToken).ConfigureAwait(false); using (var stream = UtilityService.CreateMemoryStream(bytes)) { await context.WriteAsync(stream, attachment.ContentType, attachment.IsReadable()?null : attachment.Filename, eTag, lastModified, "public", TimeSpan.FromDays(366), null, context.GetCorrelationID(), cancellationToken).ConfigureAwait(false); } if (Global.IsDebugLogEnabled) { context.WriteLogs(this.Logger, "Http.Downloads", $"Successfully flush a cached image ({requestUri})"); } } else { await context.WriteAsync(fileInfo, attachment.IsReadable()?null : attachment.Filename, eTag, cancellationToken).ConfigureAwait(false); if (cacheKey != null) { await Task.WhenAll ( Global.Cache.SetAsFragmentsAsync(cacheKey, await UtilityService.ReadBinaryFileAsync(fileInfo, cancellationToken).ConfigureAwait(false), 1440, cancellationToken), Global.Cache.SetAsync($"{cacheKey}:time", fileInfo.LastWriteTime.ToUnixTimestamp(), 1440, cancellationToken), Global.IsDebugLogEnabled?context.WriteLogsAsync(this.Logger, "Http.Downloads", $"Update an image file into cache successful ({requestUri})") : Task.CompletedTask ).ConfigureAwait(false); } if (Global.IsDebugLogEnabled) { context.WriteLogs(this.Logger, "Http.Downloads", $"Successfully flush a file [{requestUri} => {fileInfo.FullName}]"); } } await context.UpdateAsync(attachment, attachment.IsReadable()? "Direct" : "Download", cancellationToken).ConfigureAwait(false); }