From 8c42f9cb9f150dc5076d52dd10a3168b6617d648 Mon Sep 17 00:00:00 2001 From: Admin Date: Thu, 24 Sep 2026 02:10:09 +0000 Subject: [PATCH] fix(capture): make printer-byte parsing binary-safe --- package.json | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/src/capture.rs | 9 +++++- src-tauri/src/zpl.rs | 65 +++++++++++++++++++++++++-------------- src-tauri/tauri.conf.json | 2 +- src/main.ts | 2 +- 6 files changed, 54 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 65eb9ab..d0d326b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fieldlens/print", - "version": "0.1.4", + "version": "0.1.5", "private": true, "type": "module", "scripts": { diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 515d092..72241e2 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fieldlens-print" -version = "0.1.4" +version = "0.1.5" description = "FieldLens print-stream barcode capture companion" authors = ["Arrowstack"] edition = "2021" diff --git a/src-tauri/src/capture.rs b/src-tauri/src/capture.rs index 94b494a..f6aeeec 100644 --- a/src-tauri/src/capture.rs +++ b/src-tauri/src/capture.rs @@ -391,7 +391,14 @@ fn process_payload( error: Option, ) { let id = Uuid::new_v4().to_string(); - let barcodes = extract_barcodes(&bytes); + let (barcodes, parse_error) = match std::panic::catch_unwind(|| extract_barcodes(&bytes)) { + Ok(found) => (found, None), + Err(_) => ( + Vec::new(), + Some("barcode parser failed; print bytes were still captured and forwarded".to_string()), + ), + }; + let error = error.or(parse_error); for barcode in &barcodes { state.queue_scan( diff --git a/src-tauri/src/zpl.rs b/src-tauri/src/zpl.rs index d1ac0b6..8812f5e 100644 --- a/src-tauri/src/zpl.rs +++ b/src-tauri/src/zpl.rs @@ -1,12 +1,14 @@ use crate::model::DecodedBarcode; pub fn extract_barcodes(bytes: &[u8]) -> Vec { - let text = String::from_utf8_lossy(bytes); - extract_from_zpl(&text) + extract_from_zpl_bytes(bytes) } pub fn extract_from_zpl(input: &str) -> Vec { - let bytes = input.as_bytes(); + extract_from_zpl_bytes(input.as_bytes()) +} + +fn extract_from_zpl_bytes(bytes: &[u8]) -> Vec { let mut index = 0usize; let mut pending_format: Option = None; let mut barcodes = Vec::new(); @@ -18,7 +20,7 @@ pub fn extract_from_zpl(input: &str) -> Vec { } let command_start = index; - let command = &input[index + 1..index + 3]; + let command = &bytes[index + 1..index + 3]; index += 3; if let Some(format) = barcode_format(command) { @@ -26,14 +28,13 @@ pub fn extract_from_zpl(input: &str) -> Vec { continue; } - if command == "FD" { - let end = input[index..] - .find("^FS") + if command == b"FD" { + let end = find_bytes(&bytes[index..], b"^FS") .map(|offset| index + offset) - .unwrap_or(input.len()); + .unwrap_or(bytes.len()); if let Some(format) = pending_format.take() { - let raw = input[index..end].trim(); - let value = normalize_field_data(raw); + let raw = String::from_utf8_lossy(&bytes[index..end]); + let value = normalize_field_data(raw.trim()); if !value.is_empty() { barcodes.push(DecodedBarcode { format, value }); } @@ -42,7 +43,7 @@ pub fn extract_from_zpl(input: &str) -> Vec { continue; } - if command == "FS" || command == "XZ" { + if command == b"FS" || command == b"XZ" { pending_format = None; } @@ -54,23 +55,30 @@ pub fn extract_from_zpl(input: &str) -> Vec { barcodes } -fn barcode_format(command: &str) -> Option<&'static str> { +fn barcode_format(command: &[u8]) -> Option<&'static str> { match command { - "BC" => Some("CODE_128"), - "B3" => Some("CODE_39"), - "BE" => Some("EAN_13"), - "B8" => Some("EAN_8"), - "BU" => Some("UPC_A"), - "B9" => Some("UPC_E"), - "B2" => Some("ITF"), - "BQ" => Some("QR_CODE"), - "BX" => Some("DATA_MATRIX"), - "B7" => Some("PDF_417"), - "BA" => Some("CODE_93"), + b"BC" => Some("CODE_128"), + b"B3" => Some("CODE_39"), + b"BE" => Some("EAN_13"), + b"B8" => Some("EAN_8"), + b"BU" => Some("UPC_A"), + b"B9" => Some("UPC_E"), + b"B2" => Some("ITF"), + b"BQ" => Some("QR_CODE"), + b"BX" => Some("DATA_MATRIX"), + b"B7" => Some("PDF_417"), + b"BA" => Some("CODE_93"), _ => None, } } +fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option { + if needle.is_empty() { + return Some(0); + } + haystack.windows(needle.len()).position(|window| window == needle) +} + fn normalize_field_data(raw: &str) -> String { let mut value = raw.to_string(); @@ -124,4 +132,15 @@ mod tests { let zpl = "^XA^FO10,10^A0N,30,30^FDNOT-A-BARCODE^FS^XZ"; assert!(extract_from_zpl(zpl).is_empty()); } + + #[test] + fn arbitrary_binary_printer_data_never_panics() { + let payload = [ + 0x1b, b'@', 0xff, 0x00, 0xfe, b'^', 0xff, 0xfd, 0x1b, b'(', b'G', + 0x10, 0x00, 0x80, 0x81, 0x82, b'^', b'F', b'D', 0xf0, 0x9f, 0x92, + 0xa9, b'^', b'F', b'S', + ]; + let found = extract_barcodes(&payload); + assert!(found.is_empty()); + } } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 690fb56..a589ab9 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "FieldLens Print", - "version": "0.1.4", + "version": "0.1.5", "identifier": "dev.arrowstack.fieldlens.print", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/main.ts b/src/main.ts index f57b0c2..80a0311 100644 --- a/src/main.ts +++ b/src/main.ts @@ -85,7 +85,7 @@ let config: AppConfig let jobs: Job[] = [] let status: Status let loginTimer: number | undefined -let currentVersion = '0.1.4' +let currentVersion = '0.1.5' let bundleType: BundleType | null = null let availableUpdate: Update | null = null let availableManualUpdate: ManualUpdate | null = null