axlsx 1.0.14 → 1.0.15
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/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
|