origami 1.2.5 → 1.2.6

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 (54) hide show
  1. checksums.yaml +7 -0
  2. data/bin/gui/config.rb +0 -4
  3. data/bin/gui/imgview.rb +2 -2
  4. data/bin/gui/menu.rb +11 -3
  5. data/bin/gui/treeview.rb +9 -3
  6. data/bin/pdfexplode +220 -0
  7. data/bin/pdfextract +3 -0
  8. data/lib/origami/acroform.rb +2 -2
  9. data/lib/origami/actions.rb +62 -35
  10. data/lib/origami/annotations.rb +3 -2
  11. data/lib/origami/array.rb +27 -4
  12. data/lib/origami/boolean.rb +2 -2
  13. data/lib/origami/catalog.rb +45 -45
  14. data/lib/origami/dictionary.rb +87 -14
  15. data/lib/origami/encryption.rb +46 -24
  16. data/lib/origami/file.rb +1 -2
  17. data/lib/origami/filters/ccitt.rb +118 -66
  18. data/lib/origami/filters/flate.rb +5 -1
  19. data/lib/origami/filters.rb +84 -2
  20. data/lib/origami/font.rb +71 -71
  21. data/lib/origami/graphics/patterns.rb +2 -1
  22. data/lib/origami/graphics/xobject.rb +123 -1
  23. data/lib/origami/javascript.rb +2 -1
  24. data/lib/origami/name.rb +2 -2
  25. data/lib/origami/null.rb +2 -2
  26. data/lib/origami/numeric.rb +11 -3
  27. data/lib/origami/object.rb +37 -16
  28. data/lib/origami/page.rb +135 -71
  29. data/lib/origami/parser.rb +11 -4
  30. data/lib/origami/parsers/pdf/linear.rb +1 -0
  31. data/lib/origami/parsers/pdf.rb +10 -0
  32. data/lib/origami/pdf.rb +10 -70
  33. data/lib/origami/reference.rb +4 -5
  34. data/lib/origami/signature.rb +22 -8
  35. data/lib/origami/stream.rb +41 -20
  36. data/lib/origami/string.rb +15 -6
  37. data/lib/origami/trailer.rb +9 -5
  38. data/lib/origami.rb +19 -0
  39. data/samples/actions/loop/loopgoto.rb +1 -1
  40. data/samples/actions/loop/loopnamed.rb +2 -2
  41. data/samples/actions/named/named.rb +1 -1
  42. data/samples/actions/samba/smbrelay.rb +1 -1
  43. data/samples/actions/triggerevents/trigger.rb +13 -13
  44. data/samples/actions/webbug/webbug-browser.rb +1 -1
  45. data/samples/actions/webbug/webbug-js.rb +1 -1
  46. data/samples/actions/webbug/webbug-reader.rb +1 -1
  47. data/samples/attachments/attach.rb +2 -2
  48. data/samples/exploits/cve-2008-2992-utilprintf.rb +1 -1
  49. data/samples/exploits/cve-2009-0927-geticon.rb +1 -1
  50. data/samples/exploits/exploit_customdictopen.rb +2 -2
  51. data/samples/exploits/getannots.rb +1 -1
  52. data/samples/javascript/js.rb +2 -2
  53. data/test/ts_pdf.rb +23 -23
  54. metadata +71 -86
@@ -284,51 +284,6 @@ module Origami
284
284
  ATTACHMENTS = :UseAttachments
285
285
  end
286
286
 
