{"record":{"id":"e90e837a4ab971ec","repo":"flxzt/rnote","slug":"invalid-file","errorCode":null,"errorMessage":"Invalid file","messagePattern":"Invalid file","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/rnote-engine/src/fileformats/rnoteformat/mod.rs","lineNumber":52,"sourceCode":"    Ok(encoder.finish()?)\n}\n\n/// Decompress from gzip.\nfn decompress_from_gzip(compressed: &[u8]) -> Result<Vec<u8>, anyhow::Error> {\n    // Optimization for the gzip format, defined by RFC 1952\n    // capacity of the vector defined by the size of the uncompressed data\n    // given in little endian format, by the last 4 bytes of \"compressed\"\n    //\n    //   ISIZE (Input SIZE)\n    //     This contains the size of the original (uncompressed) input data modulo 2^32.\n    let mut bytes: Vec<u8> = {\n        let mut decompressed_size: [u8; 4] = [0; 4];\n        let idx_start = compressed\n            .len()\n            .checked_sub(4)\n            // only happens if the file has less than 4 bytes\n            .ok_or_else(|| {\n                anyhow::anyhow!(\"Invalid file\")\n                    .context(\"Failed to get the size of the decompressed data\")\n            })?;\n        decompressed_size.copy_from_slice(&compressed[idx_start..]);\n        // u32 -> usize to avoid issues on 32-bit architectures\n        // also more reasonable since the uncompressed size is given by 4 bytes\n        Vec::with_capacity(u32::from_le_bytes(decompressed_size) as usize)\n    };\n\n    let mut decoder = flate2::read::MultiGzDecoder::new(compressed);\n    decoder.read_to_end(&mut bytes)?;\n    Ok(bytes)\n}\n\n/// The rnote file wrapper.\n///\n/// Used to extract and match the version up front, before deserializing the data.\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[serde(rename = \"rnotefile_wrapper\")]","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/flxzt/rnote/blob/bbc5354502ba2fc83eec2670b535348825e679a6/crates/rnote-engine/src/fileformats/rnoteformat/mod.rs#L34-L70","documentation":"Rnote stores its compressed file as gzip-compressed JSON with a 4-byte little-endian trailing size trailer used to preallocate the output buffer. This error is thrown in `decompress_from_gzip` (called from `load_from_bytes`) when the input is shorter than 4 bytes, so the trailer size cannot be read — meaning the input cannot be a valid .rnote file.","triggerScenarios":"Calling `load_from_bytes` (or `decompress_from_gzip`) with fewer than 4 bytes of input: an empty file, a placeholder/URL stub, a truncated download, or passing the wrong buffer (e.g. an already-decompressed stream or metadata instead of file contents).","commonSituations":"Downloads that were cut off or saved as 0-byte/HTML error pages, cloud-sync placeholders not yet materialized, opening a wrong file type that happens to reach the loader, or callers that read only part of the file.","solutions":["Check the input file size with `ls -l` / `stat`; if it is under 4 bytes, re-obtain the real .rnote file from a backup or re-download it.","Verify you are passing the raw compressed .rnote bytes, not an already-decompressed stream or a different file.","Confirm the file path resolves to a real synced file (not a cloud placeholder) before loading.","Validate the input with a quick gzip header check (`file` command or first two bytes 0x1f 0x8b) before calling `load_from_bytes`."],"exampleFix":"// before: load whatever bytes are available\nlet bytes = std::fs::read(path)?;\nengine.load_from_bytes(&bytes).await?;\n// after: guard against truncated/empty input\nlet bytes = std::fs::read(path)?;\nif bytes.len() < 4 || &bytes[..2] != b\"\\x1f\\x8b\" {\n    anyhow::bail!(\"not a valid .rnote file (too short or not gzip)\");\n}\nengine.load_from_bytes(&bytes).await?;","handlingStrategy":"validation","validationCode":"// validate before loading\nfn looks_like_rnote(bytes: &[u8]) -> bool {\n    bytes.len() >= 4 && bytes.starts_with(b\"\\x1f\\x8b\")\n}\n// usage: ensure std::fs::read returned the full compressed file\nif !looks_like_rnote(&bytes) {\n    anyhow::bail!(\"file is too short or not gzip-compressed; refusing to load\");\n}","typeGuard":"fn is_gzip_buffer(bytes: &[u8]) -> bool {\n    bytes.len() >= 4 && bytes[0] == 0x1f && bytes[1] == 0x8b\n}","tryCatchPattern":"match engine.load_from_bytes(&bytes).await {\n    Ok(()) => /* loaded */,\n    Err(e) if e.to_string().contains(\"Invalid file\") => {\n        // input truncated or wrong file; re-download / restore from backup\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Verify file size and gzip magic bytes (1f 8b) before calling load_from_bytes.","Check download/sync completion status before opening .rnote files from cloud storage.","Never pass partially-read buffers; read the whole file with std::fs::read.","Keep automatic backups so truncated files can be replaced."],"tags":["gzip","file-format","corrupt-file","truncated-input"],"backgroundTag":"invalid-argument-format","analyzedSha":"bbc5354502ba2fc83eec2670b535348825e679a6","analyzedAt":"2026-09-08T13:20:33.747Z","contentChangedAt":"2026-09-08T13:20:33.747Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}