origami-docspring 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +70 -0
  3. data/COPYING.LESSER +165 -0
  4. data/README.md +115 -0
  5. data/bin/config/pdfcop.conf.yml +236 -0
  6. data/bin/pdf2pdfa +87 -0
  7. data/bin/pdf2ruby +333 -0
  8. data/bin/pdfcop +474 -0
  9. data/bin/pdfdecompress +97 -0
  10. data/bin/pdfdecrypt +91 -0
  11. data/bin/pdfencrypt +113 -0
  12. data/bin/pdfexplode +223 -0
  13. data/bin/pdfextract +277 -0
  14. data/bin/pdfmetadata +144 -0
  15. data/bin/pdfsh +12 -0
  16. data/bin/shell/console.rb +128 -0
  17. data/bin/shell/hexdump.rb +59 -0
  18. data/bin/shell/irbrc +69 -0
  19. data/examples/README.md +34 -0
  20. data/examples/attachments/attachment.rb +38 -0
  21. data/examples/attachments/nested_document.rb +51 -0
  22. data/examples/encryption/encryption.rb +28 -0
  23. data/examples/events/events.rb +72 -0
  24. data/examples/flash/flash.rb +37 -0
  25. data/examples/flash/helloworld.swf +0 -0
  26. data/examples/forms/javascript.rb +54 -0
  27. data/examples/forms/xfa.rb +115 -0
  28. data/examples/javascript/hello_world.rb +22 -0
  29. data/examples/javascript/js_emulation.rb +54 -0
  30. data/examples/loop/goto.rb +32 -0
  31. data/examples/loop/named.rb +33 -0
  32. data/examples/signature/signature.rb +65 -0
  33. data/examples/uri/javascript.rb +56 -0
  34. data/examples/uri/open-uri.rb +21 -0
  35. data/examples/uri/submitform.rb +47 -0
  36. data/lib/origami/3d.rb +364 -0
  37. data/lib/origami/acroform.rb +321 -0
  38. data/lib/origami/actions.rb +318 -0
  39. data/lib/origami/annotations.rb +711 -0
  40. data/lib/origami/array.rb +242 -0
  41. data/lib/origami/boolean.rb +90 -0
  42. data/lib/origami/catalog.rb +418 -0
  43. data/lib/origami/collections.rb +144 -0
  44. data/lib/origami/compound.rb +161 -0
  45. data/lib/origami/destinations.rb +252 -0
  46. data/lib/origami/dictionary.rb +192 -0
  47. data/lib/origami/encryption.rb +1085 -0
  48. data/lib/origami/extensions/fdf.rb +347 -0
  49. data/lib/origami/extensions/ppklite.rb +422 -0
  50. data/lib/origami/filespec.rb +197 -0
  51. data/lib/origami/filters/ascii.rb +211 -0
  52. data/lib/origami/filters/ccitt/tables.rb +267 -0
  53. data/lib/origami/filters/ccitt.rb +357 -0
  54. data/lib/origami/filters/crypt.rb +38 -0
  55. data/lib/origami/filters/dct.rb +54 -0
  56. data/lib/origami/filters/flate.rb +69 -0
  57. data/lib/origami/filters/jbig2.rb +57 -0
  58. data/lib/origami/filters/jpx.rb +47 -0
  59. data/lib/origami/filters/lzw.rb +170 -0
  60. data/lib/origami/filters/predictors.rb +292 -0
  61. data/lib/origami/filters/runlength.rb +129 -0
  62. data/lib/origami/filters.rb +364 -0
  63. data/lib/origami/font.rb +196 -0
  64. data/lib/origami/functions.rb +79 -0
  65. data/lib/origami/graphics/colors.rb +230 -0
  66. data/lib/origami/graphics/instruction.rb +98 -0
  67. data/lib/origami/graphics/path.rb +182 -0
  68. data/lib/origami/graphics/patterns.rb +174 -0
  69. data/lib/origami/graphics/render.rb +62 -0
  70. data/lib/origami/graphics/state.rb +149 -0
  71. data/lib/origami/graphics/text.rb +225 -0
  72. data/lib/origami/graphics/xobject.rb +918 -0
  73. data/lib/origami/graphics.rb +38 -0
  74. data/lib/origami/header.rb +75 -0
  75. data/lib/origami/javascript.rb +713 -0
  76. data/lib/origami/linearization.rb +330 -0
  77. data/lib/origami/metadata.rb +172 -0
  78. data/lib/origami/name.rb +135 -0
  79. data/lib/origami/null.rb +65 -0
  80. data/lib/origami/numeric.rb +181 -0
  81. data/lib/origami/obfuscation.rb +245 -0
  82. data/lib/origami/object.rb +760 -0
  83. data/lib/origami/optionalcontent.rb +183 -0
  84. data/lib/origami/outline.rb +54 -0
  85. data/lib/origami/outputintents.rb +85 -0
  86. data/lib/origami/page.rb +722 -0
  87. data/lib/origami/parser.rb +269 -0
  88. data/lib/origami/parsers/fdf.rb +56 -0
  89. data/lib/origami/parsers/pdf/lazy.rb +176 -0
  90. data/lib/origami/parsers/pdf/linear.rb +122 -0
  91. data/lib/origami/parsers/pdf.rb +118 -0
  92. data/lib/origami/parsers/ppklite.rb +57 -0
  93. data/lib/origami/pdf.rb +1108 -0
  94. data/lib/origami/reference.rb +134 -0
  95. data/lib/origami/signature.rb +702 -0
  96. data/lib/origami/stream.rb +705 -0
  97. data/lib/origami/string.rb +444 -0
  98. data/lib/origami/template/patterns.rb +56 -0
  99. data/lib/origami/template/widgets.rb +151 -0
  100. data/lib/origami/trailer.rb +190 -0
  101. data/lib/origami/tree.rb +62 -0
  102. data/lib/origami/version.rb +23 -0
  103. data/lib/origami/webcapture.rb +100 -0
  104. data/lib/origami/xfa/config.rb +453 -0
  105. data/lib/origami/xfa/connectionset.rb +146 -0
  106. data/lib/origami/xfa/datasets.rb +49 -0
  107. data/lib/origami/xfa/localeset.rb +42 -0
  108. data/lib/origami/xfa/package.rb +59 -0
  109. data/lib/origami/xfa/pdf.rb +73 -0
  110. data/lib/origami/xfa/signature.rb +42 -0
  111. data/lib/origami/xfa/sourceset.rb +43 -0
  112. data/lib/origami/xfa/stylesheet.rb +44 -0
  113. data/lib/origami/xfa/template.rb +1691 -0
  114. data/lib/origami/xfa/xdc.rb +42 -0
  115. data/lib/origami/xfa/xfa.rb +146 -0
  116. data/lib/origami/xfa/xfdf.rb +43 -0
  117. data/lib/origami/xfa/xmpmeta.rb +43 -0
  118. data/lib/origami/xfa.rb +62 -0
  119. data/lib/origami/xreftable.rb +557 -0
  120. data/lib/origami.rb +47 -0
  121. data/test/dataset/calc.pdf +85 -0
  122. data/test/dataset/crypto.pdf +82 -0
  123. data/test/dataset/empty.pdf +49 -0
  124. data/test/test_actions.rb +27 -0
  125. data/test/test_annotations.rb +68 -0
  126. data/test/test_forms.rb +30 -0
  127. data/test/test_native_types.rb +83 -0
  128. data/test/test_object_tree.rb +33 -0
  129. data/test/test_pages.rb +60 -0
  130. data/test/test_pdf.rb +20 -0
  131. data/test/test_pdf_attachment.rb +34 -0
  132. data/test/test_pdf_create.rb +24 -0
  133. data/test/test_pdf_encrypt.rb +95 -0
  134. data/test/test_pdf_parse.rb +134 -0
  135. data/test/test_pdf_parse_lazy.rb +69 -0
  136. data/test/test_pdf_sign.rb +97 -0
  137. data/test/test_streams.rb +184 -0
  138. data/test/test_xrefs.rb +67 -0
  139. metadata +243 -0
