Completed
Push — master ( 4de594...21f32b )
by ignace nyamagana
04:46
created

DataPath::validate()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 3

Importance

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