origami 1.0.2
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.
- data/COPYING.LESSER +165 -0
- data/README +77 -0
- data/VERSION +1 -0
- data/bin/config/pdfcop.conf.yml +237 -0
- data/bin/gui/about.rb +46 -0
- data/bin/gui/config.rb +132 -0
- data/bin/gui/file.rb +385 -0
- data/bin/gui/hexdump.rb +74 -0
- data/bin/gui/hexview.rb +91 -0
- data/bin/gui/imgview.rb +72 -0
- data/bin/gui/menu.rb +392 -0
- data/bin/gui/properties.rb +132 -0
- data/bin/gui/signing.rb +635 -0
- data/bin/gui/textview.rb +107 -0
- data/bin/gui/treeview.rb +409 -0
- data/bin/gui/walker.rb +282 -0
- data/bin/gui/xrefs.rb +79 -0
- data/bin/pdf2graph +121 -0
- data/bin/pdf2ruby +353 -0
- data/bin/pdfcocoon +104 -0
- data/bin/pdfcop +455 -0
- data/bin/pdfdecompress +104 -0
- data/bin/pdfdecrypt +95 -0
- data/bin/pdfencrypt +112 -0
- data/bin/pdfextract +221 -0
- data/bin/pdfmetadata +123 -0
- data/bin/pdfsh +13 -0
- data/bin/pdfwalker +7 -0
- data/bin/shell/.irbrc +104 -0
- data/bin/shell/console.rb +136 -0
- data/bin/shell/hexdump.rb +83 -0
- data/origami.rb +36 -0
- data/origami/3d.rb +239 -0
- data/origami/acroform.rb +321 -0
- data/origami/actions.rb +299 -0
- data/origami/adobe/fdf.rb +259 -0
- data/origami/adobe/ppklite.rb +489 -0
- data/origami/annotations.rb +775 -0
- data/origami/array.rb +187 -0
- data/origami/boolean.rb +101 -0
- data/origami/catalog.rb +486 -0
- data/origami/destinations.rb +213 -0
- data/origami/dictionary.rb +188 -0
- data/origami/docmdp.rb +96 -0
- data/origami/encryption.rb +1293 -0
- data/origami/export.rb +283 -0
- data/origami/file.rb +222 -0
- data/origami/filters.rb +250 -0
- data/origami/filters/ascii.rb +189 -0
- data/origami/filters/ccitt.rb +515 -0
- data/origami/filters/crypt.rb +47 -0
- data/origami/filters/dct.rb +61 -0
- data/origami/filters/flate.rb +112 -0
- data/origami/filters/jbig2.rb +63 -0
- data/origami/filters/jpx.rb +53 -0
- data/origami/filters/lzw.rb +195 -0
- data/origami/filters/predictors.rb +276 -0
- data/origami/filters/runlength.rb +117 -0
- data/origami/font.rb +209 -0
- data/origami/functions.rb +93 -0
- data/origami/graphics.rb +33 -0
- data/origami/graphics/colors.rb +191 -0
- data/origami/graphics/instruction.rb +126 -0
- data/origami/graphics/path.rb +154 -0
- data/origami/graphics/patterns.rb +180 -0
- data/origami/graphics/state.rb +164 -0
- data/origami/graphics/text.rb +224 -0
- data/origami/graphics/xobject.rb +493 -0
- data/origami/header.rb +90 -0
- data/origami/linearization.rb +318 -0
- data/origami/metadata.rb +114 -0
- data/origami/name.rb +170 -0
- data/origami/null.rb +75 -0
- data/origami/numeric.rb +188 -0
- data/origami/obfuscation.rb +233 -0
- data/origami/object.rb +527 -0
- data/origami/outline.rb +59 -0
- data/origami/page.rb +559 -0
- data/origami/parser.rb +268 -0
- data/origami/parsers/fdf.rb +45 -0
- data/origami/parsers/pdf.rb +27 -0
- data/origami/parsers/pdf/linear.rb +113 -0
- data/origami/parsers/ppklite.rb +86 -0
- data/origami/pdf.rb +1144 -0
- data/origami/reference.rb +113 -0
- data/origami/signature.rb +474 -0
- data/origami/stream.rb +575 -0
- data/origami/string.rb +416 -0
- data/origami/trailer.rb +173 -0
- data/origami/webcapture.rb +87 -0
- data/origami/xfa.rb +3027 -0
- data/origami/xreftable.rb +447 -0
- data/templates/patterns.rb +66 -0
- data/templates/widgets.rb +173 -0
- data/templates/xdp.rb +92 -0
- data/tests/dataset/test.dummycrt +28 -0
- data/tests/dataset/test.dummykey +27 -0
- data/tests/tc_actions.rb +32 -0
- data/tests/tc_annotations.rb +85 -0
- data/tests/tc_pages.rb +37 -0
- data/tests/tc_pdfattach.rb +24 -0
- data/tests/tc_pdfencrypt.rb +110 -0
- data/tests/tc_pdfnew.rb +32 -0
- data/tests/tc_pdfparse.rb +98 -0
- data/tests/tc_pdfsig.rb +37 -0
- data/tests/tc_streams.rb +129 -0
- data/tests/ts_pdf.rb +45 -0
- metadata +193 -0
@@ -0,0 +1,489 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
= File
|
4
|
+
adobe/ppklite.rb
|
5
|
+
|
6
|
+
= Info
|
7
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
8
|
+
Copyright (C) 2010 Guillaume Delugr� <guillaume@security-labs.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/>.
|
23
|
+
|
24
|
+
=end
|
25
|
+
|
26
|
+
require 'origami/object'
|
27
|
+
require 'origami/name'
|
28
|
+
require 'origami/dictionary'
|
29
|
+
require 'origami/reference'
|
30
|
+
require 'origami/boolean'
|
31
|
+
require 'origami/numeric'
|
32
|
+
require 'origami/string'
|
33
|
+
require 'origami/array'
|
34
|
+
require 'origami/trailer'
|
35
|
+
require 'origami/xreftable'
|
36
|
+
|
37
|
+
require 'openssl'
|
38
|
+
|
39
|
+
module Origami
|
40
|
+
|
41
|
+
module Adobe
|
42
|
+
|
43
|
+
#
|
44
|
+
# Class representing an Adobe Reader certificate store.
|
45
|
+
#
|
46
|
+
class PPKLite
|
47
|
+
|
48
|
+
#
|
49
|
+
# Class representing a certificate store header.
|
50
|
+
#
|
51
|
+
class Header
|
52
|
+
|
53
|
+
MAGIC = /\A%PPKLITE-(\d)\.(\d)/
|
54
|
+
|
55
|
+
attr_accessor :majorversion, :minorversion
|
56
|
+
|
57
|
+
#
|
58
|
+
# Creates a file header, with the given major and minor versions.
|
59
|
+
# _majorversion_:: Major version.
|
60
|
+
# _minorversion_:: Minor version.
|
61
|
+
#
|
62
|
+
def initialize(majorversion = 2, minorversion = 1)
|
63
|
+
@majorversion, @minorversion = majorversion, minorversion
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.parse(stream) #:nodoc:
|
67
|
+
|
68
|
+
if not stream.scan(MAGIC).nil?
|
69
|
+
maj = stream[1].to_i
|
70
|
+
min = stream[2].to_i
|
71
|
+
else
|
72
|
+
raise InvalidHeader, "Invalid header format"
|
73
|
+
end
|
74
|
+
|
75
|
+
PPKLite::Header.new(maj,min)
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Outputs self into PDF code.
|
80
|
+
#
|
81
|
+
def to_s
|
82
|
+
"%PPKLITE-#{@majorversion}.#{@minorversion}" + EOL
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_sym #:nodoc:
|
86
|
+
"#{@majorversion}.#{@minorversion}".to_sym
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_f #:nodoc:
|
90
|
+
to_sym.to_s.to_f
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
class Revision #:nodoc;
|
96
|
+
attr_accessor :pdf
|
97
|
+
attr_accessor :body, :xreftable, :trailer
|
98
|
+
|
99
|
+
def initialize(adbk)
|
100
|
+
@pdf = adbk
|
101
|
+
@body = {}
|
102
|
+
@xreftable = nil
|
103
|
+
@trailer = nil
|
104
|
+
end
|
105
|
+
|
106
|
+
def trailer=(trl)
|
107
|
+
trl.pdf = @pdf
|
108
|
+
@trailer = trl
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
attr_accessor :header, :revisions
|
113
|
+
|
114
|
+
def initialize #:nodoc:
|
115
|
+
@header = PPKLite::Header.new
|
116
|
+
@revisions = [ Revision.new(self) ]
|
117
|
+
@revisions.first.trailer = Trailer.new
|
118
|
+
end
|
119
|
+
|
120
|
+
def objects
|
121
|
+
def append_subobj(root, objset)
|
122
|
+
if objset.find{ |o| o.object_id == root.object_id }.nil?
|
123
|
+
objset << root
|
124
|
+
if root.is_a?(Array) or root.is_a?(Dictionary)
|
125
|
+
root.each { |subobj| append_subobj(subobj, objset) unless subobj.is_a?(Reference) }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
objset = []
|
131
|
+
@revisions.first.body.values.each do |object|
|
132
|
+
unless object.is_a?(Reference)
|
133
|
+
append_subobj(object, objset)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
objset
|
138
|
+
end
|
139
|
+
|
140
|
+
def <<(object)
|
141
|
+
|
142
|
+
object.set_indirect(true)
|
143
|
+
|
144
|
+
if object.no.zero?
|
145
|
+
maxno = 1
|
146
|
+
while get_object(maxno) do maxno = maxno.succ end
|
147
|
+
|
148
|
+
object.generation = 0
|
149
|
+
object.no = maxno
|
150
|
+
end
|
151
|
+
|
152
|
+
@revisions.first.body[object.reference] = object
|
153
|
+
|
154
|
+
object.reference
|
155
|
+
end
|
156
|
+
|
157
|
+
def Catalog
|
158
|
+
get_object(@trailer.Root)
|
159
|
+
end
|
160
|
+
|
161
|
+
def save(filename)
|
162
|
+
|
163
|
+
bin = ""
|
164
|
+
bin << @header.to_s
|
165
|
+
|
166
|
+
lastno, brange = 0, 0
|
167
|
+
|
168
|
+
xrefs = [ XRef.new(0, XRef::LASTFREE, XRef::FREE) ]
|
169
|
+
xrefsection = XRef::Section.new
|
170
|
+
|
171
|
+
@revisions.first.body.values.sort.each { |obj|
|
172
|
+
if (obj.no - lastno).abs > 1
|
173
|
+
xrefsection << XRef::Subsection.new(brange, xrefs)
|
174
|
+
brange = obj.no
|
175
|
+
xrefs.clear
|
176
|
+
end
|
177
|
+
|
178
|
+
xrefs << XRef.new(bin.size, obj.generation, XRef::USED)
|
179
|
+
lastno = obj.no
|
180
|
+
|
181
|
+
bin << obj.to_s
|
182
|
+
}
|
183
|
+
|
184
|
+
xrefsection << XRef::Subsection.new(brange, xrefs)
|
185
|
+
|
186
|
+
@xreftable = xrefsection
|
187
|
+
@trailer ||= Trailer.new
|
188
|
+
@trailer.Size = rev.body.size + 1
|
189
|
+
@trailer.startxref = bin.size
|
190
|
+
|
191
|
+
bin << @xreftable.to_s
|
192
|
+
bin << @trailer.to_s
|
193
|
+
|
194
|
+
fd = File.open(filename, "w").binmode
|
195
|
+
fd << bin
|
196
|
+
fd.close
|
197
|
+
|
198
|
+
show_entries
|
199
|
+
end
|
200
|
+
alias saveas save
|
201
|
+
|
202
|
+
#
|
203
|
+
# Prints registered users in the address book
|
204
|
+
#
|
205
|
+
def show_users
|
206
|
+
|
207
|
+
puts "----------"
|
208
|
+
puts "Users list"
|
209
|
+
puts "----------"
|
210
|
+
|
211
|
+
@revisions.first.body.values.each { |obj| if obj.is_a?(User) then obj.show; puts end }
|
212
|
+
|
213
|
+
nil
|
214
|
+
end
|
215
|
+
|
216
|
+
#
|
217
|
+
# Prints registered certificates in the addressbook
|
218
|
+
#
|
219
|
+
def show_certs
|
220
|
+
puts "-----------------"
|
221
|
+
puts "Certificates list"
|
222
|
+
puts "-----------------"
|
223
|
+
|
224
|
+
@revisions.first.body.values.each { |obj| if obj.is_a?(Certificate) then obj.show; puts end }
|
225
|
+
|
226
|
+
nil
|
227
|
+
end
|
228
|
+
|
229
|
+
#
|
230
|
+
# Prints certificate with the specified id
|
231
|
+
#
|
232
|
+
def show_cert(id)
|
233
|
+
@revisions.first.body.values.find_all { |obj| obj.is_a?(Certificate) and obj.ID == id }.each do |cert|
|
234
|
+
cert.show
|
235
|
+
puts
|
236
|
+
end
|
237
|
+
|
238
|
+
nil
|
239
|
+
end
|
240
|
+
|
241
|
+
#
|
242
|
+
# Returns a Certificate dictionary corresponding to the specified id
|
243
|
+
#
|
244
|
+
def get_cert(id)
|
245
|
+
@revisions.first.body.values.find { |obj| obj.is_a?(Certificate) and obj.ID == id }
|
246
|
+
end
|
247
|
+
|
248
|
+
def show_user(id)
|
249
|
+
users = @revisions.first.body.values.find_all { |obj| obj.is_a?(User) and obj.ID == id }.each do |user|
|
250
|
+
user.show
|
251
|
+
puts
|
252
|
+
end
|
253
|
+
|
254
|
+
nil
|
255
|
+
end
|
256
|
+
|
257
|
+
#
|
258
|
+
# Prints users and certificates registered in the address book
|
259
|
+
#
|
260
|
+
def show_entries
|
261
|
+
show_users
|
262
|
+
show_certs
|
263
|
+
|
264
|
+
puts "End of address book."
|
265
|
+
end
|
266
|
+
|
267
|
+
#
|
268
|
+
# Add a certificate into the address book
|
269
|
+
#
|
270
|
+
def add_certificate(certfile, attributes, viewable = false, editable = false)
|
271
|
+
|
272
|
+
cert = Certificate.new
|
273
|
+
cert.Cert = OpenSSL::X509::Certificate.new(certfile).to_der
|
274
|
+
cert.ID = self.Catalog.PPK.AddressBook.NextID
|
275
|
+
self.Catalog.PPK.AddressBook.NextID += 1
|
276
|
+
cert.Trust = attributes
|
277
|
+
cert.Viewable = viewable
|
278
|
+
cert.Editable = editable
|
279
|
+
|
280
|
+
self.Catalog.PPK.AddressBook.Entries.push(self << cert)
|
281
|
+
|
282
|
+
show_certs
|
283
|
+
end
|
284
|
+
|
285
|
+
alias to_s show_entries
|
286
|
+
alias to_str show_entries
|
287
|
+
|
288
|
+
class Catalog < Dictionary
|
289
|
+
|
290
|
+
include StandardObject
|
291
|
+
|
292
|
+
field :Type, :Type => Name, :Default => :Catalog, :Required => true
|
293
|
+
field :PPK, :Type => Dictionary, :Required => true
|
294
|
+
|
295
|
+
def initialize(hash = {}) #:nodoc:
|
296
|
+
super(hash)
|
297
|
+
end
|
298
|
+
|
299
|
+
end
|
300
|
+
|
301
|
+
class PPK < Dictionary
|
302
|
+
|
303
|
+
include StandardObject
|
304
|
+
|
305
|
+
field :Type, :Type => Name, :Default => :PPK, :Required => true
|
306
|
+
field :User, :Type => Dictionary, :Required => true
|
307
|
+
field :AddressBook, :Type => Dictionary, :Required => true
|
308
|
+
field :V, :Type => Integer, :Default => 0x10001, :Required => true
|
309
|
+
|
310
|
+
def initialize(hash = {}) #:nodoc:
|
311
|
+
super(hash)
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
|
316
|
+
class UserList < Dictionary
|
317
|
+
|
318
|
+
include StandardObject
|
319
|
+
|
320
|
+
field :Type, :Type => Name, :Default => :User, :Required => true
|
321
|
+
|
322
|
+
def initialize(hash = {})
|
323
|
+
super(hash)
|
324
|
+
end
|
325
|
+
|
326
|
+
end
|
327
|
+
|
328
|
+
class AddressList < Dictionary
|
329
|
+
|
330
|
+
include StandardObject
|
331
|
+
|
332
|
+
field :Type, :Type => Name, :Default => :AddressBook, :Required => true
|
333
|
+
field :NextID, :Type => Integer
|
334
|
+
field :Entries, :Type => Array, :Default => [], :Required => true
|
335
|
+
|
336
|
+
def initialize(hash = {}) #:nodoc:
|
337
|
+
super(hash)
|
338
|
+
end
|
339
|
+
|
340
|
+
end
|
341
|
+
|
342
|
+
module Descriptor
|
343
|
+
|
344
|
+
CERTIFICATE = 1
|
345
|
+
USER = 2
|
346
|
+
|
347
|
+
def self.included(receiver) #:nodoc:
|
348
|
+
receiver.field :ID, :Type => Integer, :Required => true
|
349
|
+
receiver.field :ABEType, :Type => Integer, :Default => Descriptor::CERTIFICATE, :Required => true
|
350
|
+
end
|
351
|
+
|
352
|
+
def initialize(hash = {}) #:nodoc:
|
353
|
+
super(hash)
|
354
|
+
end
|
355
|
+
|
356
|
+
end
|
357
|
+
|
358
|
+
class User < Dictionary
|
359
|
+
|
360
|
+
include StandardObject
|
361
|
+
include Descriptor
|
362
|
+
|
363
|
+
field :ABEType, :Type => Integer, :Default => Descriptor::USER, :Required => true
|
364
|
+
field :Name, :Type => String, :Required => true
|
365
|
+
field :Encrypt, :Type => Integer
|
366
|
+
field :Certs, :Type => Array, :Default => [], :Required => true
|
367
|
+
|
368
|
+
def show
|
369
|
+
puts "ID: #{self.ID}"
|
370
|
+
puts "Name: #{self.Name}"
|
371
|
+
puts "Certificates: " + self.Certs.join(", ")
|
372
|
+
end
|
373
|
+
|
374
|
+
end
|
375
|
+
|
376
|
+
class Certificate < Dictionary
|
377
|
+
|
378
|
+
include StandardObject
|
379
|
+
include Descriptor
|
380
|
+
|
381
|
+
module Flags
|
382
|
+
|
383
|
+
CAN_CERTIFY = 1 << 1
|
384
|
+
ALLOW_DYNAMIC_CONTENT = 1 << 2
|
385
|
+
UNKNOWN_1 = 1 << 3
|
386
|
+
ALLOW_HIGH_PRIV_JS = 1 << 4
|
387
|
+
UNKNOWN_2 = 1 << 5
|
388
|
+
IS_ROOT_CA = 1 << 6
|
389
|
+
|
390
|
+
#~ FULL_TRUST = 1 << 1 | 1 << 2 | 1 << 3 | 1 << 4 | 1 << 5 | 1 << 6
|
391
|
+
FULL_TRUST = 8190
|
392
|
+
end
|
393
|
+
|
394
|
+
field :ABEType, :Type => Integer, :Default => Descriptor::CERTIFICATE, :Required => true
|
395
|
+
field :Usage, :Type => Integer, :Default => 1, :Required => true
|
396
|
+
field :Viewable, :Type => Boolean, :Default => true
|
397
|
+
field :Editable, :Type => Boolean, :Default => true
|
398
|
+
field :Cert, :Type => String, :Required => true
|
399
|
+
field :Trust, :Type => Integer, :Default => Flags::UNKNOWN_2, :Required => true
|
400
|
+
|
401
|
+
def show
|
402
|
+
puts "ID: #{self.ID}"
|
403
|
+
puts "Viewable: #{self.Viewable}"
|
404
|
+
puts "Editable: #{self.Editable}"
|
405
|
+
puts "Trust attributes: #{self.Trust}"
|
406
|
+
end
|
407
|
+
|
408
|
+
end
|
409
|
+
|
410
|
+
def get_object(no, generation = 0) #:nodoc:
|
411
|
+
|
412
|
+
case no
|
413
|
+
when Reference
|
414
|
+
target = no
|
415
|
+
when ::Integer
|
416
|
+
target = Reference.new(no, generation)
|
417
|
+
when Origami::Object
|
418
|
+
return no
|
419
|
+
end
|
420
|
+
|
421
|
+
@revisions.first.body[target]
|
422
|
+
end
|
423
|
+
|
424
|
+
private
|
425
|
+
|
426
|
+
def rebuildxrefs #:nodoc:
|
427
|
+
|
428
|
+
startxref = @header.to_s.size
|
429
|
+
|
430
|
+
@revisions.first.body.values.each { |object|
|
431
|
+
startxref += object.to_s.size
|
432
|
+
}
|
433
|
+
|
434
|
+
@xreftable = buildxrefs(@revisions.first.body)
|
435
|
+
|
436
|
+
@trailer ||= Trailer.new
|
437
|
+
@trailer.Size = @revisions.first.body.size + 1
|
438
|
+
@trailer.startxref = startxref
|
439
|
+
|
440
|
+
self
|
441
|
+
end
|
442
|
+
|
443
|
+
def buildxrefs(objects) #:nodoc:
|
444
|
+
|
445
|
+
lastno = 0
|
446
|
+
brange = 0
|
447
|
+
|
448
|
+
xrefs = [ XRef.new(0, XRef::LASTFREE, XRef::FREE) ]
|
449
|
+
|
450
|
+
xrefsection = XRef::Section.new
|
451
|
+
objects.sort.each { |object|
|
452
|
+
if (object.no - lastno).abs > 1
|
453
|
+
xrefsection << XRef::Subsection.new(brange, xrefs)
|
454
|
+
brange = object.no
|
455
|
+
xrefs.clear
|
456
|
+
end
|
457
|
+
|
458
|
+
xrefs << XRef.new(get_object_offset(object.no, object.generation), object.generation, XRef::USED)
|
459
|
+
|
460
|
+
lastno = object.no
|
461
|
+
}
|
462
|
+
|
463
|
+
xrefsection << XRef::Subsection.new(brange, xrefs)
|
464
|
+
|
465
|
+
xrefsection
|
466
|
+
end
|
467
|
+
|
468
|
+
def get_object_offset(no,generation) #:nodoc:
|
469
|
+
|
470
|
+
bodyoffset = @header.to_s.size
|
471
|
+
|
472
|
+
objectoffset = bodyoffset
|
473
|
+
|
474
|
+
@revisions.first.body.values.each { |object|
|
475
|
+
if object.no == no and object.generation == generation then return objectoffset
|
476
|
+
else
|
477
|
+
objectoffset += object.to_s.size
|
478
|
+
end
|
479
|
+
}
|
480
|
+
|
481
|
+
nil
|
482
|
+
end
|
483
|
+
|
484
|
+
end
|
485
|
+
|
486
|
+
end
|
487
|
+
|
488
|
+
end
|
489
|
+
|