diff --git a/Aaru.Server.New/Aaru.Server.New.csproj b/Aaru.Server.New/Aaru.Server.New.csproj
index 09bf102e..ba5aa666 100644
--- a/Aaru.Server.New/Aaru.Server.New.csproj
+++ b/Aaru.Server.New/Aaru.Server.New.csproj
@@ -15,11 +15,14 @@
+
+
+
diff --git a/Aaru.Server.New/Controllers/UploadReportController.cs b/Aaru.Server.New/Controllers/UploadReportController.cs
new file mode 100644
index 00000000..6fbae5ff
--- /dev/null
+++ b/Aaru.Server.New/Controllers/UploadReportController.cs
@@ -0,0 +1,385 @@
+// /***************************************************************************
+// Aaru Data Preservation Suite
+// ----------------------------------------------------------------------------
+//
+// Filename : UploadReportController.cs
+// Author(s) : Natalia Portillo
+//
+// Component : Aaru Server.
+//
+// --[ Description ] ----------------------------------------------------------
+//
+// Handles report uploads.
+//
+// --[ License ] --------------------------------------------------------------
+//
+// This library is free software; you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, see .
+//
+// ----------------------------------------------------------------------------
+// Copyright © 2011-2024 Natalia Portillo
+// ****************************************************************************/
+
+using System.Diagnostics;
+using System.Net;
+using System.Text;
+using System.Xml.Serialization;
+using Aaru.CommonTypes.Metadata;
+using Aaru.Server.Database.Models;
+using Cinchoo.PGP;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using MimeKit;
+using Newtonsoft.Json;
+using DbContext = Aaru.Server.Database.DbContext;
+using SmtpClient = MailKit.Net.Smtp.SmtpClient;
+
+namespace Aaru.Server.New.Controllers;
+
+public sealed class UploadReportController : ControllerBase
+{
+ readonly DbContext _ctx;
+ readonly IWebHostEnvironment _environment;
+
+ public UploadReportController(IWebHostEnvironment environment, DbContext ctx)
+ {
+ _environment = environment;
+ _ctx = ctx;
+ }
+
+ /// Receives a report from Aaru.Core, verifies it's in the correct format and stores it on the server
+ /// HTTP response
+ [Route("api/uploadreport")]
+ [HttpPost]
+ public async Task UploadReport()
+ {
+ var response = new ContentResult
+ {
+ StatusCode = (int)HttpStatusCode.OK,
+ ContentType = "text/plain"
+ };
+
+ try
+ {
+ var newReport = new DeviceReport();
+ HttpRequest request = HttpContext.Request;
+
+ var xs = new XmlSerializer(newReport.GetType());
+
+ newReport =
+ (DeviceReport)xs.Deserialize(new StringReader(await new StreamReader(request.Body).ReadToEndAsync()));
+
+ if(newReport == null)
+ {
+ response.Content = "notstats";
+
+ return response;
+ }
+
+ var reportV2 = new DeviceReportV2(newReport);
+ var jsonSw = new StringWriter();
+
+ await jsonSw.WriteAsync(JsonConvert.SerializeObject(reportV2,
+ Formatting.Indented,
+ new JsonSerializerSettings
+ {
+ NullValueHandling = NullValueHandling.Ignore
+ }));
+
+ var reportV2String = jsonSw.ToString();
+ jsonSw.Close();
+
+ var newUploadedReport = new UploadedReport(reportV2);
+
+ // Ensure CHS and CurrentCHS are not duplicates
+ if(newUploadedReport.ATA?.ReadCapabilities is { CHS: not null, CurrentCHS: not null } &&
+ newUploadedReport.ATA.ReadCapabilities.CHS.Cylinders ==
+ newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Cylinders &&
+ newUploadedReport.ATA.ReadCapabilities.CHS.Heads ==
+ newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Heads &&
+ newUploadedReport.ATA.ReadCapabilities.CHS.Sectors ==
+ newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Sectors)
+ newUploadedReport.ATA.ReadCapabilities.CHS = newUploadedReport.ATA.ReadCapabilities.CurrentCHS;
+
+ // Check if the CHS or CurrentCHS of this report already exist in the database
+ if(newUploadedReport.ATA?.ReadCapabilities?.CHS != null)
+ {
+ Chs? existingChs =
+ await _ctx.Chs.FirstOrDefaultAsync(c =>
+ c.Cylinders ==
+ newUploadedReport.ATA.ReadCapabilities.CHS.Cylinders &&
+ c.Heads ==
+ newUploadedReport.ATA.ReadCapabilities.CHS.Heads &&
+ c.Sectors ==
+ newUploadedReport.ATA.ReadCapabilities.CHS.Sectors);
+
+ if(existingChs != null) newUploadedReport.ATA.ReadCapabilities.CHS = existingChs;
+ }
+
+ if(newUploadedReport.ATA?.ReadCapabilities?.CurrentCHS != null)
+ {
+ Chs? existingChs =
+ await _ctx.Chs.FirstOrDefaultAsync(c =>
+ c.Cylinders ==
+ newUploadedReport.ATA.ReadCapabilities.CurrentCHS
+ .Cylinders &&
+ c.Heads ==
+ newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Heads &&
+ c.Sectors ==
+ newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Sectors);
+
+ if(existingChs != null) newUploadedReport.ATA.ReadCapabilities.CurrentCHS = existingChs;
+ }
+
+ if(newUploadedReport.ATA?.RemovableMedias != null)
+ {
+ foreach(TestedMedia media in newUploadedReport.ATA.RemovableMedias)
+ {
+ Chs? existingChs;
+
+ if(media.CHS != null &&
+ media.CurrentCHS != null &&
+ media.CHS.Cylinders == media.CurrentCHS.Cylinders &&
+ media.CHS.Heads == media.CurrentCHS.Heads &&
+ media.CHS.Sectors == media.CurrentCHS.Sectors)
+ media.CHS = media.CurrentCHS;
+
+ if(media.CHS != null)
+ {
+ existingChs =
+ await _ctx.Chs.FirstOrDefaultAsync(c => c.Cylinders == media.CHS.Cylinders &&
+ c.Heads == media.CHS.Heads &&
+ c.Sectors == media.CHS.Sectors);
+
+ if(existingChs != null) media.CHS = existingChs;
+ }
+
+ if(media.CHS == null) continue;
+
+ existingChs =
+ await _ctx.Chs.FirstOrDefaultAsync(c => media.CurrentCHS != null &&
+ c.Cylinders == media.CurrentCHS.Cylinders &&
+ c.Heads == media.CurrentCHS.Heads &&
+ c.Sectors == media.CurrentCHS.Sectors);
+
+ if(existingChs != null) media.CurrentCHS = existingChs;
+ }
+ }
+
+ _ctx.Reports.Add(newUploadedReport);
+ await _ctx.SaveChangesAsync();
+
+ var pgpIn = new MemoryStream(Encoding.UTF8.GetBytes(reportV2String));
+ var pgpOut = new MemoryStream();
+ var pgp = new ChoPGPEncryptDecrypt();
+
+ await pgp.EncryptAsync(pgpIn,
+ pgpOut,
+ Path.Combine(_environment.ContentRootPath ?? throw new InvalidOperationException(),
+ "public.asc"));
+
+ pgpOut.Position = 0;
+ reportV2String = Encoding.UTF8.GetString(pgpOut.ToArray());
+
+ var message = new MimeMessage
+ {
+ Subject = "New device report (old version)",
+ Body = new TextPart("plain")
+ {
+ Text = reportV2String
+ }
+ };
+
+ message.From.Add(new MailboxAddress("Aaru Server", "aaru@claunia.com"));
+ message.To.Add(new MailboxAddress("Natalia Portillo", "claunia@claunia.com"));
+
+ using(var client = new SmtpClient())
+ {
+ await client.ConnectAsync("mail.claunia.com", 25, false);
+ await client.SendAsync(message);
+ await client.DisconnectAsync(true);
+ }
+
+ response.Content = "ok";
+
+ return response;
+ }
+
+ // ReSharper disable once RedundantCatchClause
+ catch
+ {
+#if DEBUG
+ if(Debugger.IsAttached) throw;
+#endif
+ response.Content = "error";
+
+ return response;
+ }
+ }
+
+ /// Receives a report from Aaru.Core, verifies it's in the correct format and stores it on the server
+ /// HTTP response
+ [Route("api/uploadreportv2")]
+ [HttpPost]
+ public async Task UploadReportV2()
+ {
+ var response = new ContentResult
+ {
+ StatusCode = (int)HttpStatusCode.OK,
+ ContentType = "text/plain"
+ };
+
+ try
+ {
+ HttpRequest request = HttpContext.Request;
+
+ var sr = new StreamReader(request.Body);
+ string reportJson = await sr.ReadToEndAsync();
+ DeviceReportV2 newReport = JsonConvert.DeserializeObject(reportJson);
+
+ if(newReport == null)
+ {
+ response.Content = "notstats";
+
+ return response;
+ }
+
+ var newUploadedReport = new UploadedReport(newReport);
+
+ // Ensure CHS and CurrentCHS are not duplicates
+ if(newUploadedReport.ATA?.ReadCapabilities is { CHS: not null, CurrentCHS: not null } &&
+ newUploadedReport.ATA.ReadCapabilities.CHS.Cylinders ==
+ newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Cylinders &&
+ newUploadedReport.ATA.ReadCapabilities.CHS.Heads ==
+ newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Heads &&
+ newUploadedReport.ATA.ReadCapabilities.CHS.Sectors ==
+ newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Sectors)
+ newUploadedReport.ATA.ReadCapabilities.CHS = newUploadedReport.ATA.ReadCapabilities.CurrentCHS;
+
+ Chs? existingChs;
+
+ // Check if the CHS or CurrentCHS of this report already exist in the database
+ if(newUploadedReport.ATA?.ReadCapabilities?.CHS != null)
+ {
+ existingChs =
+ await _ctx.Chs.FirstOrDefaultAsync(c =>
+ c.Cylinders ==
+ newUploadedReport.ATA.ReadCapabilities.CHS.Cylinders &&
+ c.Heads ==
+ newUploadedReport.ATA.ReadCapabilities.CHS.Heads &&
+ c.Sectors ==
+ newUploadedReport.ATA.ReadCapabilities.CHS.Sectors);
+
+ if(existingChs != null) newUploadedReport.ATA.ReadCapabilities.CHS = existingChs;
+ }
+
+ if(newUploadedReport.ATA?.ReadCapabilities?.CurrentCHS != null)
+ {
+ existingChs =
+ await _ctx.Chs.FirstOrDefaultAsync(c =>
+ c.Cylinders ==
+ newUploadedReport.ATA.ReadCapabilities.CurrentCHS
+ .Cylinders &&
+ c.Heads ==
+ newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Heads &&
+ c.Sectors ==
+ newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Sectors);
+
+ if(existingChs != null) newUploadedReport.ATA.ReadCapabilities.CurrentCHS = existingChs;
+ }
+
+ if(newUploadedReport.ATA?.RemovableMedias != null)
+ {
+ foreach(TestedMedia media in newUploadedReport.ATA.RemovableMedias)
+ {
+ if(media.CHS != null &&
+ media.CurrentCHS != null &&
+ media.CHS.Cylinders == media.CurrentCHS.Cylinders &&
+ media.CHS.Heads == media.CurrentCHS.Heads &&
+ media.CHS.Sectors == media.CurrentCHS.Sectors)
+ media.CHS = media.CurrentCHS;
+
+ if(media.CHS != null)
+ {
+ existingChs =
+ await _ctx.Chs.FirstOrDefaultAsync(c => c.Cylinders == media.CHS.Cylinders &&
+ c.Heads == media.CHS.Heads &&
+ c.Sectors == media.CHS.Sectors);
+
+ if(existingChs != null) media.CHS = existingChs;
+ }
+
+ if(media.CHS == null) continue;
+
+
+ existingChs =
+ await _ctx.Chs.FirstOrDefaultAsync(c => media.CurrentCHS != null &&
+ c.Cylinders == media.CurrentCHS.Cylinders &&
+ c.Heads == media.CurrentCHS.Heads &&
+ c.Sectors == media.CurrentCHS.Sectors);
+
+ if(existingChs != null) media.CurrentCHS = existingChs;
+ }
+ }
+
+ _ctx.Reports.Add(newUploadedReport);
+ await _ctx.SaveChangesAsync();
+
+ var pgpIn = new MemoryStream(Encoding.UTF8.GetBytes(reportJson));
+ var pgpOut = new MemoryStream();
+ var pgp = new ChoPGPEncryptDecrypt();
+
+ await pgp.EncryptAsync(pgpIn,
+ pgpOut,
+ Path.Combine(_environment.ContentRootPath ?? throw new InvalidOperationException(),
+ "public.asc"));
+
+ pgpOut.Position = 0;
+ reportJson = Encoding.UTF8.GetString(pgpOut.ToArray());
+
+ var message = new MimeMessage
+ {
+ Subject = "New device report",
+ Body = new TextPart("plain")
+ {
+ Text = reportJson
+ }
+ };
+
+ message.From.Add(new MailboxAddress("Aaru Server", "aaru@claunia.com"));
+ message.To.Add(new MailboxAddress("Natalia Portillo", "claunia@claunia.com"));
+
+ using(var client = new SmtpClient())
+ {
+ await client.ConnectAsync("mail.claunia.com", 25, false);
+ await client.SendAsync(message);
+ await client.DisconnectAsync(true);
+ }
+
+ response.Content = "ok";
+
+ return response;
+ }
+
+ // ReSharper disable once RedundantCatchClause
+ catch
+ {
+#if DEBUG
+ if(Debugger.IsAttached) throw;
+#endif
+ response.Content = "error";
+
+ return response;
+ }
+ }
+}
\ No newline at end of file