axlsx 1.0.14 → 1.0.15
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +12 -4
- data/lib/axlsx.rb +3 -2
- data/lib/axlsx/package.rb +6 -0
- data/lib/axlsx/stylesheet/styles.rb +2 -0
- data/lib/axlsx/util/cbf.rb +297 -0
- data/lib/axlsx/util/cfb.rb~ +201 -0
- data/lib/axlsx/util/ms_off_crypto.rb +141 -40
- data/lib/axlsx/util/simple_typed_list.rb +12 -8
- data/lib/axlsx/util/storage.rb +146 -0
- data/lib/axlsx/{#cfb.xlsx# → util/storage.rb~} +0 -0
- data/lib/axlsx/version.rb +4 -1
- data/lib/axlsx/workbook/worksheet/cell.rb +4 -4
- metadata +95 -142
data/README.md
CHANGED
@@ -7,10 +7,10 @@ Axlsx: Office Open XML Spreadsheet Generation
|
|
7
7
|
**Author**: Randy Morgan
|
8
8
|
**Copyright**: 2011
|
9
9
|
**License**: MIT License
|
10
|
-
**Latest Version**: 1.0.
|
10
|
+
**Latest Version**: 1.0.15
|
11
11
|
**Ruby Version**: 1.8.7, 1.9.2, 1.9.3
|
12
12
|
|
13
|
-
**Release Date**:
|
13
|
+
**Release Date**: January 6th 2012
|
14
14
|
|
15
15
|
Synopsis
|
16
16
|
--------
|
@@ -262,8 +262,14 @@ This gem has 100% test coverage using test/unit. To execute tests for this gem,
|
|
262
262
|
|
263
263
|
#Changelog
|
264
264
|
---------
|
265
|
+
- **January.6.12**: 1.0.15 release
|
266
|
+
- Bug fix add_style specified number formats must be explicity applied for libraOffice
|
267
|
+
- performance improvements from ochko when creating cells with options.
|
268
|
+
- Bug fix setting types=>[:n] when adding a row incorrectly determines the cell type to be string as the value is null during creation.
|
269
|
+
- Release in preparation for password protection merge
|
270
|
+
|
265
271
|
- **December.14.11**: 1.0.14 release
|
266
|
-
- Added support for
|
272
|
+
- Added support for merging cells
|
267
273
|
- Added support for auto filters
|
268
274
|
- Improved auto width calculations
|
269
275
|
- Improved charts
|
@@ -288,7 +294,9 @@ This gem has 100% test coverage using test/unit. To execute tests for this gem,
|
|
288
294
|
|
289
295
|
Please see the {file:CHANGELOG.md} document for past release information.
|
290
296
|
|
291
|
-
|
297
|
+
#Thanks!
|
298
|
+
--------
|
299
|
+
ochko https://github.com/ochko
|
292
300
|
#Copyright and License
|
293
301
|
----------
|
294
302
|
|
data/lib/axlsx.rb
CHANGED
@@ -6,8 +6,9 @@ require 'axlsx/version.rb'
|
|
6
6
|
require 'axlsx/util/simple_typed_list.rb'
|
7
7
|
require 'axlsx/util/constants.rb'
|
8
8
|
require 'axlsx/util/validators.rb'
|
9
|
-
|
10
|
-
require 'axlsx/util/
|
9
|
+
# require 'axlsx/util/storage.rb'
|
10
|
+
# require 'axlsx/util/cbf.rb'
|
11
|
+
# require 'axlsx/util/ms_off_crypto.rb'
|
11
12
|
|
12
13
|
|
13
14
|
# to be included with parsable intitites.
|
data/lib/axlsx/package.rb
CHANGED
@@ -4,6 +4,7 @@ module Axlsx
|
|
4
4
|
# xlsx document including valdation and serialization.
|
5
5
|
class Package
|
6
6
|
|
7
|
+
# plain text password
|
7
8
|
# provides access to the app doc properties for this package
|
8
9
|
# see App
|
9
10
|
attr_reader :app
|
@@ -87,6 +88,11 @@ module Axlsx
|
|
87
88
|
true
|
88
89
|
end
|
89
90
|
|
91
|
+
# Encrypt the package into a CFB using the password provided
|
92
|
+
# def encrypt(file_name, password)
|
93
|
+
# moc = MsOffCrypto.new(file_name, password)
|
94
|
+
# moc.save
|
95
|
+
# end
|
90
96
|
|
91
97
|
# Validate all parts of the package against xsd schema.
|
92
98
|
# @return [Array] An array of all validation errors found.
|
@@ -222,6 +222,8 @@ module Axlsx
|
|
222
222
|
applyProtection = (options[:hidden] || options[:locked]) ? 1 : 0
|
223
223
|
|
224
224
|
xf = Xf.new(:fillId => fill, :fontId=>fontId, :applyFill=>1, :applyFont=>1, :numFmtId=>numFmtId, :borderId=>borderId, :applyProtection=>applyProtection)
|
225
|
+
|
226
|
+
xf.applyNumberFormat = true if xf.numFmtId > 0
|
225
227
|
|
226
228
|
if options[:alignment]
|
227
229
|
xf.alignment = CellAlignment.new(options[:alignment])
|
@@ -0,0 +1,297 @@
|
|
1
|
+
module Axlsx
|
2
|
+
|
3
|
+
# The Cfb class is a MS-OFF-CRYPTOGRAPHY specific OLE (MS-CBF) writer implementation. No attempt is made to re-invent the wheel for read/write of compound binary files.
|
4
|
+
class Cbf
|
5
|
+
|
6
|
+
# the serialization for the CBF FAT
|
7
|
+
FAT_PACKING = "s128"
|
8
|
+
|
9
|
+
# the serialization for the MS-OFF-CRYPTO version stream
|
10
|
+
VERSION_PACKING = 'l s30 l3'
|
11
|
+
|
12
|
+
# The serialization for the MS-OFF-CRYPTO dataspace map stream
|
13
|
+
DATA_SPACE_MAP_PACKING = 'l6 s16 l s25'
|
14
|
+
|
15
|
+
# The serialization for the MS-OFF-CRYPTO strong encrytion data space stream
|
16
|
+
STRONG_ENCRYPTION_DATA_SPACE_PACKING = 'l3 s25'
|
17
|
+
|
18
|
+
# The serialization for the MS-OFF-CRYPTO primary stream
|
19
|
+
PRIMARY_PACKING = 'l3 s38 l s39 l3 x12 l x2'
|
20
|
+
|
21
|
+
# The cutoff size that determines if a stream should be in the mini-fat or the fat
|
22
|
+
MINI_CUTOFF = 4096
|
23
|
+
|
24
|
+
# The serialization for CBF header
|
25
|
+
HEADER_PACKING = "q x16 l s3 x10 l l x4 l*"
|
26
|
+
|
27
|
+
# Creates a new Cbf object based on the ms_off_crypto object provided.
|
28
|
+
# @param [MsOffCrypto] ms_off_crypto
|
29
|
+
def initialize(ms_off_crypto)
|
30
|
+
@file_name = ms_off_crypto.file_name
|
31
|
+
create_storages
|
32
|
+
mini_fat_stream
|
33
|
+
mini_fat
|
34
|
+
fat
|
35
|
+
header
|
36
|
+
end
|
37
|
+
|
38
|
+
# creates or returns the version storage
|
39
|
+
# @return [Storage]
|
40
|
+
def version
|
41
|
+
@version ||= create_version
|
42
|
+
end
|
43
|
+
|
44
|
+
# returns the data space map storage
|
45
|
+
# @return [Storage]
|
46
|
+
def data_space_map
|
47
|
+
@data_space_map ||= create_data_space_map
|
48
|
+
end
|
49
|
+
|
50
|
+
# returns the primary storage
|
51
|
+
# @return [Storgae]
|
52
|
+
def primary
|
53
|
+
@primary ||= create_primary
|
54
|
+
end
|
55
|
+
|
56
|
+
# returns the summary information storage
|
57
|
+
# @return [Storage]
|
58
|
+
def summary_information
|
59
|
+
@summary_information ||= create_summary_information
|
60
|
+
end
|
61
|
+
|
62
|
+
# returns the document summary information
|
63
|
+
# @return [Storage]
|
64
|
+
def document_summary_information
|
65
|
+
@document_summary_information ||= create_document_summary_information
|
66
|
+
end
|
67
|
+
|
68
|
+
# returns the stream of data allocated in the fat
|
69
|
+
# @return [String]
|
70
|
+
def fat_stream
|
71
|
+
@fat_stream ||= create_fat_stream
|
72
|
+
end
|
73
|
+
|
74
|
+
# returns the stream allocated in the mini fat.
|
75
|
+
# return [String]
|
76
|
+
def mini_fat_stream
|
77
|
+
@mini_fat_stream ||= create_mini_fat_stream
|
78
|
+
end
|
79
|
+
|
80
|
+
# returns the mini fat
|
81
|
+
# return [String]
|
82
|
+
def mini_fat
|
83
|
+
@mini_fat ||= create_mini_fat
|
84
|
+
end
|
85
|
+
|
86
|
+
# returns the fat
|
87
|
+
# @return [String]
|
88
|
+
def fat
|
89
|
+
@fat ||= create_fat
|
90
|
+
end
|
91
|
+
|
92
|
+
# returns the CFB header
|
93
|
+
# @return [String]
|
94
|
+
def header
|
95
|
+
@header ||= create_header
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
# writes the compound binary file to disk
|
100
|
+
def save
|
101
|
+
ole = File.open(@file_name, 'w')
|
102
|
+
ole << header
|
103
|
+
ole << fat
|
104
|
+
@storages.each { |s| ole << s.to_s }
|
105
|
+
ole << Array.new((512-(ole.pos % 512)), 0).pack('c*')
|
106
|
+
ole << mini_fat
|
107
|
+
ole << mini_fat_stream
|
108
|
+
ole << fat_stream
|
109
|
+
ole.close
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
# Generates the storages required for ms-office-cryptography cfb
|
115
|
+
def create_storages
|
116
|
+
@storages = []
|
117
|
+
@encryption_info = ms_off_crypto.encryption_info
|
118
|
+
@encrypted_package = ms_off_crypto.encrypted_package
|
119
|
+
@storages << Storage.new('R', :type=>Storage::TYPES[:root], :color=>Storage::COLORS[:red], :child=>1, :modified=>129685612742510730)
|
120
|
+
@storages.last.name_size = 2
|
121
|
+
@storages << Storage.new('EncryptionInfo', :data=>@encryption_info, :left=>3, :size => @encryption_info.size) # example shows right child. do we need the summary info????
|
122
|
+
@storages << Storage.new('EncryptedPackage', :data=>@encrypted_package, :color=>Storage::COLORS[:red], :size=>@encrypted_package.size)
|
123
|
+
@storages << Storage.new([6].pack("c")+"DataSpaces", :child=>5, :modified =>129685612740945580, :created=>129685612740819979)
|
124
|
+
@storages << version
|
125
|
+
@storages << data_space_map
|
126
|
+
@storages << Storage.new('DataSpaceInfo', :right=>8, :child=>7, :created=>129685612740828880,:modified=>129685612740831800)
|
127
|
+
@storages << strong_encryption_data_space
|
128
|
+
@storages << Storage.new('TransformInfo', :color => Storage::COLORS[:red], :child=>9, :created=>129685612740834130, :modified=>129685612740943959)
|
129
|
+
@storages << Storage.new('StrongEncryptionTransform', :child=>10, :created=>129685612740834169, :modified=>129685612740942280)
|
130
|
+
@storages << primary
|
131
|
+
end
|
132
|
+
|
133
|
+
# generates the mini fat stream
|
134
|
+
# @return [String]
|
135
|
+
def create_mini_fat_stream
|
136
|
+
mfs = []
|
137
|
+
@storages.select{ |s| s.type == Storage::TYPES[:stream] && s.size < MINI_CUTOFF}.each_with_index do |stream, index|
|
138
|
+
mfs.concat stream.data
|
139
|
+
mfs.concat Array.new(64 - (mfs.size % 64), 0) if mfs.size % 64
|
140
|
+
end
|
141
|
+
@storages[0].size = mfs.size
|
142
|
+
mfs.concat(Array.new(512 - (mfs.size % 512), 0))
|
143
|
+
mfs.pack 'c*'
|
144
|
+
end
|
145
|
+
|
146
|
+
# generates the fat stream.
|
147
|
+
# @return [String]
|
148
|
+
def create_fat_stream
|
149
|
+
mfs = []
|
150
|
+
@storages.select{ |s| s.type == Storage::TYPES[:stream] && s.size >= MINI_CUTOFF}.each_with_index do |stream, index|
|
151
|
+
mfs.concat stream.data
|
152
|
+
mfs.concat Array.new(512 - (mfs.size % 512), 0) if mfs.size % 512
|
153
|
+
end
|
154
|
+
mfs.pack 'c*'
|
155
|
+
end
|
156
|
+
|
157
|
+
# creates the mini fat
|
158
|
+
# @return [String]
|
159
|
+
def create_mini_fat
|
160
|
+
v_mf = []
|
161
|
+
@storages.select{ |s| s.type == Storage::TYPES[:stream] && s.size < MINI_CUTOFF}.each do |stream|
|
162
|
+
allocate_stream(v_mf, stream, 64)
|
163
|
+
end
|
164
|
+
v_mf.concat Array.new(128 - v_mf.size, -1)
|
165
|
+
v_mf.pack 'l*'
|
166
|
+
end
|
167
|
+
|
168
|
+
# creates the fat
|
169
|
+
# @return [String]
|
170
|
+
def create_fat
|
171
|
+
v_fat = [-3]
|
172
|
+
# storages four per sector, allocation forces directories to start at sector ID 0
|
173
|
+
allocate_stream(v_fat, @storages, 4)
|
174
|
+
# fat entry for minifat
|
175
|
+
allocate_stream(v_fat, 0, 512)
|
176
|
+
# fat entry for minifat stream
|
177
|
+
@storages[0].sector = v_fat.size
|
178
|
+
allocate_stream(v_fat, mini_fat_stream, 512)
|
179
|
+
# fat entries for encrypted package storage
|
180
|
+
# what to do about DIFAT for larger packages...
|
181
|
+
if @encrypted_package.size > (109 - v_fat.size) * 512
|
182
|
+
raise ArgumentError, "Your package is too big!"
|
183
|
+
end
|
184
|
+
|
185
|
+
if @encrypted_package.size >= MINI_CUTOFF
|
186
|
+
allocate_stream(v_fat, @encrypted_package, 512)
|
187
|
+
end
|
188
|
+
|
189
|
+
v_fat.concat Array.new(128 - v_fat.size, -1) if v_fat.size < 128 #pack in unused sectors
|
190
|
+
v_fat.pack 'l*'
|
191
|
+
end
|
192
|
+
|
193
|
+
# Creates the version storage
|
194
|
+
# @return [Storage]
|
195
|
+
def create_version
|
196
|
+
v_stream= [60, "Microsoft.Container.DataSpaces".bytes.to_a, 1, 1, 1].flatten!.pack VERSION_PACKING
|
197
|
+
Storage.new('Version', :data=>v_stream, :size=>v_stream.size)
|
198
|
+
end
|
199
|
+
|
200
|
+
# returns the strong encryption data space storage
|
201
|
+
# @return [Storgae]
|
202
|
+
def strong_encryption_data_space
|
203
|
+
@strong_encryption_data_space ||= create_strong_encryption_data_space
|
204
|
+
end
|
205
|
+
|
206
|
+
# Creates the data space map storage
|
207
|
+
# @return [Storgae]
|
208
|
+
def create_data_space_map
|
209
|
+
v_stream = [8,1,104, 1,0, 32, "EncryptedPackage".bytes.to_a, 50, "StrongEncryptionDataSpace".bytes.to_a].flatten!.pack DATA_SPACE_MAP_PACKING
|
210
|
+
Storage.new('DataSpaceMap', :data=>v_stream, :left => 4, :right => 6, :size=>v_stream.size)
|
211
|
+
end
|
212
|
+
|
213
|
+
|
214
|
+
# creates the stron encryption data space storage
|
215
|
+
# @return [Storgae]
|
216
|
+
def create_strong_encryption_data_space
|
217
|
+
v_stream = [8,1,50,"StrongEncryptionTransform".bytes.to_a].flatten.pack STRONG_ENCRYPTION_DATA_SPACE_PACKING
|
218
|
+
Storage.new("StrongEncryptionDataSpace", :data=>v_stream, :size => v_stream.size)
|
219
|
+
end
|
220
|
+
|
221
|
+
# creates the primary storage
|
222
|
+
# @return [Storgae]
|
223
|
+
def create_primary
|
224
|
+
v_stream = [88,1,76,"{FF9A3F03-56EF-4613-BDD5-5A41C1D07246}".bytes.to_a].flatten
|
225
|
+
v_stream.concat [78, "Microsoft.Container.EncryptionTransform".bytes.to_a,1,1,1,4].flatten
|
226
|
+
v_stream = v_stream.pack PRIMARY_PACKING
|
227
|
+
Storage.new([6].pack("c")+"Primary", :data=>v_stream)
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
SUMMARY_INFORMATION_PACKING = ""
|
232
|
+
# creates the summary information storage
|
233
|
+
# @return [Storage]
|
234
|
+
def create_summary_information
|
235
|
+
# FEFF 0000 030A 0100 0000 0000 0000 0000
|
236
|
+
# 0000 0000 0000 0000 0100 0000 E085 9FF2
|
237
|
+
# F94F 6810 AB91 0800 2B27 B3D9 3000 0000
|
238
|
+
# AC00 0000 0700 0000 0100 0000 4000 0000
|
239
|
+
# 0400 0000 4800 0000 0800 0000 5800 0000
|
240
|
+
# 1200 0000 6800 0000 0C00 0000 8C00 0000
|
241
|
+
# 0D00 0000 9800 0000 1300 0000 A400 0000
|
242
|
+
# 0200 0000 E9FD 0000 1E00 0000 0800 0000
|
243
|
+
# 7261 6E64 796D 0000 1E00 0000 0800 0000
|
244
|
+
# 7261 6E64 796D 0000 1E00 0000 1C00 0000
|
245
|
+
# 4D69 6372 6F73 6F66 7420 4D61 6369 6E74
|
246
|
+
# 6F73 6820 4578 6365 6C00 0000 4000 0000
|
247
|
+
# 10AC 5396 60BC CC01 4000 0000 40F4 FDAF
|
248
|
+
# 60BC CC01 0300 0000 0100 0000
|
249
|
+
v_stream = []
|
250
|
+
v_stream = v_stream.pack SUMMARY_INFORMATION_PACKING
|
251
|
+
Storage.new([5].pack('c')+"SummaryInformation", :data=>v_stream)
|
252
|
+
end
|
253
|
+
|
254
|
+
DOCUMENT_SUMMARY_INFORMATION_PACKING = ""
|
255
|
+
# creates the document summary information storage
|
256
|
+
# @return [Storage]
|
257
|
+
def create_document_summary_information
|
258
|
+
v_stream = []
|
259
|
+
v_stream = v_stream.pack DOCUMENT_SUMMARY_INFORMATION_PACKING
|
260
|
+
Storage.new([5].pack('c')+"DocumentSummaryInformation", :data=>v_stream)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Creates the header
|
264
|
+
# @return [String]
|
265
|
+
def create_header
|
266
|
+
header = []
|
267
|
+
header << -2226271756974174256 # identifier pack as q
|
268
|
+
header << 196670 # version pack as L
|
269
|
+
header << 65534 # byte order pack as s
|
270
|
+
header << 9 # sector shift
|
271
|
+
header << 6 # mini-sector shift
|
272
|
+
header << (fat.size/512.0).ceil # this is the number of FAT sectors in the file at index 6 pack as L
|
273
|
+
header << header.last # this is the first directory sector, index of 7 pack as L
|
274
|
+
header << MINI_CUTOFF # minfat cutoff pack as L
|
275
|
+
# MiniFat starts after directories
|
276
|
+
header << (fat.size/512.0).ceil + (@storages.size/4.0).ceil # this is the sector id for the first minifat index 10 pack as L
|
277
|
+
header << (mini_fat.size/512.0).ceil # minifat sector count index 11 pack as L
|
278
|
+
header << -2 # the first DIFAT - set to end of chain until we exceed a single FAT pack as L
|
279
|
+
header << 0 # number of DIFAT sectors, unless we go beyond 109 FAT sectors this will always be 0 pack as L
|
280
|
+
header << 0 # first FAT sector defined in the DIFAT pack as L
|
281
|
+
header.concat Array.new(108, -1) # Difat sectors pack as L108
|
282
|
+
header.pack(HEADER_PACKING)
|
283
|
+
end
|
284
|
+
|
285
|
+
# Allocates sector chains in a allocation table based on the sector size and stream provided
|
286
|
+
# If a storage obeject is provided, the starting sector value for the storage is updated based on the allocation performed here.
|
287
|
+
# @param [Array] table Allocation table array
|
288
|
+
# @param [Storage | String] stream
|
289
|
+
# @param [Integer] size The cutoff size for the stream.
|
290
|
+
def allocate_stream(table, stream, size)
|
291
|
+
stream.sector = table.size if stream.respond_to?(:sector)
|
292
|
+
((stream.size / size.to_f).ceil).times { table << table.size }
|
293
|
+
table[table.size-1] = -2 # this is the CBF chain terminator
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
297
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
module Axlsx
|
2
|
+
|
3
|
+
# The Cfb class is a MS-OFF-CRYPTOGRAPHY specific OLE (MS-CBF) writer implementation. No attempt is made to re-invent the wheel for read/write of compound binary files.
|
4
|
+
class Cbf
|
5
|
+
|
6
|
+
# the serialization for the CBF FAT
|
7
|
+
FAT_PACKING = "s128"
|
8
|
+
|
9
|
+
# the serialization for the MS-OFF-CRYPTO version stream
|
10
|
+
VERSION_PACKING = 'l s30 l3'
|
11
|
+
|
12
|
+
# The serialization for the MS-OFF-CRYPTO dataspace map stream
|
13
|
+
DATA_SPACE_MAP_PACKING = 'l6 s16 l s25'
|
14
|
+
|
15
|
+
# The serialization for the MS-OFF-CRYPTO strong encrytion data space stream
|
16
|
+
STRONG_ENCRYPTION_DATA_SPACE_PACKING = 'l3 s25'
|
17
|
+
|
18
|
+
# The serialization for the MS-OFF-CRYPTO primary stream
|
19
|
+
PRIMARY_PACKING = 'l3 s38 l s39 l3 x12 l x2'
|
20
|
+
|
21
|
+
# The cutoff size that determines if a stream should be in the mini-fat or the fat
|
22
|
+
MINI_CUTOFF = 4096
|
23
|
+
|
24
|
+
# Creates a new Cbf object based on a package.
|
25
|
+
def initialize(ms_off_crypto)
|
26
|
+
@file_name = ms_off_crypto.file_name
|
27
|
+
@storages = []
|
28
|
+
@encryption_info = ms_off_crypto.encryption_info
|
29
|
+
@encrypted_package = ms_off_crypto.encrypted_package
|
30
|
+
@storages << Storage.new('R', :type=>Storage::TYPES[:root], :color=>Storage::COLORS[:red], :child=>1, :modified=>129685612742510730)
|
31
|
+
@storages.last.name_size = 2
|
32
|
+
@storages << Storage.new('EncryptionInfo', :data=>@encryption_info, :left=>3, :size => @encryption_info.size) # example shows right child. do we need the summary info????
|
33
|
+
@storages << Storage.new('EncryptedPackage', :data=>@encrypted_package, :color=>Storage::COLORS[:red], :size=>@encrypted_package.size)
|
34
|
+
@storages << Storage.new([6].pack("c")+"DataSpaces", :child=>5, :modified =>129685612740945580, :created=>129685612740819979)
|
35
|
+
@storages << version
|
36
|
+
@storages << data_space_map
|
37
|
+
@storages << Storage.new('DataSpaceInfo', :right=>8, :child=>7, :created=>129685612740828880,:modified=>129685612740831800)
|
38
|
+
@storages << strong_encryption_data_space
|
39
|
+
@storages << Storage.new('TransformInfo', :color => Storage::COLORS[:red], :child=>9, :created=>129685612740834130, :modified=>129685612740943959)
|
40
|
+
@storages << Storage.new('StrongEncryptionTransform', :child=>10, :created=>129685612740834169, :modified=>129685612740942280)
|
41
|
+
@storages << primary
|
42
|
+
mini_fat_stream
|
43
|
+
mini_fat
|
44
|
+
fat
|
45
|
+
header
|
46
|
+
end
|
47
|
+
|
48
|
+
def version
|
49
|
+
@version ||= create_version
|
50
|
+
end
|
51
|
+
|
52
|
+
def data_space_map
|
53
|
+
@data_space_map ||= create_data_space_map
|
54
|
+
end
|
55
|
+
|
56
|
+
def strong_encryption_data_space
|
57
|
+
@strong_encryption_data_space ||= create_strong_encryption_data_space
|
58
|
+
end
|
59
|
+
|
60
|
+
def primary
|
61
|
+
@primary ||= create_primary
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def create_primary
|
66
|
+
v_stream = [88,1,76,"{FF9A3F03-56EF-4613-BDD5-5A41C1D07246}".bytes.to_a].flatten
|
67
|
+
v_stream.concat [78, "Microsoft.Container.EncryptionTransform".bytes.to_a,1,1,1,4].flatten
|
68
|
+
v_stream = v_stream.pack PRIMARY_PACKING
|
69
|
+
Storage.new([6].pack("c")+"Primary", :data=>v_stream, :size=>v_stream.size)
|
70
|
+
end
|
71
|
+
|
72
|
+
def create_strong_encryption_data_space
|
73
|
+
v_stream = [8,1,50,"StrongEncryptionTransform".bytes.to_a].flatten.pack STRONG_ENCRYPTION_DATA_SPACE_PACKING
|
74
|
+
Storage.new("StrongEncryptionDataSpace", :data=>v_stream, :size => v_stream.size)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Create the version storage
|
78
|
+
def create_version
|
79
|
+
v_stream= [60, "Microsoft.Container.DataSpaces".bytes.to_a, 1, 1, 1].flatten!.pack VERSION_PACKING
|
80
|
+
Storage.new('Version', :data=>v_stream, :size=>v_stream.size)
|
81
|
+
end
|
82
|
+
|
83
|
+
def create_data_space_map
|
84
|
+
v_stream = [8,1,104, 1,0, 32, "EncryptedPackage".bytes.to_a, 50, "StrongEncryptionDataSpace".bytes.to_a].flatten!.pack DATA_SPACE_MAP_PACKING
|
85
|
+
Storage.new('DataSpaceMap', :data=>v_stream, :left => 4, :right => 6, :size=>v_stream.size)
|
86
|
+
end
|
87
|
+
|
88
|
+
def allocate_stream(table, stream, size)
|
89
|
+
stream.sector = table.size if stream.respond_to?(:sector)
|
90
|
+
((stream.size / size.to_f).ceil).times { table << table.size }
|
91
|
+
table[table.size-1] = -2
|
92
|
+
end
|
93
|
+
|
94
|
+
def fat_stream
|
95
|
+
@fat_stream ||= create_fat_stream
|
96
|
+
end
|
97
|
+
def create_fat_stream
|
98
|
+
mfs = []
|
99
|
+
@storages.select{ |s| s.type == Storage::TYPES[:stream] && s.size >= MINI_CUTOFF}.each_with_index do |stream, index|
|
100
|
+
mfs.concat stream.data
|
101
|
+
mfs.concat Array.new(512 - (mfs.size % 512), 0) if mfs.size % 512
|
102
|
+
end
|
103
|
+
mfs.pack 'c*'
|
104
|
+
end
|
105
|
+
|
106
|
+
def mini_fat_stream
|
107
|
+
@mini_fat_stream ||= create_mini_fat_stream
|
108
|
+
end
|
109
|
+
|
110
|
+
def create_mini_fat_stream
|
111
|
+
mfs = []
|
112
|
+
@storages.select{ |s| s.type == Storage::TYPES[:stream] && s.size < MINI_CUTOFF}.each_with_index do |stream, index|
|
113
|
+
mfs.concat stream.data
|
114
|
+
mfs.concat Array.new(64 - (mfs.size % 64), 0) if mfs.size % 64
|
115
|
+
end
|
116
|
+
@storages[0].size = mfs.size
|
117
|
+
mfs.concat(Array.new(512 - (mfs.size % 512), 0))
|
118
|
+
mfs.pack 'c*'
|
119
|
+
end
|
120
|
+
|
121
|
+
def mini_fat
|
122
|
+
@mini_fat ||= create_mini_fat
|
123
|
+
end
|
124
|
+
|
125
|
+
def create_mini_fat
|
126
|
+
v_mf = []
|
127
|
+
@storages.select{ |s| s.type == Storage::TYPES[:stream] && s.size < MINI_CUTOFF}.each do |stream|
|
128
|
+
allocate_stream(v_mf, stream, 64)
|
129
|
+
end
|
130
|
+
v_mf.concat Array.new(128 - v_mf.size, -1)
|
131
|
+
v_mf.pack 'l*'
|
132
|
+
end
|
133
|
+
|
134
|
+
def fat
|
135
|
+
@fat ||= create_fat
|
136
|
+
end
|
137
|
+
|
138
|
+
def create_fat
|
139
|
+
v_fat = [-3]
|
140
|
+
# storages four per sector, allocation forces directories to start at sector ID 0
|
141
|
+
allocate_stream(v_fat, @storages, 4)
|
142
|
+
# fat entry for minifat
|
143
|
+
allocate_stream(v_fat, 0, 512)
|
144
|
+
# fat entry for minifat stream
|
145
|
+
@storages[0].sector = v_fat.size
|
146
|
+
allocate_stream(v_fat, mini_fat_stream, 512)
|
147
|
+
# fat entries for encrypted package storage
|
148
|
+
# what to do about DIFAT for larger packages...
|
149
|
+
if @encrypted_package.size > (109 - v_fat.size) * 512
|
150
|
+
raise ArgumentError, "Your package is too big!"
|
151
|
+
end
|
152
|
+
|
153
|
+
if @encrypted_package.size >= MINI_CUTOFF
|
154
|
+
allocate_stream(v_fat, @encrypted_package, 512)
|
155
|
+
end
|
156
|
+
|
157
|
+
v_fat.concat Array.new(128 - v_fat.size, -1) if v_fat.size < 128 #pack in unused sectors
|
158
|
+
v_fat.pack 'l*'
|
159
|
+
end
|
160
|
+
|
161
|
+
def header
|
162
|
+
@header ||= create_header
|
163
|
+
end
|
164
|
+
|
165
|
+
# The serialization for CBF header
|
166
|
+
HEADER_PACKING = "q x16 l s3 x10 l l x4 l*"
|
167
|
+
|
168
|
+
def create_header
|
169
|
+
header = []
|
170
|
+
header << -2226271756974174256 # identifier pack as q
|
171
|
+
header << 196670 # version pack as L
|
172
|
+
header << 65534 # byte order pack as s
|
173
|
+
header << 9 # sector shift
|
174
|
+
header << 6 # mini-sector shift
|
175
|
+
header << (fat.size/512.0).ceil # this is the number of FAT sectors in the file at index 6 pack as L
|
176
|
+
header << header.last # this is the first directory sector, index of 7 pack as L
|
177
|
+
header << MINI_CUTOFF # minfat cutoff pack as L
|
178
|
+
# MiniFat starts after directories
|
179
|
+
header << (fat.size/512.0).ceil + (@storages.size/4.0).ceil # this is the sector id for the first minifat index 10 pack as L
|
180
|
+
header << (mini_fat.size/512.0).ceil # minifat sector count index 11 pack as L
|
181
|
+
header << -2 # the first DIFAT - set to end of chain until we exceed a single FAT pack as L
|
182
|
+
header << 0 # number of DIFAT sectors, unless we go beyond 109 FAT sectors this will always be 0 pack as L
|
183
|
+
header << 0 # first FAT sector defined in the DIFAT pack as L
|
184
|
+
header.concat Array.new(108, -1) # Difat sectors pack as L108
|
185
|
+
end
|
186
|
+
|
187
|
+
def save
|
188
|
+
|
189
|
+
ole = File.open(@file_name, 'w')
|
190
|
+
ole << header.pack(HEADER_PACKING)
|
191
|
+
ole << fat
|
192
|
+
@storages.each { |s| ole << s.to_s }
|
193
|
+
ole << Array.new((512-(ole.pos % 512)), 0).pack('c*')
|
194
|
+
ole << mini_fat
|
195
|
+
ole << mini_fat_stream
|
196
|
+
ole << fat_stream
|
197
|
+
ole.close
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
end
|