///|
fn pdf_skip_comments(lexemes : ArrayView[PdfLexeme], index : Int) -> Int {
let mut current = index
let mut done = false
while !done && current < lexemes.length() {
match lexemes[current] {
LexComment(_) => current += 1
_ => done = true
}
}
current
}
///|
fn pdf_lexeme_indirect_reference_number(
lexemes : ArrayView[PdfLexeme],
index : Int,
) -> Int? {
if index + 2 < lexemes.length() {
match (lexemes[index], lexemes[index + 1], lexemes[index + 2]) {
(LexInt(object_number), LexInt(_), LexR) => Some(object_number)
_ => None
}
} else {
None
}
}
///|
/// Return whether lexemes at `index` start an indirect-object header.
pub fn pdf_lexeme_indirect_object_header_at(
lexemes : ArrayView[PdfLexeme],
index : Int,
) -> Bool {
if index + 2 < lexemes.length() {
match (lexemes[index], lexemes[index + 1], lexemes[index + 2]) {
(LexInt(_), LexInt(_), LexObj) => true
_ => false
}
} else {
false
}
}
///|
fn pdf_parse_array_capacity(lexemes : ArrayView[PdfLexeme], index : Int) -> Int {
let mut current = index
let mut array_depth = 0
let mut dictionary_depth = 0
while current < lexemes.length() {
match lexemes[current] {
LexLeftSquare => array_depth += 1
LexRightSquare =>
if array_depth == 0 && dictionary_depth == 0 {
break current - index
} else if array_depth > 0 {
array_depth -= 1
}
LexLeftDict => dictionary_depth += 1
LexRightDict => if dictionary_depth > 0 { dictionary_depth -= 1 }
_ => ()
}
current += 1
} nobreak {
current - index
}
}
///|
fn pdf_parse_dictionary_capacity(
lexemes : ArrayView[PdfLexeme],
index : Int,
) -> Int {
let mut current = index
let mut array_depth = 0
let mut dictionary_depth = 0
while current < lexemes.length() {
match lexemes[current] {
LexLeftSquare => array_depth += 1
LexRightSquare => if array_depth > 0 { array_depth -= 1 }
LexLeftDict => dictionary_depth += 1
LexRightDict =>
if array_depth == 0 && dictionary_depth == 0 {
break (current - index + 1) / 2
} else if dictionary_depth > 0 {
dictionary_depth -= 1
}
_ => ()
}
current += 1
} nobreak {
(current - index + 1) / 2
}
}
///|
/// Maximum array/dictionary nesting depth accepted by the object parser.
/// Deeper values are rejected instead of overflowing the stack on malicious
/// input.
let pdf_parse_max_depth : Int = 512
///|
fn pdf_parse_array_at(
lexemes : ArrayView[PdfLexeme],
index : Int,
depth : Int,
) -> (PdfObject, Int) raise @core.PdfError {
let values : Array[PdfObject] = Array(
capacity=pdf_parse_array_capacity(lexemes, index),
)
let mut current = index
let mut done = false
while !done && current < lexemes.length() {
current = pdf_skip_comments(lexemes, current)
if current >= lexemes.length() {
done = true
} else {
match lexemes[current] {
LexRightSquare => {
current += 1
done = true
}
_ => {
let (value, next) = pdf_parse_value_at(
lexemes,
current,
depth=depth + 1,
)
values.push(value)
current = next
}
}
}
}
(PdfArray(values), current)
}
///|
fn pdf_skip_dictionary_tail(lexemes : ArrayView[PdfLexeme], index : Int) -> Int {
let mut current = index
let mut depth = 0
while current < lexemes.length() {
match lexemes[current] {
LexLeftDict => depth += 1
LexRightDict => if depth == 0 { break current + 1 } else { depth -= 1 }
_ => ()
}
current += 1
} nobreak {
current
}
}
///|
fn pdf_parse_dictionary_at(
lexemes : ArrayView[PdfLexeme],
index : Int,
depth : Int,
) -> (PdfObject, Int) raise @core.PdfError {
let entries : Array[(@core.PdfName, PdfObject)] = Array(
capacity=pdf_parse_dictionary_capacity(lexemes, index),
)
let mut current = index
let mut malformed = false
let mut done = false
while !done && current < lexemes.length() {
current = pdf_skip_comments(lexemes, current)
if current >= lexemes.length() {
done = true
} else {
match lexemes[current] {
LexRightDict => {
current += 1
done = true
}
_ => {
let (key, after_key) = pdf_parse_value_at(
lexemes,
current,
depth=depth + 1,
)
match key {
PdfNameObject(name) =>
if after_key >= lexemes.length() {
malformed = true
current = after_key
done = true
} else {
let value_start = pdf_skip_comments(lexemes, after_key)
if value_start >= lexemes.length() {
malformed = true
current = value_start
done = true
} else {
match lexemes[value_start] {
LexRightDict => {
malformed = true
current = value_start + 1
done = true
}
_ => {
let (value, after_value) = pdf_parse_value_at(
lexemes,
value_start,
depth=depth + 1,
)
entries.push((name, value))
current = after_value
}
}
}
}
_ => {
malformed = true
current = pdf_skip_dictionary_tail(lexemes, after_key)
done = true
}
}
}
}
}
}
if malformed {
(PdfDictionary([]), current)
} else {
(PdfDictionary(entries), current)
}
}
///|
fn pdf_parse_value_at(
lexemes : ArrayView[PdfLexeme],
index : Int,
depth? : Int = 0,
) -> (PdfObject, Int) raise @core.PdfError {
if depth >= pdf_parse_max_depth {
raise ParseObjectExpected
}
let current = pdf_skip_comments(lexemes, index)
if current >= lexemes.length() {
raise ParseObjectExpected
}
match pdf_lexeme_indirect_reference_number(lexemes, current) {
Some(object_number) => (PdfIndirect(object_number), current + 3)
None =>
match lexemes[current] {
LexNull => (PdfNull, current + 1)
LexBool(value) => (PdfBoolean(value), current + 1)
LexInt(value) => (PdfInteger(value), current + 1)
LexReal(value) => (PdfReal(value), current + 1)
LexString(value) => (PdfString(value), current + 1)
LexName(value) => (PdfNameObject(value), current + 1)
LexLeftSquare => pdf_parse_array_at(lexemes, current + 1, depth)
LexLeftDict => pdf_parse_dictionary_at(lexemes, current + 1, depth)
_ => raise ParseObjectExpected
}
}
}