287
- #
288
- # Class representing the Catalog Dictionary of a PDF file.
289
- #
290
- class Catalog < Dictionary
291
-
292
- include StandardObject
293
-
294
- field :Type, :Type => Name, :Default => :Catalog, :Required => true
295
- field :Version, :Type => Name, :Version => "1.4"
296
- field :Pages, :Type => Dictionary, :Required => true
297
- field :PageLabels, :Type => Dictionary, :Version => "1.3"
298
- field :Names, :Type => Dictionary, :Version => "1.2"
299
- field :Dests, :Type => Dictionary, :Version => "1.1"
300
- field :ViewerPreferences, :Type => Dictionary, :Version => "1.2"
301
- field :PageLayout, :Type => Name, :Default => PageLayout::SINGLE
302
- field :PageMode, :Type => Name, :Default => PageMode::NONE
303
- field :Outlines, :Type => Dictionary
304
- field :Threads, :Type => Array, :Version => "1.1"
305
- field :OpenAction, :Type => [ Array, Dictionary ], :Version => "1.1"
306
- field :AA, :Type => Dictionary, :Version => "1.4"
307
- field :URI, :Type => Dictionary, :Version => "1.1"
308
- field :AcroForm, :Type => Dictionary, :Version => "1.2"
309
- field :Metadata, :Type => Stream, :Version => "1.4"
310
- field :StructTreeRoot, :Type => Dictionary, :Version => "1.3"
311
- field :MarkInfo, :Type => Dictionary, :Version => "1.4"
312
- field :Lang, :Type => String, :Version => "1.4"
313
- field :SpiderInfo, :Type => Dictionary, :Version => "1.3"
314
- field :OutputIntents, :Type => Array, :Version => "1.4"
315
- field :PieceInfo, :Type => Dictionary, :Version => "1.4"
316
- field :OCProperties, :Type => Dictionary, :Version => "1.5"
317
- field :Perms, :Type => Dictionary, :Version => "1.5"
318
- field :Legal, :Type => Dictionary, :Version => "1.5"
319
- field :Requirements, :Type => Array, :Version => "1.7"
320
- field :Collection, :Type => Dictionary, :Version => "1.7"
321
- field :NeedsRendering, :Type => Boolean, :Version => "1.7", :Default => false
322
- field :Extensions, :Type => Dictionary, :Version => "1.7", :ExtensionLevel => 3
323
-
324
- def initialize(hash = {})
325
- set_indirect(true)
326
-
327
- super(hash)
328
- end
329
-
330
- end
331
-
332
287
  #
333
288
  # Class representing additional actions which can be associated with a Catalog.
334
289
  #
@@ -482,4 +437,49 @@ module Origami
482
437
 
483
438
  end
484
439
 
440
+ #
441
+ # Class representing the Catalog Dictionary of a PDF file.
442
+ #
443
+ class Catalog < Dictionary
444
+
445
+ include StandardObject
446
+
447
+ field :Type, :Type => Name, :Default => :Catalog, :Required => true
448
+ field :Version, :Type => Name, :Version => "1.4"
449
+ field :Pages, :Type => Dictionary, :Required => true
450
+ field :PageLabels, :Type => Dictionary, :Version => "1.3"
451
+ field :Names, :Type => Dictionary, :Version => "1.2"
452
+ field :Dests, :Type => Dictionary, :Version => "1.1"
453
+ field :ViewerPreferences, :Type => ViewerPreferences, :Version => "1.2"
454
+ field :PageLayout, :Type => Name, :Default => PageLayout::SINGLE
455
+ field :PageMode, :Type => Name, :Default => PageMode::NONE
456
+ field :Outlines, :Type => Dictionary
457
+ field :Threads, :Type => Array, :Version => "1.1"
458
+ field :OpenAction, :Type => [ Array, Dictionary ], :Version => "1.1"
459
+ field :AA, :Type => Dictionary, :Version => "1.4"
460
+ field :URI, :Type => Dictionary, :Version => "1.1"
461
+ field :AcroForm, :Type => Dictionary, :Version => "1.2"
462
+ field :Metadata, :Type => Stream, :Version => "1.4"
463
+ field :StructTreeRoot, :Type => Dictionary, :Version => "1.3"
464
+ field :MarkInfo, :Type => Dictionary, :Version => "1.4"
465
+ field :Lang, :Type => String, :Version => "1.4"
466
+ field :SpiderInfo, :Type => Dictionary, :Version => "1.3"
467
+ field :OutputIntents, :Type => Array, :Version => "1.4"
468
+ field :PieceInfo, :Type => Dictionary, :Version => "1.4"
469
+ field :OCProperties, :Type => Dictionary, :Version => "1.5"
470
+ field :Perms, :Type => Dictionary, :Version => "1.5"
471
+ field :Legal, :Type => Dictionary, :Version => "1.5"
472
+ field :Requirements, :Type => Array, :Version => "1.7"
473
+ field :Collection, :Type => Dictionary, :Version => "1.7"
474
+ field :NeedsRendering, :Type => Boolean, :Version => "1.7", :Default => false
475
+ field :Extensions, :Type => Dictionary, :Version => "1.7", :ExtensionLevel => 3
476
+
477
+ def initialize(hash = {})
478
+ set_indirect(true)
479
+
480
+ super(hash)
481
+ end
482
+
483
+ end
484
+
485
485
  end
