origami 1.2.7 → 2.0.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 (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -0
  3. data/README.md +112 -0
  4. data/bin/config/pdfcop.conf.yml +232 -233
  5. data/bin/gui/about.rb +27 -37
  6. data/bin/gui/config.rb +108 -117
  7. data/bin/gui/file.rb +416 -365
  8. data/bin/gui/gtkhex.rb +1138 -1153
  9. data/bin/gui/hexview.rb +55 -57
  10. data/bin/gui/imgview.rb +48 -51
  11. data/bin/gui/menu.rb +388 -386
  12. data/bin/gui/properties.rb +114 -130
  13. data/bin/gui/signing.rb +571 -617
  14. data/bin/gui/textview.rb +77 -95
  15. data/bin/gui/treeview.rb +382 -387
  16. data/bin/gui/walker.rb +227 -232
  17. data/bin/gui/xrefs.rb +56 -60
  18. data/bin/pdf2pdfa +53 -57
  19. data/bin/pdf2ruby +212 -228
  20. data/bin/pdfcop +338 -348
  21. data/bin/pdfdecompress +58 -65
  22. data/bin/pdfdecrypt +56 -60
  23. data/bin/pdfencrypt +75 -80
  24. data/bin/pdfexplode +185 -182
  25. data/bin/pdfextract +201 -218
  26. data/bin/pdfmetadata +83 -82
  27. data/bin/pdfsh +4 -5
  28. data/bin/pdfwalker +1 -2
  29. data/bin/shell/.irbrc +45 -82
  30. data/bin/shell/console.rb +105 -130
  31. data/bin/shell/hexdump.rb +40 -64
  32. data/examples/README.md +34 -0
  33. data/examples/attachments/attachment.rb +38 -0
  34. data/examples/attachments/nested_document.rb +51 -0
  35. data/examples/encryption/encryption.rb +28 -0
  36. data/{samples/actions/triggerevents/trigger.rb → examples/events/events.rb} +13 -16
  37. data/examples/flash/flash.rb +37 -0
  38. data/{samples → examples}/flash/helloworld.swf +0 -0
  39. data/examples/forms/javascript.rb +54 -0
  40. data/examples/forms/xfa.rb +115 -0
  41. data/examples/javascript/hello_world.rb +22 -0
  42. data/examples/javascript/js_emulation.rb +54 -0
  43. data/examples/loop/goto.rb +32 -0
  44. data/examples/loop/named.rb +33 -0
  45. data/examples/signature/signature.rb +65 -0
  46. data/examples/uri/javascript.rb +56 -0
  47. data/examples/uri/open-uri.rb +21 -0
  48. data/examples/uri/submitform.rb +47 -0
  49. data/lib/origami.rb +29 -42
  50. data/lib/origami/3d.rb +350 -225
  51. data/lib/origami/acroform.rb +262 -288
  52. data/lib/origami/actions.rb +268 -288
  53. data/lib/origami/annotations.rb +697 -722
  54. data/lib/origami/array.rb +258 -184
  55. data/lib/origami/boolean.rb +74 -84
  56. data/lib/origami/catalog.rb +397 -434
  57. data/lib/origami/collections.rb +144 -0
  58. data/lib/origami/destinations.rb +233 -194
  59. data/lib/origami/dictionary.rb +253 -232
  60. data/lib/origami/encryption.rb +1274 -1243
  61. data/lib/origami/export.rb +232 -268
  62. data/lib/origami/extensions/fdf.rb +307 -220
  63. data/lib/origami/extensions/ppklite.rb +368 -435
  64. data/lib/origami/filespec.rb +197 -0
  65. data/lib/origami/filters.rb +301 -295
  66. data/lib/origami/filters/ascii.rb +177 -180
  67. data/lib/origami/filters/ccitt.rb +528 -535
  68. data/lib/origami/filters/crypt.rb +26 -35
  69. data/lib/origami/filters/dct.rb +46 -52
  70. data/lib/origami/filters/flate.rb +95 -94
  71. data/lib/origami/filters/jbig2.rb +49 -55
  72. data/lib/origami/filters/jpx.rb +38 -44
  73. data/lib/origami/filters/lzw.rb +189 -183
  74. data/lib/origami/filters/predictors.rb +221 -235
  75. data/lib/origami/filters/runlength.rb +103 -104
  76. data/lib/origami/font.rb +173 -186
  77. data/lib/origami/functions.rb +67 -81
  78. data/lib/origami/graphics.rb +25 -21
  79. data/lib/origami/graphics/colors.rb +178 -187
  80. data/lib/origami/graphics/instruction.rb +79 -85
  81. data/lib/origami/graphics/path.rb +142 -148
  82. data/lib/origami/graphics/patterns.rb +160 -167
  83. data/lib/origami/graphics/render.rb +43 -50
  84. data/lib/origami/graphics/state.rb +138 -153
  85. data/lib/origami/graphics/text.rb +188 -205
  86. data/lib/origami/graphics/xobject.rb +819 -815
  87. data/lib/origami/header.rb +63 -78
  88. data/lib/origami/javascript.rb +596 -597
  89. data/lib/origami/linearization.rb +285 -290
  90. data/lib/origami/metadata.rb +139 -148
  91. data/lib/origami/name.rb +112 -148
  92. data/lib/origami/null.rb +53 -62
  93. data/lib/origami/numeric.rb +162 -175
  94. data/lib/origami/obfuscation.rb +186 -174
  95. data/lib/origami/object.rb +593 -573
  96. data/lib/origami/outline.rb +42 -47
  97. data/lib/origami/outputintents.rb +73 -82
  98. data/lib/origami/page.rb +703 -592
  99. data/lib/origami/parser.rb +238 -290
  100. data/lib/origami/parsers/fdf.rb +41 -33
  101. data/lib/origami/parsers/pdf.rb +75 -95
  102. data/lib/origami/parsers/pdf/lazy.rb +137 -0
  103. data/lib/origami/parsers/pdf/linear.rb +64 -66
  104. data/lib/origami/parsers/ppklite.rb +34 -70
  105. data/lib/origami/pdf.rb +1030 -1005
  106. data/lib/origami/reference.rb +102 -102
  107. data/lib/origami/signature.rb +591 -609
  108. data/lib/origami/stream.rb +668 -551
  109. data/lib/origami/string.rb +397 -373
  110. data/lib/origami/template/patterns.rb +56 -0
  111. data/lib/origami/template/widgets.rb +151 -0
  112. data/lib/origami/trailer.rb +144 -158
  113. data/lib/origami/tree.rb +62 -0
  114. data/lib/origami/version.rb +23 -0
  115. data/lib/origami/webcapture.rb +88 -79
  116. data/lib/origami/xfa.rb +2863 -2882
  117. data/lib/origami/xreftable.rb +472 -384
  118. data/test/dataset/calc.pdf +85 -0
  119. data/test/dataset/crypto.pdf +82 -0
  120. data/test/dataset/empty.pdf +49 -0
  121. data/test/test_actions.rb +27 -0
  122. data/test/test_annotations.rb +90 -0
  123. data/test/test_pages.rb +31 -0
  124. data/test/test_pdf.rb +16 -0
  125. data/test/test_pdf_attachment.rb +34 -0
  126. data/test/test_pdf_create.rb +24 -0
  127. data/test/test_pdf_encrypt.rb +95 -0
  128. data/test/test_pdf_parse.rb +96 -0
  129. data/test/test_pdf_sign.rb +58 -0
  130. data/test/test_streams.rb +182 -0
  131. data/test/test_xrefs.rb +67 -0
  132. metadata +88 -58
  133. data/README +0 -67
  134. data/bin/pdf2graph +0 -121
  135. data/bin/pdfcocoon +0 -104
  136. data/lib/origami/file.rb +0 -233
  137. data/samples/README.txt +0 -45
  138. data/samples/actions/launch/calc.rb +0 -87
  139. data/samples/actions/launch/winparams.rb +0 -22
  140. data/samples/actions/loop/loopgoto.rb +0 -24
  141. data/samples/actions/loop/loopnamed.rb +0 -21
  142. data/samples/actions/named/named.rb +0 -31
  143. data/samples/actions/samba/smbrelay.rb +0 -26
  144. data/samples/actions/webbug/submitform.js +0 -26
  145. data/samples/actions/webbug/webbug-browser.rb +0 -68
  146. data/samples/actions/webbug/webbug-js.rb +0 -67
  147. data/samples/actions/webbug/webbug-reader.rb +0 -90
  148. data/samples/attachments/attach.rb +0 -40
  149. data/samples/attachments/attached.txt +0 -1
  150. data/samples/crypto/crypto.rb +0 -28
  151. data/samples/digsig/signed.rb +0 -46
  152. data/samples/exploits/cve-2008-2992-utilprintf.rb +0 -87
  153. data/samples/exploits/cve-2009-0927-geticon.rb +0 -65
  154. data/samples/exploits/exploit_customdictopen.rb +0 -55
  155. data/samples/exploits/getannots.rb +0 -69
  156. data/samples/flash/flash.rb +0 -31
  157. data/samples/javascript/attached.txt +0 -1
  158. data/samples/javascript/js.rb +0 -52
  159. data/templates/patterns.rb +0 -66
  160. data/templates/widgets.rb +0 -173
  161. data/templates/xdp.rb +0 -92
  162. data/test/ts_pdf.rb +0 -50
@@ -1,433 +1,457 @@
1
1
  =begin
2
2
 
3
- = File
4
- string.rb
5
-
6
- = Info
7
- This file is part of Origami, PDF manipulation framework for Ruby
8
- Copyright (C) 2010 Guillaume Delugré <guillaume AT security-labs DOT org>
9
- All right reserved.
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/>.
3
+ This file is part of Origami, PDF manipulation framework for Ruby
4
+ Copyright (C) 2016 Guillaume Delugré.
5
+
6
+ Origami is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU Lesser General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ Origami is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU Lesser General Public License for more details.
15
+
16
+ You should have received a copy of the GNU Lesser General Public License
17
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
23
18
 
24
19
  =end
25
20
 
21
+ require 'date'
22
+
26
23
  module Origami
27
24
 
28
- #
29
- # Module common to String objects.
30
- #
31
- module String
32
-
33
- module Encoding
34
- class EncodingError < Exception #:nodoc:
35
- end
36
-
37
- module PDFDocEncoding
38
-
39
- CHARMAP =
40
- [
41
- "\x00\x00", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd",
42
- "\xff\xfd", "\x00\x09", "\x00\x0a", "\xff\xfd", "\x00\x0c", "\x00\x0d", "\xff\xfd", "\xff\xfd",
43
- "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd",
44
- "\x02\xd8", "\x02\xc7", "\x02\xc6", "\x02\xd9", "\x02\xdd", "\x02\xdb", "\x02\xda", "\x02\xdc",
45
- "\x00\x20", "\x00\x21", "\x00\x22", "\x00\x23", "\x00\x24", "\x00\x25", "\x00\x26", "\x00\x27",
46
- "\x00\x28", "\x00\x29", "\x00\x2a", "\x00\x2b", "\x00\x2c", "\x00\x2d", "\x00\x2e", "\x00\x2f",
47
- "\x00\x30", "\x00\x31", "\x00\x32", "\x00\x33", "\x00\x34", "\x00\x35", "\x00\x36", "\x00\x37",
48
- "\x00\x38", "\x00\x39", "\x00\x3a", "\x00\x3b", "\x00\x3c", "\x00\x3d", "\x00\x3e", "\x00\x3f",
49
- "\x00\x40", "\x00\x41", "\x00\x42", "\x00\x43", "\x00\x44", "\x00\x45", "\x00\x46", "\x00\x47",
50
- "\x00\x48", "\x00\x49", "\x00\x4a", "\x00\x4b", "\x00\x4c", "\x00\x4d", "\x00\x4e", "\x00\x4f",
51
- "\x00\x50", "\x00\x51", "\x00\x52", "\x00\x53", "\x00\x54", "\x00\x55", "\x00\x56", "\x00\x57",
52
- "\x00\x58", "\x00\x59", "\x00\x5a", "\x00\x5b", "\x00\x5c", "\x00\x5d", "\x00\x5e", "\x00\x5f",
53
- "\x00\x60", "\x00\x61", "\x00\x62", "\x00\x63", "\x00\x64", "\x00\x65", "\x00\x66", "\x00\x67",
54
- "\x00\x68", "\x00\x69", "\x00\x6a", "\x00\x6b", "\x00\x6c", "\x00\x6d", "\x00\x6e", "\x00\x6f",
55
- "\x00\x70", "\x00\x71", "\x00\x72", "\x00\x73", "\x00\x74", "\x00\x75", "\x00\x76", "\x00\x77",
56
- "\x00\x78", "\x00\x79", "\x00\x7a", "\x00\x7b", "\x00\x7c", "\x00\x7d", "\x00\x7e", "\xff\xfd",
57
- "\x20\x22", "\x20\x20", "\x20\x21", "\x20\x26", "\x20\x14", "\x20\x13", "\x01\x92", "\x20\x44",
58
- "\x20\x39", "\x20\x3a", "\x22\x12", "\x20\x30", "\x20\x1e", "\x20\x1c", "\x20\x1d", "\x20\x18",
59
- "\x20\x19", "\x20\x1a", "\x21\x22", "\xfb\x01", "\xfb\x02", "\x01\x41", "\x01\x52", "\x01\x60",
60
- "\x01\x78", "\x01\x7d", "\x01\x31", "\x01\x42", "\x01\x53", "\x01\x61", "\x01\x7e", "\xff\xfd",
61
- "\x20\xac", "\x00\xa1", "\x00\xa2", "\x00\xa3", "\x00\xa4", "\x00\xa5", "\x00\xa6", "\x00\xa7",
62
- "\x00\xa8", "\x00\xa9", "\x00\xaa", "\x00\xab", "\x00\xac", "\xff\xfd", "\x00\xae", "\x00\xaf",
63
- "\x00\xb0", "\x00\xb1", "\x00\xb2", "\x00\xb3", "\x00\xb4", "\x00\xb5", "\x00\xb6", "\x00\xb7",
64
- "\x00\xb8", "\x00\xb9", "\x00\xba", "\x00\xbb", "\x00\xbc", "\x00\xbd", "\x00\xbe", "\x00\xbf",
65
- "\x00\xc0", "\x00\xc1", "\x00\xc2", "\x00\xc3", "\x00\xc4", "\x00\xc5", "\x00\xc6", "\x00\xc7",
66
- "\x00\xc8", "\x00\xc9", "\x00\xca", "\x00\xcb", "\x00\xcc", "\x00\xcd", "\x00\xce", "\x00\xcf",
67
- "\x00\xd0", "\x00\xd1", "\x00\xd2", "\x00\xd3", "\x00\xd4", "\x00\xd5", "\x00\xd6", "\x00\xd7",
68
- "\x00\xd8", "\x00\xd9", "\x00\xda", "\x00\xdb", "\x00\xdc", "\x00\xdd", "\x00\xde", "\x00\xdf",
69
- "\x00\xe0", "\x00\xe1", "\x00\xe2", "\x00\xe3", "\x00\xe4", "\x00\xe5", "\x00\xe6", "\x00\xe7",
70
- "\x00\xe8", "\x00\xe9", "\x00\xea", "\x00\xeb", "\x00\xec", "\x00\xed", "\x00\xee", "\x00\xef",
71
- "\x00\xf0", "\x00\xf1", "\x00\xf2", "\x00\xf3", "\x00\xf4", "\x00\xf5", "\x00\xf6", "\x00\xf7",
72
- "\x00\xf8", "\x00\xf9", "\x00\xfa", "\x00\xfb", "\x00\xfc", "\x00\xfd", "\x00\xfe", "\x00\xff"
73
- ]
74
-
75
- def PDFDocEncoding.to_utf16be(pdfdocstr)
76
-
77
- utf16bestr = "#{UTF16BE::MAGIC}"
78
- pdfdocstr.each_byte do |byte|
79
- utf16bestr << CHARMAP[byte]
80
- end
81
-
82
- utf16bestr
25
+ #
26
+ # Module common to String objects.
27
+ #
28
+ module String
29
+
30
+ module Encoding
31
+ class EncodingError < Error #:nodoc:
32
+ end
33
+
34
+ module PDFDocEncoding
35
+ CHARMAP =
36
+ [
37
+ "\x00\x00", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd",
38
+ "\xff\xfd", "\x00\x09", "\x00\x0a", "\xff\xfd", "\x00\x0c", "\x00\x0d", "\xff\xfd", "\xff\xfd",
39
+ "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd",
40
+ "\x02\xd8", "\x02\xc7", "\x02\xc6", "\x02\xd9", "\x02\xdd", "\x02\xdb", "\x02\xda", "\x02\xdc",
41
+ "\x00\x20", "\x00\x21", "\x00\x22", "\x00\x23", "\x00\x24", "\x00\x25", "\x00\x26", "\x00\x27",
42
+ "\x00\x28", "\x00\x29", "\x00\x2a", "\x00\x2b", "\x00\x2c", "\x00\x2d", "\x00\x2e", "\x00\x2f",
43
+ "\x00\x30", "\x00\x31", "\x00\x32", "\x00\x33", "\x00\x34", "\x00\x35", "\x00\x36", "\x00\x37",
44
+ "\x00\x38", "\x00\x39", "\x00\x3a", "\x00\x3b", "\x00\x3c", "\x00\x3d", "\x00\x3e", "\x00\x3f",
45
+ "\x00\x40", "\x00\x41", "\x00\x42", "\x00\x43", "\x00\x44", "\x00\x45", "\x00\x46", "\x00\x47",
46
+ "\x00\x48", "\x00\x49", "\x00\x4a", "\x00\x4b", "\x00\x4c", "\x00\x4d", "\x00\x4e", "\x00\x4f",
47
+ "\x00\x50", "\x00\x51", "\x00\x52", "\x00\x53", "\x00\x54", "\x00\x55", "\x00\x56", "\x00\x57",
48
+ "\x00\x58", "\x00\x59", "\x00\x5a", "\x00\x5b", "\x00\x5c", "\x00\x5d", "\x00\x5e", "\x00\x5f",
49
+ "\x00\x60", "\x00\x61", "\x00\x62", "\x00\x63", "\x00\x64", "\x00\x65", "\x00\x66", "\x00\x67",
50
+ "\x00\x68", "\x00\x69", "\x00\x6a", "\x00\x6b", "\x00\x6c", "\x00\x6d", "\x00\x6e", "\x00\x6f",
51
+ "\x00\x70", "\x00\x71", "\x00\x72", "\x00\x73", "\x00\x74", "\x00\x75", "\x00\x76", "\x00\x77",
52
+ "\x00\x78", "\x00\x79", "\x00\x7a", "\x00\x7b", "\x00\x7c", "\x00\x7d", "\x00\x7e", "\xff\xfd",
53
+ "\x20\x22", "\x20\x20", "\x20\x21", "\x20\x26", "\x20\x14", "\x20\x13", "\x01\x92", "\x20\x44",
54
+ "\x20\x39", "\x20\x3a", "\x22\x12", "\x20\x30", "\x20\x1e", "\x20\x1c", "\x20\x1d", "\x20\x18",
55
+ "\x20\x19", "\x20\x1a", "\x21\x22", "\xfb\x01", "\xfb\x02", "\x01\x41", "\x01\x52", "\x01\x60",
56
+ "\x01\x78", "\x01\x7d", "\x01\x31", "\x01\x42", "\x01\x53", "\x01\x61", "\x01\x7e", "\xff\xfd",
57
+ "\x20\xac", "\x00\xa1", "\x00\xa2", "\x00\xa3", "\x00\xa4", "\x00\xa5", "\x00\xa6", "\x00\xa7",
58
+ "\x00\xa8", "\x00\xa9", "\x00\xaa", "\x00\xab", "\x00\xac", "\xff\xfd", "\x00\xae", "\x00\xaf",
59
+ "\x00\xb0", "\x00\xb1", "\x00\xb2", "\x00\xb3", "\x00\xb4", "\x00\xb5", "\x00\xb6", "\x00\xb7",
60
+ "\x00\xb8", "\x00\xb9", "\x00\xba", "\x00\xbb", "\x00\xbc", "\x00\xbd", "\x00\xbe", "\x00\xbf",
61
+ "\x00\xc0", "\x00\xc1", "\x00\xc2", "\x00\xc3", "\x00\xc4", "\x00\xc5", "\x00\xc6", "\x00\xc7",
62
+ "\x00\xc8", "\x00\xc9", "\x00\xca", "\x00\xcb", "\x00\xcc", "\x00\xcd", "\x00\xce", "\x00\xcf",
63
+ "\x00\xd0", "\x00\xd1", "\x00\xd2", "\x00\xd3", "\x00\xd4", "\x00\xd5", "\x00\xd6", "\x00\xd7",
64
+ "\x00\xd8", "\x00\xd9", "\x00\xda", "\x00\xdb", "\x00\xdc", "\x00\xdd", "\x00\xde", "\x00\xdf",
65
+ "\x00\xe0", "\x00\xe1", "\x00\xe2", "\x00\xe3", "\x00\xe4", "\x00\xe5", "\x00\xe6", "\x00\xe7",
66
+ "\x00\xe8", "\x00\xe9", "\x00\xea", "\x00\xeb", "\x00\xec", "\x00\xed", "\x00\xee", "\x00\xef",
67
+ "\x00\xf0", "\x00\xf1", "\x00\xf2", "\x00\xf3", "\x00\xf4", "\x00\xf5", "\x00\xf6", "\x00\xf7",
68
+ "\x00\xf8", "\x00\xf9", "\x00\xfa", "\x00\xfb", "\x00\xfc", "\x00\xfd", "\x00\xfe", "\x00\xff"
69
+ ].map(&:b)
70
+
71
+ def PDFDocEncoding.to_utf16be(pdfdocstr)
72
+ utf16bestr = UTF16BE::BOM.dup
73
+ pdfdocstr.each_byte do |byte|
74
+ utf16bestr << CHARMAP[byte]
75
+ end
76
+
77
+ utf16bestr.force_encoding('binary')
78
+ end
79
+
80
+ def PDFDocEncoding.to_pdfdoc(str)
81
+ str
82
+ end
83
+ end
84
+
85
+ module UTF16BE
86
+ BOM = "\xFE\xFF".b
87
+
88
+ def UTF16BE.to_utf16be(str)
89
+ str
90
+ end
91
+
92
+ def UTF16BE.to_pdfdoc(str)
93
+ pdfdoc = []
94
+ i = 2
95
+
96
+ while i < str.size
97
+ char = PDFDocEncoding::CHARMAP.index(str[i,2])
98
+ raise EncodingError, "Can't convert UTF16-BE character to PDFDocEncoding" if char.nil?
99
+ pdfdoc << char
100
+ i = i + 2
101
+ end
102
+
103
+ pdfdoc.pack("C*")
104
+ end
105
+ end
83
106
  end
84
107
 
85
- def PDFDocEncoding.to_pdfdoc(str)
86
- str
108
+ module ClassMethods #:nodoc:all
109
+ def native_type; Origami::String end
87
110
  end
88
111
 
89
- end
112
+ def self.included(receiver) #:nodoc:
113
+ receiver.extend(ClassMethods)
114
+ end
115
+
116
+ def self.native_type; Origami::String end #:nodoc:
117
+
118
+ include Origami::Object
90
119
 
91
- module UTF16BE
120
+ attr_accessor :encoding
92
121
 
93
- MAGIC = "\xFE\xFF"
122
+ def initialize(str) #:nodoc:
123
+ super(str.force_encoding('binary'))
94
124
 
95
- def UTF16BE.to_utf16be(str)
96
- str
125
+ detect_encoding
97
126
  end
98
127
 
99
- def UTF16BE.to_pdfdoc(str)
100
- pdfdoc = []
101
- i = 2
128
+ #
129
+ # Convert String object to an UTF8 encoded Ruby string.
130
+ #
131
+ def to_utf8
132
+ detect_encoding
102
133
 
103
- while i < str.size
104
- char = PDFDocEncoding::CHARMAP.index(str[i,2])
105
- raise EncodingError, "Can't convert UTF16-BE character to PDFDocEncoding" if char.nil?
106
- pdfdoc << char
107
- i = i + 2
108
- end
134
+ utf16 = self.encoding.to_utf16be(self.value)
135
+ utf16.slice!(0, Encoding::UTF16BE::BOM.size)
109
136
 
110
- pdfdoc.pack("C*")
137
+ utf16.encode("utf-8", "utf-16be")
111
138
  end
112
139
 
113
- end
140
+ #
141
+ # Convert String object to an UTF16-BE encoded binary Ruby string.
142
+ #
143
+ def to_utf16be
144
+ detect_encoding
145
+ self.encoding.to_utf16be(self.value)
146
+ end
114
147
 
115
- end
148
+ #
149
+ # Convert String object to a PDFDocEncoding encoded binary Ruby string.
150
+ #
151
+ def to_pdfdoc
152
+ detect_encoding
153
+ self.encoding.to_pdfdoc(self.value)
154
+ end
116
155
 
117
- module ClassMethods #:nodoc:all
118
- def native_type; Origami::String end
156
+ def detect_encoding #:nodoc:
157
+ if self.value[0,2] == Encoding::UTF16BE::BOM
158
+ @encoding = Encoding::UTF16BE
159
+ else
160
+ @encoding = Encoding::PDFDocEncoding
161
+ end
162
+ end
119
163
  end
120
164
 
121
- def self.included(receiver) #:nodoc:
122
- receiver.extend(ClassMethods)
123
- end
124
-
125
- def self.native_type; Origami::String end #:nodoc:
126
-
127
- include Origami::Object
128
-
129
- attr_accessor :encoding
130
-
131
- def initialize(str) #:nodoc:
132
- infer_encoding
133
- super(str)
165
+ class InvalidHexaStringObjectError < InvalidObjectError #:nodoc:
134
166
  end
135
167
 
136
168
  #
137
- # Convert String object to an UTF8 encoded Ruby string.
169
+ # Class representing an hexadecimal-writen String Object.
138
170
  #
139
- def to_utf8
140
- infer_encoding
141
-
142
- if RUBY_VERSION < '1.9'
143
- require 'iconv'
144
- i = Iconv.new("UTF-8", "UTF-16")
145
- utf8str = i.iconv(self.encoding.to_utf16be(self.value))
146
- i.close
147
- else
148
- utf8str = self.encoding.to_utf16be(self.value).encode("utf-8", "utf-16")
149
- end
150
-
151
- utf8str
152
- end
171
+ class HexaString < ::String
172
+ include String
153
173
 
154
- #
155
- # Convert String object to an UTF16-BE encoded Ruby string.
156
- #
157
- def to_utf16be
158
- infer_encoding
159
- self.encoding.to_utf16be(self.value)
160
- end
174
+ TOKENS = %w{ < > } #:nodoc:
161
175
 
162
- #
163
- # Convert String object to a PDFDocEncoding encoded Ruby string.
164
- #
165
- def to_pdfdoc
166
- infer_encoding
167
- self.encoding.to_pdfdoc(self.value)
168
- end
176
+ @@regexp_open = Regexp.new(WHITESPACES + TOKENS.first)
177
+ @@regexp_close = Regexp.new(TOKENS.last)
169
178
 
170
- def infer_encoding #:nodoc:
171
- @encoding =
172
- if self.value[0,2] == Encoding::UTF16BE::MAGIC
173
- Encoding::UTF16BE
174
- else
175
- Encoding::PDFDocEncoding
176
- end
177
- end
178
- end
179
-
180
- class InvalidHexaStringObjectError < InvalidObjectError #:nodoc:
181
- end
182
-
183
- #
184
- # Class representing an hexadecimal-writen String Object.
185
- #
186
- class HexaString < ::String
187
- include String
188
-
189
- TOKENS = %w{ < > } #:nodoc:
190
-
191
- @@regexp_open = Regexp.new(WHITESPACES + TOKENS.first)
192
- @@regexp_close = Regexp.new(TOKENS.last)
179
+ #
180
+ # Creates a new PDF hexadecimal String.
181
+ # _str_:: The string value.
182
+ #
183
+ def initialize(str = "")
184
+ unless str.is_a?(::String)
185
+ raise TypeError, "Expected type String, received #{str.class}."
186
+ end
193
187
 
194
- #
195
- # Creates a new PDF hexadecimal String.
196
- # _str_:: The string value.
197
- #
198
- def initialize(str = "")
199
-
200
- unless str.is_a?(::String)
201
- raise TypeError, "Expected type String, received #{str.class}."
202
- end
203
-
204
- super(str)
205
- end
206
-
207
- def self.parse(stream, parser = nil) #:nodoc:
208
-
209
- offset = stream.pos
210
-
211
- if stream.skip(@@regexp_open).nil?
212
- raise InvalidHexaStringObjectError, "Hexadecimal string shall start with a '#{TOKENS.first}' token"
213
- end
214
-
215
- hexa = stream.scan_until(@@regexp_close)
216
- if hexa.nil?
217
- raise InvalidHexaStringObjectError, "Hexadecimal string shall end with a '#{TOKENS.last}' token"
218
- end
219
-
220
- decoded = Filter::ASCIIHex.decode(hexa.chomp!(TOKENS.last))
221
-
222
- hexastr = HexaString.new(decoded)
223
- hexastr.file_offset = offset
224
-
225
- hexastr
226
- end
227
-
228
- def to_s #:nodoc:
229
- super(TOKENS.first + Filter::ASCIIHex.encode(to_str) + TOKENS.last)
230
- end
188
+ super(str)
189
+ end
231
190
 
232
- #
233
- # Converts self to ByteString
234
- #
235
- def to_raw
236
- ByteString.new(self.value)
237
- end
191
+ def self.parse(stream, parser = nil) #:nodoc:
192
+ offset = stream.pos
193
+
194
+ if stream.skip(@@regexp_open).nil?
195
+ raise InvalidHexaStringObjectError, "Hexadecimal string shall start with a '#{TOKENS.first}' token"
196
+ end
197
+
198
+ hexa = stream.scan_until(@@regexp_close)
199
+ if hexa.nil?
200
+ raise InvalidHexaStringObjectError, "Hexadecimal string shall end with a '#{TOKENS.last}' token"
201
+ end
202
+
203
+ decoded = Filter::ASCIIHex.decode(hexa.chomp!(TOKENS.last))
204
+
205
+ hexastr = HexaString.new(decoded)
206
+ hexastr.file_offset = offset
238
207
 
239
- def value
240
- self.decrypt! if self.is_a?(Encryption::EncryptedString) and not @decrypted
208
+ hexastr
209
+ end
210
+
211
+ def to_s #:nodoc:
212
+ super(TOKENS.first + Filter::ASCIIHex.encode(to_str) + TOKENS.last)
213
+ end
241
214
 
242
- to_str
215
+ #
216
+ # Converts self to a literal String.
217
+ #
218
+ def to_literal
219
+ LiteralString.new(self.value)
220
+ end
221
+
222
+ def value
223
+ self.decrypt! if self.is_a?(Encryption::EncryptedString) and not @decrypted
224
+
225
+ to_str
226
+ end
227
+ end
228
+
229
+ class InvalidLiteralStringObjectError < InvalidObjectError #:nodoc:
243
230
  end
244
231
 
245
- end
246
-
247
- class InvalidByteStringObjectError < InvalidObjectError #:nodoc:
248
- end
249
-
250
- #
251
- # Class representing an ASCII String Object.
252
- #
253
- class ByteString < ::String
254
-
255
- include String
256
-
257
- TOKENS = %w{ ( ) } #:nodoc:
258
-
259
- @@regexp_open = Regexp.new(WHITESPACES + Regexp.escape(TOKENS.first))
260
- @@regexp_close = Regexp.new(Regexp.escape(TOKENS.last))
261
-
262
232
  #
263
- # Creates a new PDF String.
264
- # _str_:: The string value.
233
+ # Class representing a literal String Object.
265
234
  #
266
- def initialize(str = "")
267
-
268
- unless str.is_a?(::String)
269
- raise TypeError, "Expected type String, received #{str.class}."
270
- end
271
-
272
- super(str)
273
- end
235
+ class LiteralString < ::String
236
+ include String
274
237
 
275
- def self.parse(stream, parser = nil) #:nodoc:
238
+ TOKENS = %w{ ( ) } #:nodoc:
276
239
 
277
- offset = stream.pos
278
-
279
- if not stream.skip(@@regexp_open)
280
- raise InvalidByteStringObjectError, "No literal string start token found"
281
- end
282
-
283
- result = ""
284
- depth = 0
285
- while depth != 0 or stream.peek(1) != TOKENS.last do
240
+ @@regexp_open = Regexp.new(WHITESPACES + Regexp.escape(TOKENS.first))
241
+ @@regexp_close = Regexp.new(Regexp.escape(TOKENS.last))
286
242
 
287
- if stream.eos?
288
- raise InvalidByteStringObjectError, "Non-terminated string"
243
+ #
244
+ # Creates a new PDF String.
245
+ # _str_:: The string value.
246
+ #
247
+ def initialize(str = "")
248
+ unless str.is_a?(::String)
249
+ raise TypeError, "Expected type String, received #{str.class}."
250
+ end
251
+
252
+ super(str)
289
253
  end
290
254
 
291
- c = stream.get_byte
292
- case c
293
- when "\\"
294
- if stream.match?(/\d{1,3}/)
295
- oct = stream.peek(3).oct.chr
296
- stream.pos += 3
297
- result << oct
298
- elsif stream.match?(/((\r?\n)|(\r\n?))/)
299
-
300
- stream.skip(/((\r?\n)|(\r\n?))/)
301
- next
302
-
303
- else
304
- flag = stream.get_byte
305
- case flag
306
- when "n" then result << "\n"
307
- when "r" then result << "\r"
308
- when "t" then result << "\t"
309
- when "b" then result << "\b"
310
- when "f" then result << "\f"
311
- when "(" then result << "("
312
- when ")" then result << ")"
313
- when "\\" then result << "\\"
314
- when "\r"
315
- if str.peek(1) == "\n" then stream.pos += 1 end
316
- when "\n"
317
- else
318
- result << flag
255
+ def self.parse(stream, parser = nil) #:nodoc:
256
+ offset = stream.pos
257
+
258
+ unless stream.skip(@@regexp_open)
259
+ raise InvalidLiteralStringObjectError, "No literal string start token found"
260
+ end
261
+
262
+ result = ""
263
+ depth = 0
264
+ while depth != 0 or stream.peek(1) != TOKENS.last do
265
+ raise InvalidLiteralStringObjectError, "Non-terminated string" if stream.eos?
266
+
267
+ c = stream.get_byte
268
+ case c
269
+ when "\\"
270
+ if stream.match?(/\d{1,3}/)
271
+ oct = stream.peek(3).oct.chr
272
+ stream.pos += 3
273
+ result << oct
274
+ elsif stream.match?(/((\r?\n)|(\r\n?))/)
275
+ stream.skip(/((\r?\n)|(\r\n?))/)
276
+ next
277
+ else
278
+ flag = stream.get_byte
279
+ case flag
280
+ when "n" then result << "\n"
281
+ when "r" then result << "\r"
282
+ when "t" then result << "\t"
283
+ when "b" then result << "\b"
284
+ when "f" then result << "\f"
285
+ when "(" then result << "("
286
+ when ")" then result << ")"
287
+ when "\\" then result << "\\"
288
+ when "\r"
289
+ stream.pos += 1 if stream.peek(1) == "\n"
290
+ when "\n"
291
+ else
292
+ result << flag
293
+ end
294
+ end
295
+
296
+ when "(" then
297
+ depth = depth + 1
298
+ result << c
299
+ when ")" then
300
+ depth = depth - 1
301
+ result << c
302
+ else
303
+ result << c
304
+ end
305
+ end
306
+
307
+ unless stream.skip(@@regexp_close)
308
+ raise InvalidLiteralStringObjectError, "Byte string shall be terminated with '#{TOKENS.last}'"
309
+ end
310
+
311
+ # Try to cast as a Date object if possible.
312
+ if result[0, 2] == 'D:'
313
+ begin
314
+ date = Date.parse(result)
315
+ date.file_offset = offset
316
+ return date
317
+ rescue InvalidDateError
318
+ end
319
319
  end
320
- end
321
-
322
- when "(" then
323
- depth = depth + 1
324
- result << c
325
- when ")" then
326
- depth = depth - 1
327
- result << c
328
- else
329
- result << c
320
+
321
+ bytestr = self.new(result)
322
+ bytestr.file_offset = offset
323
+
324
+ bytestr
330
325
  end
331
326
 
332
- end
327
+ def expand #:nodoc:
328
+ extended = self.gsub("\\", "\\\\\\\\")
329
+ extended.gsub!(/\)/, "\\)")
330
+ extended.gsub!("\n", "\\n")
331
+ extended.gsub!("\r", "\\r")
332
+ extended.gsub!(/\(/, "\\(")
333
333
 
334
- if not stream.skip(@@regexp_close)
335
- raise InvalidByteStringObjectError, "Byte string shall be terminated with '#{TOKENS.last}'"
336
- end
334
+ extended
335
+ end
337
336
 
338
- bytestr = ByteString.new(result)
339
- bytestr.file_offset
337
+ def to_s #:nodoc:
338
+ super(TOKENS.first + self.expand + TOKENS.last)
339
+ end
340
340
 
341
- bytestr
342
- end
341
+ #
342
+ # Converts self to HexaString
343
+ #
344
+ def to_hex
345
+ HexaString.new(self.value)
346
+ end
343
347
 
344
- def expand #:nodoc:
345
-
346
- extended = self.gsub("\\", "\\\\\\\\")
347
- extended.gsub!(/\)/, "\\)")
348
- extended.gsub!("\n", "\\n")
349
- extended.gsub!("\r", "\\r")
350
- extended.gsub!(/\(/, "\\(")
351
-
352
- extended
353
- end
354
-
355
- def to_s #:nodoc:
356
- super(TOKENS.first + self.expand + TOKENS.last)
357
- end
348
+ #
349
+ # Returns a standard String representation.
350
+ #
351
+ def value
352
+ self.decrypt! if self.is_a?(Encryption::EncryptedString) and not @decrypted
358
353
 
359
- #
360
- # Converts self to HexaString
361
- #
362
- def to_hex
363
- HexaString.new(self.value)
354
+ to_str
355
+ end
364
356
  end
365
-
366
- def value
367
- self.decrypt! if self.is_a?(Encryption::EncryptedString) and not @decrypted
368
357
 
369
- to_str
370
- end
371
- end
372
-
373
- #
374
- # Class representing a Date string.
375
- # _Not used_
376
- # _Not tested_
377
- #
378
- class Date < ByteString #:nodoc:
379
-
380
- REGEXP_TOKEN = "(D:)?(\\d{4})(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?(?:([\\+-Z])(?:(\\d{2})')?(?:(\\d{2})')?)?"
381
-
382
- def initialize(year, month = nil, day = nil, hour = nil, minute = nil, second = nil, ut_sign = nil, ut_hours = nil, ut_min = nil)
383
-
384
- year_str = '%04d' % year
385
- month_str = month.nil? ? '01' : '%02d' % month
386
- day_str = day.nil? ? '01' : '%02d' % day
387
- hour_str = '%02d' % hour
388
- minute_str = '%02d' % minute
389
- second_str = '%02d' % second
390
-
391
- date_str = "D:#{year_str}#{month_str}#{day_str}#{hour_str}#{minute_str}#{second_str}"
392
- date_str << "#{ut_sign}#{'%02d' % ut_hours}'#{'%02d' % ut_min}" unless ut_sign.nil?
393
-
394
- super(date_str)
358
+ class InvalidDateError < Error #:nodoc:
395
359
  end
396
-
397
- def self.parse(stream, parser = nil) #:nodoc:
398
-
399
- dateReg = Regexp.new(REGEXP_TOKEN)
400
-
401
- raise InvalidDate if stream.scan(dateReg).nil?
402
-
403
- year = stream[2].to_i
404
- month = stream[3] and stream[3].to_i
405
- day = stream[4] and stream[4].to_i
406
- hour = stream[5] and stream[5].to_i
407
- min = stream[6] and stream[6].to_i
408
- sec = stream[7] and stream[7].to_i
409
- ut_sign = stream[8]
410
- ut_hours = stream[9] and stream[9].to_i
411
- ut_min = stream[10] and stream[10].to_i
412
-
413
- Origami::Date.new(year, month, day, hour, min, sec, ut_sign, ut_hours, ut_min)
414
- end
415
-
360
+
416
361
  #
417
- # Returns current Date String in UTC time.
362
+ # Class representing a Date string.
418
363
  #
419
- def self.now
420
- now = Time.now.getutc
421
- year = now.strftime("%Y").to_i
422
- month = now.strftime("%m").to_i
423
- day = now.strftime("%d").to_i
424
- hour = now.strftime("%H").to_i
425
- min = now.strftime("%M").to_i
426
- sec = now.strftime("%S").to_i
427
-
428
- Origami::Date.new(year, month, day, hour, min, sec, 'Z', 0, 0)
364
+ class Date < LiteralString #:nodoc:
365
+
366
+ REGEXP_TOKEN =
367
+ /D: # Date header
368
+ (?<year>\d{4}) # Year
369
+ (?<month>\d{2})? # Month
370
+ (?<day>\d{2})? # Day
371
+ (?<hour>\d{2})? # Hour
372
+ (?<min>\d{2})? # Minute
373
+ (?<sec>\d{2})? # Second
374
+ (?:
375
+ (?<ut>[\+\-Z]) # UT relationship
376
+ (?<ut_hour_off>\d{2}) # UT hour offset
377
+ ('(?<ut_min_off>\d{2}))? # UT minute offset
378
+ )?
379
+ /x
380
+
381
+ attr_reader :year, :month, :day, :hour, :min, :sec, :utc_offset
382
+
383
+ def initialize(year:, month: 1, day: 1, hour: 0, min: 0, sec: 0, utc_offset: 0)
384
+ raise InvalidDateError, "Invalid year #{year}" unless (0..9999) === year
385
+ raise InvalidDateError, "Invalid month #{month}" unless (1..12) === month
386
+ raise InvalidDateError, "Invalid day #{day}" unless (1..31) === day
387
+ raise InvalidDateError, "Invalid hour #{hour}" unless (0..23) === hour
388
+ raise InvalidDateError, "Invalid minute #{min}" unless (0..59) === min
389
+ raise InvalidDateError, "Invalid second #{sec}" unless (0..59) === sec
390
+
391
+ @year, @month, @day, @hour, @min, @sec = year, month, day, hour, min, sec
392
+ @utc_offset = utc_offset
393
+
394
+ date = "D:%04d%02d%02d%02d%02d%02d" % [year, month, day, hour, min, sec ]
395
+
396
+ if utc_offset == 0
397
+ date << "Z00'00"
398
+ else
399
+ date << (if utc_offset < 0 then '-' else '+' end)
400
+ off_hours, off_secs = utc_offset.abs.divmod(3600)
401
+ off_mins = off_secs / 60
402
+ date << "%02d'%02d" % [ off_hours, off_mins ]
403
+ end
404
+
405
+ super(date)
406
+ end
407
+
408
+ def to_datetime
409
+ ::DateTime.new(@year, @month, @day, @hour, @min, @sec, (@utc_offset / 3600).to_s)
410
+ end
411
+
412
+ def self.parse(str) #:nodoc:
413
+ raise InvalidDateError, "Not a valid Date string" unless str =~ REGEXP_TOKEN
414
+
415
+ date =
416
+ {
417
+ year: $~['year'].to_i
418
+ }
419
+
420
+ date[:month] = $~['month'].to_i if $~['month']
421
+ date[:day] = $~['day'].to_i if $~['day']
422
+ date[:hour] = $~['hour'].to_i if $~['hour']
423
+ date[:min] = $~['min'].to_i if $~['min']
424
+ date[:sec] = $~['sec'].to_i if $~['sec']
425
+
426
+ if %w[+ -].include?($~['ut'])
427
+ utc_offset = $~['ut_hour_off'].to_i * 3600 + $~['ut_min_off'].to_i * 60
428
+ utc_offset = -utc_offset if $~['ut'] == '-'
429
+
430
+ date[:utc_offset] = utc_offset
431
+ end
432
+
433
+ Origami::Date.new(date)
434
+ end
435
+
436
+ #
437
+ # Returns current Date String in UTC time.
438
+ #
439
+ def self.now
440
+ now = Time.now.utc
441
+
442
+ date =
443
+ {
444
+ year: now.strftime("%Y").to_i,
445
+ month: now.strftime("%m").to_i,
446
+ day: now.strftime("%d").to_i,
447
+ hour: now.strftime("%H").to_i,
448
+ min: now.strftime("%M").to_i,
449
+ sec: now.strftime("%S").to_i,
450
+ utc_offset: now.utc_offset
451
+ }
452
+
453
+ Origami::Date.new(date)
454
+ end
429
455
  end
430
-
431
- end
432
456
 
433
457
  end