PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 4.8.1
WP STAGING – WordPress Backup, Restore, Migration & Clone v4.8.1
4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Backup / FileHeader / ExtraFieldCodec.php
wp-staging / Backup / FileHeader Last commit date
ExtraFieldCodec.php 1 month ago ExtraFieldType.php 1 month ago
ExtraFieldCodec.php
143 lines
1 <?php
2
3 namespace WPStaging\Backup\FileHeader;
4
5 /**
6 * Type-Length-Value codec for FileHeader::extraField.
7 *
8 * Wire format (all multi-byte integers are big-endian):
9 *
10 * bytes = [magic:2] [entry] [entry] ...
11 * magic = 0x57 0x54 ("WT")
12 * entry = [type:1] [length:2] [value:length]
13 *
14 * Decode rules:
15 * - Empty input -> []
16 * - Input without "WT" magic -> [LEGACY_RAW => $bytes] (pre-2.1.0 raw value)
17 * - Magic present -> entries are parsed to end of input
18 * - Type 0xFF on the wire -> UnexpectedValueException (LEGACY_RAW is parser-only)
19 * - Truncated entry -> UnexpectedValueException
20 * - Unknown types -> preserved in the map and round-tripped
21 *
22 * Magic-byte collision: backups produced before backup version 2.1.0 always
23 * wrote an empty extraField, so no legacy bytes can ever start with "WT".
24 *
25 * @see ExtraFieldType
26 */
27 final class ExtraFieldCodec
28 {
29 /**
30 * Two-byte magic prefix that distinguishes a TLV-encoded extraField from a
31 * legacy raw value. Chosen as ASCII "WT" (W = WP Staging, T = TLV).
32 */
33 const MAGIC = "\x57\x54";
34
35 /**
36 * Maximum number of bytes a single entry value may hold. Bounded by the
37 * 2-byte big-endian length field.
38 */
39 const MAX_VALUE_LENGTH = 65535;
40
41 /**
42 * Encode a map of TLV entries into a byte string.
43 *
44 * The LEGACY_RAW sentinel is parser-only and is silently skipped on encode
45 * so that round-tripping a legacy value through decode/encode does not
46 * smuggle the sentinel back onto disk.
47 *
48 * @param array<int,string> $entries Type-keyed map of value bytes.
49 * @return string
50 * @throws \UnexpectedValueException When a type is out of range, a value
51 * exceeds MAX_VALUE_LENGTH, or a known
52 * type with a fixed wire size receives a
53 * value of the wrong length.
54 */
55 public function encode(array $entries): string
56 {
57 if (empty($entries)) {
58 return '';
59 }
60
61 $out = self::MAGIC;
62 foreach ($entries as $type => $value) {
63 if ($type === ExtraFieldType::LEGACY_RAW) {
64 continue;
65 }
66
67 if (!is_int($type) || $type < 0 || $type > 0xFF) {
68 throw new \UnexpectedValueException(sprintf('ExtraFieldCodec: type %s is out of the 0x00-0xFF range.', var_export($type, true)));
69 }
70
71 $length = strlen($value);
72 if ($length > self::MAX_VALUE_LENGTH) {
73 throw new \UnexpectedValueException(sprintf('ExtraFieldCodec: value for type 0x%02X is %d bytes, exceeding the %d-byte limit.', $type, $length, self::MAX_VALUE_LENGTH));
74 }
75
76 if (isset(ExtraFieldType::FIXED_WIRE_SIZES[$type]) && $length !== ExtraFieldType::FIXED_WIRE_SIZES[$type]) {
77 throw new \UnexpectedValueException(sprintf('ExtraFieldCodec: type 0x%02X requires exactly %d bytes, got %d.', $type, ExtraFieldType::FIXED_WIRE_SIZES[$type], $length));
78 }
79
80 $out .= chr($type) . pack('n', $length) . $value;
81 }
82
83 // No real entries (e.g. caller passed only LEGACY_RAW): emit empty rather than a bare magic.
84 if ($out === self::MAGIC) {
85 return '';
86 }
87
88 return $out;
89 }
90
91 /**
92 * Decode a byte string into a map of TLV entries.
93 *
94 * @param string $bytes
95 * @return array<int,string>
96 * @throws \UnexpectedValueException When the input has the magic prefix but
97 * is malformed (truncated header, length
98 * overrun, duplicate type, or carries the
99 * parser-only LEGACY_RAW type on the wire).
100 */
101 public function decode(string $bytes): array
102 {
103 if ($bytes === '') {
104 return [];
105 }
106
107 if (substr($bytes, 0, 2) !== self::MAGIC) {
108 return [ExtraFieldType::LEGACY_RAW => $bytes];
109 }
110
111 $entries = [];
112 $offset = 2;
113 $total = strlen($bytes);
114
115 while ($offset < $total) {
116 if ($total - $offset < 3) {
117 throw new \UnexpectedValueException('ExtraFieldCodec: truncated entry header.');
118 }
119
120 $type = ord($bytes[$offset]);
121 $length = unpack('n', substr($bytes, $offset + 1, 2))[1];
122 $offset += 3;
123
124 if ($type === ExtraFieldType::LEGACY_RAW) {
125 throw new \UnexpectedValueException(sprintf('ExtraFieldCodec: type 0x%02X is reserved as a parser-only sentinel and is not valid on the wire.', ExtraFieldType::LEGACY_RAW));
126 }
127
128 if ($total - $offset < $length) {
129 throw new \UnexpectedValueException(sprintf('ExtraFieldCodec: declared length %d for type 0x%02X overruns end of input.', $length, $type));
130 }
131
132 if (array_key_exists($type, $entries)) {
133 throw new \UnexpectedValueException(sprintf('ExtraFieldCodec: duplicate entry of type 0x%02X.', $type));
134 }
135
136 $entries[$type] = substr($bytes, $offset, $length);
137 $offset += $length;
138 }
139
140 return $entries;
141 }
142 }
143