CFPropertyList 2.0.13a → 2.0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/rbBinaryCFPropertyList.rb +198 -95
  2. metadata +6 -8
@@ -7,6 +7,9 @@ module CFPropertyList
7
7
  def load(opts)
8
8
  @unique_table = {}
9
9
  @count_objects = 0
10
+ @string_size = 0
11
+ @int_size = 0
12
+ @misc_size = 0
10
13
  @object_refs = 0
11
14
 
12
15
  @written_object_count = 0
@@ -56,6 +59,9 @@ module CFPropertyList
56
59
  def to_str(opts={})
57
60
  @unique_table = {}
58
61
  @count_objects = 0
62
+ @string_size = 0
63
+ @int_size = 0
64
+ @misc_size = 0
59
65
  @object_refs = 0
60
66
 
61
67
  @written_object_count = 0
@@ -65,32 +71,36 @@ module CFPropertyList
65
71
  @offsets = []
66
72
 
67
73
  binary_str = "bplist00"
74
+ unique_and_count_values(opts[:root])
68
75
 
69
- @object_refs = count_object_refs(opts[:root])
70
- @object_ref_size = Binary.bytes_needed(@object_refs)
76
+ @count_objects += @unique_table.size
77
+ @object_ref_size = Binary.bytes_needed(@count_objects)
78
+
79
+ file_size = @string_size + @int_size + @misc_size + @object_refs * @object_ref_size + 40
80
+ offset_size = Binary.bytes_needed(file_size)
81
+ table_offset = file_size - 32
82
+
83
+ @object_table = []
84
+ @written_object_count = 0
85
+ @unique_table = {} # we needed it to calculate several values, but now we need an empty table
71
86
 
72
87
  opts[:root].to_binary(self)
73
88
 
74
- next_offset = 8
75
- offsets = @object_table.collect { |object| offset = next_offset; next_offset += object.bytesize; offset }
76
- binary_str << @object_table.join
89
+ object_offset = 8
90
+ offsets = []
77
91
 
78
- table_offset = next_offset
79
- offset_size = Binary.bytes_needed(table_offset)
92
+ 0.upto(@object_table.size-1) do |i|
93
+ binary_str << @object_table[i]
94
+ offsets[i] = object_offset
95
+ object_offset += @object_table[i].bytesize
96
+ end
80
97
 
81
- if offset_size < 8
82
- # Fast path: encode the entire offset array at once.
83
- binary_str << offsets.pack((%w(C n N N)[offset_size - 1]) + '*')
84
- else
85
- # Slow path: host may be little or big endian, must pack each offset
86
- # separately.
87
- offsets.each do |offset|
88
- binary_str << "#{Binary.pack_it_with_size(offset_size,offset)}"
89
- end
98
+ offsets.each do |offset|
99
+ binary_str << "#{Binary.pack_it_with_size(offset_size,offset)}"
90
100
  end
91
101
 
92
102
  binary_str << [offset_size, @object_ref_size].pack("x6CC")
93
- binary_str << [@object_table.size].pack("x4N")
103
+ binary_str << [@count_objects].pack("x4N")
94
104
  binary_str << [0].pack("x4N")
95
105
  binary_str << [table_offset].pack("x4N")
96
106
 
@@ -342,35 +352,39 @@ module CFPropertyList
342
352
  end
343
353
  protected :read_binary_object_at
344
354
 
355
+ # calculate the bytes needed for a size integer value
356
+ def Binary.bytes_size_int(int)
357
+ nbytes = 0
358
+
359
+ nbytes += 2 if int > 0xE # 2 bytes int
360
+ nbytes += 1 if int > 0xFF # 3 bytes int
361
+ nbytes += 2 if int > 0xFFFF # 5 bytes int
362
+
363
+ return nbytes
364
+ end
365
+
366
+ # Calculate the byte needed for a „normal” integer value
367
+ def Binary.bytes_int(int)
368
+ nbytes = 1
369
+
370
+ nbytes += 1 if int > 0xFF # 2 byte int
371
+ nbytes += 2 if int > 0xFFFF # 4 byte int
372
+ nbytes += 4 if int > 0xFFFFFFFF # 8 byte int
373
+ nbytes += 7 if int < 0 # 8 byte int (since it is signed)
374
+
375
+ return nbytes + 1 # one „marker” byte
376
+ end
377
+
345
378
  # pack an +int+ of +nbytes+ with size
346
379
  def Binary.pack_it_with_size(nbytes,int)
