Compare commits

...

4 Commits

Author SHA1 Message Date
Přemysl Eric Janouch 62206ed344
Go: documentation cleanup 2018-10-04 13:18:37 +02:00
Přemysl Eric Janouch 9ac8360979
Go: use multiple return values
The compiler has made it more obvious where we eat error messages.
2018-10-04 13:09:29 +02:00
Přemysl Eric Janouch 50578fe99f
Go: add Object constructors 2018-10-04 12:51:23 +02:00
Přemysl Eric Janouch eedd9a550c
Go: cleanups 2018-10-04 12:11:43 +02:00
1 changed files with 167 additions and 133 deletions

View File

@ -65,21 +65,14 @@ const (
// Object is a PDF token/object thingy. Objects may be composed either from // Object is a PDF token/object thingy. Objects may be composed either from
// one or a sequence of tokens. The PDF Reference doesn't actually speak // one or a sequence of tokens. The PDF Reference doesn't actually speak
// of tokens. // of tokens.
//
// TODO(p): We probably want constructors like NewString, NewBool, NewArray, ...
type Object struct { type Object struct {
Kind ObjectKind Kind ObjectKind
// End (error message), Comment/Keyword/Name/String String string // Comment/Keyword/Name/String
String string Number float64 // Bool, Numeric
// Bool, Numeric Array []Object // Array, Indirect
Number float64 Dict map[string]Object // Dict, in the future also Stream
// Array, Indirect N, Generation uint // Indirect, Reference
Array []Object
// Dict, in the future also Stream
Dict map[string]Object
// Indirect, Reference
N, Generation uint
} }
// IsInteger checks if the PDF object is an integer number. // IsInteger checks if the PDF object is an integer number.
@ -93,6 +86,48 @@ func (o *Object) IsUint() bool {
return o.IsInteger() && o.Number >= 0 && o.Number <= float64(^uint(0)) return o.IsInteger() && o.Number >= 0 && o.Number <= float64(^uint(0))
} }
// A slew of constructors that will hopefully get all inlined.
// New returns a new Object of the given kind, with default values.
func New(kind ObjectKind) Object { return Object{Kind: kind} }
func NewComment(c string) Object { return Object{Kind: Comment, String: c} }
func NewKeyword(k string) Object { return Object{Kind: Keyword, String: k} }
func NewBool(b bool) Object {
var b64 float64
if b {
b64 = 1
}
return Object{Kind: Bool, Number: b64}
}
func NewNumeric(n float64) Object { return Object{Kind: Numeric, Number: n} }
func NewName(n string) Object { return Object{Kind: Name, String: n} }
func NewString(s string) Object { return Object{Kind: String, String: s} }
func NewArray(a []Object) Object {
return Object{Kind: Array, Array: a}
}
func NewDict(d map[string]Object) Object {
if d == nil {
d = make(map[string]Object)
}
return Object{Kind: Dict, Dict: d}
}
func NewIndirect(o Object, n, generation uint) Object {
return Object{Kind: Indirect, N: n, Generation: generation,
Array: []Object{o}}
}
func NewReference(n, generation uint) Object {
return Object{Kind: Reference, N: n, Generation: generation}
}
func newError(msg string) (Object, error) { return New(End), errors.New(msg) }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
const ( const (
@ -165,13 +200,13 @@ func (lex *Lexer) unescape(ch byte) byte {
return ch return ch
} }
func (lex *Lexer) string() Object { func (lex *Lexer) string() (Object, error) {
var value []byte var value []byte
parens := 1 parens := 1
for { for {
ch, ok := lex.read() ch, ok := lex.read()
if !ok { if !ok {
return Object{Kind: End, String: "unexpected end of string"} return newError("unexpected end of string")
} }
if lex.eatNewline(ch) { if lex.eatNewline(ch) {
ch = '\n' ch = '\n'
@ -183,7 +218,7 @@ func (lex *Lexer) string() Object {
} }
} else if ch == '\\' { } else if ch == '\\' {
if ch, ok = lex.read(); !ok { if ch, ok = lex.read(); !ok {
return Object{Kind: End, String: "unexpected end of string"} return newError("unexpected end of string")
} else if lex.eatNewline(ch) { } else if lex.eatNewline(ch) {
continue continue
} else { } else {
@ -192,19 +227,19 @@ func (lex *Lexer) string() Object {
} }
value = append(value, ch) value = append(value, ch)
} }
return Object{Kind: String, String: string(value)} return NewString(string(value)), nil
} }
func (lex *Lexer) stringHex() Object { func (lex *Lexer) stringHex() (Object, error) {
var value, buf []byte var value, buf []byte
for { for {
ch, ok := lex.read() ch, ok := lex.read()
if !ok { if !ok {
return Object{Kind: End, String: "unexpected end of hex string"} return newError("unexpected end of hex string")
} else if ch == '>' { } else if ch == '>' {
break break
} else if strings.IndexByte(hexAlphabet, ch) < 0 { } else if strings.IndexByte(hexAlphabet, ch) < 0 {
return Object{Kind: End, String: "invalid hex string"} return newError("invalid hex string")
} else if buf = append(buf, ch); len(buf) == 2 { } else if buf = append(buf, ch); len(buf) == 2 {
u, _ := strconv.ParseUint(string(buf), 16, 8) u, _ := strconv.ParseUint(string(buf), 16, 8)
value = append(value, byte(u)) value = append(value, byte(u))
@ -215,10 +250,10 @@ func (lex *Lexer) stringHex() Object {
u, _ := strconv.ParseUint(string(buf)+"0", 16, 8) u, _ := strconv.ParseUint(string(buf)+"0", 16, 8)
value = append(value, byte(u)) value = append(value, byte(u))
} }
return Object{Kind: String, String: string(value)} return NewString(string(value)), nil
} }
func (lex *Lexer) name() Object { func (lex *Lexer) name() (Object, error) {
var value []byte var value []byte
for { for {
ch, ok := lex.peek() ch, ok := lex.peek()
@ -237,7 +272,7 @@ func (lex *Lexer) name() Object {
lex.read() lex.read()
} }
if len(hexa) != 2 { if len(hexa) != 2 {
return Object{Kind: End, String: "invalid name hexa escape"} return newError("invalid name hexa escape")
} }
u, _ := strconv.ParseUint(string(value), 16, 8) u, _ := strconv.ParseUint(string(value), 16, 8)
ch = byte(u) ch = byte(u)
@ -245,12 +280,12 @@ func (lex *Lexer) name() Object {
value = append(value, ch) value = append(value, ch)
} }
if len(value) == 0 { if len(value) == 0 {
return Object{Kind: End, String: "unexpected end of name"} return newError("unexpected end of name")
} }
return Object{Kind: Name, String: string(value)} return NewName(string(value)), nil
} }
func (lex *Lexer) comment() Object { func (lex *Lexer) comment() (Object, error) {
var value []byte var value []byte
for { for {
ch, ok := lex.peek() ch, ok := lex.peek()
@ -260,11 +295,11 @@ func (lex *Lexer) comment() Object {
value = append(value, ch) value = append(value, ch)
lex.read() lex.read()
} }
return Object{Kind: Comment, String: string(value)} return NewComment(string(value)), nil
} }
// XXX: Maybe invalid numbers should rather be interpreted as keywords. // XXX: Maybe invalid numbers should rather be interpreted as keywords.
func (lex *Lexer) number() Object { func (lex *Lexer) number() (Object, error) {
var value []byte var value []byte
ch, ok := lex.peek() ch, ok := lex.peek()
if ch == '-' { if ch == '-' {
@ -287,16 +322,16 @@ func (lex *Lexer) number() Object {
lex.read() lex.read()
} }
if !digits { if !digits {
return Object{Kind: End, String: "invalid number"} return newError("invalid number")
} }
f, _ := strconv.ParseFloat(string(value), 64) f, _ := strconv.ParseFloat(string(value), 64)
return Object{Kind: Numeric, Number: f} return NewNumeric(f), nil
} }
func (lex *Lexer) Next() Object { func (lex *Lexer) Next() (Object, error) {
ch, ok := lex.peek() ch, ok := lex.peek()
if !ok { if !ok {
return Object{Kind: End} return New(End), nil
} }
if strings.IndexByte("-0123456789.", ch) >= 0 { if strings.IndexByte("-0123456789.", ch) >= 0 {
return lex.number() return lex.number()
@ -315,13 +350,13 @@ func (lex *Lexer) Next() Object {
switch v := string(value); v { switch v := string(value); v {
case "": case "":
case "null": case "null":
return Object{Kind: Nil} return New(Nil), nil
case "true": case "true":
return Object{Kind: Bool, Number: 1} return NewBool(true), nil
case "false": case "false":
return Object{Kind: Bool, Number: 0} return NewBool(false), nil
default: default:
return Object{Kind: Keyword, String: v} return NewKeyword(v), nil
} }
switch ch, _ := lex.read(); ch { switch ch, _ := lex.read(); ch {
@ -332,29 +367,29 @@ func (lex *Lexer) Next() Object {
case '(': case '(':
return lex.string() return lex.string()
case '[': case '[':
return Object{Kind: BArray} return New(BArray), nil
case ']': case ']':
return Object{Kind: EArray} return New(EArray), nil
case '<': case '<':
if ch, _ := lex.peek(); ch == '<' { if ch, _ := lex.peek(); ch == '<' {
lex.read() lex.read()
return Object{Kind: BDict} return New(BDict), nil
} }
return lex.stringHex() return lex.stringHex()
case '>': case '>':
if ch, _ := lex.peek(); ch == '>' { if ch, _ := lex.peek(); ch == '>' {
lex.read() lex.read()
return Object{Kind: EDict} return New(EDict), nil
} }
return Object{Kind: End, String: "unexpected '>'"} return newError("unexpected '>'")
default: default:
if lex.eatNewline(ch) { if lex.eatNewline(ch) {
return Object{Kind: NL} return New(NL), nil
} }
if strings.IndexByte(whitespace, ch) >= 0 { if strings.IndexByte(whitespace, ch) >= 0 {
return lex.Next() return lex.Next()
} }
return Object{Kind: End, String: "unexpected input"} return newError("unexpected input")
} }
} }
@ -461,10 +496,10 @@ type Updater struct {
Trailer map[string]Object Trailer map[string]Object
} }
func (u *Updater) parseIndirect(lex *Lexer, stack *[]Object) Object { func (u *Updater) parseIndirect(lex *Lexer, stack *[]Object) (Object, error) {
lenStack := len(*stack) lenStack := len(*stack)
if lenStack < 2 { if lenStack < 2 {
return Object{Kind: End, String: "missing object ID pair"} return newError("missing object ID pair")
} }
n := (*stack)[lenStack-2] n := (*stack)[lenStack-2]
@ -472,28 +507,30 @@ func (u *Updater) parseIndirect(lex *Lexer, stack *[]Object) Object {
*stack = (*stack)[:lenStack-2] *stack = (*stack)[:lenStack-2]
if !g.IsUint() || !n.IsUint() { if !g.IsUint() || !n.IsUint() {
return Object{Kind: End, String: "invalid object ID pair"} return newError("invalid object ID pair")
} }
obj := Object{ var inner []Object
Kind: Indirect, N: uint(n.Number), Generation: uint(g.Number)}
for { for {
object := u.parse(lex, &obj.Array) object, _ := u.parse(lex, &inner)
if object.Kind == End { if object.Kind == End {
return Object{Kind: End, String: "object doesn't end"} return newError("object doesn't end")
} }
if object.Kind == Keyword && object.String == "endobj" { if object.Kind == Keyword && object.String == "endobj" {
break break
} }
obj.Array = append(obj.Array, object) inner = append(inner, object)
} }
return obj if len(inner) != 1 {
return newError("indirect objects must contain exactly one object")
}
return NewIndirect(inner[0], uint(n.Number), uint(g.Number)), nil
} }
func (u *Updater) parseR(stack *[]Object) Object { func (u *Updater) parseR(stack *[]Object) (Object, error) {
lenStack := len(*stack) lenStack := len(*stack)
if lenStack < 2 { if lenStack < 2 {
return Object{Kind: End, String: "missing reference ID pair"} return newError("missing reference ID pair")
} }
n := (*stack)[lenStack-2] n := (*stack)[lenStack-2]
@ -501,15 +538,16 @@ func (u *Updater) parseR(stack *[]Object) Object {
*stack = (*stack)[:lenStack-2] *stack = (*stack)[:lenStack-2]
if !g.IsUint() || !n.IsUint() { if !g.IsUint() || !n.IsUint() {
return Object{Kind: End, String: "invalid reference ID pair"} return newError("invalid reference ID pair")
} }
return Object{ return NewReference(uint(n.Number), uint(g.Number)), nil
Kind: Reference, N: uint(n.Number), Generation: uint(g.Number)}
} }
/// parse reads an object at the lexer's position. Not a strict parser. // parse reads an object at the lexer's position. Not a strict parser.
func (u *Updater) parse(lex *Lexer, stack *[]Object) Object { //
switch token := lex.Next(); token.Kind { // TODO(p): We should fix all uses of this not to eat the error.
func (u *Updater) parse(lex *Lexer, stack *[]Object) (Object, error) {
switch token, err := lex.Next(); token.Kind {
case NL, Comment: case NL, Comment:
// These are not important to parsing, // These are not important to parsing,
// not even for this procedure's needs. // not even for this procedure's needs.
@ -517,22 +555,22 @@ func (u *Updater) parse(lex *Lexer, stack *[]Object) Object {
case BArray: case BArray:
var array []Object var array []Object
for { for {
object := u.parse(lex, &array) object, _ := u.parse(lex, &array)
if object.Kind == End { if object.Kind == End {
return Object{Kind: End, String: "array doesn't end"} return newError("array doesn't end")
} }
if object.Kind == EArray { if object.Kind == EArray {
break break
} }
array = append(array, object) array = append(array, object)
} }
return Object{Kind: Array, Array: array} return NewArray(array), nil
case BDict: case BDict:
var array []Object var array []Object
for { for {
object := u.parse(lex, &array) object, _ := u.parse(lex, &array)
if object.Kind == End { if object.Kind == End {
return Object{Kind: End, String: "dictionary doesn't end"} return newError("dictionary doesn't end")
} }
if object.Kind == EDict { if object.Kind == EDict {
break break
@ -540,17 +578,16 @@ func (u *Updater) parse(lex *Lexer, stack *[]Object) Object {
array = append(array, object) array = append(array, object)
} }
if len(array)%2 != 0 { if len(array)%2 != 0 {
return Object{Kind: End, String: "unbalanced dictionary"} return newError("unbalanced dictionary")
} }
dict := make(map[string]Object) dict := make(map[string]Object)
for i := 0; i < len(array); i += 2 { for i := 0; i < len(array); i += 2 {
if array[i].Kind != Name { if array[i].Kind != Name {
return Object{ return newError("invalid dictionary key type")
Kind: End, String: "invalid dictionary key type"}
} }
dict[array[i].String] = array[i+1] dict[array[i].String] = array[i+1]
} }
return Object{Kind: Dict, Dict: dict} return NewDict(dict), nil
case Keyword: case Keyword:
// Appears in the document body, typically needs // Appears in the document body, typically needs
// to access the cross-reference table. // to access the cross-reference table.
@ -560,7 +597,7 @@ func (u *Updater) parse(lex *Lexer, stack *[]Object) Object {
// streams can use the Object.String member. // streams can use the Object.String member.
switch token.String { switch token.String {
case "stream": case "stream":
return Object{Kind: End, String: "streams are not supported yet"} return newError("streams are not supported yet")
case "obj": case "obj":
return u.parseIndirect(lex, stack) return u.parseIndirect(lex, stack)
case "R": case "R":
@ -568,18 +605,18 @@ func (u *Updater) parse(lex *Lexer, stack *[]Object) Object {
} }
fallthrough fallthrough
default: default:
return token return token, err
} }
} }
func (u *Updater) loadXref(lex *Lexer, loadedEntries map[uint]struct{}) error { func (u *Updater) loadXref(lex *Lexer, loadedEntries map[uint]struct{}) error {
var throwawayStack []Object var throwawayStack []Object
if keyword := u.parse(lex, if keyword, _ := u.parse(lex,
&throwawayStack); keyword.Kind != Keyword || keyword.String != "xref" { &throwawayStack); keyword.Kind != Keyword || keyword.String != "xref" {
return errors.New("invalid xref table") return errors.New("invalid xref table")
} }
for { for {
object := u.parse(lex, &throwawayStack) object, _ := u.parse(lex, &throwawayStack)
if object.Kind == End { if object.Kind == End {
return errors.New("unexpected EOF while looking for the trailer") return errors.New("unexpected EOF while looking for the trailer")
} }
@ -587,16 +624,16 @@ func (u *Updater) loadXref(lex *Lexer, loadedEntries map[uint]struct{}) error {
break break
} }
second := u.parse(lex, &throwawayStack) second, _ := u.parse(lex, &throwawayStack)
if !object.IsUint() || !second.IsUint() { if !object.IsUint() || !second.IsUint() {
return errors.New("invalid xref section header") return errors.New("invalid xref section header")
} }
start, count := uint(object.Number), uint(second.Number) start, count := uint(object.Number), uint(second.Number)
for i := uint(0); i < count; i++ { for i := uint(0); i < count; i++ {
off := u.parse(lex, &throwawayStack) off, _ := u.parse(lex, &throwawayStack)
gen := u.parse(lex, &throwawayStack) gen, _ := u.parse(lex, &throwawayStack)
key := u.parse(lex, &throwawayStack) key, _ := u.parse(lex, &throwawayStack)
if !off.IsInteger() || off.Number < 0 || if !off.IsInteger() || off.Number < 0 ||
off.Number > float64(len(u.Document)) || off.Number > float64(len(u.Document)) ||
!gen.IsInteger() || gen.Number < 0 || gen.Number > 65535 || !gen.IsInteger() || gen.Number < 0 || gen.Number > 65535 ||
@ -670,7 +707,7 @@ func (u *Updater) Initialize() error {
return err return err
} }
trailer := u.parse(&lex, &throwawayStack) trailer, _ := u.parse(&lex, &throwawayStack)
if trailer.Kind != Dict { if trailer.Kind != Dict {
return errors.New("invalid trailer dictionary") return errors.New("invalid trailer dictionary")
} }
@ -690,8 +727,7 @@ func (u *Updater) Initialize() error {
xrefOffset = int64(prevOffset.Number) xrefOffset = int64(prevOffset.Number)
} }
u.Trailer["Prev"] = Object{ u.Trailer["Prev"] = NewNumeric(float64(lastXrefOffset))
Kind: Numeric, Number: float64(lastXrefOffset)}
lastSize, ok := u.Trailer["Size"] lastSize, ok := u.Trailer["Size"]
if !ok || !lastSize.IsInteger() || lastSize.Number <= 0 { if !ok || !lastSize.IsInteger() || lastSize.Number <= 0 {
@ -703,30 +739,32 @@ func (u *Updater) Initialize() error {
// Get retrieves an object by its number and generation--may return // Get retrieves an object by its number and generation--may return
// Nil or End with an error. // Nil or End with an error.
func (u *Updater) Get(n, generation uint) Object { //
// TODO(p): We should fix all uses of this not to eat the error.
func (u *Updater) Get(n, generation uint) (Object, error) {
if n >= u.xrefSize { if n >= u.xrefSize {
return Object{Kind: Nil} return New(Nil), nil
} }
ref := u.xref[n] ref := u.xref[n]
if !ref.nonfree || ref.generation != generation || if !ref.nonfree || ref.generation != generation ||
ref.offset >= int64(len(u.Document)) { ref.offset >= int64(len(u.Document)) {
return Object{Kind: Nil} return New(Nil), nil
} }
lex := Lexer{u.Document[ref.offset:]} lex := Lexer{u.Document[ref.offset:]}
var stack []Object var stack []Object
for { for {
object := u.parse(&lex, &stack) object, err := u.parse(&lex, &stack)
if object.Kind == End { if object.Kind == End {
return object return object, err
} }
if object.Kind != Indirect { if object.Kind != Indirect {
stack = append(stack, object) stack = append(stack, object)
} else if object.N != n || object.Generation != generation { } else if object.N != n || object.Generation != generation {
return Object{Kind: End, String: "object mismatch"} return newError("object mismatch")
} else { } else {
return object.Array[0] return object.Array[0], nil
} }
} }
} }
@ -819,8 +857,8 @@ func (u *Updater) FlushUpdates() {
} }
} }
u.Trailer["Size"] = Object{Kind: Numeric, Number: float64(u.xrefSize)} u.Trailer["Size"] = NewNumeric(float64(u.xrefSize))
trailer := Object{Kind: Dict, Dict: u.Trailer} trailer := NewDict(u.Trailer)
fmt.Fprintf(buf, "trailer\n%s\nstartxref\n%d\n%%%%EOF\n", fmt.Fprintf(buf, "trailer\n%s\nstartxref\n%d\n%%%%EOF\n",
trailer.Serialize(), startXref) trailer.Serialize(), startXref)
@ -829,8 +867,8 @@ func (u *Updater) FlushUpdates() {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// PdfDate makes a PDF object representing the given point in time. // NewDate makes a PDF object representing the given point in time.
func PdfDate(ts time.Time) Object { func NewDate(ts time.Time) Object {
buf := ts.AppendFormat(nil, "D:20060102150405") buf := ts.AppendFormat(nil, "D:20060102150405")
// "Z07'00'" doesn't work, we need to do some of it manually. // "Z07'00'" doesn't work, we need to do some of it manually.
if _, offset := ts.Zone(); offset != 0 { if _, offset := ts.Zone(); offset != 0 {
@ -839,14 +877,14 @@ func PdfDate(ts time.Time) Object {
} else { } else {
buf = append(buf, 'Z') buf = append(buf, 'Z')
} }
return Object{Kind: String, String: string(buf)} return NewString(string(buf))
} }
// PdfGetFirstPage retrieves the first page of the document or a Nil object. // GetFirstPage retrieves the first page of the document or a Nil object.
func PdfGetFirstPage(pdf *Updater, nodeN, nodeGeneration uint) Object { func (u *Updater) GetFirstPage(nodeN, nodeGeneration uint) Object {
obj := pdf.Get(nodeN, nodeGeneration) obj, _ := u.Get(nodeN, nodeGeneration)
if obj.Kind != Dict { if obj.Kind != Dict {
return Object{Kind: Nil} return New(Nil)
} }
// Out of convenience; these aren't filled normally. // Out of convenience; these aren't filled normally.
@ -854,11 +892,11 @@ func PdfGetFirstPage(pdf *Updater, nodeN, nodeGeneration uint) Object {
obj.Generation = nodeGeneration obj.Generation = nodeGeneration
if typ, ok := obj.Dict["Type"]; !ok || typ.Kind != Name { if typ, ok := obj.Dict["Type"]; !ok || typ.Kind != Name {
return Object{Kind: Nil} return New(Nil)
} else if typ.String == "Page" { } else if typ.String == "Page" {
return obj return obj
} else if typ.String != "Pages" { } else if typ.String != "Pages" {
return Object{Kind: Nil} return New(Nil)
} }
// XXX: Technically speaking, this may be an indirect reference. // XXX: Technically speaking, this may be an indirect reference.
@ -867,11 +905,11 @@ func PdfGetFirstPage(pdf *Updater, nodeN, nodeGeneration uint) Object {
kids, ok := obj.Dict["Kids"] kids, ok := obj.Dict["Kids"]
if !ok || kids.Kind != Array || len(kids.Array) == 0 || if !ok || kids.Kind != Array || len(kids.Array) == 0 ||
kids.Array[0].Kind != Reference { kids.Array[0].Kind != Reference {
return Object{Kind: Nil} return New(Nil)
} }
// XXX: Nothing prevents us from recursing in an evil circular graph. // XXX: Nothing prevents us from recursing in an evil circular graph.
return PdfGetFirstPage(pdf, kids.Array[0].N, kids.Array[0].Generation) return u.GetFirstPage(kids.Array[0].N, kids.Array[0].Generation)
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -968,8 +1006,9 @@ func PKCS12Parse(p12 []byte, password string) (
// FillInSignature signs PDF contents and writes the signature into the given // FillInSignature signs PDF contents and writes the signature into the given
// window that has been reserved for this specific purpose. // window that has been reserved for this specific purpose.
// This is a very low-level function.
func FillInSignature(document []byte, signOff, signLen int, func FillInSignature(document []byte, signOff, signLen int,
key crypto.PublicKey, certs []*x509.Certificate) error { key crypto.PrivateKey, certs []*x509.Certificate) error {
if signOff < 0 || signOff > len(document) || if signOff < 0 || signOff > len(document) ||
signLen < 2 || signOff+signLen > len(document) { signLen < 2 || signOff+signLen > len(document) {
return errors.New("invalid signing window") return errors.New("invalid signing window")
@ -1039,7 +1078,13 @@ func FillInSignature(document []byte, signOff, signLen int,
return nil return nil
} }
// https://www.adobe.com/devnet-docs/acrobatetk/tools/DigSig/Acrobat_DigitalSignatures_in_PDF.pdf
// https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf
// https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PPKAppearances.pdf
// Sign signs the given document, growing and returning the passed-in slice. // Sign signs the given document, growing and returning the passed-in slice.
// There must be at least one certificate, matching the private key.
// The certificates must form a chain.
// //
// The presumption here is that the document is valid and that it doesn't // The presumption here is that the document is valid and that it doesn't
// employ cross-reference streams from PDF 1.5, or at least constitutes // employ cross-reference streams from PDF 1.5, or at least constitutes
@ -1048,12 +1093,8 @@ func FillInSignature(document []byte, signOff, signLen int,
// //
// Carelessly assumes that the version of the original document is at most // Carelessly assumes that the version of the original document is at most
// PDF 1.6. // PDF 1.6.
// func Sign(document []byte,
// https://www.adobe.com/devnet-docs/acrobatetk/tools/DigSig/Acrobat_DigitalSignatures_in_PDF.pdf key crypto.PrivateKey, certs []*x509.Certificate) ([]byte, error) {
// https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf
// https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PPKAppearances.pdf
func Sign(document []byte, key crypto.PublicKey, certs []*x509.Certificate) (
[]byte, error) {
pdf := &Updater{Document: document} pdf := &Updater{Document: document}
if err := pdf.Initialize(); err != nil { if err := pdf.Initialize(); err != nil {
return nil, err return nil, err
@ -1063,7 +1104,7 @@ func Sign(document []byte, key crypto.PublicKey, certs []*x509.Certificate) (
if !ok || rootRef.Kind != Reference { if !ok || rootRef.Kind != Reference {
return nil, errors.New("trailer does not contain a reference to Root") return nil, errors.New("trailer does not contain a reference to Root")
} }
root := pdf.Get(rootRef.N, rootRef.Generation) root, _ := pdf.Get(rootRef.N, rootRef.Generation)
if root.Kind != Dict { if root.Kind != Dict {
return nil, errors.New("invalid Root dictionary reference") return nil, errors.New("invalid Root dictionary reference")
} }
@ -1074,7 +1115,7 @@ func Sign(document []byte, key crypto.PublicKey, certs []*x509.Certificate) (
pdf.Update(sigdictN, func(buf BytesWriter) { pdf.Update(sigdictN, func(buf BytesWriter) {
// The timestamp is important for Adobe Acrobat Reader DC. // The timestamp is important for Adobe Acrobat Reader DC.
// The ideal would be to use RFC 3161. // The ideal would be to use RFC 3161.
now := PdfDate(time.Now()) now := NewDate(time.Now())
buf.WriteString("<< /Type/Sig /Filter/Adobe.PPKLite" + buf.WriteString("<< /Type/Sig /Filter/Adobe.PPKLite" +
" /SubFilter/adbe.pkcs7.detached\n" + " /SubFilter/adbe.pkcs7.detached\n" +
" /M" + now.Serialize() + " /ByteRange ") " /M" + now.Serialize() + " /ByteRange ")
@ -1094,22 +1135,19 @@ func Sign(document []byte, key crypto.PublicKey, certs []*x509.Certificate) (
signLen += 2 signLen += 2
}) })
sigfield := Object{Kind: Dict, Dict: map[string]Object{ sigfield := NewDict(map[string]Object{
// 8.6.3 Field Types - Signature Fields // 8.6.3 Field Types - Signature Fields
"FT": {Kind: Name, String: "Sig"}, "FT": NewName("Sig"),
"V": {Kind: Reference, N: sigdictN, Generation: 0}, "V": NewReference(sigdictN, 0),
// 8.4.5 Annotations Types - Widget Annotations // 8.4.5 Annotations Types - Widget Annotations
// We can merge the Signature Annotation and omit Kids here. // We can merge the Signature Annotation and omit Kids here.
"Subtype": {Kind: Name, String: "Widget"}, "Subtype": NewName("Widget"),
"F": {Kind: Numeric, Number: 2 /* Hidden */}, "F": NewNumeric(2 /* Hidden */),
"T": {Kind: String, String: "Signature1"}, "T": NewString("Signature1"),
"Rect": {Kind: Array, Array: []Object{ "Rect": NewArray([]Object{
{Kind: Numeric, Number: 0}, NewNumeric(0), NewNumeric(0), NewNumeric(0), NewNumeric(0),
{Kind: Numeric, Number: 0}, }),
{Kind: Numeric, Number: 0}, })
{Kind: Numeric, Number: 0},
}},
}}
sigfieldN := pdf.Allocate() sigfieldN := pdf.Allocate()
pdf.Update(sigfieldN, func(buf BytesWriter) { pdf.Update(sigfieldN, func(buf BytesWriter) {
@ -1120,7 +1158,7 @@ func Sign(document []byte, key crypto.PublicKey, certs []*x509.Certificate) (
if !ok || pagesRef.Kind != Reference { if !ok || pagesRef.Kind != Reference {
return nil, errors.New("invalid Pages reference") return nil, errors.New("invalid Pages reference")
} }
page := PdfGetFirstPage(pdf, pagesRef.N, pagesRef.Generation) page := pdf.GetFirstPage(pagesRef.N, pagesRef.Generation)
if page.Kind != Dict { if page.Kind != Dict {
return nil, errors.New("invalid or unsupported page tree") return nil, errors.New("invalid or unsupported page tree")
} }
@ -1128,10 +1166,9 @@ func Sign(document []byte, key crypto.PublicKey, certs []*x509.Certificate) (
// XXX: Assuming this won't be an indirectly referenced array. // XXX: Assuming this won't be an indirectly referenced array.
annots := page.Dict["Annots"] annots := page.Dict["Annots"]
if annots.Kind != Array { if annots.Kind != Array {
annots = Object{Kind: Array} annots = NewArray(nil)
} }
annots.Array = append(annots.Array, Object{ annots.Array = append(annots.Array, NewReference(sigfieldN, 0))
Kind: Reference, N: sigfieldN, Generation: 0})
page.Dict["Annots"] = annots page.Dict["Annots"] = annots
pdf.Update(page.N, func(buf BytesWriter) { pdf.Update(page.N, func(buf BytesWriter) {
@ -1140,19 +1177,16 @@ func Sign(document []byte, key crypto.PublicKey, certs []*x509.Certificate) (
// 8.6.1 Interactive Form Dictionary // 8.6.1 Interactive Form Dictionary
// XXX: Assuming there are no forms already, overwriting everything. // XXX: Assuming there are no forms already, overwriting everything.
root.Dict["AcroForm"] = Object{Kind: Dict, Dict: map[string]Object{ root.Dict["AcroForm"] = NewDict(map[string]Object{
"Fields": {Kind: Array, Array: []Object{ "Fields": NewArray([]Object{NewReference(sigfieldN, 0)}),
{Kind: Reference, N: sigfieldN, Generation: 0}, "SigFlags": NewNumeric(3 /* SignaturesExist | AppendOnly */),
}}, })
"SigFlags": {Kind: Numeric,
Number: 3 /* SignaturesExist | AppendOnly */},
}}
// Upgrade the document version for SHA-256 etc. // Upgrade the document version for SHA-256 etc.
// XXX: Assuming that it's not newer than 1.6 already--while Cairo can't // XXX: Assuming that it's not newer than 1.6 already--while Cairo can't
// currently use a newer version that 1.5, it's not a bad idea to use // currently use a newer version that 1.5, it's not a bad idea to use
// cairo_pdf_surface_restrict_to_version(). // cairo_pdf_surface_restrict_to_version().
root.Dict["Version"] = Object{Kind: Name, String: "1.6"} root.Dict["Version"] = NewName("1.6")
pdf.Update(rootRef.N, func(buf BytesWriter) { pdf.Update(rootRef.N, func(buf BytesWriter) {
buf.WriteString(root.Serialize()) buf.WriteString(root.Serialize())
}) })