mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-11 16:36:13 -06:00
fix(render): vendor hayro-postscript so a run of << in a CMap can't overflow the stack (#4363)
hayro-cmap reads CMap streams with hayro-postscript's token scanner, whose object::read skipped each `<<` and `>>` by calling itself again. A CMap holding a long run of `<` or `>` used one stack frame per pair, overflowed the stack and aborted the process. Vendor hayro-postscript 0.1.0 with read() looping over the delimiters instead (from storytold/craft-libs#263), patched in beside hayro-cmap. Four object::tests cover long `<<` and `>>` runs and the skipped delimiters, plain and nested.
This commit is contained in:
Vendored
+127
@@ -0,0 +1,127 @@
|
||||
use crate::error::{Error, Result};
|
||||
use crate::object;
|
||||
use crate::reader::Reader;
|
||||
use crate::string;
|
||||
|
||||
/// A PostScript array object.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Array<'a> {
|
||||
data: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> Array<'a> {
|
||||
pub(crate) fn new(data: &'a [u8]) -> Self {
|
||||
Self { data }
|
||||
}
|
||||
|
||||
/// Return a [`Scanner`](crate::Scanner) that iterates over the objects inside
|
||||
/// this array.
|
||||
pub fn objects(&self) -> crate::Scanner<'a> {
|
||||
crate::Scanner::new(self.data)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse<'a>(r: &mut Reader<'a>) -> Result<&'a [u8]> {
|
||||
r.forward_tag(b"[").ok_or(Error::SyntaxError)?;
|
||||
|
||||
let start = r.offset();
|
||||
skip_array(r)?;
|
||||
let end = r.offset() - 1;
|
||||
|
||||
r.range(start..end).ok_or(Error::SyntaxError)
|
||||
}
|
||||
|
||||
fn skip_array(r: &mut Reader<'_>) -> Result<()> {
|
||||
let mut depth = 1_u32;
|
||||
|
||||
while depth > 0 {
|
||||
match r.peek_byte().ok_or(Error::SyntaxError)? {
|
||||
b'[' => {
|
||||
r.forward();
|
||||
depth += 1;
|
||||
}
|
||||
b']' => {
|
||||
r.forward();
|
||||
depth -= 1;
|
||||
}
|
||||
b'(' => {
|
||||
let _ = string::parse_literal(r).ok_or(Error::SyntaxError)?;
|
||||
}
|
||||
b'<' => {
|
||||
if r.peek_bytes(2) == Some(b"<~") {
|
||||
let _ = string::parse_ascii85(r).ok_or(Error::SyntaxError)?;
|
||||
} else if r.peek_bytes(2) == Some(b"<<") {
|
||||
r.forward();
|
||||
r.forward();
|
||||
} else {
|
||||
let _ = string::parse_hex(r).ok_or(Error::SyntaxError)?;
|
||||
}
|
||||
}
|
||||
b'%' => object::skip_whitespace_and_comments(r),
|
||||
_ => {
|
||||
r.forward();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn parse_array(input: &[u8]) -> Result<&[u8]> {
|
||||
let mut r = Reader::new(input);
|
||||
parse(&mut r)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
assert_eq!(parse_array(b"[]").unwrap(), b"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
assert_eq!(parse_array(b"[1 2 3]").unwrap(), b"1 2 3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested() {
|
||||
assert_eq!(parse_array(b"[1 [2 3] 4]").unwrap(), b"1 [2 3] 4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_string() {
|
||||
// The ']' inside the string should not close the array.
|
||||
assert_eq!(parse_array(b"[1 (str]) 2]").unwrap(), b"1 (str]) 2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_hex_string() {
|
||||
assert_eq!(parse_array(b"[<48> /name]").unwrap(), b"<48> /name");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_ascii85_string() {
|
||||
assert_eq!(parse_array(b"[<~87cURDZ~> 1]").unwrap(), b"<~87cURDZ~> 1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_comment() {
|
||||
assert_eq!(
|
||||
parse_array(b"[1 % comment with ]\n2]").unwrap(),
|
||||
b"1 % comment with ]\n2"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unterminated() {
|
||||
assert_eq!(parse_array(b"[1 2"), Err(Error::SyntaxError));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_an_array() {
|
||||
assert_eq!(parse_array(b"1 2]"), Err(Error::SyntaxError));
|
||||
}
|
||||
}
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
//! Error types for the PostScript scanner.
|
||||
|
||||
use core::fmt;
|
||||
|
||||
/// A specialized [`Result`] type for PostScript scanner operations.
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
/// An error encountered while scanning a PostScript token stream.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
/// A syntax error in the input.
|
||||
SyntaxError,
|
||||
/// A numeric value exceeded implementation limits.
|
||||
LimitCheck,
|
||||
/// An unsupported PostScript type was encountered (like dictionaries or
|
||||
/// procedures, which will be added in the future).
|
||||
UnsupportedType,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::SyntaxError => f.write_str("syntaxerror"),
|
||||
Self::LimitCheck => f.write_str("limitcheck"),
|
||||
Self::UnsupportedType => f.write_str("unsupported type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::error::Error for Error {}
|
||||
Vendored
+179
@@ -0,0 +1,179 @@
|
||||
/*!
|
||||
A lightweight PostScript scanner.
|
||||
|
||||
This crate provides a scanner for tokenizing PostScript programs into typed objects.
|
||||
It currently only implements a very small subset of the PostScript language,
|
||||
with the main goal of being enough to parse CMAP files, but the scope _might_
|
||||
be expanded upon in the future.
|
||||
|
||||
The supported types include integers and real numbers, name objects, strings and arrays.
|
||||
Unsupported is anything else, including dictionaries, procedures, etc. An error
|
||||
will be returned in case any of these is encountered.
|
||||
|
||||
## Safety
|
||||
This crate forbids unsafe code via a crate-level attribute.
|
||||
*/
|
||||
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod array;
|
||||
mod error;
|
||||
mod name;
|
||||
mod number;
|
||||
mod object;
|
||||
mod reader;
|
||||
mod string;
|
||||
|
||||
pub use array::Array;
|
||||
pub use error::{Error, Result};
|
||||
pub use name::Name;
|
||||
pub use number::Number;
|
||||
pub use object::Object;
|
||||
pub use string::String;
|
||||
|
||||
use reader::Reader;
|
||||
|
||||
/// A PostScript scanner that parses [`Object`]s from a byte stream.
|
||||
pub struct Scanner<'a> {
|
||||
reader: Reader<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Scanner<'a> {
|
||||
/// Create a new scanner over the given bytes of a PostScript program.
|
||||
pub fn new(data: &'a [u8]) -> Self {
|
||||
Self {
|
||||
reader: Reader::new(data),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if there are no more objects to parse.
|
||||
pub fn at_end(&mut self) -> bool {
|
||||
object::at_end(&mut self.reader)
|
||||
}
|
||||
|
||||
/// Parse the next object.
|
||||
pub fn parse_object(&mut self) -> Result<Object<'a>> {
|
||||
object::read(&mut self.reader)
|
||||
}
|
||||
|
||||
/// Parse the next object as a [`Number`].
|
||||
pub fn parse_number(&mut self) -> Result<Number> {
|
||||
match self.parse_object()? {
|
||||
Object::Number(n) => Ok(n),
|
||||
_ => Err(Error::SyntaxError),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the next object as a [`Name`].
|
||||
pub fn parse_name(&mut self) -> Result<Name<'a>> {
|
||||
match self.parse_object()? {
|
||||
Object::Name(n) => Ok(n),
|
||||
_ => Err(Error::SyntaxError),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the next object as a [`String`].
|
||||
pub fn parse_string(&mut self) -> Result<String<'a>> {
|
||||
match self.parse_object()? {
|
||||
Object::String(s) => Ok(s),
|
||||
_ => Err(Error::SyntaxError),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the next object as an [`Array`].
|
||||
pub fn parse_array(&mut self) -> Result<Array<'a>> {
|
||||
match self.parse_object()? {
|
||||
Object::Array(a) => Ok(a),
|
||||
_ => Err(Error::SyntaxError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cmap_snippet() {
|
||||
let input = br#"/CIDInit /ProcSet findresource begin
|
||||
12 dict begin
|
||||
begincmap
|
||||
/CMapName /Test-H def
|
||||
1 begincodespacerange
|
||||
<00> <FF>
|
||||
endcodespacerange
|
||||
2 beginbfchar
|
||||
<03> <0041>
|
||||
<04> <0042>
|
||||
endbfchar
|
||||
endcmap"#;
|
||||
|
||||
let mut s = Scanner::new(input);
|
||||
|
||||
assert_eq!(s.parse_name().unwrap(), Name::new(b"CIDInit", true));
|
||||
assert_eq!(s.parse_name().unwrap(), Name::new(b"ProcSet", true));
|
||||
assert_eq!(s.parse_name().unwrap(), Name::new(b"findresource", false));
|
||||
assert_eq!(s.parse_name().unwrap(), Name::new(b"begin", false));
|
||||
assert_eq!(s.parse_number().unwrap(), Number::Integer(12));
|
||||
assert_eq!(s.parse_name().unwrap(), Name::new(b"dict", false));
|
||||
assert_eq!(s.parse_name().unwrap(), Name::new(b"begin", false));
|
||||
assert_eq!(s.parse_name().unwrap(), Name::new(b"begincmap", false));
|
||||
assert_eq!(s.parse_name().unwrap(), Name::new(b"CMapName", true));
|
||||
assert_eq!(s.parse_name().unwrap(), Name::new(b"Test-H", true));
|
||||
assert_eq!(s.parse_name().unwrap(), Name::new(b"def", false));
|
||||
assert_eq!(s.parse_number().unwrap(), Number::Integer(1));
|
||||
assert_eq!(
|
||||
s.parse_name().unwrap(),
|
||||
Name::new(b"begincodespacerange", false)
|
||||
);
|
||||
assert_eq!(s.parse_string().unwrap(), String::from_hex(b"00"));
|
||||
assert_eq!(s.parse_string().unwrap(), String::from_hex(b"FF"));
|
||||
assert_eq!(
|
||||
s.parse_name().unwrap(),
|
||||
Name::new(b"endcodespacerange", false)
|
||||
);
|
||||
assert_eq!(s.parse_number().unwrap(), Number::Integer(2));
|
||||
assert_eq!(s.parse_name().unwrap(), Name::new(b"beginbfchar", false));
|
||||
assert_eq!(s.parse_string().unwrap(), String::from_hex(b"03"));
|
||||
assert_eq!(s.parse_string().unwrap(), String::from_hex(b"0041"));
|
||||
assert_eq!(s.parse_string().unwrap(), String::from_hex(b"04"));
|
||||
assert_eq!(s.parse_string().unwrap(), String::from_hex(b"0042"));
|
||||
assert_eq!(s.parse_name().unwrap(), Name::new(b"endbfchar", false));
|
||||
assert_eq!(s.parse_name().unwrap(), Name::new(b"endcmap", false));
|
||||
assert!(s.at_end());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_round_trip() {
|
||||
let input = b"[123 /abc (xyz)]";
|
||||
let mut scanner = Scanner::new(input);
|
||||
let arr = scanner.parse_array().unwrap();
|
||||
assert!(scanner.at_end());
|
||||
|
||||
let mut inner = arr.objects();
|
||||
assert_eq!(inner.parse_number().unwrap(), Number::Integer(123));
|
||||
assert_eq!(inner.parse_name().unwrap(), Name::new(b"abc", true));
|
||||
assert_eq!(inner.parse_string().unwrap(), String::from_literal(b"xyz"));
|
||||
assert!(inner.at_end());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comments_skipped() {
|
||||
let input = b"% comment\n42 % another\n/Name";
|
||||
let mut scanner = Scanner::new(input);
|
||||
|
||||
assert_eq!(scanner.parse_number().unwrap(), Number::Integer(42));
|
||||
assert_eq!(scanner.parse_name().unwrap(), Name::new(b"Name", true));
|
||||
assert!(scanner.at_end());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_type_is_error() {
|
||||
let mut scanner = Scanner::new(b"42 ");
|
||||
assert!(scanner.parse_name().is_err());
|
||||
}
|
||||
}
|
||||
Vendored
+191
@@ -0,0 +1,191 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::reader::{Reader, is_regular};
|
||||
use crate::string::ascii_hex::decode_hex_digit;
|
||||
|
||||
/// A PostScript name object.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Name<'a> {
|
||||
data: &'a [u8],
|
||||
literal: bool,
|
||||
}
|
||||
|
||||
impl<'a> Name<'a> {
|
||||
pub(crate) fn new(data: &'a [u8], literal: bool) -> Self {
|
||||
Self { data, literal }
|
||||
}
|
||||
|
||||
/// Returns `true` if this is a literal name.
|
||||
pub fn is_literal(&self) -> bool {
|
||||
self.literal
|
||||
}
|
||||
|
||||
/// Returns the name as a string if it is valid UTF-8, or
|
||||
/// `None` if it contains non-ASCII bytes.
|
||||
pub fn as_str(&self) -> Option<&'a str> {
|
||||
core::str::from_utf8(self.data).ok()
|
||||
}
|
||||
|
||||
/// Decode the name into `out`, replacing any previous contents.
|
||||
pub fn decode_into(&self, out: &mut Vec<u8>) -> Result<()> {
|
||||
out.clear();
|
||||
|
||||
// Fast path: no escape sequences.
|
||||
if !self.data.contains(&b'#') {
|
||||
out.extend_from_slice(self.data);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Slow path: Process escape sequences.
|
||||
let mut inner = Reader::new(self.data);
|
||||
|
||||
while let Some(b) = inner.read_byte() {
|
||||
if b == b'#' {
|
||||
let hex = inner.read_bytes(2).ok_or(Error::SyntaxError)?;
|
||||
let hi = decode_hex_digit(hex[0]).ok_or(Error::SyntaxError)?;
|
||||
let lo = decode_hex_digit(hex[1]).ok_or(Error::SyntaxError)?;
|
||||
out.push(hi << 4 | lo);
|
||||
} else {
|
||||
out.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decode the name.
|
||||
pub fn decode(&self) -> Result<Vec<u8>> {
|
||||
let mut out = Vec::new();
|
||||
self.decode_into(&mut out)?;
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_literal<'a>(r: &mut Reader<'a>) -> Option<&'a [u8]> {
|
||||
r.forward_tag(b"/")?;
|
||||
let start = r.offset();
|
||||
while r.eat(is_regular).is_some() {}
|
||||
r.range(start..r.offset())
|
||||
}
|
||||
|
||||
pub(crate) fn parse_executable<'a>(r: &mut Reader<'a>) -> Option<&'a [u8]> {
|
||||
let start = r.offset();
|
||||
r.forward_while(is_regular);
|
||||
if r.offset() == start {
|
||||
return None;
|
||||
}
|
||||
r.range(start..r.offset())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn read_literal(input: &[u8]) -> Option<Name<'_>> {
|
||||
let mut r = Reader::new(input);
|
||||
parse_literal(&mut r).map(|d| Name::new(d, true))
|
||||
}
|
||||
|
||||
fn read_executable(input: &[u8]) -> Option<Name<'_>> {
|
||||
let mut r = Reader::new(input);
|
||||
parse_executable(&mut r).map(|d| Name::new(d, false))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_simple() {
|
||||
let n = read_literal(b"/Name1").unwrap();
|
||||
assert_eq!(n.as_str().unwrap(), "Name1");
|
||||
assert!(n.is_literal());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_empty_name() {
|
||||
let n = read_literal(b"/").unwrap();
|
||||
assert_eq!(n.as_str().unwrap(), "");
|
||||
assert!(n.is_literal());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_with_hex_escape() {
|
||||
let n = read_literal(b"/lime#20Green").unwrap();
|
||||
assert_eq!(n.as_str().unwrap(), "lime#20Green");
|
||||
assert_eq!(n.decode().unwrap(), b"lime Green");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_multiple_hex_escapes() {
|
||||
let n = read_literal(b"/paired#28#29parentheses").unwrap();
|
||||
assert_eq!(n.decode().unwrap(), b"paired()parentheses");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_special_chars() {
|
||||
let n = read_literal(b"/A;Name_With-Various***Characters?").unwrap();
|
||||
assert_eq!(n.as_str().unwrap(), "A;Name_With-Various***Characters?");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_stops_at_delimiter() {
|
||||
let mut r = Reader::new(b"/Name(rest");
|
||||
let data = parse_literal(&mut r).unwrap();
|
||||
assert_eq!(data, b"Name");
|
||||
assert_eq!(r.peek_byte(), Some(b'('));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_stops_at_whitespace() {
|
||||
let mut r = Reader::new(b"/Name rest");
|
||||
let data = parse_literal(&mut r).unwrap();
|
||||
assert_eq!(data, b"Name");
|
||||
assert_eq!(r.peek_byte(), Some(b' '));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_not_a_name() {
|
||||
assert!(read_literal(b"Name").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executable_simple() {
|
||||
let n = read_executable(b"beginbfchar ").unwrap();
|
||||
assert_eq!(n.as_str().unwrap(), "beginbfchar");
|
||||
assert!(!n.is_literal());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executable_stops_at_delimiter() {
|
||||
let mut r = Reader::new(b"def/name");
|
||||
let data = parse_executable(&mut r).unwrap();
|
||||
assert_eq!(data, b"def");
|
||||
assert_eq!(r.peek_byte(), Some(b'/'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executable_at_eof() {
|
||||
let n = read_executable(b"endcmap").unwrap();
|
||||
assert_eq!(n.as_str().unwrap(), "endcmap");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executable_empty() {
|
||||
assert!(read_executable(b"").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executable_starts_at_delimiter() {
|
||||
assert!(read_executable(b"(foo)").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_no_escapes() {
|
||||
let n = Name::new(b"simple", true);
|
||||
assert_eq!(n.decode().unwrap(), b"simple");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_with_escapes() {
|
||||
let n = Name::new(b"lime#20Green", true);
|
||||
assert_eq!(n.decode().unwrap(), b"lime Green");
|
||||
}
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
use crate::error::{Error, Result};
|
||||
use crate::reader::{Reader, is_delimiter, is_whitespace};
|
||||
|
||||
/// A PostScript number object (integer or real).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Number {
|
||||
Integer(i32),
|
||||
Real(f32),
|
||||
}
|
||||
|
||||
impl Number {
|
||||
/// Return the value as an `i32`. Reals are truncated.
|
||||
pub fn as_i32(self) -> i32 {
|
||||
match self {
|
||||
Self::Integer(v) => v,
|
||||
Self::Real(v) => v as i32,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the value as an `f32`.
|
||||
pub fn as_f32(self) -> f32 {
|
||||
match self {
|
||||
Self::Integer(v) => v as f32,
|
||||
Self::Real(v) => v,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the value as an `f64`.
|
||||
pub fn as_f64(self) -> f64 {
|
||||
match self {
|
||||
Self::Integer(v) => v as f64,
|
||||
Self::Real(v) => v as f64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_terminated(r: &Reader<'_>) -> bool {
|
||||
match r.peek_byte() {
|
||||
None => true,
|
||||
Some(b) => is_whitespace(b) || is_delimiter(b),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn read(r: &mut Reader<'_>) -> Result<Number> {
|
||||
let saved = r.offset();
|
||||
|
||||
// Optional sign.
|
||||
let first = r.peek_byte().ok_or(Error::SyntaxError)?;
|
||||
let has_sign = first == b'+' || first == b'-';
|
||||
|
||||
if has_sign {
|
||||
r.forward();
|
||||
}
|
||||
|
||||
// Consume leading digits.
|
||||
let digit_start = r.offset();
|
||||
r.forward_while(|b| b.is_ascii_digit());
|
||||
let has_digits = r.offset() > digit_start;
|
||||
|
||||
// Check if number is a radix number.
|
||||
if !has_sign && has_digits && r.peek_byte() == Some(b'#') {
|
||||
let base_bytes = r.range(digit_start..r.offset()).ok_or(Error::SyntaxError)?;
|
||||
let base_str = core::str::from_utf8(base_bytes).map_err(|_| Error::SyntaxError)?;
|
||||
let base = base_str.parse::<u32>().map_err(|_| Error::SyntaxError)?;
|
||||
|
||||
if !(2..=36).contains(&base) {
|
||||
return Err(Error::SyntaxError);
|
||||
}
|
||||
|
||||
// Skip `#`.
|
||||
r.forward();
|
||||
|
||||
let num_start = r.offset();
|
||||
r.forward_while(|b| b.is_ascii_alphanumeric());
|
||||
|
||||
if r.offset() == num_start || !is_terminated(r) {
|
||||
return Err(Error::SyntaxError);
|
||||
}
|
||||
|
||||
let num_bytes = r.range(num_start..r.offset()).ok_or(Error::SyntaxError)?;
|
||||
let num_str = core::str::from_utf8(num_bytes).map_err(|_| Error::SyntaxError)?;
|
||||
let value = i32::from_str_radix(num_str, base).map_err(|_| Error::SyntaxError)?;
|
||||
|
||||
return Ok(Number::Integer(value));
|
||||
}
|
||||
|
||||
// Check for real number indicators: `.` or `e`/`E`.
|
||||
let has_dot = r.peek_byte() == Some(b'.');
|
||||
|
||||
if has_dot {
|
||||
r.forward(); // skip '.'
|
||||
r.forward_while(|b| b.is_ascii_digit());
|
||||
}
|
||||
|
||||
// At this point we need at least some digits (before or after the dot).
|
||||
if !has_digits && !has_dot {
|
||||
return Err(Error::SyntaxError);
|
||||
}
|
||||
|
||||
let has_exponent = matches!(r.peek_byte(), Some(b'e' | b'E'));
|
||||
if has_exponent {
|
||||
r.forward();
|
||||
|
||||
// Optional exponent sign.
|
||||
if matches!(r.peek_byte(), Some(b'+' | b'-')) {
|
||||
r.forward();
|
||||
}
|
||||
|
||||
r.forward_while(|b| b.is_ascii_digit());
|
||||
}
|
||||
|
||||
if !is_terminated(r) {
|
||||
return Err(Error::SyntaxError);
|
||||
}
|
||||
|
||||
let token = r.range(saved..r.offset()).ok_or(Error::SyntaxError)?;
|
||||
let str = core::str::from_utf8(token).map_err(|_| Error::SyntaxError)?;
|
||||
|
||||
if has_dot || has_exponent {
|
||||
let value = str.parse::<f32>().map_err(|_| Error::SyntaxError)?;
|
||||
|
||||
Ok(Number::Real(value))
|
||||
} else {
|
||||
if !has_digits {
|
||||
return Err(Error::SyntaxError);
|
||||
}
|
||||
|
||||
let value = str.parse::<i32>().map_err(|_| Error::SyntaxError)?;
|
||||
|
||||
Ok(Number::Integer(value))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn read_num(input: &[u8]) -> Result<Number> {
|
||||
let mut r = Reader::new(input);
|
||||
read(&mut r)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_integers() {
|
||||
assert_eq!(read_num(b"123 ").unwrap(), Number::Integer(123));
|
||||
assert_eq!(read_num(b"-98 ").unwrap(), Number::Integer(-98));
|
||||
assert_eq!(read_num(b"43445 ").unwrap(), Number::Integer(43445));
|
||||
assert_eq!(read_num(b"0 ").unwrap(), Number::Integer(0));
|
||||
assert_eq!(read_num(b"+17 ").unwrap(), Number::Integer(17));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn real_numbers() {
|
||||
assert_eq!(read_num(b"-.002 ").unwrap(), Number::Real(-0.002));
|
||||
assert_eq!(read_num(b"34.5 ").unwrap(), Number::Real(34.5));
|
||||
assert_eq!(read_num(b"-3.62 ").unwrap(), Number::Real(-3.62));
|
||||
assert_eq!(read_num(b"123.6e10 ").unwrap(), Number::Real(123.6e10));
|
||||
assert_eq!(read_num(b"1.0E-5 ").unwrap(), Number::Real(1.0E-5));
|
||||
assert_eq!(read_num(b"1E6 ").unwrap(), Number::Real(1E6));
|
||||
assert_eq!(read_num(b"-1. ").unwrap(), Number::Real(-1.0));
|
||||
assert_eq!(read_num(b"0.0 ").unwrap(), Number::Real(0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn radix_numbers() {
|
||||
assert_eq!(read_num(b"8#1777 ").unwrap(), Number::Integer(0o1777));
|
||||
assert_eq!(read_num(b"16#FFFE ").unwrap(), Number::Integer(0xFFFE));
|
||||
assert_eq!(read_num(b"2#1000 ").unwrap(), Number::Integer(0b1000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid() {
|
||||
assert!(read_num(b"abc").is_err());
|
||||
assert!(read_num(b"+abc").is_err());
|
||||
assert!(read_num(b"1a").is_err());
|
||||
}
|
||||
}
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
use crate::array::{self, Array};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::name::{self, Name};
|
||||
use crate::number::{self, Number};
|
||||
use crate::reader::Reader;
|
||||
use crate::string::{self, String};
|
||||
|
||||
/// A PostScript object.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Object<'a> {
|
||||
/// A number object.
|
||||
Number(Number),
|
||||
/// A name object.
|
||||
Name(Name<'a>),
|
||||
/// A string object.
|
||||
String(String<'a>),
|
||||
/// An array object.
|
||||
Array(Array<'a>),
|
||||
}
|
||||
|
||||
pub(crate) fn read<'a>(r: &mut Reader<'a>) -> Result<Object<'a>> {
|
||||
// PdfCraft patch: `<<` and `>>` are skipped by looping instead of
|
||||
// recursing once per delimiter, so a long run cannot overflow the stack.
|
||||
loop {
|
||||
skip_whitespace_and_comments(r);
|
||||
|
||||
let b = r.peek_byte().ok_or(Error::SyntaxError)?;
|
||||
|
||||
return match b {
|
||||
b'(' => string::parse_literal(r)
|
||||
.map(|s| Object::String(String::from_literal(s)))
|
||||
.ok_or(Error::SyntaxError),
|
||||
b'<' => {
|
||||
if r.peek_bytes(2) == Some(b"<~") {
|
||||
string::parse_ascii85(r)
|
||||
.map(|s| Object::String(String::from_ascii85(s)))
|
||||
.ok_or(Error::SyntaxError)
|
||||
} else if r.peek_bytes(2) == Some(b"<<") {
|
||||
// TODO: Proper dict support. For now, skip `<<` and return
|
||||
// the next object so callers see the inner tokens.
|
||||
r.forward();
|
||||
r.forward();
|
||||
continue;
|
||||
} else {
|
||||
string::parse_hex(r)
|
||||
.map(|s| Object::String(String::from_hex(s)))
|
||||
.ok_or(Error::SyntaxError)
|
||||
}
|
||||
}
|
||||
b'/' => name::parse_literal(r)
|
||||
.map(|s| Object::Name(Name::new(s, true)))
|
||||
.ok_or(Error::SyntaxError),
|
||||
b'[' => array::parse(r).map(|d| Object::Array(Array::new(d))),
|
||||
b'>' => {
|
||||
if r.peek_bytes(2) == Some(b">>") {
|
||||
// TODO: Proper dict support. Skip `>>` closing delimiter.
|
||||
r.forward();
|
||||
r.forward();
|
||||
continue;
|
||||
} else {
|
||||
Err(Error::SyntaxError)
|
||||
}
|
||||
}
|
||||
b'{' => {
|
||||
r.forward();
|
||||
Err(Error::UnsupportedType)
|
||||
}
|
||||
b'.' | b'+' | b'-' | b'0'..=b'9' => number::read(r).map(Object::Number),
|
||||
_ => name::parse_executable(r)
|
||||
.map(|s| Object::Name(Name::new(s, false)))
|
||||
.ok_or(Error::SyntaxError),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn at_end(r: &mut Reader<'_>) -> bool {
|
||||
skip_whitespace_and_comments(r);
|
||||
r.peek_byte().is_none()
|
||||
}
|
||||
|
||||
pub(crate) fn skip_whitespace_and_comments(r: &mut Reader<'_>) {
|
||||
loop {
|
||||
match r.peek_byte() {
|
||||
Some(b) if crate::reader::is_whitespace(b) => {
|
||||
r.forward();
|
||||
}
|
||||
Some(b'%') => {
|
||||
r.forward();
|
||||
r.forward_while(|b| !crate::reader::is_eol(b));
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn read_one(input: &[u8]) -> Result<Object<'_>> {
|
||||
let mut r = Reader::new(input);
|
||||
read(&mut r)
|
||||
}
|
||||
|
||||
fn read_ok(input: &[u8]) -> Object<'_> {
|
||||
read_one(input).unwrap()
|
||||
}
|
||||
|
||||
fn read_err(input: &[u8]) -> Error {
|
||||
read_one(input).unwrap_err()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integer() {
|
||||
assert_eq!(read_ok(b"42 "), Object::Number(Number::Integer(42)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negative_integer() {
|
||||
assert_eq!(read_ok(b"-7 "), Object::Number(Number::Integer(-7)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_name() {
|
||||
assert_eq!(
|
||||
read_ok(b"/CMapName "),
|
||||
Object::Name(Name::new(b"CMapName", true))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executable_name() {
|
||||
let obj = read_ok(b"beginbfchar ");
|
||||
assert_eq!(obj, Object::Name(Name::new(b"beginbfchar", false)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_string() {
|
||||
assert_eq!(
|
||||
read_ok(b"(Hello)"),
|
||||
Object::String(String::from_literal(b"Hello"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_string() {
|
||||
assert_eq!(
|
||||
read_ok(b"<48656C6C6F>"),
|
||||
Object::String(String::from_hex(b"48656C6C6F"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ascii85_string() {
|
||||
assert_eq!(
|
||||
read_ok(b"<~87cURDZ~>"),
|
||||
Object::String(String::from_ascii85(b"87cURDZ"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_simple() {
|
||||
let obj = read_ok(b"[1 2 3]");
|
||||
assert_eq!(obj, Object::Array(Array::new(b"1 2 3")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stray_close_bracket() {
|
||||
assert_eq!(read_err(b"]"), Error::SyntaxError);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stray_gt() {
|
||||
assert_eq!(read_err(b">x"), Error::SyntaxError);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skips_whitespace() {
|
||||
assert_eq!(read_ok(b" \t\n 42 "), Object::Number(Number::Integer(42)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skips_comments() {
|
||||
assert_eq!(
|
||||
read_ok(b"% this is a comment\n42 "),
|
||||
Object::Number(Number::Integer(42))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eof() {
|
||||
assert!(read_one(b"").is_err());
|
||||
assert!(read_one(b" ").is_err());
|
||||
assert!(read_one(b"% comment only\n").is_err());
|
||||
}
|
||||
|
||||
// PdfCraft patch: `<<` and `>>` runs are skipped without recursion.
|
||||
#[test]
|
||||
fn long_open_dict_run_does_not_overflow() {
|
||||
let input = alloc::vec![b'<'; 1_000_000];
|
||||
assert_eq!(read_err(&input), Error::SyntaxError);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_close_dict_run_does_not_overflow() {
|
||||
let input = b">>".repeat(500_000);
|
||||
assert_eq!(read_err(&input), Error::SyntaxError);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dict_delimiters_are_skipped() {
|
||||
let mut r = Reader::new(b"<< /A 1 >>");
|
||||
assert_eq!(read(&mut r), Ok(Object::Name(Name::new(b"A", true))));
|
||||
assert_eq!(read(&mut r), Ok(Object::Number(Number::Integer(1))));
|
||||
assert_eq!(read(&mut r), Err(Error::SyntaxError));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_dict_delimiters_are_skipped() {
|
||||
let mut r = Reader::new(b"<<<< /X >>>> 7 <<\n% c\n<< >> 8");
|
||||
assert_eq!(read(&mut r), Ok(Object::Name(Name::new(b"X", true))));
|
||||
assert_eq!(read(&mut r), Ok(Object::Number(Number::Integer(7))));
|
||||
assert_eq!(read(&mut r), Ok(Object::Number(Number::Integer(8))));
|
||||
assert!(at_end(&mut r));
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
use core::ops::Range;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Reader<'a> {
|
||||
pub(crate) data: &'a [u8],
|
||||
pub(crate) offset: usize,
|
||||
}
|
||||
|
||||
impl<'a> Reader<'a> {
|
||||
#[inline]
|
||||
pub(crate) fn new(data: &'a [u8]) -> Self {
|
||||
Self { data, offset: 0 }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn jump(&mut self, offset: usize) {
|
||||
self.offset = offset;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn range(&self, range: Range<usize>) -> Option<&'a [u8]> {
|
||||
self.data.get(range)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn offset(&self) -> usize {
|
||||
self.offset
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn read_bytes(&mut self, len: usize) -> Option<&'a [u8]> {
|
||||
let v = self.peek_bytes(len)?;
|
||||
self.offset += len;
|
||||
Some(v)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn read_byte(&mut self) -> Option<u8> {
|
||||
let v = self.peek_byte()?;
|
||||
self.offset += 1;
|
||||
Some(v)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn peek_bytes(&self, len: usize) -> Option<&'a [u8]> {
|
||||
self.data.get(self.offset..self.offset + len)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn peek_byte(&self) -> Option<u8> {
|
||||
self.data.get(self.offset).copied()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn eat(&mut self, f: impl Fn(u8) -> bool) -> Option<u8> {
|
||||
let val = self.peek_byte()?;
|
||||
if f(val) {
|
||||
self.forward();
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn forward(&mut self) {
|
||||
self.offset += 1;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn forward_while(&mut self, f: impl Fn(u8) -> bool) {
|
||||
while let Some(b) = self.peek_byte() {
|
||||
if f(b) {
|
||||
self.forward();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn forward_tag(&mut self, tag: &[u8]) -> Option<()> {
|
||||
self.peek_tag(tag)?;
|
||||
self.offset += tag.len();
|
||||
Some(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn peek_tag(&self, tag: &[u8]) -> Option<()> {
|
||||
let mut cloned = self.clone();
|
||||
|
||||
for b in tag.iter().copied() {
|
||||
if cloned.peek_byte() == Some(b) {
|
||||
cloned.forward();
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn skip_eol(&mut self) {
|
||||
while let Some(b) = self.peek_byte() {
|
||||
if is_eol(b) {
|
||||
self.forward();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn is_whitespace(b: u8) -> bool {
|
||||
matches!(b, 0x00 | 0x09 | 0x0a | 0x0c | 0x0d | 0x20)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn is_delimiter(b: u8) -> bool {
|
||||
matches!(
|
||||
b,
|
||||
b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%'
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn is_regular(b: u8) -> bool {
|
||||
!is_whitespace(b) && !is_delimiter(b)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn is_eol(b: u8) -> bool {
|
||||
matches!(b, 0x0a | 0x0d)
|
||||
}
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
mod ascii_85;
|
||||
pub(crate) mod ascii_hex;
|
||||
mod literal;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::reader::Reader;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum StringInner<'a> {
|
||||
Literal(&'a [u8]),
|
||||
Hex(&'a [u8]),
|
||||
Ascii85(&'a [u8]),
|
||||
}
|
||||
|
||||
/// A PostScript string object.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct String<'a> {
|
||||
inner: StringInner<'a>,
|
||||
}
|
||||
|
||||
impl<'a> String<'a> {
|
||||
pub(crate) const fn from_literal(data: &'a [u8]) -> Self {
|
||||
Self {
|
||||
inner: StringInner::Literal(data),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn from_hex(data: &'a [u8]) -> Self {
|
||||
Self {
|
||||
inner: StringInner::Hex(data),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn from_ascii85(data: &'a [u8]) -> Self {
|
||||
Self {
|
||||
inner: StringInner::Ascii85(data),
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode the string content into `out`, replacing any previous contents.
|
||||
pub fn decode_into(&self, out: &mut Vec<u8>) -> Result<()> {
|
||||
out.clear();
|
||||
match self.inner {
|
||||
StringInner::Literal(data) => literal::decode_into(data, out),
|
||||
StringInner::Hex(data) => ascii_hex::decode_into(data, out),
|
||||
StringInner::Ascii85(data) => ascii_85::decode_into(data, out),
|
||||
}
|
||||
.ok_or(Error::SyntaxError)
|
||||
}
|
||||
|
||||
/// Decode the string content.
|
||||
pub fn decode(&self) -> Result<Vec<u8>> {
|
||||
let mut out = Vec::new();
|
||||
self.decode_into(&mut out)?;
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_literal<'a>(r: &mut Reader<'a>) -> Option<&'a [u8]> {
|
||||
let start = r.offset();
|
||||
skip_literal(r)?;
|
||||
let end = r.offset();
|
||||
// Exclude outer parentheses.
|
||||
r.range(start + 1..end - 1)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_hex<'a>(r: &mut Reader<'a>) -> Option<&'a [u8]> {
|
||||
r.forward_tag(b"<")?;
|
||||
let start = r.offset();
|
||||
while let Some(b) = r.read_byte() {
|
||||
if b == b'>' {
|
||||
return r.range(start..r.offset() - 1);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn parse_ascii85<'a>(r: &mut Reader<'a>) -> Option<&'a [u8]> {
|
||||
r.forward_tag(b"<~")?;
|
||||
let start = r.offset();
|
||||
loop {
|
||||
let b = r.read_byte()?;
|
||||
if b == b'~' {
|
||||
let end = r.offset() - 1;
|
||||
r.forward_tag(b">")?;
|
||||
return r.range(start..end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_literal(r: &mut Reader<'_>) -> Option<()> {
|
||||
r.forward_tag(b"(")?;
|
||||
let mut depth = 1_u32;
|
||||
|
||||
while depth > 0 {
|
||||
let byte = r.read_byte()?;
|
||||
match byte {
|
||||
b'\\' => {
|
||||
let _ = r.read_byte()?;
|
||||
}
|
||||
b'(' => depth += 1,
|
||||
b')' => depth -= 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn decode_literal(input: &[u8]) -> Result<Vec<u8>> {
|
||||
let mut r = Reader::new(input);
|
||||
let data = parse_literal(&mut r).ok_or(Error::SyntaxError)?;
|
||||
String::from_literal(data).decode()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_empty() {
|
||||
assert_eq!(decode_literal(b"()").unwrap(), b"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_simple() {
|
||||
assert_eq!(decode_literal(b"(Hello)").unwrap(), b"Hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_nested_parens() {
|
||||
assert_eq!(
|
||||
decode_literal(b"(Hi (()) there)").unwrap(),
|
||||
b"Hi (()) there"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_escape_n() {
|
||||
assert_eq!(decode_literal(b"(a\\nb)").unwrap(), b"a\nb");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_escape_r() {
|
||||
assert_eq!(decode_literal(b"(a\\rb)").unwrap(), b"a\rb");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_escape_t() {
|
||||
assert_eq!(decode_literal(b"(a\\tb)").unwrap(), b"a\tb");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_escape_b() {
|
||||
assert_eq!(decode_literal(b"(a\\bb)").unwrap(), &[b'a', 0x08, b'b']);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_escape_f() {
|
||||
assert_eq!(decode_literal(b"(a\\fb)").unwrap(), &[b'a', 0x0C, b'b']);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_escape_backslash() {
|
||||
assert_eq!(decode_literal(b"(a\\\\b)").unwrap(), b"a\\b");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_escape_parens() {
|
||||
assert_eq!(decode_literal(b"(Hi \\()").unwrap(), b"Hi (");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_octal_three_digits() {
|
||||
assert_eq!(decode_literal(b"(\\053)").unwrap(), b"+");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_octal_two_digits() {
|
||||
assert_eq!(decode_literal(b"(\\36)").unwrap(), b"\x1e");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_octal_one_digit() {
|
||||
assert_eq!(decode_literal(b"(\\3)").unwrap(), b"\x03");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_line_continuation_lf() {
|
||||
assert_eq!(decode_literal(b"(Hi \\\nthere)").unwrap(), b"Hi there");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_line_continuation_cr() {
|
||||
assert_eq!(decode_literal(b"(Hi \\\rthere)").unwrap(), b"Hi there");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_line_continuation_crlf() {
|
||||
assert_eq!(decode_literal(b"(Hi \\\r\nthere)").unwrap(), b"Hi there");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_bare_eol_lf() {
|
||||
assert_eq!(decode_literal(b"(a\nb)").unwrap(), b"a\nb");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_bare_eol_cr() {
|
||||
assert_eq!(decode_literal(b"(a\rb)").unwrap(), b"a\nb");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_bare_eol_crlf() {
|
||||
assert_eq!(decode_literal(b"(a\r\nb)").unwrap(), b"a\nb");
|
||||
}
|
||||
|
||||
fn decode_hex(input: &[u8]) -> Result<Vec<u8>> {
|
||||
let mut r = Reader::new(input);
|
||||
let data = parse_hex(&mut r).ok_or(Error::SyntaxError)?;
|
||||
String::from_hex(data).decode()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_simple() {
|
||||
assert_eq!(decode_hex(b"<48656C6C6F>").unwrap(), b"Hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_with_whitespace() {
|
||||
assert_eq!(decode_hex(b"<48 65 6C 6C 6F>").unwrap(), b"Hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_odd_nibble() {
|
||||
assert_eq!(decode_hex(b"<ABC>").unwrap(), &[0xAB, 0xC0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_empty() {
|
||||
assert_eq!(decode_hex(b"<>").unwrap(), b"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_lowercase() {
|
||||
assert_eq!(decode_hex(b"<abcd>").unwrap(), &[0xAB, 0xCD]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_mixed_case() {
|
||||
assert_eq!(decode_hex(b"<aB3E>").unwrap(), &[0xAB, 0x3E]);
|
||||
}
|
||||
|
||||
fn decode_a85(input: &[u8]) -> Result<Vec<u8>> {
|
||||
let mut r = Reader::new(input);
|
||||
let data = parse_ascii85(&mut r).ok_or(Error::SyntaxError)?;
|
||||
String::from_ascii85(data).decode()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ascii85_simple() {
|
||||
// "Hello" in ASCII85 is "87cURDZ"
|
||||
assert_eq!(decode_a85(b"<~87cURDZ~>").unwrap(), b"Hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ascii85_empty() {
|
||||
assert_eq!(decode_a85(b"<~~>").unwrap(), b"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ascii85_z_shorthand() {
|
||||
assert_eq!(decode_a85(b"<~z~>").unwrap(), &[0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ascii85_partial_group() {
|
||||
let result = decode_a85(b"<~87~>").unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ascii85_with_whitespace() {
|
||||
assert_eq!(decode_a85(b"<~87cU RDZ~>").unwrap(), b"Hello");
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// Keep in sync with `hayro-syntax/src/filter/ascii_85.rs`.
|
||||
|
||||
use crate::reader::{Reader, is_whitespace};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
pub(crate) fn decode_into(data: &[u8], out: &mut Vec<u8>) -> Option<()> {
|
||||
const POW_85: [u32; 5] = [52200625, 614125, 7225, 85, 1];
|
||||
|
||||
let mut reader = Reader::new(data);
|
||||
|
||||
let mut read_byte = || -> Option<u8> {
|
||||
loop {
|
||||
let b = reader.read_byte()?;
|
||||
|
||||
// White space characters should be ignored.
|
||||
if !is_whitespace(b) {
|
||||
return Some(b);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let flush_group = |group: &mut Vec<u8>, decoded: &mut Vec<u8>| -> Option<()> {
|
||||
let (digits, output_len): ([u32; 5], usize) = match group.len() {
|
||||
0 => return Some(()),
|
||||
1 => return None, // A single character is not valid.
|
||||
2 => (
|
||||
[group[0], group[1], b'u', b'u', b'u'].map(|b| (b - b'!') as u32),
|
||||
1,
|
||||
),
|
||||
3 => (
|
||||
[group[0], group[1], group[2], b'u', b'u'].map(|b| (b - b'!') as u32),
|
||||
2,
|
||||
),
|
||||
4 => (
|
||||
[group[0], group[1], group[2], group[3], b'u'].map(|b| (b - b'!') as u32),
|
||||
3,
|
||||
),
|
||||
5 => (
|
||||
[group[0], group[1], group[2], group[3], group[4]].map(|b| (b - b'!') as u32),
|
||||
4,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let value = digits[0]
|
||||
.checked_mul(POW_85[0])?
|
||||
.checked_add(digits[1].checked_mul(POW_85[1])?)?
|
||||
.checked_add(digits[2].checked_mul(POW_85[2])?)?
|
||||
.checked_add(digits[3].checked_mul(POW_85[3])?)?
|
||||
.checked_add(digits[4])?;
|
||||
|
||||
decoded.extend_from_slice(&value.to_be_bytes()[..output_len]);
|
||||
group.clear();
|
||||
Some(())
|
||||
};
|
||||
|
||||
out.reserve(data.len() * 4 / 5);
|
||||
let mut group = Vec::with_capacity(5);
|
||||
|
||||
loop {
|
||||
let Some(b) = read_byte() else {
|
||||
// Be lenient and accept what we have (see PDFBOX-5910).
|
||||
flush_group(&mut group, out)?;
|
||||
|
||||
return Some(());
|
||||
};
|
||||
|
||||
match b {
|
||||
b'!'..=b'u' => {
|
||||
group.push(b);
|
||||
|
||||
if group.len() == 5 {
|
||||
flush_group(&mut group, out)?;
|
||||
}
|
||||
}
|
||||
b'z' => {
|
||||
flush_group(&mut group, out)?;
|
||||
out.extend_from_slice(&[0, 0, 0, 0]);
|
||||
}
|
||||
b'~' => {
|
||||
// Technically requires a '>', but there is a PDF where it isn't
|
||||
// appended and decodes fine in other viewers.
|
||||
flush_group(&mut group, out)?;
|
||||
|
||||
return Some(());
|
||||
}
|
||||
_ => return None, // Invalid character.
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// Keep in sync with `hayro-syntax/src/filter/ascii_hex.rs`.
|
||||
|
||||
use crate::reader::is_whitespace;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
pub(crate) fn decode_into(data: &[u8], out: &mut Vec<u8>) -> Option<()> {
|
||||
let has_whitespace = data.iter().any(|&b| is_whitespace(b));
|
||||
|
||||
out.reserve(data.len().div_ceil(2));
|
||||
|
||||
if !has_whitespace {
|
||||
// Fast path, don't need to worry about white spaces.
|
||||
let mut i = 0;
|
||||
while i + 1 < data.len() {
|
||||
out.push(decode_hex_digit(data[i])? << 4 | decode_hex_digit(data[i + 1])?);
|
||||
i += 2;
|
||||
}
|
||||
if i < data.len() {
|
||||
out.push(decode_hex_digit(data[i])? << 4);
|
||||
}
|
||||
} else {
|
||||
// Slow path, need to strip white spaces.
|
||||
let mut iter = data.iter().copied();
|
||||
let mut read_byte = || -> Option<u8> {
|
||||
loop {
|
||||
let b = iter.next()?;
|
||||
if !is_whitespace(b) {
|
||||
return Some(b);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
match (read_byte(), read_byte()) {
|
||||
(Some(hi), Some(lo)) => {
|
||||
out.push(decode_hex_digit(hi)? << 4 | decode_hex_digit(lo)?);
|
||||
}
|
||||
(Some(hi), None) => {
|
||||
out.push(decode_hex_digit(hi)? << 4);
|
||||
|
||||
break;
|
||||
}
|
||||
(None, _) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn decode_hex_digit(c: u8) -> Option<u8> {
|
||||
match c {
|
||||
b'0'..=b'9' => Some(c - b'0'),
|
||||
b'A'..=b'F' => Some(c - b'A' + 10),
|
||||
b'a'..=b'f' => Some(c - b'a' + 10),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
// Keep in sync with `hayro-syntax/src/object/string.rs` (`read_literal`).
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::reader::Reader;
|
||||
|
||||
pub(crate) fn decode_into(data: &[u8], out: &mut Vec<u8>) -> Option<()> {
|
||||
let mut r = Reader::new(data);
|
||||
|
||||
while let Some(byte) = r.read_byte() {
|
||||
match byte {
|
||||
b'\\' => {
|
||||
let next = r.read_byte()?;
|
||||
|
||||
if is_octal_digit(next) {
|
||||
let second = r.read_byte();
|
||||
let third = r.read_byte();
|
||||
|
||||
let bytes = match (second, third) {
|
||||
(Some(n1), Some(n2)) => match (is_octal_digit(n1), is_octal_digit(n2)) {
|
||||
(true, true) => [next, n1, n2],
|
||||
(true, _) => {
|
||||
r.jump(r.offset() - 1);
|
||||
[b'0', next, n1]
|
||||
}
|
||||
_ => {
|
||||
r.jump(r.offset() - 2);
|
||||
[b'0', b'0', next]
|
||||
}
|
||||
},
|
||||
(Some(n1), None) => {
|
||||
if is_octal_digit(n1) {
|
||||
[b'0', next, n1]
|
||||
} else {
|
||||
r.jump(r.offset() - 1);
|
||||
[b'0', b'0', next]
|
||||
}
|
||||
}
|
||||
_ => [b'0', b'0', next],
|
||||
};
|
||||
|
||||
let str = core::str::from_utf8(&bytes).unwrap();
|
||||
|
||||
if let Ok(num) = u8::from_str_radix(str, 8) {
|
||||
out.push(num);
|
||||
}
|
||||
} else {
|
||||
match next {
|
||||
b'n' => out.push(0xA),
|
||||
b'r' => out.push(0xD),
|
||||
b't' => out.push(0x9),
|
||||
b'b' => out.push(0x8),
|
||||
b'f' => out.push(0xC),
|
||||
b'(' => out.push(b'('),
|
||||
b')' => out.push(b')'),
|
||||
b'\\' => out.push(b'\\'),
|
||||
b'\n' | b'\r' => {
|
||||
// "If the \ is followed immediately by a newline
|
||||
// (CR, LF, or CR-LF pair), the scanner ignores
|
||||
// both the initial \ and the newline; this breaks
|
||||
// a string into multiple lines without including
|
||||
// the newline character as part of the string."
|
||||
r.skip_eol();
|
||||
}
|
||||
_ => out.push(next),
|
||||
}
|
||||
}
|
||||
}
|
||||
b'(' | b')' => out.push(byte),
|
||||
// "But if a newline appears without a preceding \, the result is
|
||||
// equivalent to \n."
|
||||
b'\n' | b'\r' => {
|
||||
out.push(b'\n');
|
||||
r.skip_eol();
|
||||
}
|
||||
other => out.push(other),
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn is_octal_digit(byte: u8) -> bool {
|
||||
matches!(byte, b'0'..=b'7')
|
||||
}
|
||||
Reference in New Issue
Block a user