mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-30 21:17:55 -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user