347
- case nbytes
348
- when 1
349
- return [int].pack('c')
350
- when 2
351
- return [int].pack('n')
352
- when 4
353
- return [int].pack('N')
354
- when 8
355
- return [int >> 32, int & 0xFFFFFFFF].pack('NN')
356
- else
357
- raise CFFormatError.new("Don't know how to pack #{nbytes} byte integer")
358
- end
359
- end
360
-
361
- def Binary.pack_int_array_with_size(nbytes, array)
362
- case nbytes
363
- when 1
364
- array.pack('C*')
365
- when 2
366
- array.pack('n*')
367
- when 4
368
- array.pack('N*')
369
- when 8
370
- array.collect { |int| [int >> 32, int & 0xFFFFFFFF].pack('NN') }.join
371
- else
372
- raise CFFormatError.new("Don't know how to pack #{nbytes} byte integer")
380
+ format = ["C", "n", "N", "N"][nbytes-1]
381
+
382
+ if(nbytes == 3) then
383
+ val = [int].pack(format)
384
+ return val.slice(-3)
373
385
  end
386
+
387
+ return [int].pack(format)
374
388
  end
375
389
 
376
390
  # calculate how many bytes are needed to save +count+
@@ -390,53 +404,130 @@ module CFPropertyList
390
404
  return nbytes
391
405
  end
392
406
 
393
- # Create a type byte for binary format as defined by apple
394
- def Binary.type_bytes(type, length)
395
- if length < 15
396
- return [(type << 4) | length].pack('C')
397
- else
398
- bytes = [(type << 4) | 0xF]
399
- if length <= 0xFF
400
- return bytes.push(0x10, length).pack('CCC') # 1 byte length
401
- elsif length <= 0xFFFF
402
- return bytes.push(0x11, length).pack('CCn') # 2 byte length
403
- elsif length <= 0xFFFFFFFF
404
- return bytes.push(0x12, length).pack('CCN') # 4 byte length
405
- elsif length <= 0x7FFFFFFFFFFFFFFF
406
- return bytes.push(0x13, length >> 32, length & 0xFFFFFFFF).pack('CCNN') # 8 byte length
407
+ # create integer bytes of +int+
408
+ def Binary.int_bytes(int)
409
+ intbytes = ""
410
+
411
+ if(int >= 0) then
412
+ if (int <= 0xFF) then
413
+ intbytes = "\x10"+[int].pack("c") # 1 byte integer
414
+ elsif(int <= 0xFFFF) then
415
+ intbytes = "\x11"+[int].pack("n") # 2 byte integer
416
+ elsif(int <= 0xFFFFFFFF) then
417
+ intbytes = "\x12"+[int].pack("N") # 4 byte integer
418
+ elsif(int <= 0x7FFFFFFFFFFFFFFF)
419
+ intbytes = "\x13"+[int >> 32, int & 0xFFFFFFFF].pack("NN") # 8 byte integer
407
420
  else
408
421
  raise CFFormatError.new("Integer too large: #{int}")
409
422
  end
423
+ else
424
+ intbytes = "\x13"+[int >> 32, int & 0xFFFFFFFF].pack("NN") # 8 byte integer
410
425
  end
426
+
427
+ return intbytes;
411
428
  end
412
-
413
- def count_object_refs(object)
414
- case object
415
- when CFArray
416
- contained_refs = 0
417
- object.value.each do |element|
418
- if CFArray === element || CFDictionary === element
419
- contained_refs += count_object_refs(element)
420
- end
429
+
430
+ # Create a type byte for binary format as defined by apple
431
+ def Binary.type_bytes(type,type_len)
432
+ optional_int = ""
433
+
434
+ if(type_len < 15) then
435
+ type += sprintf("%x",type_len)
436
+ else
437
+ type += "f"
438
+ optional_int = Binary.int_bytes(type_len)
439
+ end
440
+
441
+ return [type].pack("H*") + optional_int
442
+ end
443
+
444
+ # „unique” and count values. „Unique” means, several objects (e.g. strings)
445
+ # will only be saved once and referenced later
446
+ def unique_and_count_values(value)
447
+ # no uniquing for other types than CFString and CFData
448
+ if(value.is_a?(CFInteger) || value.is_a?(CFReal)) then
449
+ val = value.value
450
+ if(value.is_a?(CFInteger)) then
451
+ @int_size += Binary.bytes_int(val)
452
+ else
453
+ @misc_size += 9 # 9 bytes (8 + marker byte) for real
421
454
  end
