ExtraField::parse()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.0116

Importance

Changes 0
Metric Value
cc 2
eloc 9
nc 2
nop 2
dl 0
loc 14
ccs 6
cts 7
cp 0.8571
crap 2.0116
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace morgue\zip;
4
5
use morgue\zip\extraField\Zip64ExtendedInformation;
6
7
final class ExtraField implements ExtraFieldInterface
8
{
9
    /**
10
     * Minimum length an extra field if the data is empty
11
     */
12
    const MIN_LENGTH = 4;
13
14
    /**
15
     * Maximum length an extra field can have if the data is completely used
16
     */
17
    const MAX_LENGTH = self::MIN_LENGTH + self::DATA_MAX_LENGTH;
18
19
    /**
20
     * Data can not be longer than this (the length field has only 2 bytes)
21
     */
22
    const DATA_MAX_LENGTH = (255 * 255) - 1;
23
24
    /**
25
     * Mapping from header ID => implementing class
26
     * @var array
27
     */
28
    private static $extraFieldTypes = [
29
        Zip64ExtendedInformation::ID => Zip64ExtendedInformation::class,
30
    ];
31
32
    /**
33
     * @var int
34
     */
35
    private $headerId;
36
37
    /**
38
     * @var string
39
     */
40
    private $data;
41
42 5
    public function __construct(int $headerId, string $data = "")
43
    {
44 5
        $this->headerId = $headerId;
45 5
        $this->data = $data;
46 5
    }
47
48 5
    public static function parse(string $input, $context = null)
49
    {
50 5
        $parsed = \unpack(
51
            'vheaderId'
52 5
            . '/vdataLength',
53 5
            $input
54
        );
55
56 5
        if (isset(self::$extraFieldTypes[$parsed['headerId']])) {
57
            return self::$extraFieldTypes[$parsed['headerId']]::parse(\substr($input, 0, self::MIN_LENGTH + $parsed['dataLength']), $context);
58
        } else {
59 5
            return new self($parsed['headerId'], \substr($input, self::MIN_LENGTH, $parsed['dataLength']));
60
        }
61
    }
62
63
    /**
64
     * @param string $extraFieldData
65
     * @param null $context The context this extra field comes from, e.g. a CentralDirectoryHeader
66
     * @return ExtraFieldInterface[]
67
     */
68 2048
    public static function parseAll(string $extraFieldData, $context = null)
69
    {
70 2048
        $fields = [];
71
72 2048
        $offset = 0;
73 2048
        while ($offset < \strlen($extraFieldData)) {
74 5
            $fields[] = $field = self::parse(\substr($extraFieldData, $offset), $context);
75 5
            $offset += self::MIN_LENGTH + $field->getDataSize();
76
        }
77 2048
        return $fields;
78
    }
79
80
    public function getHeaderId()
81
    {
82
        return $this->headerId;
83
    }
84
85 5
    public function getDataSize()
86
    {
87 5
        return \strlen($this->data);
88
    }
89
90
    public function getData()
91
    {
92
        return $this->data;
93
    }
94
95
    /**
96
     * Register a custom extra field type that can parse extra fields identified by $id.
97
     * The class must implement ExtraFieldInterface
98
     *
99
     * @param int $id
100
     * @param string $className
101
     */
102
    public static function registerExtraFieldType(int $id, string $className)
103
    {
104
        if (!\is_a($className, ExtraFieldInterface::class, true)) {
105
            throw new \InvalidArgumentException("Extra field implementations must implement " . ExtraFieldInterface::class);
106
        }
107
108
        self::$extraFieldTypes[$id] = $className;
109
    }
110
111
    /**
112
     * Convert an extra field to it's binary string representation
113
     * @param ExtraFieldInterface $extraField
114
     * @return string
115
     */
116 1080
    public static function marshal(ExtraFieldInterface $extraField)
117
    {
118 1080
        return \pack('vv', $extraField->getHeaderId(), $extraField->getDataSize()) . $extraField->getData();
119
    }
120
}
121