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 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.14
10
+ **Latest Version**: 1.0.15
11
11
  **Ruby Version**: 1.8.7, 1.9.2, 1.9.3
12
12
 
13
- **Release Date**: December 14th 2011
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 mergin cells
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
 
@@ -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/ms_off_crypto.rb'
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.
@@ -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