Completed
Pull Request — master (#40)
by ignace nyamagana
11:26
created

DataPath::__set_state()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
ccs 0
cts 0
cp 0
rs 9.6666
cc 1
eloc 6
nc 1
nop 1
crap 2
1
<?php
2
3
/**
4
 * League.Uri (http://uri.thephpleague.com)
5
 *
6
 * @package   League.uri
7
 * @author    Ignace Nyamagana Butera <[email protected]>
8
 * @copyright 2013-2015 Ignace Nyamagana Butera
9
 * @license   https://github.com/thephpleague/uri/blob/master/LICENSE (MIT License)
10
 * @version   4.1.0
11
 * @link      https://github.com/thephpleague/uri/
12
 */
13
namespace League\Uri\Components;
14
15
use InvalidArgumentException;
16
use League\Uri\Interfaces\DataPath as DataPathInterface;
17
use RuntimeException;
18
use SplFileObject;
19
20
/**
21
 * Value object representing a URI path component.
22
 *
23
 * @package League.uri
24
 * @author  Ignace Nyamagana Butera <[email protected]>
25
 * @since   4.0.0
26
 */
27
class DataPath extends AbstractComponent implements DataPathInterface
28
{
29
    use PathTrait;
30
31
    const DEFAULT_MIMETYPE = 'text/plain';
32
33
    const DEFAULT_PARAMETER = 'charset=us-ascii';
34
35
    const BINARY_PARAMETER = 'base64';
36
37
    const REGEXP_MIMETYPE = ',^\w+/[-.\w]+(?:\+[-.\w]+)?$,';
38
39
    /**
40
     * The mediatype mimetype
41
     *
42
     * @var string
43
     */
44
    protected $mimetype;
45
46
    /**
47
     * The mediatype parameters
48
     *
49
     * @var string[]
50
     */
51
    protected $parameters;
52
53
    /**
54
     * Is the Document bas64 encoded
55
     *
56
     * @var bool
57
     */
58
    protected $isBinaryData;
59
60
    /**
61
     * The document string representation
62
     *
63
     * @var string
64
     */
65
    protected $document;
66
67
    /**
68
     * @inheritdoc
69
     */
70
    protected static $characters_set = [
71
        '/', ':', '@', '!', '$', '&', "'", '%',
72
        '(', ')', '*', '+', ',', ';', '=', '?',
73
    ];
74
75
    /**
76
     * @inheritdoc
77
     */
78
    protected static $characters_set_encoded = [
79
        '%2F', '%3A', '%40', '%21', '%24', '%26', '%27', '%25',
80
        '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D', '%3F',
81
    ];
82
83
    /**
84
     * @inheritdoc
85
     */
86
    protected static $invalidCharactersRegex = ',[?#],';
87
88
    /**
89
     * new instance
90
     *
91
     * @param string $path the component value
92
     */
93 84
    public function __construct($path = '')
94
    {
95 84
        $path = $this->validateString($path);
96 84
        if ('' === $path) {
97 2
            $path = static::DEFAULT_MIMETYPE.';'.static::DEFAULT_PARAMETER.',';
98 2
        }
99 84
        $this->setComponentProperties($path);
100 68
    }
101
102
    /**
103
     * Set Data Path properties
104
     *
105
     * @param string $path
106
     */
107 84
    protected function setComponentProperties($path)
108
    {
109 84
        $this->assertValidComponent($path);
110 74
        $parts = explode(',', $path, 2);
111 74
        $mediatype = array_shift($parts);
112 74
        $this->document = (string) array_shift($parts);
113 74
        $mimetype = static::DEFAULT_MIMETYPE;
114 74
        $parameters = static::DEFAULT_PARAMETER;
115 74
        if ('' !== $mediatype) {
116 68
            $mediatype = explode(';', $mediatype, 2);
117 68
            $mimetype = array_shift($mediatype);
118 68
            $parameters = (string) array_shift($mediatype);
119 68
        }
120 74
        $this->mimetype = $this->filterMimeType($mimetype);
121 72
        $this->parameters = $this->filterParameters($parameters);
122 70
        if ($this->isBinaryData) {
123 38
            $this->validateDocument();
124 36
        }
125 68
    }
126
127
    /**
128
     * @inheritdoc
129
     */
130 84
    protected function assertValidComponent($path)
131
    {
132 84
        parent::assertValidComponent($path);
133 84
        if (!mb_detect_encoding($path, 'US-ASCII', true)
134 84
            || false === strpos($path, ',')
135 80
            || false !== strpos($path, '\n')
136 84
        ) {
137 10
            throw new InvalidArgumentException(
138 10
                sprintf('The submitted path `%s` is invalid according to RFC2937', $path)
139 10
            );
140
        }
141 74
    }
142
143
    /**
144
     * Filter the mimeType property
145
     *
146
     * @param string $mimetype
147
     *
148
     * @throws InvalidArgumentException If the mimetype is invalid
149
     *
150
     * @return string
151
     */
152 74
    protected function filterMimeType($mimetype)
153
    {
154 74
        if (!preg_match(static::REGEXP_MIMETYPE, $mimetype)) {
155 2
            throw new InvalidArgumentException(sprintf('invalid mimeType, `%s`', $mimetype));
156
        }
157
158 72
        return $mimetype;
159
    }
160
161
    /**
162
     * Extract and set the binary flag from the parameters if it exists
163
     *
164
     * @param string $parameters
165
     *
166
     * @throws InvalidArgumentException If the mediatype parameters contain invalid data
167
     *
168
     * @return string[]
169
     */
170 72
    protected function filterParameters($parameters)
171
    {
172 72
        $this->isBinaryData = false;
173 72
        if ('' == $parameters) {
174 4
            return [static::DEFAULT_PARAMETER];
175
        }
176
177 70
        if (preg_match(',(;|^)'.static::BINARY_PARAMETER.'$,', $parameters, $matches)) {
178 40
            $parameters = mb_substr($parameters, 0, - strlen($matches[0]));
179 40
            $this->isBinaryData = true;
180 40
        }
181
182 70
        $params = array_filter(explode(';', $parameters));
183 70
        if (!empty(array_filter($params, [$this, 'validateParameter']))) {
184 8
            throw new InvalidArgumentException(sprintf('invalid mediatype parameters, `%s`', $parameters));
185
        }
186
187 68
        return $params;
188
    }
189
190
    /**
191
     * Validate mediatype parameter
192
     *
193
     * @param string $parameter a mediatype parameter
194
     *
195
     * @return bool
196
     */
197 66
    protected function validateParameter($parameter)
198
    {
199 66
        $properties = explode('=', $parameter);
200
201 66
        return 2 != count($properties) || mb_strtolower($properties[0], 'UTF-8') == static::BINARY_PARAMETER;
202
    }
203
204
    /**
205
     * Validate the path document string representation
206
     *
207
     * @throws InvalidArgumentException If the data is invalid
208
     */
209 38
    protected function validateDocument()
210
    {
211 38
        $res = base64_decode($this->document, true);
212 38
        if (false === $res || $this->document !== base64_encode($res)) {
213 2
            throw new InvalidArgumentException('The path data is invalid');
214
        }
215 36
    }
216
217
    /**
218
     * Retrieves the data string.
219
     *
220 12
     * Retrieves the data part of the path. If no data part is provided return
221
     * a empty string
222 12
     *
223
     * @return string
224
     */
225
    public function getData()
226
    {
227
        return $this->document;
228 18
    }
229
230 18
    /**
231
     * Tells whether the data is binary safe encoded
232
     *
233
     * @return bool
234
     */
235
    public function isBinaryData()
236 20
    {
237
        return $this->isBinaryData;
238 20
    }
239
240
    /**
241
     * Retrieve the data mime type associated to the URI.
242
     *
243
     * If no mimetype is present, this method MUST return the default mimetype 'text/plain'.
244 60
     *
245
     * @see http://tools.ietf.org/html/rfc2397#section-2
246 60
     *
247
     * @return string The URI scheme.
248
     */
249
    public function getMimeType()
250
    {
251
        return $this->mimetype;
252 10
    }
253
254 10
    /**
255
     * Retrieve the parameters associated with the Mime Type of the URI.
256
     *
257
     * If no parameters is present, this method MUST return the default parameter 'charset=US-ASCII'.
258
     *
259
     * @see http://tools.ietf.org/html/rfc2397#section-2
260 6
     *
261
     * @return string The URI scheme.
262 6
     */
263 4
    public function getParameters()
264 4
    {
265
        return implode(';', $this->parameters);
266 4
    }
267
268
    /**
269
     * Retrieve the mediatype associated with the URI.
270
     *
271
     * If no mediatype is present, this method MUST return the default parameter 'text/plain;charset=US-ASCII'.
272 36
     *
273
     * @see http://tools.ietf.org/html/rfc2397#section-3
274 36
     *
275
     * @return string The URI scheme.
276
     */
277
    public function getMediaType()
278
    {
279
        return $this->getMimeType().';'.$this->getParameters();
280 44
    }
281
282 44
    /**
283 44
     * Save the data to a specific file
284 44
     *
285 44
     * @param string $path The path to the file where to save the data
286 44
     * @param string $mode The mode parameter specifies the type of access you require to the stream.
287 44
     *
288
     * @throws RuntimeException if the path is not reachable
289
     *
290
     * @return SplFileObject
291
     */
292
    public function save($path, $mode = 'w')
293
    {
294
        $file = new SplFileObject($path, $mode);
295
        $data = $this->isBinaryData ? base64_decode($this->document) : rawurldecode($this->document);
296
        $file->fwrite($data);
297
298
        return $file;
299
    }
300 66
301
    /**
302 66
     * Returns the instance string representation
303 54
     * with its optional URI delimiters
304 54
     *
305
     * @return string
306 66
     */
307 38
    public function getUriComponent()
308 38
    {
309
        return $this->__toString();
310 66
    }
311
312
    /**
313
     * Returns the instance string representation; If the
314
     * instance is not defined an empty string is returned
315
     *
316 8
     * @return string
317
     */
318 8
    public function __toString()
319 2
    {
320
        return $this->format(
321
            $this->mimetype,
322 6
            $this->getParameters(),
323 6
            $this->isBinaryData,
324 6
            $this->document
325 6
        );
326 6
    }
327 6
328
    /**
329
     * Format the DataURI string
330
     *
331
     * @param string $mimetype
332
     * @param string $parameters
333 8
     * @param bool   $isBinaryData
334
     * @param string $data
335 8
     *
336 2
     * @return string
337
     */
338
    protected static function format($mimetype, $parameters, $isBinaryData, $data)
339 6
    {
340 6
        if ('' != $parameters) {
341 6
            $parameters = ';'.$parameters;
342 6
        }
343 6
344 6
        if ($isBinaryData) {
345
            $parameters .= ';'.static::BINARY_PARAMETER;
346
        }
347
348
        return static::encode($mimetype.$parameters.','.$data);
349
    }
350 14
351
    /**
352 14
     * Returns an instance where the data part is base64 encoded
353 2
     *
354
     * This method MUST retain the state of the current instance, and return
355
     * an instance where the data part is base64 encoded
356 12
     *
357 2
     * @return static
358
     */
359
    public function toBinary()
360 10
    {
361 10
        if ($this->isBinaryData) {
362 10
            return $this;
363 10
        }
364 10
365 10
        return new static($this->format(
366
            $this->mimetype,
367
            $this->getParameters(),
368
            !$this->isBinaryData,
369
            base64_encode(rawurldecode($this->document))
370
        ));
371
    }
372
373
    /**
374
     * Returns an instance where the data part is url encoded following RFC3986 rules
375
     *
376
     * This method MUST retain the state of the current instance, and return
377 34
     * an instance where the data part is url encoded
378
     *
379 34
     * @return static
380 12
     */
381
    public function toAscii()
382
    {
383 22
        if (!$this->isBinaryData) {
384 22
            return $this;
385 22
        }
386 22
387 22
        return new static($this->format(
388 22
            $this->mimetype,
389
            $this->getParameters(),
390
            !$this->isBinaryData,
391
            rawurlencode(base64_decode($this->document))
392
        ));
393
    }
394
395
    /**
396
     * @inheritdoc
397
     */
398
    public static function __set_state(array $properties)
399
    {
400
        return new static(static::format(
401
            $properties['mimetype'],
402
            implode(';', $properties['parameters']),
403
            $properties['isBinaryData'],
404
            $properties['document']
405
        ));
406
    }
407
408
    /**
409
     * Return an instance with the specified mediatype parameters.
410
     *
411
     * This method MUST retain the state of the current instance, and return
412
     * an instance that contains the specified mediatype parameters.
413
     *
414
     * Users must provide encoded characters.
415
     *
416
     * An empty parameters value is equivalent to removing the parameter.
417
     *
418
     * @param string $parameters The mediatype parameters to use with the new instance.
419
     *
420
     * @throws InvalidArgumentException for invalid query strings.
421
     *
422
     * @return static A new instance with the specified mediatype parameters.
423
     */
424
    public function withParameters($parameters)
425
    {
426
        if ($parameters == $this->getParameters()) {
427
            return $this;
428
        }
429
430
        if (preg_match(',(;|^)'.static::BINARY_PARAMETER.'$,', $parameters)) {
431
            throw new InvalidArgumentException('The parameter data is invalid');
432
        }
433
434
        return new static($this->format(
435
            $this->mimetype,
436
            $parameters,
437
            $this->isBinaryData,
438
            $this->document
439
        ));
440
    }
441
442
    /**
443
     * Create a new instance from a file path
444
     *
445
     * @param string $path
446
     *
447
     * @throws RuntimeException If the File is not readable
448
     *
449
     * @return static
450
     */
451
    public static function createFromPath($path)
452
    {
453
        if (!is_readable($path)) {
454
            throw new RuntimeException(sprintf('The specified file `%s` is not readable', $path));
455
        }
456
457
        return new static(static::format(
458
            str_replace(' ', '', (new \finfo(FILEINFO_MIME))->file($path)),
459
            '',
460
            true,
461
            base64_encode(file_get_contents($path))
462
        ));
463
    }
464
}
465