@@ -1,5 +1,4 @@
1
1
  =begin
2
-
3
2
  = File
4
3
  dictionary.rb
5
4
 
@@ -39,6 +38,7 @@ module Origami
39
38
  @@regexp_open = Regexp.new(WHITESPACES + Regexp.escape(TOKENS.first) + WHITESPACES)
40
39
  @@regexp_close = Regexp.new(WHITESPACES + Regexp.escape(TOKENS.last) + WHITESPACES)
41
40
 
41
+ @@cast_fingerprints = {}
42
42
  attr_reader :strings_cache, :names_cache, :xref_cache
43
43
 
44
44
  #
@@ -76,7 +76,7 @@ module Origami
76
76
  end
77
77
  end
78
78
 
79
- def self.parse(stream) #:nodoc:
79
+ def self.parse(stream, parser = nil) #:nodoc:
80
80
 
81
81
  offset = stream.pos
82
82
 
@@ -86,28 +86,44 @@ module Origami
86
86
 
87
87
  pairs = {}
88
88
  while stream.skip(@@regexp_close).nil? do
89
- key = Name.parse(stream)
89
+ key = Name.parse(stream, parser)
90
90
 
91
91
  type = Object.typeof(stream)
92
92
  if type.nil?
93
93
  raise InvalidDictionaryObjectError, "Invalid object for field #{key.to_s}"
94
94
  end
95
- value = type.parse(stream)
96
95
 
96
+ value = type.parse(stream, parser)
97
97
  pairs[key] = value
98
98
  end
99
99
 
100
100
  dict =
101
101
  if Origami::OPTIONS[:enable_type_guessing]
102
- type = pairs[Name.new(:Type)]
103
- if type.is_a?(Name) and DICT_SPECIAL_TYPES.include?(type.value)
104
- DICT_SPECIAL_TYPES[type.value].new(pairs)
102
+ guessed_type = self.guess_type(pairs)
103
+
104
+ if Origami::OPTIONS[:enable_type_propagation]
105
+ guessed_type.new(
106
+ Hash[
107
+ pairs.map {|key, value|
108
+ hint_type = guessed_type.hint_type(key.value)
109
+ if hint_type.is_a?(::Array) and not value.is_a?(Reference) # Choose best match
110
+ hint_type.find {|type| type.native_type == value.native_type}
111
+ end
112
+
113
+ if hint_type.is_a?(Class) and hint_type.native_type == value.native_type
114
+ [key, value.cast_to(hint_type)]
115
+ elsif hint_type and value.is_a?(Reference) and parser
116
+ parser.defer_type_cast(value, hint_type)
117
+ [key, value]
118
+ else
119
+ [key, value]
120
+ end
121
+ }])
105
122
  else
106
- Dictionary.new(pairs)
123
+ guessed_type.new(pairs)
107
124
  end
108
-
109
125
  else
110
- Dictionary.new(pairs)
126
+ self.new(pairs)
111
127
  end
112
128
 
113
129
  dict.file_offset = offset
@@ -117,7 +133,7 @@ module Origami
117
133
 