422
- return object.value.size + contained_refs
423
- when CFDictionary
424
- contained_refs = 0
425
- object.value.each_value do |value|
426
- if CFArray === value || CFDictionary === value
427
- contained_refs += count_object_refs(value)
455
+
456
+ @count_objects += 1
457
+ return
458
+ elsif(value.is_a?(CFDate)) then
459
+ @misc_size += 9
460
+ @count_objects += 1
461
+ return
462
+ elsif(value.is_a?(CFBoolean)) then
463
+ @count_objects += 1
464
+ @misc_size += 1
465
+ return
466
+ elsif(value.is_a?(CFArray)) then
467
+ cnt = 0
468
+
469
+ value.value.each do |v|
470
+ cnt += 1
471
+ unique_and_count_values(v)
472
+ @object_refs += 1 # each array member is a ref
473
+ end
474
+
475
+ @count_objects += 1
476
+ @int_size += Binary.bytes_size_int(cnt)
477
+ @misc_size += 1 # marker byte for array
478
+ return
479
+ elsif(value.is_a?(CFDictionary)) then
480
+ cnt = 0
481
+
482
+ value.value.each_pair do |k,v|
483
+ cnt += 1
484
+
485
+ if(!@unique_table.has_key?(k))
486
+ @unique_table[k] = 0
487
+ @string_size += Binary.binary_strlen(k) + 1
488
+ @int_size += Binary.bytes_size_int(Binary.charset_strlen(k,'UTF-8'))
428
489
  end
490
+
491
+ @object_refs += 2 # both, key and value, are refs
492
+ @unique_table[k] += 1
493
+ unique_and_count_values(v)
429
494
  end
430
- return object.value.keys.size * 2 + contained_refs
431
- else
432
- return 0
495
+
496
+ @count_objects += 1
497
+ @misc_size += 1 # marker byte for dict
498
+ @int_size += Binary.bytes_size_int(cnt)
499
+ return
500
+ elsif(value.is_a?(CFData)) then
501
+ val = value.decoded_value
502
+ @int_size += Binary.bytes_size_int(val.length)
503
+ @misc_size += val.length + 1
504
+ @count_objects += 1
505
+ return
433
506
  end
507
+
508
+ val = value.value
509
+ if(!@unique_table.has_key?(val)) then
510
+ @unique_table[val] = 0
511
+ @string_size += Binary.binary_strlen(val) + 1
512
+ @int_size += Binary.bytes_size_int(Binary.charset_strlen(val,'UTF-8'))
513
+ end
514
+
515
+ @unique_table[val] += 1
434
516
  end
517
+ protected :unique_and_count_values
518
+
519
+ # Counts the number of bytes the string will have when coded; utf-16be if non-ascii characters are present.
520
+ def Binary.binary_strlen(val)
521
+ val.each_byte do |b|
522
+ if(b > 127) then
523
+ val = Binary.charset_convert(val, 'UTF-8', 'UTF-16BE')
524
+ return val.bytesize
525
+ end
526
+ end
435
527
 
436
- def Binary.ascii_string?(str)
437
- return str.scan(/[\x80-\xFF]/m).size == 0
528
+ return val.bytesize
438
529
  end
439
-
530
+
440
531
  # Uniques and transforms a string value to binary format and adds it to the object table
441
532
  def string_to_binary(val)
442
533
  saved_object_count = -1
@@ -446,19 +537,23 @@ module CFPropertyList
446
537
  @written_object_count += 1
447
538
 
448
539
  @unique_table[val] = saved_object_count
449
- utf16 = !Binary.ascii_string?(val)
540
+ utf16 = false
541
+
542
+ val.each_byte do |b|
543
+ if(b > 127) then
544
+ utf16 = true
545
+ break
546
+ end
547
+ end
450
548
 
451
- utf8_strlen = 0
452
549
  if(utf16) then
453
- utf8_strlen = Binary.charset_strlen(val, "UTF-8")
454
550
  val = Binary.charset_convert(val,"UTF-8","UTF-16BE")
455
- bdata = Binary.type_bytes(0b0110, Binary.charset_strlen(val,"UTF-16BE"))
551
+ bdata = Binary.type_bytes("6",Binary.charset_strlen(val,"UTF-16BE")) # 6 is 0110, unicode string (utf16be)
456
552
 
457
553
  val.force_encoding("ASCII-8BIT") if val.respond_to?("encode")
458
554
  @object_table[saved_object_count] = bdata + val
459
555
  else
460
- utf8_strlen = val.bytesize
461
- bdata = Binary.type_bytes(0b0101,val.bytesize)
556
+ bdata = Binary.type_bytes("5",val.bytesize) # 5 is 0101 which is an ASCII string (seems to be ASCII encoded)
462
557
  @object_table[saved_object_count] = bdata + val
463
558
  end
464
559
  else