data/bin/pdfcop ADDED
@@ -0,0 +1,474 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ =begin
4
+
5
+ = Info
6
+ This is a PDF document filtering engine using Origami.
7
+ Security policies are based on a white list of PDF features.
8
+ Default policies details can be found in the default configuration file.
9
+ You can also add your own policy and activate it using the -p switch.
10
+
11
+ = License
12
+ Copyright (C) 2016 Guillaume Delugré.
13
+
14
+ Origami is free software: you can redistribute it and/or modify
15
+ it under the terms of the GNU Lesser General Public License as published by
16
+ the Free Software Foundation, either version 3 of the License, or
17
+ (at your option) any later version.
18
+
19
+ Origami is distributed in the hope that it will be useful,
20
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
21
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
+ GNU Lesser General Public License for more details.
23
+
24
+ You should have received a copy of the GNU Lesser General Public License
25
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
26
+
27
+ =end
28
+
29
+ begin
30
+ require 'origami'
31
+ rescue LoadError
32
+ $: << File.join(__dir__, '../lib')
33
+ require 'origami'
34
+ end
35
+
36
+ require 'optparse'
37
+ require 'yaml'
38
+ require 'rexml/document'
39
+ require 'digest/sha2'
40
+ require 'fileutils'
41
+ require 'colorize'
42
+
43
+ DEFAULT_CONFIG_FILE = "#{File.dirname(__FILE__)}/config/pdfcop.conf.yml"
44
+ DEFAULT_POLICY = "standard"
45
+ SECURITY_POLICIES = {}
46
+ ANNOTATION_RIGHTS = {
47
+ FileAttachment: %i[allowAttachments allowFileAttachmentAnnotation],
48
+ Sound: %i[allowSoundAnnotation],
49
+ Movie: %i[allowMovieAnnotation],
50
+ Screen: %i[allowScreenAnnotation],
51
+ Widget: %i[allowAcroforms],
52
+ RichMedia: %i[allowRichMediaAnnotation],
53
+ :"3D" => %i[allow3DAnnotation]
54
+ }
55
+
56
+
57
+ def load_config_file(path)
58
+ SECURITY_POLICIES.update(Hash.new(false).update YAML.load(File.read(path)))
59
+ end
60
+
61
+ class OptParser
62
+ BANNER = <<USAGE
63
+ Usage: #{$0} [options] <PDF-file>
64
+ The PDF filtering engine. Scans PDF documents for malicious structures.
65
+ Bug reports or feature requests at: http://github.com/gdelugre/origami
66
+
67
+ Options:
68
+ USAGE
69
+
70
+ def self.parse(args)
71
+ options = {colors: true, password: ''}
72
+
73
+ parser = OptionParser.new do |opts|
74
+ opts.banner = BANNER
75
+
76
+ opts.on("-o", "--output LOG_FILE", "Output log file (default STDOUT)") do |o|
77
+ options[:output_log] = o
78
+ end
79
+
80
+ opts.on("-c", "--config CONFIG_FILE", "Load security policies from given configuration file") do |cf|
81
+ options[:config_file] = cf
82
+ end
83
+
84
+ opts.on("-p", "--policy POLICY_NAME", "Specify applied policy. Predefined policies: 'none', 'standard', 'strong', 'paranoid'") do |policy|
85
+ options[:policy] = policy
86
+ end
87
+
88
+ opts.on("-m", "--move PATH", "Move rejected documents to the specified directory.") do |dir|
89
+ options[:move_dir] = dir
90
+ end
91
+
92
+ opts.on("-P", "--password PASSWORD", "Password to use if the document is encrypted") do |passwd|
93
+ options[:password] = passwd
94
+ end
95
+
96
+ opts.on("-n", "--no-color", "Turn off colorized output") do
97
+ options[:disable_colors] = true
98
+ end
99
+
100
+ opts.on_tail("-h", "--help", "Show this message") do
101
+ puts opts
102
+ exit
103
+ end
104
+ end
105
+
106
+ parser.parse!(args)
107
+
108
+ options
109
+ end
110
+ end
111
+
112
+ @options = OptParser.parse(ARGV)
113
+ if @options.key?(:output_log)
114
+ LOGGER = File.open(@options[:output_log], "a+")
115
+ else
116
+ LOGGER = STDOUT
117
+ end
118
+
119
+ if not @options.key?(:policy)
120
+ @options[:policy] = DEFAULT_POLICY
121
+ end
122
+
123
+ if @options.key?(:move_dir) and not File.directory?(@options[:move_dir])
124
+ abort "Error: #{@options[:move_dir]} is not a valid directory."
125
+ end
126
+
127
+ String.disable_colorization @options[:disable_colors]
128
+
129
+ load_config_file(@options[:config_file] || DEFAULT_CONFIG_FILE)
130
+ unless SECURITY_POLICIES.key?("POLICY_#{@options[:policy].upcase}")
131
+ abort "Undeclared policy `#{@options[:policy]}'"
132
+ end
133
+
134
+ if ARGV.empty?
135
+ abort "Error: No filename was specified. #{$0} --help for details."
136
+ else
137
+ TARGET = ARGV.shift
138
+ end
139
+
140
+ def log(str, color = :default)
141
+ LOGGER.puts("[#{Time.now}]".cyan + " #{str.colorize(color)}")
142
+ end
143
+
144
+ def reject(cause)
145
+ log("Document rejected by policy `#{@options[:policy]}', caused by #{cause.inspect}.", :red)
146
+
147
+ if @options.key?(:move_dir)
148
+ quarantine(TARGET, @options[:move_dir])
149
+ end
150
+
151
+ abort
152
+ end
153
+
154
+ def quarantine(file, quarantine_folder)
155
+ digest = Digest::SHA256.file(file)
156
+ ext = File.extname(file)
157
+ dest_name = "#{File.basename(file, ext)}_#{digest}#{ext}"
158
+ dest_path = File.join(quarantine_folder, dest_name)
159
+
160
+ FileUtils.move(file, dest_path)
161
+ end
162
+
163
+ def check_rights(*required_rights)
164
+ current_rights = SECURITY_POLICIES["POLICY_#{@options[:policy].upcase}"]
165
+
166
+ reject(required_rights) if required_rights.any?{|right| current_rights[right.to_s] == false}
167
+ end
168
+
169
+ def analyze_xfa_forms(xfa)
170
+ case xfa
171
+ when Origami::Array then
172
+ xml = ""
173
+ i = 0
174
+ xfa.each do |packet|
175
+ if i % 2 == 1
176
+ xml << packet.solve.data
177
+ end
178
+
179
+ i = i + 1
180
+ end
181
+ when Origami::Stream then
182
+ xml = xfa.data
183
+ else
184
+ reject("Malformed XFA dictionary")
185
+ end
186
+
187
+ xfadoc = REXML::Document.new(xml)
188
+ REXML::XPath.match(xfadoc, "//script").each do |script|
189
+ case script.attributes["contentType"]
190
+ when "application/x-formcalc" then
191
+ check_rights(:allowFormCalc)
192
+ else
193
+ check_rights(:allowJS)
194
+ end
195
+ end
196
+ end
197
+
198
+ def check_annotation_rights(annot)
199
+ subtype = annot.Subtype.value
200
+
201
+ check_rights(*ANNOTATION_RIGHTS[subtype]) if ANNOTATION_RIGHTS.include?(subtype)
202
+ end
203
+
204
+ def analyze_annotation(annot, _level = 0)
205
+ check_rights(:allowAnnotations)
206
+
207
+ if annot.is_a?(Origami::Dictionary) and annot.key?(:Subtype)
208
+ check_annotation_rights(annot)
209
+
210
+ analyze_3d_annotation(annot) if annot.Subtype.value == :"3D"
211
+ end
212
+ end
213
+
214
+ def analyze_3d_annotation(annot)
215
+ # 3D annotation might pull in JavaScript for real-time driven behavior.
216
+ return unless annot.key?(:"3DD")
217
+
218
+ dd = annot[:"3DD"].solve
219
+ u3dstream = nil
220
+
221
+ case dd
222
+ when Origami::Stream
223
+ u3dstream = dd
224
+ when Origami::Dictionary
225
+ u3dstream = dd[:"3DD"].solve
226
+ end
227
+
228
+ if u3dstream.is_a?(Stream) and u3dstream.key?(:OnInstantiate)
229
+ check_rights(:allowJS)
230
+
231
+ if annot.key?(:"3DA") # is 3d view instantiated automatically?
232
+ u3dactiv = annot[:"3DA"].solve
233
+
234
+ check_rights(:allowJSAtOpening) if u3dactiv.is_a?(Origami::Dictionary) and (u3dactiv.A == :PO or u3dactiv.A == :PV)
235
+ end
236
+ end
237
+ end
238
+
239
+ def analyze_page(page, level = 0)
240
+ section_prefix = " " * 2 * level + ">" * (level + 1)
241
+ log(section_prefix + " Inspecting page...")
242
+
243
+ text_prefix = " " * 2 * (level + 1) + "." * (level + 1)
244
+ if page.is_a?(Origami::Dictionary)
245
+ #
246
+ # Checking page additional actions.
247
+ #
248
+ if page.key?(:AA)
249
+ if page.AA.is_a?(Origami::Dictionary)
250
+ log(text_prefix + " Page has an action dictionary.")
251
+
252
+ aa = Origami::Page::AdditionalActions.new(page.AA); aa.parent = page.AA.parent
253
+ analyze_action(aa.O, true, level + 1) if aa.key?(:O)
254
+ analyze_action(aa.C, false, level + 1) if aa.key?(:C)
255
+ end
256
+ end
257
+
258
+ #
259
+ # Looking for page annotations.
260
+ #
261
+ page.each_annotation do |annot|
262
+ analyze_annotation(annot, level + 1)
263
+ end
264
+ end
265
+ end
266
+
267
+ def analyze_action(action, triggered_at_opening, level = 0)
268
+ section_prefix = " " * 2 * level + ">" * (level + 1)
269
+ log(section_prefix + " Inspecting action...")
270
+
271
+ text_prefix = " " * 2 * (level + 1) + "." * (level + 1)
272
+ if action.is_a?(Origami::Dictionary)
273
+ log(text_prefix + " Found #{action[:S]} action.")
274
+ type = action[:S].is_a?(Origami::Reference) ? action[:S].solve : action[:S]
275
+
276
+ case type.value
277
+ when :JavaScript
278
+ check_rights(:allowJS)
279
+ check_rights(:allowJSAtOpening) if triggered_at_opening
280
+
281
+ when :Launch
282
+ check_rights(:allowLaunchAction)
283
+
284
+ when :Named
285
+ check_rights(:allowNamedAction)
286
+
287
+ when :GoTo
288
+ check_rights(:allowGoToAction)
289
+ dest = action[:D].is_a?(Origami::Reference) ? action[:D].solve : action[:D]
290
+ if dest.is_a?(Origami::Array) and dest.length > 0 and dest.first.is_a?(Origami::Reference)
291
+ dest_page = dest.first.solve
292
+ if dest_page.is_a?(Origami::Page)
293
+ log(text_prefix + " Destination page found.")
294
+ analyze_page(dest_page, level + 1)
295
+ end
296
+ end
297
+
298
+ when :GoToE
299
+ check_rights(:allowAttachments,:allowGoToEAction)
300
+
301
+ when :GoToR
302
+ check_rights(:allowGoToRAction)
303
+
304
+ when :Thread
305
+ check_rights(:allowGoToRAction) if action.key?(:F)
306
+
307
+ when :URI
308
+ check_rights(:allowURIAction)
309
+
310
+ when :SubmitForm
311
+ check_rights(:allowAcroForms,:allowSubmitFormAction)
312
+
313
+ when :ImportData
314
+ check_rights(:allowAcroForms,:allowImportDataAction)
315
+
316
+ when :Rendition
317
+ check_rights(:allowScreenAnnotation,:allowRenditionAction)
318
+
319
+ when :Sound
320
+ check_rights(:allowSoundAnnotation,:allowSoundAction)
321
+
322
+ when :Movie
323
+ check_rights(:allowMovieAnnotation,:allowMovieAction)
324
+
325
+ when :RichMediaExecute
326
+ check_rights(:allowRichMediaAnnotation,:allowRichMediaAction)
327
+
328
+ when :GoTo3DView
329
+ check_rights(:allow3DAnnotation,:allowGoTo3DAction)
330
+ end
331
+
332
+ if action.key?(:Next)
333
+ log(text_prefix + "This action is chained to another action!")
334
+ check_rights(:allowChainedActions)
335
+ analyze_action(action.Next)
336
+ end
337
+
338
+ elsif action.is_a?(Origami::Array)
339
+ dest = action
340
+ if dest.length > 0 and dest.first.is_a?(Origami::Reference)
341
+ dest_page = dest.first.solve
342
+ if dest_page.is_a?(Origami::Page)
343
+ log(text_prefix + " Destination page found.")
344
+ check_rights(:allowGoToAction)
345
+ analyze_page(dest_page, level + 1)
346
+ end
347
+ end
348
+ end
349
+ end
350
+
351
+ begin
352
+ log("PDFcop is running on target `#{TARGET}', policy = `#{@options[:policy]}'", :green)
353
+ log(" File size: #{File.size(TARGET)} bytes", :magenta)
354
+ log(" SHA256: #{Digest::SHA256.file(TARGET)}", :magenta)
355
+
356
+ @pdf = Origami::PDF.read(TARGET,
357
+ verbosity: Origami::Parser::VERBOSE_QUIET,
358
+ ignore_errors: SECURITY_POLICIES["POLICY_#{@options[:policy].upcase}"]['allowParserErrors'],
359
+ decrypt: SECURITY_POLICIES["POLICY_#{@options[:policy].upcase}"]['allowEncryption'],
360
+ prompt_password: lambda { '' },
361
+ password: @options[:password]
362
+ )
363
+
364
+ log("> Inspecting document structure...", :yellow)
365
+ if @pdf.encrypted?
366
+ log(" . Encryption = YES")
367
+ check_rights(:allowEncryption)
368
+ end
369
+
370
+ log("> Inspecting document catalog...", :yellow)
371
+ catalog = @pdf.Catalog
372
+ reject("Invalid document catalog") unless catalog.is_a?(Origami::Catalog)
373
+
374
+ if catalog.key?(:OpenAction)
375
+ log(" . OpenAction entry = YES")
376
+ check_rights(:allowOpenAction)
377
+ action = catalog.OpenAction
378
+ analyze_action(action, true, 1)
379
+ end
380
+
381
+ if catalog.key?(:AA)
382
+ if catalog.AA.is_a?(Origami::Dictionary)
383
+ aa = Origami::CatalogAdditionalActions.new(catalog.AA); aa.parent = catalog;
384
+ log(" . Additional actions dictionary = YES")
385
+ analyze_action(aa.WC, false, 1) if aa.key?(:WC)
386
+ analyze_action(aa.WS, false, 1) if aa.key?(:WS)
387
+ analyze_action(aa.DS, false, 1) if aa.key?(:DS)
388
+ analyze_action(aa.WP, false, 1) if aa.key?(:WP)
389
+ analyze_action(aa.DP, false, 1) if aa.key?(:DP)
390
+ end
391
+ end
392
+
393
+ if catalog.key?(:AcroForm)
394
+ acroform = catalog.AcroForm
395
+ if acroform.is_a?(Origami::Dictionary)
396
+ log(" . AcroForm = YES")
397
+ check_rights(:allowAcroForms)
398
+ if acroform.key?(:XFA)
399
+ log(" . XFA = YES")
400
+ check_rights(:allowXFAForms)
401
+
402
+ analyze_xfa_forms(acroform[:XFA].solve)
403
+ end
404
+ end
405
+ end
406
+
407
+ log("> Inspecting JavaScript names directory...", :yellow)
408
+ if @pdf.each_named_script.any?
409
+ check_rights(:allowJS)
410
+ check_rights(:allowJSAtOpening)
411
+ end
412
+
413
+ log("> Inspecting attachment names directory...", :yellow)
414
+ if @pdf.each_attachment.any?
415
+ check_rights(:allowAttachments)
416
+ end
417
+
418
+ log("> Inspecting document pages...", :yellow)
419
+ @pdf.each_page do |page|
420
+ analyze_page(page, 1)
421
+ end
422
+
423
+ log("> Inspecting document streams...", :yellow)
424
+ @pdf.each_object.select{|obj| obj.is_a?(Origami::Stream)}.each do |stream|
425
+ if stream.dictionary.key?(:Filter)
426
+ filters = stream.Filter
427
+ filters = [ filters ] if filters.is_a?(Origami::Name)
428
+
429
+ if filters.is_a?(Origami::Array)
430
+ filters.each do |filter|
431
+ case filter.value
432
+ when :ASCIIHexDecode
433
+ check_rights(:allowASCIIHexFilter)
434
+ when :ASCII85Decode
435
+ check_rights(:allowASCII85Filter)
436
+ when :LZWDecode
437
+ check_rights(:allowLZWFilter)
438
+ when :FlateDecode
439
+ check_rights(:allowFlateDecode)
440
+ when :RunLengthDecode
441
+ check_rights(:allowRunLengthFilter)
442
+ when :CCITTFaxDecode
443
+ check_rights(:allowCCITTFaxFilter)
444
+ when :JBIG2Decode
445
+ check_rights(:allowJBIG2Filter)
446
+ when :DCTDecode
447
+ check_rights(:allowDCTFilter)
448
+ when :JPXDecode
449
+ check_rights(:allowJPXFilter)
450
+ when :Crypt
451
+ check_rights(:allowCryptFilter)
452
+ end
453
+ end
454
+ end
455
+ end
456
+ end
457
+
458
+ #
459
+ # TODO: Detect JS at opening in XFA (check event tag)
460
+ # Check image encoding in XFA ?
461
+ # Only allow valid signed documents ?
462
+ # Recursively scan attached files.
463
+ # On-the-fly injection of prerun JS code to hook vulnerable methods (dynamic exploit detection) ???
464
+ # ...
465
+ #
466
+
467
+ log("Document accepted by policy `#{@options[:policy]}'.", :green)
468
+
469
+ rescue
470
+ log("An error occured during analysis : #{$!.class} (#{$!.message})")
471
+ reject("Analysis failure")
472
+ ensure
473
+ LOGGER.close
474
+ end
data/bin/pdfdecompress ADDED
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ =begin
4
+
5
+ = Info
6
+ Uncompresses all binary streams of a PDF document.
7
+
8
+ = License
9
+ Copyright (C) 2016 Guillaume Delugré.
10
+
11
+ Origami is free software: you can redistribute it and/or modify
12
+ it under the terms of the GNU Lesser General Public License as published by
13
+ the Free Software Foundation, either version 3 of the License, or
14
+ (at your option) any later version.
15
+
16
+ Origami is distributed in the hope that it will be useful,
17
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ GNU Lesser General Public License for more details.
20
+
21
+ You should have received a copy of the GNU Lesser General Public License
22
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
23
+
24
+ =end
25
+
26
+ begin
27
+ require 'origami'
28
+ rescue LoadError
29
+ $: << File.join(__dir__, '../lib')
30
+ require 'origami'
31
+ end
32
+ include Origami
33
+
34
+ require 'optparse'
35
+
36
+ class OptParser
37
+ BANNER = <<USAGE
38
+ Usage: #{$0} [<PDF-file>] [-p <password>] [-o <output-file>]
39
+ Uncompresses all binary streams of a PDF document.
40
+ Bug reports or feature requests at: http://github.com/gdelugre/origami
41
+
42
+ Options:
43
+ USAGE
44
+
45
+ def self.parser(options)
46
+ OptionParser.new do |opts|
47
+ opts.banner = BANNER
48
+
49
+ opts.on("-o", "--output FILE", "Output PDF file (stdout by default)") do |o|
50
+ options[:output] = o
51
+ end
52
+
53
+ opts.on_tail("-h", "--help", "Show this message") do
54
+ puts opts
55
+ exit
56
+ end
57
+ end
58
+ end
59
+
60
+ def self.parse(args)
61
+ options =
62
+ {
63
+ output: STDOUT,
64
+ }
65
+
66
+ self.parser(options).parse!(args)
67
+
68
+ options
69
+ end
70
+ end
71
+
72
+ begin
73
+ @options = OptParser.parse(ARGV)
74
+
75
+ target = (ARGV.empty?) ? STDIN : ARGV.shift
76
+ params =
77
+ {
78
+ verbosity: Parser::VERBOSE_QUIET,
79
+ }
80
+
81
+ pdf = PDF.read(target, params)
82
+
83
+ pdf.each_object
84
+ .select { |obj| obj.is_a?(Stream) }
85
+ .each { |stream|
86
+ unless stream.filters.any?{|filter| %i[JPXDecode DCTDecode JBIG2Decode].include?(filter.value) }
87
+ stream.encoded_data = stream.data
88
+ stream.dictionary.delete(:Filter)
89
+ end
90
+ }
91
+
92
+ pdf.save(@options[:output], noindent: true)
93
+
94
+ rescue
95
+ STDERR.puts $!.backtrace.join($/)
96
+ abort "#{$!.class}: #{$!.message}"
97
+ end
data/bin/pdfdecrypt ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ =begin
4
+
5
+ = Info
6
+ Decrypts a PDF document.
7
+
8
+ = License
9
+ Copyright (C) 2016 Guillaume Delugré.
10
+
11
+ Origami is free software: you can redistribute it and/or modify
12
+ it under the terms of the GNU Lesser General Public License as published by
13
+ the Free Software Foundation, either version 3 of the License, or
14
+ (at your option) any later version.
15
+
16
+ Origami is distributed in the hope that it will be useful,
17
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ GNU Lesser General Public License for more details.
20
+
21
+ You should have received a copy of the GNU Lesser General Public License
22
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
23
+
24
+ =end
25
+
26
+ begin
27
+ require 'origami'
28
+ rescue LoadError
29
+ $: << File.join(__dir__, '../lib')
30
+ require 'origami'
31
+ end
32
+ include Origami
33
+
34
+ require 'optparse'
35
+
36
+ class OptParser
37
+ BANNER = <<USAGE
38
+ Usage: #{$0} [<PDF-file>] [-p <password>] [-o <output-file>]
39
+ Decrypts a PDF document. Supports RC4 40 to 128 bits, AES128, AES256.
40
+ Bug reports or feature requests at: http://github.com/gdelugre/origami
41
+
42
+ Options:
43
+ USAGE
44
+
45
+ def self.parser(options)
46
+ OptionParser.new do |opts|
47
+ opts.banner = BANNER
48
+
49
+ opts.on("-o", "--output FILE", "Output PDF file (stdout by default)") do |o|
50
+ options[:output] = o
51
+ end
52
+
53
+ opts.on("-p", "--password PASSWORD", "Password of the document") do |p|
54
+ options[:password] = p
55
+ end
56
+
57
+ opts.on_tail("-h", "--help", "Show this message") do
58
+ puts opts
59
+ exit
60
+ end
61
+ end
62
+ end
63
+
64
+ def self.parse(args)
65
+ options =
66
+ {
67
+ output: STDOUT,
68
+ password: ''
69
+ }
70
+
71
+ self.parser(options).parse!(args)
72
+
73
+ options
74
+ end
75
+ end
76
+
77
+ begin
78
+ @options = OptParser.parse(ARGV)
79
+
80
+ target = (ARGV.empty?) ? STDIN : ARGV.shift
81
+ params =
82
+ {
83
+ verbosity: Parser::VERBOSE_QUIET,
84
+ password: @options[:password]
85
+ }
86
+
87
+ PDF.read(target, params).save(@options[:output], decrypt: true, noindent: true)
88
+
89
+ rescue
90
+ abort "#{$!.class}: #{$!.message}"
91
+ end