Completed
Pull Request — master (#58)
by ignace nyamagana
03:54
created

DataPath::getComponent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

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