CFPropertyList 2.0.13 → 2.0.13.1

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 +95 -198
  2. metadata +2 -1
@@ -7,9 +7,6 @@ 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
13
10
  @object_refs = 0
14
11
 
15
12
  @written_object_count = 0
@@ -59,9 +56,6 @@ module CFPropertyList
59
56
  def to_str(opts={})
60
57
  @unique_table = {}
61
58
  @count_objects = 0
62
- @string_size = 0
63
- @int_size = 0
64
- @misc_size = 0
65
59
  @object_refs = 0
66
60
 
67
61
  @written_object_count = 0
@@ -71,36 +65,32 @@ module CFPropertyList
71
65
  @offsets = []
72
66
 
73
67
  binary_str = "bplist00"
74
- unique_and_count_values(opts[:root])
75
68
 
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
69
+ @object_refs = count_object_refs(opts[:root])
70
+ @object_ref_size = Binary.bytes_needed(@object_refs)
86
71
 
87
72
  opts[:root].to_binary(self)
88
73
 
89
- object_offset = 8
90
- offsets = []
74
+ next_offset = 8
75
+ offsets = @object_table.collect { |object| offset = next_offset; next_offset += object.bytesize; offset }
76
+ binary_str << @object_table.join
91
77
 
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
78
+ table_offset = next_offset
79
+ offset_size = Binary.bytes_needed(table_offset)
97
80
 
98
- offsets.each do |offset|
99
- binary_str << "#{Binary.pack_it_with_size(offset_size,offset)}"
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
100
90
  end
101
91
 
102
92
  binary_str << [offset_size, @object_ref_size].pack("x6CC")
103
- binary_str << [@count_objects].pack("x4N")
93
+ binary_str << [@object_table.size].pack("x4N")
104
94
  binary_str << [0].pack("x4N")
105
95
  binary_str << [table_offset].pack("x4N")
106
96
 
@@ -352,39 +342,35 @@ module CFPropertyList
352
342
  end
353
343
  protected :read_binary_object_at
354
344
 
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
-
378
345
  # pack an +int+ of +nbytes+ with size
379
346
  def Binary.pack_it_with_size(nbytes,int)
380
- format = ["C", "n", "N", "N"][nbytes-1]
381
-
382
- if(nbytes == 3) then
383
- val = [int].pack(format)
384
- return val.slice(-3)
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")
385
373
  end
386
-
387
- return [int].pack(format)
388
374
  end
389
375
 
390
376
  # calculate how many bytes are needed to save +count+
@@ -404,130 +390,53 @@ module CFPropertyList
404
390
  return nbytes
405
391
  end
406
392
 
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
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
420
407
  else
421
408
  raise CFFormatError.new("Integer too large: #{int}")
422
409
  end
423
- else
424
- intbytes = "\x13"+[int >> 32, int & 0xFFFFFFFF].pack("NN") # 8 byte integer
425
410
  end
426
-
427
- return intbytes;
428
411
  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
454
- end
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
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
473
421
  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'))
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)
489
428
  end
490
-
491
- @object_refs += 2 # both, key and value, are refs
492
- @unique_table[k] += 1
493
- unique_and_count_values(v)
494
429
  end
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
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'))
430
+ return object.value.keys.size * 2 + contained_refs
431
+ else
432
+ return 0
513
433
  end
514
-
515
- @unique_table[val] += 1
516
434
  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
527
435
 
528
- return val.bytesize
436
+ def Binary.ascii_string?(str)
437
+ return str.scan(/[\x80-\xFF]/m).size == 0
529
438
  end
530
-
439
+
531
440
  # Uniques and transforms a string value to binary format and adds it to the object table
532
441
  def string_to_binary(val)
533
442
  saved_object_count = -1
@@ -537,23 +446,19 @@ module CFPropertyList
537
446
  @written_object_count += 1
538
447
 
539
448
  @unique_table[val] = saved_object_count
540
- utf16 = false
541
-
542
- val.each_byte do |b|
543
- if(b > 127) then
544
- utf16 = true
545
- break
546
- end
547
- end
449
+ utf16 = !Binary.ascii_string?(val)
548
450
 
451
+ utf8_strlen = 0
549
452
  if(utf16) then
