using Core; using ICSharpCode.SharpZipLib.Core; using ICSharpCode.SharpZipLib.Zip; using LiMan.APi.Data; using LiMan.DB.DBModels; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Configuration; using NLog; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; namespace LiMan.APi.Controllers { /// /// Controller caricamento file /// [ApiController] [Route("api/filesave")] public class FilesaveController : ControllerBase { #region Public Constructors /// /// Init generico /// /// /// /// public FilesaveController(IConfiguration configuration, ApiDataService DataService, IWebHostEnvironment env) { dataService = DataService; _configuration = configuration; this.env = env; Log.Info("Avviata classe FilesaveController"); } #endregion Public Constructors #region Public Methods /// GET api/filesave/id/filename /// /// Recupera un singolo file dato ticket code + nome file (safe) /// /// Ticket code formato T00000000 /// Nome file (safe) /// Nome file da scaricare /// [HttpGet("{id}/{secureName}/{fileName}")] public async Task DownloadFile(string id, string secureName, string fileName) { string relDir = _configuration["ServerConf:FileShareTickets"]; string ticketDir = Path.Combine(relDir, id); var filePath = Path.Combine(ticketDir, secureName); // verifico esistenza.. if (System.IO.File.Exists(filePath)) { var bytes = await System.IO.File.ReadAllBytesAsync(filePath); return File(bytes, GetMimeType(fileName), fileName); } else { return File(new byte[0], "text/plain", "Empty.txt"); } } /// /// Elenco files associati a ticket supporto POST api/filesave/list/1 /// /// /// /// [HttpPost("list/{id}")] public async Task> list(int id, [FromBody] SupportRequest CurrRequest) { List result = new List(); // controllo valori if (CurrRequest.IsValid) { // cerco i files dato ticket result = await dataService.FileGetFilt(id); await dataService.recordCall(CurrRequest.CodInst, CurrRequest.CodApp, $"POST:api/files/list:{id}"); } return result; } /// /// Caricamento file effettivo via POST /// /// TicketId x riferimento /// Elenco files da caricare /// [HttpPost()] public async Task>> PostFiles([FromForm] int ticketId, [FromForm] IEnumerable files) { // max 10 files var maxAllowedFiles = 10; // max 50 mb long maxFileSize = 1024 * 1024 * 50; var filesProcessed = 0; string ticketDir = $"T{ticketId:000000000}"; var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/api/filesave/list/{ticketId}"); List uploadResults = new(); string fileDir = env.ContentRootPath; string relDir = env.EnvironmentName; foreach (var file in files) { var uploadResult = new UploadResult(); string trustedFileNameForFileStorage; var untrustedFileName = file.FileName; uploadResult.FileName = untrustedFileName; var trustedFileNameForDisplay = WebUtility.HtmlEncode(untrustedFileName); if (filesProcessed < maxAllowedFiles) { if (file.Length == 0) { Log.Info($"{trustedFileNameForDisplay} length is 0 (Err: 1)"); uploadResult.ErrorCode = 1; } else if (file.Length > maxFileSize) { Log.Info($"{trustedFileNameForDisplay} of {CalcSize(file.Length)} is larger than the limit of {CalcSize(maxFileSize)} (Err: 2)"); uploadResult.ErrorCode = 2; } else { try { DateTime oggi = DateTime.Today; trustedFileNameForFileStorage = Path.GetRandomFileName(); relDir = _configuration["ServerConf:FileShareTickets"]; fileDir = Path.Combine(relDir, ticketDir); if (!Directory.Exists(fileDir)) { Directory.CreateDirectory(fileDir); } var path = Path.Combine(fileDir, trustedFileNameForFileStorage); await using FileStream fs = new(path, FileMode.Create); await file.CopyToAsync(fs); Log.Info($"{trustedFileNameForDisplay} saved at {path}"); uploadResult.Uploaded = true; uploadResult.StoredFileName = trustedFileNameForFileStorage; } catch (IOException ex) { Log.Error($"{trustedFileNameForDisplay} error on upload (Err: 3): {ex.Message}"); uploadResult.ErrorCode = 3; } } filesProcessed++; } else { Log.Info($"{trustedFileNameForDisplay} not uploaded because the request exceeded the allowed {maxAllowedFiles} of files (Err: 4)"); uploadResult.ErrorCode = 4; } uploadResults.Add(uploadResult); } // salvo su DB var fatto = dataService.FileAdd(ticketId, ticketDir, uploadResults); Log.Info($"Ticket: {ticketId} | dir: {ticketDir} | {uploadResults.Count} files"); return new CreatedResult(resourcePath, uploadResults); } /// /// Caricamento file effettivo via POST /// /// TicketId x riferimento /// Elenco files da caricare /// [HttpPost("single")] public async Task> PostSingleFile([FromForm] int ticketId, [FromForm] IFormFile file) { // max 200 mb long maxFileSize = 1024 * 1024 * 200; string ticketDir = $"T{ticketId:000000000}"; var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/api/filesave/list/{ticketId}"); List uploadResults = new(); string fileDir = env.ContentRootPath; string relDir = env.EnvironmentName; var uploadResult = new UploadResult(); string trustedFileNameForFileStorage; var untrustedFileName = file.FileName; uploadResult.FileName = untrustedFileName; var trustedFileNameForDisplay = WebUtility.HtmlEncode(untrustedFileName); if (file.Length == 0) { Log.Info($"{trustedFileNameForDisplay} length is 0 (Err: 1)"); uploadResult.ErrorCode = 1; } else if (file.Length > maxFileSize) { Log.Info($"{trustedFileNameForDisplay} of {CalcSize(file.Length)} is larger than the limit of {CalcSize(maxFileSize)} (Err: 2)"); uploadResult.ErrorCode = 2; } else { try { DateTime oggi = DateTime.Today; trustedFileNameForFileStorage = Path.GetRandomFileName(); relDir = _configuration["ServerConf:FileShareTickets"]; fileDir = Path.Combine(relDir, ticketDir); if (!Directory.Exists(fileDir)) { Directory.CreateDirectory(fileDir); } var path = Path.Combine(fileDir, trustedFileNameForFileStorage); await using FileStream fs = new(path, FileMode.Create); await file.CopyToAsync(fs); Log.Info($"{trustedFileNameForDisplay} saved at {path}"); uploadResult.Uploaded = true; uploadResult.StoredFileName = trustedFileNameForFileStorage; } catch (IOException ex) { Log.Error($"{trustedFileNameForDisplay} error on upload (Err: 3): {ex.Message}"); uploadResult.ErrorCode = 3; } } uploadResults.Add(uploadResult); // salvo su DB var fatto = dataService.FileAdd(ticketId, ticketDir, uploadResults); Log.Info($"Ticket: {ticketId} | dir: {ticketDir} | {uploadResults.Count} files"); return new CreatedResult(resourcePath, uploadResult); } /// /// Caricamento file backup applicativo in formato ZIP via FORM POST (backup configurazione applicativo, file protetto da masterKey) /// /// Applicazione di riferimento /// Installazione di riferimento /// Richiesta UnZip file post caricamento /// /// Richiesta di approvazione salvataggio files modificati post upload + unzip /// /// File da caricare ed estrarre /// [HttpPost("zipbackup")] public async Task> PostZipFile([FromForm] string CodApp, [FromForm] string CodInst, [FromForm] bool DoUnzip, [FromForm] bool ForceApprov, [FromForm] IFormFile ZipFile) { // max 200 mb long maxFileSize = 1024 * 1024 * 200; // preparo oggetti x risposta var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/api/filesave/"); List uploadResults = new List(); string fileDir = env.ContentRootPath; string relDir = env.EnvironmentName; string authKey = ""; var uploadResult = new UploadResult(); uploadResult.FileName = ZipFile.FileName; // controllo size e procedo if (ZipFile.Length == 0) { Log.Info($"{ZipFile.FileName} length is 0 (Err: 1)"); uploadResult.ErrorCode = 1; } else if (ZipFile.Length > maxFileSize) { Log.Info($"{ZipFile.FileName} of {CalcSize(ZipFile.Length)} is larger than the limit of {CalcSize(maxFileSize)} (Err: 2)"); uploadResult.ErrorCode = 2; } else { try { DateTime oggi = DateTime.Today; relDir = _configuration["ServerConf:FileShareAppBackup"]; fileDir = Path.Combine(relDir, CodApp, CodInst); if (!Directory.Exists(fileDir)) { Directory.CreateDirectory(fileDir); } var filePath = Path.Combine(fileDir, ZipFile.FileName); // elimino se ci fosse già il file... if (System.IO.File.Exists(filePath)) { System.IO.File.Delete(filePath); } // salvo da filestream a file locale using (FileStream fs = new(filePath, FileMode.Create)) { await ZipFile.CopyToAsync(fs); } // log! Log.Info($"{ZipFile.FileName} saved at {filePath}"); uploadResult.Uploaded = true; uploadResult.StoredFileName = ZipFile.FileName; // se richiesto unzip eseguo if (DoUnzip) { bool extractDone = false; // recupero applicativi connessi var listLic = await dataService.AppDtoSearch(CodInst, CodApp, false); var currLic = listLic.Where(x => x.IsActive).FirstOrDefault(); // procedo SOLO SE ho una licenza attiva x questo cliente if (currLic != null) { //recupero authKey authKey = currLic.Chiave; using (ZipFile zf = new ZipFile(filePath)) { zf.Password = authKey; foreach (ZipEntry zipEntry in zf) { // Manipulate the output filename here as desired. var entryFileName = zipEntry.Name; var fullZipToPath = Path.Combine(fileDir, entryFileName); Console.WriteLine(zipEntry.Name); if (!zipEntry.IsFile) { Directory.CreateDirectory(fullZipToPath); // Ignore directories continue; } // 4K is optimum var buffer = new byte[4096]; // Unzip file in buffered chunks. This is just as fast as // unpacking to a buffer the full size of the file, but does not // waste memory. The "using" will close the stream even if an // exception occurs. using (var zipStream = zf.GetInputStream(zipEntry)) { using (Stream fsOutput = System.IO.File.Create(fullZipToPath)) { StreamUtils.Copy(zipStream, fsOutput, buffer); } } extractDone = true; } } // elimino zip e altro... if (extractDone) { System.IO.File.Delete(filePath); } // se richiesta auto approvazione eseguo... string reqAppFile = Path.Combine(fileDir, "ChangeApprove.req"); if (System.IO.File.Exists(reqAppFile)) { System.IO.File.Delete(reqAppFile); } if (ForceApprov) { // FixMe ToDo !!! // deve chiamare metodo x approvare, in MP-PROG, la directory dei // documenti caricati.. x ora segnaposto con file (così se scansiona // trova e approva)... System.IO.File.WriteAllText(reqAppFile, authKey); } } } } catch (IOException ex) { Log.Error($"{ZipFile.FileName} error on upload (Err: 3): {ex.Message}"); uploadResult.ErrorCode = 3; } } uploadResults.Add(uploadResult); Log.Info($"ZipUpload backup completed | CodInst: {CodInst} | CodApp: {CodApp} | {uploadResults.Count} files"); return new CreatedResult(resourcePath, uploadResult); } #endregion Public Methods #region Protected Properties /// /// Dataservice x accesso DB /// protected ApiDataService dataService { get; set; } #endregion Protected Properties #region Protected Methods /// /// Restituisce size calcolata /// /// /// protected string CalcSize(long origSize) { return MeasureUtils.SizeSuffix(origSize, 1); } /// /// Calcolo correetto mimetype da nome file /// /// /// protected string GetMimeType(string fileName) { var provider = new FileExtensionContentTypeProvider(); string contentType; if (!provider.TryGetContentType(fileName, out contentType)) { contentType = "application/octet-stream"; } return contentType; } #endregion Protected Methods #region Private Fields private static IConfiguration _configuration; /// /// Classe per logging /// private static Logger Log = LogManager.GetCurrentClassLogger(); private readonly IWebHostEnvironment env; #endregion Private Fields } }