using System.IO; using System.Text; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Localization; using iText.Kernel.Pdf; using iText.Kernel.Pdf.Canvas.Parser; using iText.Kernel.Pdf.Canvas.Parser.Listener; using iText.Kernel.Exceptions; namespace Convert_It_Online.Areas.DocumentConverters.Controllers { [Area("DocumentConverters")] public class PdfBarcodeLineController : Controller { private readonly IStringLocalizer _localizer; private readonly ILogger _logger; private const long MaxPreviewSize = 5 * 1024 * 1024; // 5MB private static readonly Regex CandidatePattern = new(@"[0-9\s\.-]{44,70}", RegexOptions.Compiled); public PdfBarcodeLineController(IStringLocalizer localizer, ILogger logger) { _localizer = localizer; _logger = logger; } private void SetCommonViewBagProperties() { ViewBag.HomeLink = _localizer["HomeLink"]; ViewBag.TextMenuTitle = _localizer["TextMenuTitle"]; ViewBag.ImageMenuTitle = _localizer["ImageMenuTitle"]; ViewBag.DocumentMenuTitle = _localizer["DocumentMenuTitle"]; ViewBag.CaseConverterTitle = _localizer["CaseConverterTitle"]; ViewBag.JpgToWebpTitle = _localizer["JpgToWebpTitle"]; ViewBag.HeicToJpgTitle = _localizer["HeicToJpgTitle"]; ViewBag.PdfToTextTitle = _localizer["PdfToTextTitle"]; ViewBag.PdfBarcodeTitle = _localizer["PdfBarcodeTitle"]; ViewBag.FooterText = _localizer["FooterText"]; ViewBag.About = _localizer["About"]; ViewBag.Contact = _localizer["Contact"]; ViewBag.Terms = _localizer["Terms"]; } private void PrepareIndexView() { SetCommonViewBagProperties(); ViewBag.PageTitle = _localizer["PdfBarcodeConverterPageTitle"]; ViewBag.PageDescription = _localizer["PdfBarcodeConverterPageDescription"]; ViewBag.PdfBarcodeDetectTabTitle = _localizer["PdfBarcodeDetectTabTitle"]; ViewBag.PdfBarcodeDownloadTabTitle = _localizer["PdfBarcodeDownloadTabTitle"]; ViewBag.PdfBarcodeFileInputLabel = _localizer["PdfBarcodeFileInputLabel"]; ViewBag.PdfPasswordLabel = _localizer["PdfPasswordLabel"]; ViewBag.PdfPasswordPlaceholder = _localizer["PdfPasswordPlaceholder"]; ViewBag.PdfBarcodePasswordHint = _localizer["PdfBarcodePasswordHint"]; ViewBag.DetectBarcodeButton = _localizer["DetectBarcodeButton"]; ViewBag.DownloadBarcodeButton = _localizer["DownloadBarcodeButton"]; ViewBag.PdfBarcodePreviewTitle = _localizer["PdfBarcodePreviewTitle"]; ViewBag.SelectFileError = _localizer["SelectFileError"]; ViewBag.CopyButtonLabel = _localizer["CopyButtonLabel"]; ViewBag.CopyButtonSuccess = _localizer["CopyButtonSuccess"]; ViewBag.FaqWhatTitle = _localizer["PdfBarcodeFaqWhatTitle"]; ViewBag.FaqWhatContent = _localizer["PdfBarcodeFaqWhatContent"]; ViewBag.FaqHowTitle = _localizer["PdfBarcodeFaqHowTitle"]; ViewBag.FaqHowContent = _localizer["PdfBarcodeFaqHowContent"]; ViewBag.FaqWhyTitle = _localizer["PdfBarcodeFaqWhyTitle"]; ViewBag.FaqWhyContent = _localizer["PdfBarcodeFaqWhyContent"]; ViewBag.FaqSecurityTitle = _localizer["PdfBarcodeFaqSecurityTitle"]; ViewBag.FaqSecurityContent = _localizer["PdfBarcodeFaqSecurityContent"]; ViewBag.FaqLimitsTitle = _localizer["PdfBarcodeFaqLimitsTitle"]; ViewBag.FaqLimitsContent = _localizer["PdfBarcodeFaqLimitsContent"]; ViewBag.MetaDescription = ViewBag.PageDescription; } public IActionResult Index() { PrepareIndexView(); return View(); } [HttpPost] public async Task DetectLine(IFormFile pdfFile, string? password, bool preview = false) { return await HandleDetection(pdfFile, password, preview, forDownload: false); } [HttpPost] public async Task DownloadLine(IFormFile pdfFile, string? password, bool preview = false) { return await HandleDetection(pdfFile, password, preview, forDownload: true); } private async Task HandleDetection(IFormFile? pdfFile, string? password, bool preview, bool forDownload) { if (pdfFile == null || pdfFile.Length == 0) { _logger.LogWarning("[PDF-BARCODE] Attempt without file"); if (preview) { return Json(new { success = false, message = _localizer["SelectFileError"].Value }); } ModelState.AddModelError("pdfFile", _localizer["SelectFileError"]); PrepareIndexView(); return View("Index"); } if (!IsValidPdf(pdfFile)) { _logger.LogWarning("[PDF-BARCODE] Invalid file type: {ContentType}", pdfFile.ContentType); if (preview) { return Json(new { success = false, message = _localizer["InvalidPdfFileError"].Value }); } ModelState.AddModelError("pdfFile", _localizer["InvalidPdfFileError"]); PrepareIndexView(); return View("Index"); } if (preview && pdfFile.Length > MaxPreviewSize) { return Json(new { success = false, message = _localizer["PdfPreviewTooLarge"].Value }); } var extraction = await TryExtractTextAsync(pdfFile, password); if (!extraction.Success) { var message = _localizer[extraction.ErrorKey].Value; if (preview) { return Json(new { success = false, message }); } ModelState.AddModelError("pdfFile", _localizer[extraction.ErrorKey]); ViewBag.ConversionError = message; PrepareIndexView(); return View("Index"); } var lineResult = FindBarcodeLine(extraction.Content ?? string.Empty); if (lineResult == null) { var message = _localizer["PdfNoBarcodeFound"].Value; if (preview) { return Json(new { success = false, message }); } ModelState.AddModelError("pdfFile", _localizer["PdfNoBarcodeFound"]); ViewBag.ConversionError = message; PrepareIndexView(); return View("Index"); } if (preview) { return Json(new { success = true, content = lineResult, format = "digit-line" }); } var fileBaseName = Path.GetFileNameWithoutExtension(pdfFile.FileName); var downloadFileName = string.IsNullOrWhiteSpace(fileBaseName) ? "linha-digitavel.txt" : fileBaseName + "-linha-digitavel.txt"; var payload = Encoding.UTF8.GetBytes(lineResult + Environment.NewLine); return File(payload, "text/plain", downloadFileName); } private async Task<(bool Success, string? Content, string ErrorKey)> TryExtractTextAsync(IFormFile pdfFile, string? password) { try { await using var memoryStream = new MemoryStream(); await pdfFile.CopyToAsync(memoryStream); var pdfBytes = memoryStream.ToArray(); // Try multiple password encodings var passwords = new List { null }; // Start with no password if (!string.IsNullOrEmpty(password)) { passwords.Add(System.Text.Encoding.UTF8.GetBytes(password)); passwords.Add(System.Text.Encoding.ASCII.GetBytes(password)); passwords.Add(System.Text.Encoding.Latin1.GetBytes(password)); } foreach (var pwd in passwords) { try { ReaderProperties readerProperties = new ReaderProperties(); if (pwd != null) { readerProperties.SetPassword(pwd); } using var pdfReader = new PdfReader(new MemoryStream(pdfBytes), readerProperties); using var pdfDocument = new PdfDocument(pdfReader); var builder = new StringBuilder(); int numberOfPages = pdfDocument.GetNumberOfPages(); for (int i = 1; i <= numberOfPages; i++) { var page = pdfDocument.GetPage(i); // Try multiple extraction strategies for barcode detection var strategies = new ITextExtractionStrategy[] { new LocationTextExtractionStrategy(), new SimpleTextExtractionStrategy() }; foreach (var strategy in strategies) { try { var pageText = PdfTextExtractor.GetTextFromPage(page, strategy); if (!string.IsNullOrWhiteSpace(pageText)) { builder.AppendLine(pageText); break; } } catch { continue; } } } var result = builder.ToString(); if (!string.IsNullOrWhiteSpace(result)) { return (true, result, string.Empty); } } catch (BadPasswordException) when (pwd != null) { continue; // Try next password encoding } catch (BadPasswordException) when (pwd == null && !string.IsNullOrEmpty(password)) { break; } } if (!string.IsNullOrEmpty(password)) { return (false, null, "PdfInvalidPassword"); } return (false, null, "InvalidPdfFileError"); } catch (BadPasswordException) { return (false, null, string.IsNullOrEmpty(password) ? "PdfPasswordRequired" : "PdfInvalidPassword"); } catch (Exception ex) { _logger.LogError(ex, "[PDF-BARCODE] Failed to extract text from {FileName}", pdfFile.FileName); return (false, null, "InvalidPdfFileError"); } } private static bool IsValidPdf(IFormFile file) { if (file == null) { return false; } var contentType = file.ContentType?.ToLowerInvariant(); if (contentType == "application/pdf" || contentType == "application/x-pdf") { return true; } return Path.GetExtension(file.FileName).Equals(".pdf", StringComparison.OrdinalIgnoreCase); } private static string? FindBarcodeLine(string rawText) { if (string.IsNullOrWhiteSpace(rawText)) { return null; } foreach (Match match in CandidatePattern.Matches(rawText)) { var digits = Regex.Replace(match.Value, "[^0-9]", string.Empty); if (digits.Length == 47 || digits.Length == 48 || digits.Length == 44) { return digits; } } // fallback: check full text digits var fullDigits = Regex.Replace(rawText, "[^0-9]", string.Empty); if (fullDigits.Length >= 44) { var candidates = new[] { 48, 47, 44 }; foreach (var length in candidates) { if (fullDigits.Length >= length) { return fullDigits.Substring(0, length); } } } return null; } } }