453
+ utf8_strlen = Binary.charset_strlen(val, "UTF-8")
550
454
  val = Binary.charset_convert(val,"UTF-8","UTF-16BE")
551
- bdata = Binary.type_bytes("6",Binary.charset_strlen(val,"UTF-16BE")) # 6 is 0110, unicode string (utf16be)
455
+ bdata = Binary.type_bytes(0b0110, Binary.charset_strlen(val,"UTF-16BE"))
552
456
 
553
457
  val.force_encoding("ASCII-8BIT") if val.respond_to?("encode")
554
458
  @object_table[saved_object_count] = bdata + val
555
459
  else
556
- bdata = Binary.type_bytes("5",val.bytesize) # 5 is 0101 which is an ASCII string (seems to be ASCII encoded)
460
+ utf8_strlen = val.bytesize
461
+ bdata = Binary.type_bytes(0b0101,val.bytesize)
557
462
  @object_table[saved_object_count] = bdata + val
558
463
  end
559
464
  else
@@ -571,7 +476,7 @@ module CFPropertyList
571
476
  nbytes += 1 if value > 0xFFFFFFFF # 8 byte integer
572
477
  nbytes = 3 if value < 0 # 8 byte integer, since signed
573
478
 
574
- bdata = Binary.type_bytes("1", nbytes) # 1 is 0001, type indicator for integer
479
+ bdata = Binary.type_bytes(0b0001, nbytes)
575
480
  buff = ""
576
481
 
577
482
  if(nbytes < 3) then
@@ -596,7 +501,7 @@ module CFPropertyList
596
501
 
597
502
  # Codes a real value to binary format
598
503
  def real_to_binary(val)
599
- bdata = Binary.type_bytes("2",3) # 2 is 0010, type indicator for reals
504
+ bdata = Binary.type_bytes(0b0010,3)
600
505
  buff = [val].pack("d")
601
506
  return bdata + buff.reverse
602
507
  end
@@ -624,7 +529,7 @@ module CFPropertyList
624
529
 
625
530
  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
626
531
 
627
- bdata = Binary.type_bytes("3", 3) # 3 is 0011, type indicator for date
532
+ bdata = Binary.type_bytes(0b0011, 3)
628
533
  @object_table[saved_object_count] = bdata + [val].pack("d").reverse
629
534
 
630
535
  return saved_object_count
@@ -644,7 +549,7 @@ module CFPropertyList
644
549
  saved_object_count = @written_object_count
645
550
  @written_object_count += 1
646
551
 
647
- bdata = Binary.type_bytes("4", val.bytesize) # a is 1000, type indicator for data
552
+ bdata = Binary.type_bytes(0b0100, val.bytesize)
648
553
  @object_table[saved_object_count] = bdata + val
649
554
 
650
555
  return saved_object_count
@@ -655,11 +560,8 @@ module CFPropertyList
655
560
  saved_object_count = @written_object_count
656
561
  @written_object_count += 1
657
562
 
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
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) })
663
565
 
664
566
  @object_table[saved_object_count] = bdata
665
567
  return saved_object_count
@@ -670,17 +572,12 @@ module CFPropertyList
670
572
  saved_object_count = @written_object_count
671
573
  @written_object_count += 1
672
574
 
673
- bdata = Binary.type_bytes("d",val.value.size) # d=1101, type indicator for dictionary
575
+ keys_and_values = val.value.collect do |k, v|
576
+ [ CFString.new(k).to_binary(self), v.to_binary(self) ]
577
+ end.flatten
674
578
 
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
579
+ bdata = Binary.type_bytes(0b1101,val.value.size) +
580
+ Binary.pack_int_array_with_size(@object_ref_size, keys_and_values)
684
581
 
685
582
  @object_table[saved_object_count] = bdata
686
583
  return saved_object_count
metadata CHANGED
@@ -6,7 +6,8 @@ version: !ruby/object:Gem::Version
6
6
  - 2
7
7
  - 0
8
8
  - 13
9
- version: 2.0.13
9
+ - 1
10
+ version: 2.0.13.1
10
11
  platform: ruby
11
12
  authors:
12
13
  - Christian Kruse