118
134
  alias to_h to_hash
119
135
 
120
- def to_s(indent = 1) #:nodoc:
136
+ def to_s(indent = 1) #:nodoc:
121
137
  if indent > 0
122
138
  content = TOKENS.first + EOL
123
139
  self.each_pair do |key,value|
@@ -177,9 +193,26 @@ module Origami
177
193
  super(key.to_o)
178
194
  end
179
195
 
180
- alias each each_value
196
+ def cast_to(type)
197
+ super(type)
198
+
199
+ cast = type.new(self)
200
+ cast.parent = self.parent
201
+ cast.no, cast.generation = self.no, self.generation
202
+ if self.is_indirect?
203
+ cast.set_indirect(true)
204
+ cast.set_pdf(self.pdf)
205
+ cast.file_offset = self.file_offset # cast can replace self
206
+ end
181
207
 
182
- def real_type ; Dictionary end
208
+ cast.xref_cache.update(self.xref_cache)
209
+ cast.names_cache.concat(self.names_cache)
210
+ cast.strings_cache.concat(self.strings_cache)
211
+
212
+ cast
213
+ end
214
+
215
+ alias each each_value
183
216
 
184
217
  alias value to_h
185
218
 
@@ -194,6 +227,46 @@ module Origami
194
227
  end
195
228
  end
196
229
 
230
+ def copy
231
+ copy = self.class.new
232
+ self.each_pair do |k,v|
233
+ copy[k] = v.copy
234
+ end
235
+
236
+ copy.parent = @parent
237
+ copy.no, copy.generation = @no, @generation
238
+ copy.set_indirect(true) if is_indirect?
239
+ copy.set_pdf(@pdf) if is_indirect?
240
+ copy
241
+ end
242
+
243
+ def self.native_type; Dictionary end
244
+
245
+ def self.add_type_info(typeclass, key, value) #:nodoc:
246
+ if not @@cast_fingerprints.has_key?(typeclass) and typeclass.superclass != Dictionary and
247
+ @@cast_fingerprints.has_key?(typeclass.superclass)
248
+ @@cast_fingerprints[typeclass] = @@cast_fingerprints[typeclass.superclass].dup
249
+ end
250
+
251
+ @@cast_fingerprints[typeclass] ||= {}
252
+ @@cast_fingerprints[typeclass][key.to_o] = value.to_o
253
+ end
254
+
255
+ def self.guess_type(hash) #:nodoc:
256
+ best_type = self
257
+
258
+ @@cast_fingerprints.each_pair do |typeclass, keys|
259
+ best_type = typeclass if keys.all? { |k,v|
260
+ hash.has_key?(k) and hash[k] == v
261
+ } and typeclass < best_type
262
+ end
263
+
264
+ best_type
265
+ end
266
+
267
+ def self.hint_type(name); nil end #:nodoc:
268
+
197
269
  end #class
198
270
 
199
- end # Origami
271
+ end
272
+ # Origami
@@ -23,6 +23,12 @@
23
23
 
24
24
  =end
25
25
 
26
+ begin
27
+ require 'openssl' if Origami::OPTIONS[:use_openssl]
28
+ rescue LoadError
29
+ Origami::OPTIONS[:use_openssl] = false
30
+ end
31
+
26
32
  require 'digest/md5'
27
33
  require 'digest/sha2'
28
34
 
@@ -163,20 +169,18 @@ module Origami
163
169
  obj.equal?(encrypt_dict[:Perms]) or
164
170
  (obj.parent.is_a?(Signature::DigitalSignature) and obj.equal?(obj.parent[:Contents]))
165
171
 
166
- obj.extend(Encryption::EncryptedString)
172
+ obj.extend(Encryption::EncryptedString) unless obj.is_a?(Encryption::EncryptedString)
167
173
  obj.encryption_handler = handler
168
174
  obj.encryption_key = encryption_key
169
175
  obj.algorithm = str_algo
170
- obj.decrypted = false
171
176
  obj.decrypt!