@@ -476,7 +571,7 @@ module CFPropertyList
476
571
  nbytes += 1 if value > 0xFFFFFFFF # 8 byte integer
477
572
  nbytes = 3 if value < 0 # 8 byte integer, since signed
478
573
 
479
- bdata = Binary.type_bytes(0b0001, nbytes)
574
+ bdata = Binary.type_bytes("1", nbytes) # 1 is 0001, type indicator for integer
480
575
  buff = ""
481
576
 
482
577
  if(nbytes < 3) then
@@ -501,7 +596,7 @@ module CFPropertyList
501
596
 
502
597
  # Codes a real value to binary format
503
598
  def real_to_binary(val)
504
- bdata = Binary.type_bytes(0b0010,3)
599
+ bdata = Binary.type_bytes("2",3) # 2 is 0010, type indicator for reals
505
600
  buff = [val].pack("d")
506
601
  return bdata + buff.reverse
507
602
  end
@@ -529,7 +624,7 @@ module CFPropertyList
529
624
 
530
625
  val = val.getutc.to_f - CFDate::DATE_DIFF_APPLE_UNIX # CFDate is a real, number of seconds since 01/01/2001 00:00:00 GMT
531
626
 
532
- bdata = Binary.type_bytes(0b0011, 3)
627
+ bdata = Binary.type_bytes("3", 3) # 3 is 0011, type indicator for date
533
628
  @object_table[saved_object_count] = bdata + [val].pack("d").reverse
534
629
 
535
630
  return saved_object_count
@@ -549,7 +644,7 @@ module CFPropertyList
549
644
  saved_object_count = @written_object_count
550
645
  @written_object_count += 1
551
646
 
552
- bdata = Binary.type_bytes(0b0100, val.bytesize)
647
+ bdata = Binary.type_bytes("4", val.bytesize) # a is 1000, type indicator for data
553
648
  @object_table[saved_object_count] = bdata + val
554
649
 
555
650
  return saved_object_count
@@ -560,8 +655,11 @@ module CFPropertyList
560
655
  saved_object_count = @written_object_count
561
656
  @written_object_count += 1
562
657
 
563
- bdata = Binary.type_bytes(0b1010, val.value.size) +
564
- Binary.pack_int_array_with_size(@object_ref_size, val.value.collect { |v| v.to_binary(self) })
658
+ bdata = Binary.type_bytes("a", val.value.size) # a is 1010, type indicator for arrays
659
+
660
+ val.value.each do |v|
661
+ bdata << Binary.pack_it_with_size(@object_ref_size, v.to_binary(self));
662
+ end
565
663
 
566
664
  @object_table[saved_object_count] = bdata
567
665
  return saved_object_count
@@ -572,12 +670,17 @@ module CFPropertyList
572
670
  saved_object_count = @written_object_count
573
671
  @written_object_count += 1
574
672
 
575
- keys_and_values = val.value.collect do |k, v|
576
- [ CFString.new(k).to_binary(self), v.to_binary(self) ]
577
- end.flatten
673
+ bdata = Binary.type_bytes("d",val.value.size) # d=1101, type indicator for dictionary
578
674
 
579
- bdata = Binary.type_bytes(0b1101,val.value.size) +
580
- Binary.pack_int_array_with_size(@object_ref_size, keys_and_values)
675
+ val.value.each_key do |k|
676
+ str = CFString.new(k)
677
+ key = str.to_binary(self)
678
+ bdata << Binary.pack_it_with_size(@object_ref_size,key)
679
+ end
680
+
681
+ val.value.each_value do |v|
682
+ bdata << Binary.pack_it_with_size(@object_ref_size,v.to_binary(self))
683
+ end
581
684
 
582
685
  @object_table[saved_object_count] = bdata
583
686
  return saved_object_count
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: CFPropertyList
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: true
4
+ prerelease: false
5
5
  segments:
6
6
  - 2
7
7
  - 0
8
- - 13a
9
- version: 2.0.13a
8
+ - 13
9
+ version: 2.0.13
10
10
  platform: ruby
11
11
  authors:
12
12
  - Christian Kruse
@@ -83,13 +83,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
83
83
  required_rubygems_version: !ruby/object:Gem::Requirement
84
84
  none: false
85
85
  requirements:
86
- - - ">"
86
+ - - ">="
87
87
  - !ruby/object:Gem::Version
88
88
  segments:
89
- - 1
90
- - 3
91
- - 1
92
- version: 1.3.1
89
+ - 0
90
+ version: "0"
93
91
  requirements: []
94
92
 
95
93
  rubyforge_project: cfpropertylist