Class: Origami::PDF
- Inherits:
-
Object
- Object
- Origami::PDF
- Defined in:
- lib/origami/pdf.rb,
lib/origami/xfa.rb,
lib/origami/page.rb,
lib/origami/header.rb,
lib/origami/actions.rb,
lib/origami/catalog.rb,
lib/origami/trailer.rb,
lib/origami/acroform.rb,
lib/origami/filespec.rb,
lib/origami/metadata.rb,
lib/origami/signature.rb,
lib/origami/xreftable.rb,
lib/origami/encryption.rb,
lib/origami/collections.rb,
lib/origami/parsers/pdf.rb,
lib/origami/destinations.rb,
lib/origami/linearization.rb,
lib/origami/outputintents.rb,
lib/origami/parsers/pdf/lazy.rb,
lib/origami/parsers/pdf/linear.rb
Overview
Main class representing a PDF file and its inner contents. A PDF file contains a set of Revision.
Defined Under Namespace
Classes: Header, Instruction, InvalidHeaderError, LazyParser, LinearParser, LinearizationError, Parser, Revision, SignatureError
Instance Attribute Summary collapse
-
#header ⇒ Object
Document header and revisions.
-
#revisions ⇒ Object
Document header and revisions.
Class Method Summary collapse
-
.create(output, options = {}) {|pdf| ... } ⇒ Object
(also: write)
Creates a new PDF and saves it.
-
.read(path, options = {}) ⇒ Object
Reads and parses a PDF file from disk.
Instance Method Summary collapse
-
#<<(object) ⇒ Object
(also: #insert)
Adds a new object to the PDF file.
-
#add_fields(*fields) ⇒ Object
Add a field to the Acrobat form.
-
#add_new_revision ⇒ Object
Ends the current Revision, and starts a new one.
-
#add_to_revision(object, revision) ⇒ Object
Adds a new object to a specific revision.
-
#allocate_new_object_number ⇒ Object
Returns a new number/generation for future object.
-
#append_page(page = Page.new) {|page| ... } ⇒ Object
Appends a page or list of pages to the end of the page tree.
-
#attach_file(path, register: true, name: nil, filter: :FlateDecode) ⇒ Object
Attachs an embedded file to the PDF.
- #author ⇒ Object
-
#cast_object(reference, type) ⇒ Object
Casts a PDF object into another object type.
-
#Catalog ⇒ Object
Returns the current Catalog Dictionary.
-
#Catalog=(cat) ⇒ Object
Sets the current Catalog Dictionary.
-
#create_form(*fields) ⇒ Object
Creates a new AcroForm with specified fields.
-
#create_metadata(info = {}) ⇒ Object
Modifies or creates a metadata stream.
- #create_xfa_form(xdp, *fields) ⇒ Object
- #creation_date ⇒ Object
- #creator ⇒ Object
-
#decrypt(passwd = "") ⇒ Object
Decrypts the current document (only RC4 40..128 bits).
-
#delete_object(no, generation = 0) ⇒ Object
Remove an object.
-
#delinearize! ⇒ Object
Tries to delinearize the document if it has been linearized.
-
#document_info ⇒ Object
Returns the document information dictionary if present.
-
#document_info? ⇒ Boolean
Returns true if the document has a document information dictionary.
-
#each_field ⇒ Object
Iterates over each Acroform Field.
-
#each_name(root, &block) ⇒ Object
Returns an Enumerator of all names under the specified root name directory.
-
#each_named_dest(&b) ⇒ Object
Calls block for each named destination.
-
#each_named_embedded_file(&b) ⇒ Object
(also: #each_attachment)
Calls block for each named embedded file.
-
#each_named_page(&b) ⇒ Object
Calls block for each named page.
-
#each_named_script(&b) ⇒ Object
Calls block for each named JavaScript script.
-
#each_object(compressed: false, recursive: false, &block) ⇒ Object
Iterates over the objects of the document.
-
#each_page(&b) ⇒ Object
Iterate through each page, returns self.
-
#enable_usage_rights(cert, pkey, *rights) ⇒ Object
Enable the document Usage Rights.
-
#encrypt(options = {}) ⇒ Object
Encrypts the current document with the provided passwords.
-
#encrypted? ⇒ Boolean
Returns whether the PDF file is encrypted.
-
#fields ⇒ Object
Returns an Array of Acroform fields.
-
#form? ⇒ Boolean
Returns true if the document contains an acrobat form.
-
#get_destination_by_name(name) ⇒ Object
Lookup destination in the destination name directory.
-
#get_embedded_file_by_name(name) ⇒ Object
Lookup embedded file in the embedded files name directory.
-
#get_field(name) ⇒ Object
Returns the corresponding named Field.
-
#get_object(no, generation = 0, use_xrefstm: true) ⇒ Object
(also: #[])
Search for an indirect object in the document.
-
#get_object_by_offset(offset) ⇒ Object
Looking for an object present at a specified file offset.
-
#get_page(n) ⇒ Object
Get the n-th Page object.
-
#get_page_by_name(name) ⇒ Object
Lookup page in the page name directory.
-
#get_script_by_name(name) ⇒ Object
Lookup script in the scripts name directory.
-
#grep(pattern, streams: true, object_streams: true) ⇒ Object
Returns an array of strings, names and streams matching the given pattern.
-
#import(object) ⇒ Object
Similar to PDF#insert or PDF#<<, but for an object belonging to another document.
-
#indirect_objects ⇒ Object
(also: #root_objects)
Return an array of indirect objects.
-
#initialize(parser = nil) ⇒ PDF
constructor
Creates a new PDF instance.
-
#insert_page(index, page = Page.new) {|page| ... } ⇒ Object
Inserts a page at position index into the document.
- #keywords ⇒ Object
-
#linearized? ⇒ Boolean
Returns whether the current document is linearized.
-
#loaded! ⇒ Object
Mark the document as complete.
-
#ls(pattern, follow_references: true) ⇒ Object
Returns an array of Objects whose name (in a Dictionary) is matching pattern.
-
#metadata ⇒ Object
Returns a Hash of the information found in the metadata stream.
-
#metadata? ⇒ Boolean
Returns true if the document has a catalog metadata stream.
- #mod_date ⇒ Object
-
#names(root) ⇒ Object
Returns a Hash of all names under the specified root name directory.
-
#onDocumentClose(action) ⇒ Object
Sets an action to run on document closing.
-
#onDocumentOpen(action) ⇒ Object
Sets an action to run on document opening.
-
#onDocumentPrint(action) ⇒ Object
Sets an action to run on document printing.
-
#original_data ⇒ Object
Original data parsed to create this document, nil if created from scratch.
-
#original_filename ⇒ Object
Original file name if parsed from disk, nil otherwise.
-
#original_filesize ⇒ Object
Original file size if parsed from a data stream, nil otherwise.
-
#pages ⇒ Object
Returns an Enumerator of Page.
- #pdfa1? ⇒ Boolean
-
#portfolio? ⇒ Boolean
Returns true if the document behaves as a portfolio for embedded files.
- #producer ⇒ Object
-
#register(root, name, value) ⇒ Object
Registers an object into a specific Names root dictionary.
-
#remove_revision(index) ⇒ Object
Removes a whole document revision.
-
#remove_xrefs ⇒ Object
Tries to strip any xrefs information off the document.
-
#resolve_name(root, name) ⇒ Object
Retrieve the corresponding value associated with name in the specified root name directory, or nil if the value does not exist.
-
#save(path, params = {}) ⇒ Object
(also: #write)
Saves the current document.
-
#save_upto(revision, filename) ⇒ Object
Saves the file up to given revision number.
-
#set_extension_level(version, level) ⇒ Object
Sets PDF extension level and version.
-
#sign(certificate, key, method: Signature::PKCS7_DETACHED, ca: [], annotation: nil, issuer: nil, location: nil, contact: nil, reason: nil) ⇒ Object
Sign the document with the given key and x509 certificate.
- #signature ⇒ Object
-
#signed? ⇒ Boolean
Returns whether the document contains a digital signature.
- #subject ⇒ Object
- #title ⇒ Object
-
#trailer ⇒ Object
Returns the current trailer.
- #usage_rights? ⇒ Boolean
-
#verify(trusted_certs: []) ⇒ Object
Verify a document signature.
- #xfa_form? ⇒ Boolean
Constructor Details
#initialize(parser = nil) ⇒ PDF
Creates a new PDF instance.
- parser
-
The Parser object creating the document. If none is specified, some default structures are automatically created to get a minimal working document.
156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/origami/pdf.rb', line 156 def initialize(parser = nil) @header = PDF::Header.new @revisions = [] @parser = parser @loaded = false add_new_revision @revisions.first.trailer = Trailer.new init if parser.nil? end |
Instance Attribute Details
#header ⇒ Object
Document header and revisions.
120 121 122 |
# File 'lib/origami/pdf.rb', line 120 def header @header end |
#revisions ⇒ Object
Document header and revisions.
120 121 122 |
# File 'lib/origami/pdf.rb', line 120 def revisions @revisions end |
Class Method Details
.create(output, options = {}) {|pdf| ... } ⇒ Object Also known as: write
Creates a new PDF and saves it. If a block is passed, the PDF instance can be processed before saving.
143 144 145 146 147 |
# File 'lib/origami/pdf.rb', line 143 def create(output, = {}) pdf = PDF.new yield(pdf) if block_given? pdf.save(output, ) end |
.read(path, options = {}) ⇒ Object
Reads and parses a PDF file from disk.
126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/origami/pdf.rb', line 126 def read(path, = {}) path = File.(path) if path.is_a?(::String) lazy = [:lazy] if lazy parser_class = PDF::LazyParser else parser_class = PDF::LinearParser end parser_class.new().parse(path) end |
Instance Method Details
#<<(object) ⇒ Object Also known as: insert
Adds a new object to the PDF file. If this object has no version number, then a new one will be automatically computed and assignated to him.
It returns a Reference to this Object.
- object
-
The object to add.
317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/origami/pdf.rb', line 317 def <<(object) owner = object.document # # Does object belongs to another PDF ? # if owner and not owner.equal?(self) import object else add_to_revision(object, @revisions.last) end end |
#add_fields(*fields) ⇒ Object
Add a field to the Acrobat form.
- field
-
The Field to add.
46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/origami/acroform.rb', line 46 def add_fields(*fields) raise TypeError, "Expected Field arguments" unless fields.all? { |f| f.is_a?(Field) } self.Catalog.AcroForm ||= InteractiveForm.new.set_indirect(true) self.Catalog.AcroForm.Fields ||= [] self.Catalog.AcroForm.Fields.concat(fields) fields.each do |field| field.set_indirect(true) end self end |
#add_new_revision ⇒ Object
Ends the current Revision, and starts a new one.
364 365 366 367 368 369 370 371 372 |
# File 'lib/origami/pdf.rb', line 364 def add_new_revision root = @revisions.last.trailer[:Root] unless @revisions.empty? @revisions << Revision.new(self) @revisions.last.trailer = Trailer.new @revisions.last.trailer.Root = root self end |
#add_to_revision(object, revision) ⇒ Object
Adds a new object to a specific revision. If this object has no version number, then a new one will be automatically computed and assignated to him.
It returns a Reference to this Object.
- object
-
The object to add.
- revision
-
The revision to add the object to.
350 351 352 353 354 355 356 357 358 359 |
# File 'lib/origami/pdf.rb', line 350 def add_to_revision(object, revision) object.set_indirect(true) object.set_document(self) object.no, object.generation = allocate_new_object_number if object.no == 0 revision.body[object.reference] = object object.reference end |
#allocate_new_object_number ⇒ Object
Returns a new number/generation for future object.
519 520 521 522 523 524 525 526 527 528 529 |
# File 'lib/origami/pdf.rb', line 519 def allocate_new_object_number last_object = self.each_object(compressed: true).max_by {|object| object.no } if last_object.nil? no = 1 else no = last_object.no + 1 end [ no, 0 ] end |
#append_page(page = Page.new) {|page| ... } ⇒ Object
Appends a page or list of pages to the end of the page tree.
- page
-
The page to append to the document. Creates a new Page if not specified.
Pass the Page object if a block is present.
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/origami/page.rb', line 31 def append_page(page = Page.new) init_page_tree treeroot = self.Catalog.Pages treeroot.Kids ||= [] #:nodoc: treeroot.Kids.push(page) treeroot.Count ||= 0 treeroot.Count += 1 page.Parent = treeroot yield(page) if block_given? self end |
#attach_file(path, register: true, name: nil, filter: :FlateDecode) ⇒ Object
Attachs an embedded file to the PDF.
- path
-
The path to the file to attach.
- register
-
Whether the file shall be registered in the name directory.
- name
-
The embedded file name of the attachment.
- filter
-
The stream filter used to store the file contents.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/origami/filespec.rb', line 32 def attach_file(path, register: true, name: nil, filter: :FlateDecode) if path.is_a? FileSpec filespec = path name ||= '' else if path.respond_to?(:read) data = path.read.force_encoding('binary') name ||= '' else data = File.binread(File.(path)) name ||= File.basename(path) end fstream = EmbeddedFileStream.new fstream.data = data fstream.Filter = filter filespec = FileSpec.new(:F => fstream) end fspec = FileSpec.new.setType(:Filespec).setF(name.dup).setEF(filespec) self.register( Names::EMBEDDED_FILES, name.dup, fspec ) if register fspec end |
#author ⇒ Object
41 |
# File 'lib/origami/metadata.rb', line 41 def ; get_document_info_field(:Author) end |
#cast_object(reference, type) ⇒ Object
Casts a PDF object into another object type. The target type must be a subtype of the original type.
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 |
# File 'lib/origami/pdf.rb', line 499 def cast_object(reference, type) #:nodoc: @revisions.each do |rev| if rev.body.include?(reference) object = rev.body[reference] return object if object.is_a?(type) if type < rev.body[reference].class rev.body[reference] = object.cast_to(type, @parser) return rev.body[reference] end end end nil end |
#Catalog ⇒ Object
Returns the current Catalog Dictionary.
40 41 42 43 44 45 |
# File 'lib/origami/catalog.rb', line 40 def Catalog cat = trailer_key(:Root) raise InvalidPDFError, "Broken catalog" unless cat.is_a?(Catalog) cat end |
#Catalog=(cat) ⇒ Object
Sets the current Catalog Dictionary.
50 51 52 53 54 55 56 |
# File 'lib/origami/catalog.rb', line 50 def Catalog=(cat) raise TypeError, "Must be a Catalog object" unless cat.is_a?(Catalog) delete_object(@revisions.last.trailer[:Root]) if @revisions.last.trailer[:Root] @revisions.last.trailer.Root = self << cat end |
#create_form(*fields) ⇒ Object
Creates a new AcroForm with specified fields.
35 36 37 38 39 40 |
# File 'lib/origami/acroform.rb', line 35 def create_form(*fields) acroform = self.Catalog.AcroForm ||= InteractiveForm.new.set_indirect(true) self.add_fields(*fields) acroform end |
#create_metadata(info = {}) ⇒ Object
Modifies or creates a metadata stream.
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/origami/metadata.rb', line 88 def (info = {}) skeleton = <<-XMP <?packet begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/"> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="w"?> XMP xml = if self.Catalog.Metadata.is_a?(Stream) self.Catalog.Metadata.data else skeleton end doc = REXML::Document.new(xml) desc = doc.elements['*/*/rdf:Description'] info.each do |name, value| elt = REXML::Element.new "pdf:#{name}" elt.text = value desc.elements << elt end xml = ""; doc.write(xml, 4) if self.Catalog.Metadata.is_a?(Stream) self.Catalog.Metadata.data = xml else self.Catalog.Metadata = Stream.new(xml) end self.Catalog.Metadata end |
#create_xfa_form(xdp, *fields) ⇒ Object
50 51 52 53 54 55 |
# File 'lib/origami/xfa.rb', line 50 def create_xfa_form(xdp, *fields) acroform = create_form(*fields) acroform.XFA = XFAStream.new(xdp, :Filter => :FlateDecode) acroform end |
#creation_date ⇒ Object
46 |
# File 'lib/origami/metadata.rb', line 46 def creation_date; get_document_info_field(:CreationDate) end |
#creator ⇒ Object
44 |
# File 'lib/origami/metadata.rb', line 44 def creator; get_document_info_field(:Creator) end |
#decrypt(passwd = "") ⇒ Object
Decrypts the current document (only RC4 40..128 bits).
- passwd
-
The password to decrypt the document.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/origami/encryption.rb', line 50 def decrypt(passwd = "") raise EncryptionError, "PDF is not encrypted" unless self.encrypted? # Turn the encryption dictionary into a standard encryption dictionary. handler = trailer_key(:Encrypt) handler = self.cast_object(handler.reference, Encryption::Standard::Dictionary) unless handler.Filter == :Standard raise EncryptionNotSupportedError, "Unknown security handler : '#{handler.Filter}'" end doc_id = trailer_key(:ID) unless doc_id.is_a?(Array) raise EncryptionError, "Document ID was not found or is invalid" unless handler.V.to_i == 5 else doc_id = doc_id.first end encryption_key = handler.derive_encryption_key(passwd, doc_id) self.extend(Encryption::EncryptedDocument) self.encryption_handler = handler self.encryption_key = encryption_key decrypt_objects self end |
#delete_object(no, generation = 0) ⇒ Object
Remove an object.
401 402 403 404 405 406 407 408 409 410 411 412 413 414 |
# File 'lib/origami/pdf.rb', line 401 def delete_object(no, generation = 0) case no when Reference target = no when ::Integer target = Reference.new(no, generation) else raise TypeError, "Invalid parameter type : #{no.class}" end @revisions.each do |rev| rev.body.delete(target) end end |
#delinearize! ⇒ Object
Tries to delinearize the document if it has been linearized. This operation is xrefs destructive, should be fixed in the future to merge tables.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/origami/linearization.rb', line 45 def delinearize! raise LinearizationError, 'Not a linearized document' unless self.linearized? # # Saves the first trailer. # prev_trailer = @revisions.first.trailer linear_dict = @revisions.first.objects.min_by{|obj| obj.file_offset} # # Removes hint streams used by linearization. # delete_hint_streams(linear_dict) # # Update the trailer. # last_trailer = (@revisions.last.trailer ||= Trailer.new) last_trailer.dictionary ||= Dictionary.new if prev_trailer.dictionary? last_trailer.dictionary = last_trailer.dictionary.merge(prev_trailer.dictionary) else xrefstm = @revisions.last.xrefstm raise LinearizationError, 'Cannot find trailer info while delinearizing document' unless xrefstm.is_a?(XRefStream) last_trailer.dictionary[:Root] = xrefstm[:Root] last_trailer.dictionary[:Encrypt] = xrefstm[:Encrypt] last_trailer.dictionary[:Info] = xrefstm[:Info] last_trailer.dictionary[:ID] = xrefstm[:ID] end # # Remove all xrefs. # Fix: Should be merged instead. # remove_xrefs # # Remove the linearization revision. # @revisions.first.body.delete(linear_dict.reference) @revisions.last.body.merge! @revisions.first.body remove_revision(0) self end |
#document_info ⇒ Object
Returns the document information dictionary if present.
36 37 38 |
# File 'lib/origami/metadata.rb', line 36 def document_info trailer_key :Info end |
#document_info? ⇒ Boolean
Returns true if the document has a document information dictionary.
29 30 31 |
# File 'lib/origami/metadata.rb', line 29 def document_info? trailer_key? :Info end |
#each_field ⇒ Object
Iterates over each Acroform Field.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/origami/acroform.rb', line 68 def each_field return enum_for(__method__) do if self.form? and self.Catalog.AcroForm.Fields.is_a?(Array) self.Catalog.AcroForm.Fields.length else 0 end end unless block_given? if self.form? and self.Catalog.AcroForm.Fields.is_a?(Array) self.Catalog.AcroForm.Fields.each do |field| yield(field.solve) end end end |
#each_name(root, &block) ⇒ Object
Returns an Enumerator of all names under the specified root name directory.
157 158 159 160 161 162 163 164 165 |
# File 'lib/origami/catalog.rb', line 157 def each_name(root, &block) return enum_for(__method__, root) unless block_given? names_root = get_names_root(root) return if names_root.nil? names_from_node(names_root, &block) self end |
#each_named_dest(&b) ⇒ Object
Calls block for each named destination.
34 35 36 |
# File 'lib/origami/destinations.rb', line 34 def each_named_dest(&b) each_name(Names::DESTINATIONS, &b) end |
#each_named_embedded_file(&b) ⇒ Object Also known as: each_attachment
Calls block for each named embedded file.
74 75 76 |
# File 'lib/origami/filespec.rb', line 74 def (&b) each_name(Names::EMBEDDED_FILES, &b) end |
#each_named_page(&b) ⇒ Object
Calls block for each named page.
105 106 107 |
# File 'lib/origami/page.rb', line 105 def each_named_page(&b) each_name(Names::PAGES, &b) end |
#each_named_script(&b) ⇒ Object
Calls block for each named JavaScript script.
34 35 36 |
# File 'lib/origami/actions.rb', line 34 def each_named_script(&b) each_name(Names::JAVASCRIPT, &b) end |
#each_object(compressed: false, recursive: false, &block) ⇒ Object
Iterates over the objects of the document. compressed: iterates over the objects inside object streams. recursive: iterates recursively inside objects like arrays and dictionaries.
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/origami/pdf.rb', line 278 def each_object(compressed: false, recursive: false, &block) return enum_for(__method__, compressed: compressed, recursive: recursive ) unless block_given? @revisions.each do |revision| revision.each_object do |object| block.call(object) walk_object(object, &block) if recursive if object.is_a?(ObjectStream) and compressed object.each do |child_obj| block.call(child_obj) walk_object(child_obj) if recursive end end end end end |
#each_page(&b) ⇒ Object
Iterate through each page, returns self.
80 81 82 83 84 |
# File 'lib/origami/page.rb', line 80 def each_page(&b) init_page_tree self.Catalog.Pages.each_page(&b) end |
#enable_usage_rights(cert, pkey, *rights) ⇒ Object
Enable the document Usage Rights.
- rights
-
list of rights defined in UsageRights::Rights
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/origami/signature.rb', line 185 def enable_usage_rights(cert, pkey, *rights) # Always uses a detached PKCS7 signature for UR. method = Signature::PKCS7_DETACHED # # Load key pair # key = pkey.is_a?(OpenSSL::PKey::RSA) ? pkey : OpenSSL::PKey::RSA.new(pkey) certificate = cert.is_a?(OpenSSL::X509::Certificate) ? cert : OpenSSL::X509::Certificate.new(cert) # # Forge digital signature dictionary # digsig = Signature::DigitalSignature.new.set_indirect(true) self.Catalog.AcroForm ||= InteractiveForm.new #self.Catalog.AcroForm.SigFlags = InteractiveForm::SigFlags::APPEND_ONLY digsig.Type = :Sig digsig.Contents = HexaString.new("\x00" * Signature.required_size(method, certificate, key, [])) digsig.Filter = :"Adobe.PPKLite" digsig.Name = "ARE Acrobat Product v8.0 P23 0002337" digsig.SubFilter = Name.new(method ) digsig.ByteRange = [0, 0, 0, 0] sigref = Signature::Reference.new sigref.Type = :SigRef sigref.TransformMethod = :UR3 sigref.Data = self.Catalog sigref.TransformParams = UsageRights::TransformParams.new sigref.TransformParams.P = true sigref.TransformParams.Type = :TransformParams sigref.TransformParams.V = UsageRights::TransformParams::VERSION rights.each do |right| sigref.TransformParams[right.first] ||= [] sigref.TransformParams[right.first].concat(right[1..-1]) end digsig.Reference = [ sigref ] self.Catalog.Perms ||= Perms.new self.Catalog.Perms.UR3 = digsig # # Flattening the PDF to get file view. # compile # # Creating an empty Xref table to compute signature byte range. # rebuild_dummy_xrefs sig_offset = get_object_offset(digsig.no, digsig.generation) + digsig.signature_offset digsig.ByteRange[0] = 0 digsig.ByteRange[1] = sig_offset digsig.ByteRange[2] = sig_offset + digsig.Contents.size until digsig.ByteRange[3] == filesize - digsig.ByteRange[2] digsig.ByteRange[3] = filesize - digsig.ByteRange[2] end # From that point on, the file size remains constant # # Correct Xrefs variations caused by ByteRange modifications. # rebuild_xrefs file_data = output() signable_data = file_data[digsig.ByteRange[0],digsig.ByteRange[1]] + file_data[digsig.ByteRange[2],digsig.ByteRange[3]] signature = Signature.compute(method, signable_data, certificate, key, []) digsig.Contents[0, signature.size] = signature # # No more modification are allowed after signing. # self.freeze end |
#encrypt(options = {}) ⇒ Object
Encrypts the current document with the provided passwords. The document will be encrypted at writing-on-disk time.
- userpasswd
-
The user password.
- ownerpasswd
-
The owner password.
- options
-
A set of options to configure encryption.
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/origami/encryption.rb', line 86 def encrypt( = {}) raise EncryptionError, "PDF is already encrypted" if self.encrypted? # # Default encryption options. # params = { :user_passwd => '', :owner_passwd => '', :cipher => 'aes', # :RC4 or :AES :key_size => 128, # Key size in bits :hardened => false, # Use newer password validation (since Reader X) :encrypt_metadata => true, # Metadata shall be encrypted? :permissions => Encryption::Standard::Permissions::ALL # Document permissions }.update() # Get the cryptographic parameters. version, revision = (params) # Create the security handler. handler, encryption_key = create_security_handler(version, revision, params) # Turn this document into an EncryptedDocument instance. self.extend(Encryption::EncryptedDocument) self.encryption_handler = handler self.encryption_key = encryption_key self end |
#encrypted? ⇒ Boolean
Returns whether the PDF file is encrypted.
42 43 44 |
# File 'lib/origami/encryption.rb', line 42 def encrypted? trailer_key? :Encrypt end |
#fields ⇒ Object
Returns an Array of Acroform fields.
61 62 63 |
# File 'lib/origami/acroform.rb', line 61 def fields self.each_field.to_a end |
#form? ⇒ Boolean
Returns true if the document contains an acrobat form.
28 29 30 |
# File 'lib/origami/acroform.rb', line 28 def form? self.Catalog.key? :AcroForm end |
#get_destination_by_name(name) ⇒ Object
Lookup destination in the destination name directory.
27 28 29 |
# File 'lib/origami/destinations.rb', line 27 def get_destination_by_name(name) resolve_name Names::DESTINATIONS, name end |
#get_embedded_file_by_name(name) ⇒ Object
Lookup embedded file in the embedded files name directory.
67 68 69 |
# File 'lib/origami/filespec.rb', line 67 def (name) resolve_name Names::EMBEDDED_FILES, name end |
#get_field(name) ⇒ Object
Returns the corresponding named Field.
87 88 89 90 91 92 93 |
# File 'lib/origami/acroform.rb', line 87 def get_field(name) self.each_field do |field| return field if field[:T].solve == name end nil end |
#get_object(no, generation = 0, use_xrefstm: true) ⇒ Object Also known as: []
Search for an indirect object in the document.
- no
-
Reference or number of the object.
- generation
-
Object generation.
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 |
# File 'lib/origami/pdf.rb', line 421 def get_object(no, generation = 0, use_xrefstm: true) #:nodoc: case no when Reference target = no when ::Integer target = Reference.new(no, generation) when Origami::Object return no else raise TypeError, "Invalid parameter type : #{no.class}" end # # Search through accessible indirect objects. # @revisions.reverse_each do |rev| return rev.body[target] if rev.body.include?(target) end # # Search through xref sections. # @revisions.reverse_each do |rev| next unless rev.has_xreftable? xref = rev.xreftable.find(target.refno) next if xref.nil? or xref.free? # Try loading the object if it is not present. object = load_object_at_offset(rev, xref.offset) return object unless object.nil? end return nil unless use_xrefstm # Search through xref streams. @revisions.reverse_each do |rev| next unless rev.has_xrefstm? xrefstm = rev.xrefstm xref = xrefstm.find(target.refno) next if xref.nil? # # We found a matching XRef. # if xref.is_a?(XRefToCompressedObject) objstm = get_object(xref.objstmno, 0, use_xrefstm: use_xrefstm) object = objstm.extract_by_index(xref.index) if object.is_a?(Origami::Object) and object.no == target.refno return object else return objstm.extract(target.refno) end elsif xref.is_a?(XRef) object = load_object_at_offset(rev, xref.offset) return object unless object.nil? end end # # Lastly search directly into Object streams (might be very slow). # @revisions.reverse_each do |rev| stream = rev.objects.find{|obj| obj.is_a?(ObjectStream) and obj.include?(target.refno)} return stream.extract(target.refno) unless stream.nil? end nil end |
#get_object_by_offset(offset) ⇒ Object
Looking for an object present at a specified file offset.
394 395 396 |
# File 'lib/origami/pdf.rb', line 394 def get_object_by_offset(offset) #:nodoc: self.indirect_objects.find { |obj| obj.file_offset == offset } end |
#get_page(n) ⇒ Object
Get the n-th Page object.
89 90 91 92 93 |
# File 'lib/origami/page.rb', line 89 def get_page(n) init_page_tree self.Catalog.Pages.get_page(n) end |
#get_page_by_name(name) ⇒ Object
Lookup page in the page name directory.
98 99 100 |
# File 'lib/origami/page.rb', line 98 def get_page_by_name(name) resolve_name Names::PAGES, name end |
#get_script_by_name(name) ⇒ Object
Lookup script in the scripts name directory.
27 28 29 |
# File 'lib/origami/actions.rb', line 27 def get_script_by_name(name) resolve_name Names::JAVASCRIPT, name end |
#grep(pattern, streams: true, object_streams: true) ⇒ Object
Returns an array of strings, names and streams matching the given pattern. streams: Search into decoded stream data. object_streams: Search into objects inside object streams.
245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/origami/pdf.rb', line 245 def grep(pattern, streams: true, object_streams: true) #:nodoc: pattern = /#{Regexp.escape(pattern)}/i if pattern.is_a?(::String) raise TypeError, "Expected a String or Regexp" unless pattern.is_a?(Regexp) result = [] self.indirect_objects.each do |object| result.concat search_object(object, pattern, streams: streams, object_streams: object_streams) end result end |
#import(object) ⇒ Object
Similar to PDF#insert or PDF#<<, but for an object belonging to another document. Object will be recursively copied and new version numbers will be assigned. Returns the new reference to the imported object.
- object
-
The object to import.
337 338 339 |
# File 'lib/origami/pdf.rb', line 337 def import(object) self.insert(object.export) end |
#indirect_objects ⇒ Object Also known as: root_objects
Return an array of indirect objects.
304 305 306 |
# File 'lib/origami/pdf.rb', line 304 def indirect_objects @revisions.inject([]) do |set, rev| set.concat(rev.objects) end end |
#insert_page(index, page = Page.new) {|page| ... } ⇒ Object
Inserts a page at position index into the document.
- index
-
Page index (starting from zero).
- page
-
The page to insert into the document. Creates a new one if none given.
Pass the Page object if a block is present.
55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/origami/page.rb', line 55 def insert_page(index, page = Page.new) init_page_tree # Page from another document must be exported. page = page.export if page.document and page.document != self self.Catalog.Pages.insert_page(index, page) yield(page) if block_given? self end |
#keywords ⇒ Object
43 |
# File 'lib/origami/metadata.rb', line 43 def keywords; get_document_info_field(:Keywords) end |
#linearized? ⇒ Boolean
Returns whether the current document is linearized.
31 32 33 34 35 36 37 38 39 |
# File 'lib/origami/linearization.rb', line 31 def linearized? begin first_obj = @revisions.first.objects.min_by{|obj| obj.file_offset} rescue return false end @revisions.size > 1 and first_obj.is_a?(Dictionary) and first_obj.has_key? :Linearized end |
#loaded! ⇒ Object
Mark the document as complete. No more objects needs to be fetched by the parser.
535 536 537 |
# File 'lib/origami/pdf.rb', line 535 def loaded! @loaded = true end |
#ls(pattern, follow_references: true) ⇒ Object
Returns an array of Objects whose name (in a Dictionary) is matching pattern.
263 264 265 266 267 268 269 270 271 |
# File 'lib/origami/pdf.rb', line 263 def ls(pattern, follow_references: true) pattern = /#{Regexp.escape(pattern)}/i if pattern.is_a?(::String) raise TypeError, "Expected a String or Regexp" unless pattern.is_a?(Regexp) self.grep(pattern, streams: false, object_streams: true) .select {|object| object.is_a?(Name) and object.parent.is_a?(Dictionary) and object.parent.key?(object) } .collect {|object| result = object.parent[object]; follow_references ? result.solve : result } end |
#metadata ⇒ Object
Returns a Hash of the information found in the metadata stream
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/origami/metadata.rb', line 59 def = self.Catalog.Metadata if .is_a?(Stream) doc = REXML::Document.new(.data) info = {} doc.elements.each('*/*/rdf:Description') do |description| description.attributes.each_attribute do |attr| case attr.prefix when 'pdf','xap' info[attr.name] = attr.value end end description.elements.each('*') do |element| value = (element.elements['.//rdf:li'] || element).text info[element.name] = value.to_s end end info end end |
#metadata? ⇒ Boolean
Returns true if the document has a catalog metadata stream.
52 53 54 |
# File 'lib/origami/metadata.rb', line 52 def self.Catalog.Metadata.is_a?(Stream) end |
#mod_date ⇒ Object
47 |
# File 'lib/origami/metadata.rb', line 47 def mod_date; get_document_info_field(:ModDate) end |
#names(root) ⇒ Object
Returns a Hash of all names under the specified root name directory.
150 151 152 |
# File 'lib/origami/catalog.rb', line 150 def names(root) self.each_name(root).to_h end |
#onDocumentClose(action) ⇒ Object
Sets an action to run on document closing.
- action
-
A JavaScript Action Object.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/origami/catalog.rb', line 80 def onDocumentClose(action) unless action.is_a?(Action::JavaScript) or action.is_a?(Reference) raise TypeError, "An Action::JavaScript object must be passed." end unless self.Catalog raise InvalidPDFError, "A catalog object must exist to add this action." end self.Catalog.AA ||= CatalogAdditionalActions.new self.Catalog.AA.WC = action self end |
#onDocumentOpen(action) ⇒ Object
Sets an action to run on document opening.
- action
-
An Action Object.
62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/origami/catalog.rb', line 62 def onDocumentOpen(action) unless action.is_a?(Action) or action.is_a?(Destination) or action.is_a?(Reference) raise TypeError, "An Action object must be passed." end unless self.Catalog raise InvalidPDFError, "A catalog object must exist to add this action." end self.Catalog.OpenAction = action self end |
#onDocumentPrint(action) ⇒ Object
Sets an action to run on document printing.
- action
-
A JavaScript Action Object.
99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/origami/catalog.rb', line 99 def onDocumentPrint(action) unless action.is_a?(Action::JavaScript) or action.is_a?(Reference) raise TypeError, "An Action::JavaScript object must be passed." end unless self.Catalog raise InvalidPDFError, "A catalog object must exist to add this action." end self.Catalog.AA ||= CatalogAdditionalActions.new self.Catalog.AA.WP = action self end |
#original_data ⇒ Object
Original data parsed to create this document, nil if created from scratch.
185 186 187 |
# File 'lib/origami/pdf.rb', line 185 def original_data @parser.target_data if @parser end |
#original_filename ⇒ Object
Original file name if parsed from disk, nil otherwise.
171 172 173 |
# File 'lib/origami/pdf.rb', line 171 def original_filename @parser.target_filename if @parser end |
#original_filesize ⇒ Object
Original file size if parsed from a data stream, nil otherwise.
178 179 180 |
# File 'lib/origami/pdf.rb', line 178 def original_filesize @parser.target_filesize if @parser end |
#pages ⇒ Object
Returns an Enumerator of Page
71 72 73 74 75 |
# File 'lib/origami/page.rb', line 71 def pages init_page_tree self.Catalog.Pages.pages end |
#pdfa1? ⇒ Boolean
42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/origami/outputintents.rb', line 42 def pdfa1? self.Catalog.OutputIntents.is_a?(Array) and self.Catalog.OutputIntents.any?{|intent| intent.solve.S == OutputIntent::Intent::PDFA1 } and self. and ( doc = REXML::Document.new self.Catalog.Metadata.data; REXML::XPath.match(doc, "*/*/rdf:Description[@xmlns:pdfaid]").any? {|desc| desc.elements["pdfaid:conformance"].text == "A" and desc.elements["pdfaid:part"].text == "1" } ) end |
#portfolio? ⇒ Boolean
Returns true if the document behaves as a portfolio for embedded files.
27 28 29 |
# File 'lib/origami/collections.rb', line 27 def portfolio? self.Catalog.Collection.is_a?(Dictionary) end |
#producer ⇒ Object
45 |
# File 'lib/origami/metadata.rb', line 45 def producer; get_document_info_field(:Producer) end |
#register(root, name, value) ⇒ Object
Registers an object into a specific Names root dictionary.
- root
-
The root dictionary (see Names::Root)
- name
-
The value name.
- value
-
The value to associate with this name.
120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/origami/catalog.rb', line 120 def register(root, name, value) self.Catalog.Names ||= Names.new value.set_indirect(true) unless value.is_a?(Reference) namesroot = self.Catalog.Names[root] if namesroot.nil? names = NameTreeNode.new(:Names => []).set_indirect(true) self.Catalog.Names[root] = names names.Names << name << value else namesroot.solve[:Names] << name << value end end |
#remove_revision(index) ⇒ Object
Removes a whole document revision.
- index
-
Revision index, first is 0.
378 379 380 381 382 383 384 385 386 387 388 389 |
# File 'lib/origami/pdf.rb', line 378 def remove_revision(index) if index < 0 or index > @revisions.size raise IndexError, "Not a valid revision index" end if @revisions.size == 1 raise InvalidPDFError, "Cannot remove last revision" end @revisions.delete_at(index) self end |
#remove_xrefs ⇒ Object
Tries to strip any xrefs information off the document.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/origami/xreftable.rb', line 27 def remove_xrefs @revisions.reverse_each do |rev| if rev.has_xrefstm? delete_object(rev.xrefstm.reference) end if rev.trailer.XRefStm.is_a?(Integer) xrefstm = get_object_by_offset(rev.trailer.XRefStm) delete_object(xrefstm.reference) if xrefstm.is_a?(XRefStream) end rev.xrefstm = rev.xreftable = nil end end |
#resolve_name(root, name) ⇒ Object
Retrieve the corresponding value associated with name in the specified root name directory, or nil if the value does not exist.
140 141 142 143 144 145 |
# File 'lib/origami/catalog.rb', line 140 def resolve_name(root, name) namesroot = get_names_root(root) return nil if namesroot.nil? resolve_name_from_node(namesroot, name) end |
#save(path, params = {}) ⇒ Object Also known as: write
Saves the current document.
- filename
-
The path where to save this PDF.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/origami/pdf.rb', line 193 def save(path, params = {}) = { delinearize: true, recompile: true, decrypt: false } .update(params) if self.frozen? # incompatible flags with frozen doc (signed) [:recompile] = [:rebuild_xrefs] = [:noindent] = [:obfuscate] = false end if path.respond_to?(:write) fd = path else path = File.(path) fd = File.open(path, 'w').binmode close = true end load_all_objects unless @loaded intents_as_pdfa1 if [:intent] =~ /pdf[\/-]?A1?/i self.delinearize! if [:delinearize] and self.linearized? compile() if [:recompile] fd.write output() fd.close if close self end |
#save_upto(revision, filename) ⇒ Object
Saves the file up to given revision number. This can be useful to visualize the modifications over different incremental updates.
- revision
-
The revision number to save.
- filename
-
The path where to save this PDF.
236 237 238 |
# File 'lib/origami/pdf.rb', line 236 def save_upto(revision, filename) save(filename, up_to_revision: revision) end |
#set_extension_level(version, level) ⇒ Object
Sets PDF extension level and version. Only supported values are “1.7” and 3.
27 28 29 30 31 32 33 34 35 |
# File 'lib/origami/catalog.rb', line 27 def set_extension_level(version, level) exts = (self.Catalog.Extensions ||= Extensions.new) exts[:ADBE] = DeveloperExtension.new exts[:ADBE].BaseVersion = Name.new(version) exts[:ADBE].ExtensionLevel = level self end |
#sign(certificate, key, method: Signature::PKCS7_DETACHED, ca: [], annotation: nil, issuer: nil, location: nil, contact: nil, reason: nil) ⇒ Object
Sign the document with the given key and x509 certificate.
- certificate
-
The X509 certificate containing the public key.
- key
-
The private key associated with the certificate.
- method
-
The PDF signature identifier.
- ca
-
Optional CA certificates used to sign the user certificate.
- annotation
-
Annotation associated with the signature.
- issuer
-
Issuer name.
- location
-
Signature location.
- contact
-
Signer contact.
- reason
-
Signing reason.
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/origami/signature.rb', line 67 def sign(certificate, key, method: Signature::PKCS7_DETACHED, ca: [], annotation: nil, issuer: nil, location: nil, contact: nil, reason: nil) unless certificate.is_a?(OpenSSL::X509::Certificate) raise TypeError, "A OpenSSL::X509::Certificate object must be passed." end unless key.is_a?(OpenSSL::PKey::RSA) raise TypeError, "A OpenSSL::PKey::RSA object must be passed." end unless ca.is_a?(::Array) raise TypeError, "Expected an Array of CA certificate." end unless annotation.nil? or annotation.is_a?(Annotation::Widget::Signature) raise TypeError, "Expected a Annotation::Widget::Signature object." end digsig = Signature::DigitalSignature.new.set_indirect(true) if annotation.nil? annotation = Annotation::Widget::Signature.new annotation.Rect = Rectangle[:llx => 0.0, :lly => 0.0, :urx => 0.0, :ury => 0.0] end annotation.V = digsig add_fields(annotation) self.Catalog.AcroForm.SigFlags = InteractiveForm::SigFlags::SIGNATURES_EXIST | InteractiveForm::SigFlags::APPEND_ONLY digsig.Type = :Sig digsig.Contents = HexaString.new("\x00" * Signature::required_size(method, certificate, key, ca)) digsig.Filter = :"Adobe.PPKLite" digsig.SubFilter = Name.new(method) digsig.ByteRange = [0, 0, 0, 0] digsig.Name = issuer digsig.Location = HexaString.new(location) if location digsig.ContactInfo = HexaString.new(contact) if contact digsig.Reason = HexaString.new(reason) if reason # PKCS1 signatures require a Cert entry. if method == Signature::PKCS1_RSA_SHA1 digsig.Cert = if ca.empty? HexaString.new(certificate.to_der) else [ HexaString.new(certificate.to_der) ] + ca.map{ |crt| HexaString.new(crt.to_der) } end end # # Flattening the PDF to get file view. # compile # # Creating an empty Xref table to compute signature byte range. # rebuild_dummy_xrefs sig_offset = get_object_offset(digsig.no, digsig.generation) + digsig.signature_offset digsig.ByteRange[0] = 0 digsig.ByteRange[1] = sig_offset digsig.ByteRange[2] = sig_offset + digsig.Contents.to_s.bytesize until digsig.ByteRange[3] == filesize - digsig.ByteRange[2] digsig.ByteRange[3] = filesize - digsig.ByteRange[2] end # From that point on, the file size remains constant # # Correct Xrefs variations caused by ByteRange modifications. # rebuild_xrefs file_data = output() signable_data = file_data[digsig.ByteRange[0],digsig.ByteRange[1]] + file_data[digsig.ByteRange[2],digsig.ByteRange[3]] # # Computes and inserts the signature. # signature = Signature.compute(method, signable_data, certificate, key, ca) digsig.Contents[0, signature.size] = signature # # No more modification are allowed after signing. # self.freeze end |
#signature ⇒ Object
276 277 278 279 280 281 282 283 284 |
# File 'lib/origami/signature.rb', line 276 def signature raise SignatureError, "Not a signed document" unless self.signed? self.each_field do |field| return field.V if field.FT == :Sig and field.V.is_a?(Dictionary) end raise SignatureError, "Cannot find digital signature" end |
#signed? ⇒ Boolean
Returns whether the document contains a digital signature.
171 172 173 174 175 176 177 178 179 |
# File 'lib/origami/signature.rb', line 171 def signed? begin self.Catalog.AcroForm.is_a?(Dictionary) and self.Catalog.AcroForm.SigFlags.is_a?(Integer) and (self.Catalog.AcroForm.SigFlags & InteractiveForm::SigFlags::SIGNATURES_EXIST != 0) rescue InvalidReferenceError false end end |
#subject ⇒ Object
42 |
# File 'lib/origami/metadata.rb', line 42 def subject; get_document_info_field(:Subject) end |
#title ⇒ Object
40 |
# File 'lib/origami/metadata.rb', line 40 def title; get_document_info_field(:Title) end |
#trailer ⇒ Object
Returns the current trailer. This might be either a Trailer or XRefStream.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/origami/trailer.rb', line 29 def trailer # # First look for a standard trailer dictionary # if @revisions.last.trailer.dictionary? trl = @revisions.last.trailer # # Otherwise look for a xref stream. # else trl = @revisions.last.xrefstm end raise InvalidPDFError, "No trailer found" if trl.nil? trl end |
#usage_rights? ⇒ Boolean
271 272 273 274 |
# File 'lib/origami/signature.rb', line 271 def usage_rights? not self.Catalog.Perms.nil? and (not self.Catalog.Perms.has_key?(:UR3) or not self.Catalog.Perms.has_key?(:UR)) end |
#verify(trusted_certs: []) ⇒ Object
Verify a document signature.
_:trusted_certs_: an array of trusted X509 certificates.
If no argument is passed, embedded certificates are treated as trusted.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/origami/signature.rb', line 35 def verify(trusted_certs: []) digsig = self.signature digsig = digsig.cast_to(Signature::DigitalSignature) unless digsig.is_a?(Signature::DigitalSignature) unless digsig[:Contents].is_a?(String) raise SignatureError, "Invalid digital signature contents" end store = OpenSSL::X509::Store.new trusted_certs.each { |ca| store.add_cert(ca) } flags = 0 flags |= OpenSSL::PKCS7::NOVERIFY if trusted_certs.empty? data = extract_signed_data(digsig) signature = digsig[:Contents] subfilter = digsig.SubFilter.value Signature.verify(subfilter.to_s, data, signature, store, flags) end |
#xfa_form? ⇒ Boolean
57 58 59 |
# File 'lib/origami/xfa.rb', line 57 def xfa_form? self.form? and self.Catalog.AcroForm.key?(:XFA) end |