172
177
 
173
178
  when Stream
174
179
  next if obj.is_a?(XRefStream) or (not encrypt_metadata and obj.equal?(metadata))
175
- obj.extend(Encryption::EncryptedStream)
180
+ obj.extend(Encryption::EncryptedStream) unless obj.is_a?(Encryption::EncryptedStream)
176
181
  obj.encryption_handler = handler
177
182
  obj.encryption_key = encryption_key
178
183
  obj.algorithm = stm_algo
179
- obj.decrypted = false
180
184
  end
181
185
  end
182
186
  end
@@ -293,6 +297,26 @@ module Origami
293
297
  #
294
298
  module Encryption
295
299
 
300
+ #
301
+ # Generates _n_ random bytes from a fast PRNG.
302
+ #
303
+ def self.rand_bytes(n)
304
+ ::Array.new(n) { rand(256) }.pack("C*")
305
+ end
306
+
307
+ #
308
+ # Generates _n_ random bytes from a crypto PRNG.
309
+ #
310
+ def self.strong_rand_bytes(n)
311
+ if Origami::OPTIONS[:use_openssl]
312
+ OpenSSL::Random.random_bytes(n)
313
+ elsif RUBY_VERSION >= '1.9'
314
+ Random.new.bytes(n)
315
+ else
316
+ self.rand_bytes(n)
317
+ end
318
+ end
319
+
296
320
  module EncryptedDocument
297
321
 
298
322
  attr_writer :encryption_key
@@ -442,7 +466,7 @@ module Origami
442
466
  if @algorithm == ARC4 or @algorithm == Identity
443
467
  @algorithm.encrypt(key, self.value)
444
468
  else
445
- iv = ::Array.new(AES::BLOCKSIZE) { rand(256) }.pack('C*')
469
+ iv = Encryption.rand_bytes(AES::BLOCKSIZE)
446
470
  @algorithm.encrypt(key, iv, self.value)
447
471
  end
448
472
 
@@ -482,7 +506,7 @@ module Origami
482
506
  if @algorithm == ARC4 or @algorithm == Identity
483
507
  @algorithm.encrypt(key, self.rawdata)
484
508
  else
485
- iv = ::Array.new(AES::BLOCKSIZE) { rand(256) }.pack('C*')
509
+ iv = Encryption.rand_bytes(AES::BLOCKSIZE)
486
510
  @algorithm.encrypt(key, iv, @rawdata)
487
511
  end
488
512
 
@@ -1043,6 +1067,7 @@ module Origami
1043
1067
  module Standard
1044
1068
 
1045
1069
  PADDING = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A" #:nodoc:
1070
+ PADDING.force_encoding('binary') if RUBY_VERSION > '1.8'
1046
1071
 
1047
1072
  #
1048
1073
  # Permission constants for encrypted documents.
@@ -1090,6 +1115,7 @@ module Origami
1090
1115
 
1091
1116
  if self.R < 5
1092
1117
  padded = pad_password(userpassword)
1118
+ padded.force_encoding('binary') if RUBY_VERSION > '1.8'
1093
1119
 
1094
1120
  padded << self.O
1095
1121
  padded << [ self.P ].pack("i")
@@ -1097,7 +1123,7 @@ module Origami
1097
1123
  padded << fileid
1098
1124
 
1099
1125
  encrypt_metadata = self.EncryptMetadata != false
1100
- padded << "\xFF\xFF\xFF\xFF" if self.R >= 4 and not encrypt_metadata
1126
+ padded << [ -1 ].pack("i") if self.R >= 4 and not encrypt_metadata
1101
1127
 
1102
1128
  key = Digest::MD5.digest(padded)
1103
1129
 
@@ -1135,9 +1161,9 @@ module Origami
1135
1161
  oks = self.O[40, 8]
1136
1162
 
1137
1163
  if self.R == 5
1138
- okey = Digest::SHA256.digest(passwd + oks)
1164
+ okey = Digest::SHA256.digest(passwd + oks + self.U)
1139
1165
  else
