CFPropertyList 2.0.13a → 2.0.13

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.
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