1140
- okey = compute_hardened_hash(passwd, oks)
1166
+ okey = compute_hardened_hash(passwd, oks, self.U)
1141
1167
  end
1142
1168
 
1143
1169
  iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
@@ -1163,35 +1189,31 @@ module Origami
1163
1189
  upass = password_to_utf8(userpassword)
1164
1190
  opass = password_to_utf8(ownerpassword)
1165
1191
 
1166
- uvs, uks, ovs, oks = ::Array.new(4) { ::Array.new(8) { rand(255) }.pack("C*") }
1167
- file_key = ::Array.new(32) { rand(256) }.pack("C*")
1192
+ uvs, uks, ovs, oks = ::Array.new(4) { Encryption.rand_bytes(8) }
1193
+ file_key = Encryption.strong_rand_bytes(32)
1168
1194
  iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
1169
1195
 
1170
1196
  if self.R == 5
1197
+ self.U = Digest::SHA256.digest(upass + uvs) + uvs + uks
1198
+ self.O = Digest::SHA256.digest(opass + ovs + self.U) + ovs + oks
1171
1199
  ukey = Digest::SHA256.digest(upass + uks)
1172
- okey = Digest::SHA256.digest(opass + oks)
1200
+ okey = Digest::SHA256.digest(opass + oks + self.U)
1173
1201
  else
1202
+ self.U = compute_hardened_hash(upass, uvs) + uvs + uks
1203
+ self.O = compute_hardened_hash(opass, ovs, self.U) + ovs + oks
1174
1204
  ukey = compute_hardened_hash(upass, uks)
1175
- okey = compute_hardened_hash(upass, oks)
1205
+ okey = compute_hardened_hash(opass, oks, self.U)
1176
1206
  end
1177
1207
 
1178
1208
  self.UE = AES.new(ukey, iv, false).encrypt(file_key)[iv.size, 32]
1179
1209
  self.OE = AES.new(okey, iv, false).encrypt(file_key)[iv.size, 32]
1180
-
1181
- if self.R == 5
1182
- self.U = Digest::SHA256.digest(upass + uvs) + uvs + uks
1183
- self.O = Digest::SHA256.digest(opass + ovs + self.U) + ovs + oks
1184
- else
1185
- self.U = compute_hardened_hash(upass, uvs) + uvs + uks
1186
- self.O = compute_hardened_hash(opass, ovs, self.U) + ovs + oks
1187
- end
1188
1210
 
1189
1211
  perms =
1190
1212
  [ self.P ].pack("V") + # 0-3
1191
- "\xff" * 4 + # 4-7
1213
+ [ -1 ].pack("V") + # 4-7
1192
1214
  (self.EncryptMetadata == true ? "T" : "F") + # 8
1193
1215
  "adb" + # 9-11
1194
- "\x00" * 4 # 12-15
1216
+ [ 0 ].pack("V") # 12-15
1195
1217
 
1196
1218
  self.Perms = AES.new(file_key, iv, false).encrypt(perms)[iv.size, 16]
1197
1219
 
@@ -1294,7 +1316,7 @@ module Origami
1294
1316
 
1295
1317
  19.times { |i| user_key = ARC4.encrypt(xor(key,i+1), user_key) }
1296
1318
 
1297
- user_key.ljust(32, "\xFF")
1319
+ user_key.ljust(32, 0xFF.chr)
1298
1320
  end
1299
1321
  end
1300
1322
 
data/lib/origami/file.rb CHANGED
@@ -182,8 +182,7 @@ module Origami
182
182
  # A class representing a file outside the current PDF file.
183
183
  #
184
184
  class ExternalFile < FileSpec
185
-
186
- field :Type, :Type => Name, :Default => :FileSpec, :Required => true
185
+ field :Type, :Type => Name, :Default => :FileSpec #, :Required => true
187
186
 
188
187
  #
189
188
  # Creates a new external file specification.