Failed Conditions
Push — issue#666 ( 82e9d5...91903a )
by Guilherme
08:00
created

TagUri::getQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of the login-cidadao project or it's bundles.
4
 *
5
 * (c) Guilherme Donato <guilhermednt on github>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace LoginCidadao\RemoteClaimsBundle\Model;
12
13
use Egulias\EmailValidator\EmailValidator;
14
use Psr\Http\Message\UriInterface;
15
16
class TagUri implements UriInterface
17
{
18
    const REGEX_OVERALL = '^tag:(?<taggingEntity>[^:]+):(?<specific>[^#]+)(?:#(?<fragment>[\w\d&?@:_]+))?$';
19
    const REGEX_DATE = '\d{4}(?:-(?:0\d|1[012])(?:-(?:[012]\d|3[01]))?)?';
20
    const REGEX_DATE_YEAR = '(?<year>\d{4})';
21
    const REGEX_DATE_MONTH = '(?<month>0\d|1[012])';
22
    const REGEX_DATE_DAY = '(?<day>[012]\d|3[01])';
23
    const REGEX_DNScomp = '(?:[\w\d](?:[\w\d-]*[\w\d])?)';
24
    const REGEX_date = '(?:\d{4}(?:-\d{2}(?:-\d{2})?)?)';
25
26
    protected static $supportedSchemes = ['tag'];
27
28
    /** @var string */
29
    private $authorityName;
30
31
    /** @var string */
32
    private $date;
33
34
    /** @var string */
35
    private $specific;
36
37
    /** @var string */
38
    private $fragment = '';
39
40 35
    private static function getDnsNameRegex()
41
    {
42 35
        return '(?:'.self::REGEX_DNScomp.'(?:[.]'.self::REGEX_DNScomp.')*)';
43
    }
44
45 35
    private static function getTaggingEntityRegex()
46
    {
47 35
        return '(?<authorityName>'.self::getDnsNameRegex().'|'.static::getEmailAddressRegex().'),(?<date>'.self::REGEX_date.')';
48
    }
49
50 35
    private static function getEmailAddressRegex()
51
    {
52 35
        return '(?:[\w\d-._+]*@'.self::getDnsNameRegex().')';
53
    }
54
55 35
    private static function getDateRegex()
56
    {
57 35
        $day = self::REGEX_DATE_DAY;
58 35
        $month = self::REGEX_DATE_MONTH;
59 35
        $year = self::REGEX_DATE_YEAR;
60
61 35
        return "$year(?:-$month(?:-$day)?)?";
62
    }
63
64
    /**
65
     * Retrieve the scheme component of the URI.
66
     *
67
     * If no scheme is present, this method MUST return an empty string.
68
     *
69
     * The value returned MUST be normalized to lowercase, per RFC 3986
70
     * Section 3.1.
71
     *
72
     * The trailing ":" character is not part of the scheme and MUST NOT be
73
     * added.
74
     *
75
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
76
     * @return string The URI scheme.
77
     */
78 1
    public function getScheme()
79
    {
80 1
        return 'tag';
81
    }
82
83
    /**
84
     * Retrieve the authority component of the URI.
85
     *
86
     * If no authority information is present, this method MUST return an empty
87
     * string.
88
     *
89
     * The authority syntax of the URI is:
90
     *
91
     * <pre>
92
     * [user-info@]host[:port]
93
     * </pre>
94
     *
95
     * If the port component is not set or is the standard port for the current
96
     * scheme, it SHOULD NOT be included.
97
     *
98
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
99
     * @return string The URI authority, in "[user-info@]host[:port]" format.
100
     */
101 18
    public function getAuthority()
102
    {
103 18
        return $this->authorityName;
104
    }
105
106 9
    public function getAuthorityName()
107
    {
108 9
        return $this->getAuthority();
109
    }
110
111
    /**
112
     * Retrieve the user information component of the URI.
113
     *
114
     * If no user information is present, this method MUST return an empty
115
     * string.
116
     *
117
     * If a user is present in the URI, this will return that value;
118
     * additionally, if the password is also present, it will be appended to the
119
     * user value, with a colon (":") separating the values.
120
     *
121
     * The trailing "@" character is not part of the user information and MUST
122
     * NOT be added.
123
     *
124
     * @return string The URI user information, in "username[:password]" format.
125
     */
126 1
    public function getUserInfo()
127
    {
128 1
        return '';
129
    }
130
131
    /**
132
     * Retrieve the host component of the URI.
133
     *
134
     * If no host is present, this method MUST return an empty string.
135
     *
136
     * The value returned MUST be normalized to lowercase, per RFC 3986
137
     * Section 3.2.2.
138
     *
139
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
140
     * @return string The URI host.
141
     */
142 1
    public function getHost()
143
    {
144 1
        return $this->extractHost($this->getAuthority());
145
    }
146
147
    /**
148
     * Retrieve the port component of the URI.
149
     *
150
     * If a port is present, and it is non-standard for the current scheme,
151
     * this method MUST return it as an integer. If the port is the standard port
152
     * used with the current scheme, this method SHOULD return null.
153
     *
154
     * If no port is present, and no scheme is present, this method MUST return
155
     * a null value.
156
     *
157
     * If no port is present, but a scheme is present, this method MAY return
158
     * the standard port for that scheme, but SHOULD return null.
159
     *
160
     * @return null|int The URI port.
161
     */
162 1
    public function getPort()
163
    {
164 1
        return null;
165
    }
166
167
    /**
168
     * Retrieve the path component of the URI.
169
     *
170
     * The path can either be empty or absolute (starting with a slash) or
171
     * rootless (not starting with a slash). Implementations MUST support all
172
     * three syntaxes.
173
     *
174
     * Normally, the empty path "" and absolute path "/" are considered equal as
175
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
176
     * do this normalization because in contexts with a trimmed base path, e.g.
177
     * the front controller, this difference becomes significant. It's the task
178
     * of the user to handle both "" and "/".
179
     *
180
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
181
     * any characters. To determine what characters to encode, please refer to
182
     * RFC 3986, Sections 2 and 3.3.
183
     *
184
     * As an example, if the value should include a slash ("/") not intended as
185
     * delimiter between path segments, that value MUST be passed in encoded
186
     * form (e.g., "%2F") to the instance.
187
     *
188
     * @see https://tools.ietf.org/html/rfc3986#section-2
189
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
190
     * @return string The URI path.
191
     */
192 1
    public function getPath()
193
    {
194 1
        return '';
195
    }
196
197
    /**
198
     * Retrieve the query string of the URI.
199
     *
200
     * If no query string is present, this method MUST return an empty string.
201
     *
202
     * The leading "?" character is not part of the query and MUST NOT be
203
     * added.
204
     *
205
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
206
     * any characters. To determine what characters to encode, please refer to
207
     * RFC 3986, Sections 2 and 3.4.
208
     *
209
     * As an example, if a value in a key/value pair of the query string should
210
     * include an ampersand ("&") not intended as a delimiter between values,
211
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
212
     *
213
     * @see https://tools.ietf.org/html/rfc3986#section-2
214
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
215
     * @return string The URI query string.
216
     */
217 1
    public function getQuery()
218
    {
219 1
        return '';
220
    }
221
222
    /**
223
     * Retrieve the fragment component of the URI.
224
     *
225
     * If no fragment is present, this method MUST return an empty string.
226
     *
227
     * The leading "#" character is not part of the fragment and MUST NOT be
228
     * added.
229
     *
230
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
231
     * any characters. To determine what characters to encode, please refer to
232
     * RFC 3986, Sections 2 and 3.5.
233
     *
234
     * @see https://tools.ietf.org/html/rfc3986#section-2
235
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
236
     * @return string The URI fragment.
237
     */
238 14
    public function getFragment()
239
    {
240 14
        return $this->fragment;
241
    }
242
243
    /**
244
     * Retrieve the specific component of the tag URI.
245
     *
246
     * If no specific is present, this method MUST return an empty string.
247
     *
248
     * The leading ":" character is not part of the specific and MUST NOT be
249
     * added.
250
     *
251
     * @see https://tools.ietf.org/html/rfc4151#section-2
252
     * @return string The tag URI specific.
253
     */
254 14
    public function getSpecific()
255
    {
256 14
        return $this->specific;
257
    }
258
259
    /**
260
     * Retrieve the date component of the tag URI.
261
     *
262
     * The leading "," character is not part of the date and MUST NOT be
263
     * added.
264
     *
265
     * @see https://tools.ietf.org/html/rfc4151#section-2
266
     * @return string The tag URI date.
267
     */
268 14
    public function getDate()
269
    {
270 14
        return $this->date;
271
    }
272
273 13
    public function getTaggingEntity()
274
    {
275 13
        return sprintf("%s,%s", $this->getAuthority(), $this->getDate());
276
    }
277
278
    /**
279
     * Return an instance with the specified scheme.
280
     *
281
     * This method MUST retain the state of the current instance, and return
282
     * an instance that contains the specified scheme.
283
     *
284
     * Implementations MUST support the schemes "http" and "https" case
285
     * insensitively, and MAY accommodate other schemes if required.
286
     *
287
     * An empty scheme is equivalent to removing the scheme.
288
     *
289
     * @param string $scheme The scheme to use with the new instance.
290
     * @return static A new instance with the specified scheme.
291
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
292
     */
293 1
    public function withScheme($scheme)
294
    {
295 1
        throw new \BadMethodCallException("This method is not supported");
296
    }
297
298
    /**
299
     * Return an instance with the specified user information.
300
     *
301
     * This method MUST retain the state of the current instance, and return
302
     * an instance that contains the specified user information.
303
     *
304
     * Password is optional, but the user information MUST include the
305
     * user; an empty string for the user is equivalent to removing user
306
     * information.
307
     *
308
     * @param string $user The user name to use for authority.
309
     * @param null|string $password The password associated with $user.
310
     * @return static A new instance with the specified user information.
311
     */
312 1
    public function withUserInfo($user, $password = null)
313
    {
314 1
        throw new \BadMethodCallException("This method is not supported");
315
    }
316
317
    /**
318
     * Return an instance with the specified host.
319
     *
320
     * This method MUST retain the state of the current instance, and return
321
     * an instance that contains the specified host.
322
     *
323
     * An empty host value is equivalent to removing the host.
324
     *
325
     * @param string $host The hostname to use with the new instance.
326
     * @return static A new instance with the specified host.
327
     * @throws \InvalidArgumentException for invalid hostnames.
328
     */
329 1
    public function withHost($host)
330
    {
331 1
        throw new \BadMethodCallException("This method is not supported");
332
    }
333
334
    /**
335
     * Return an instance with the specified port.
336
     *
337
     * This method MUST retain the state of the current instance, and return
338
     * an instance that contains the specified port.
339
     *
340
     * Implementations MUST raise an exception for ports outside the
341
     * established TCP and UDP port ranges.
342
     *
343
     * A null value provided for the port is equivalent to removing the port
344
     * information.
345
     *
346
     * @param null|int $port The port to use with the new instance; a null value
347
     *     removes the port information.
348
     * @return static A new instance with the specified port.
349
     * @throws \InvalidArgumentException for invalid ports.
350
     */
351 1
    public function withPort($port)
352
    {
353 1
        throw new \BadMethodCallException("This method is not supported");
354
    }
355
356
    /**
357
     * Return an instance with the specified path.
358
     *
359
     * This method MUST retain the state of the current instance, and return
360
     * an instance that contains the specified path.
361
     *
362
     * The path can either be empty or absolute (starting with a slash) or
363
     * rootless (not starting with a slash). Implementations MUST support all
364
     * three syntaxes.
365
     *
366
     * If the path is intended to be domain-relative rather than path relative then
367
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
368
     * are assumed to be relative to some base path known to the application or
369
     * consumer.
370
     *
371
     * Users can provide both encoded and decoded path characters.
372
     * Implementations ensure the correct encoding as outlined in getPath().
373
     *
374
     * @param string $path The path to use with the new instance.
375
     * @return static A new instance with the specified path.
376
     * @throws \InvalidArgumentException for invalid paths.
377
     */
378 1
    public function withPath($path)
379
    {
380 1
        throw new \BadMethodCallException("This method is not supported");
381
    }
382
383
    /**
384
     * Return an instance with the specified query string.
385
     *
386
     * This method MUST retain the state of the current instance, and return
387
     * an instance that contains the specified query string.
388
     *
389
     * Users can provide both encoded and decoded query characters.
390
     * Implementations ensure the correct encoding as outlined in getQuery().
391
     *
392
     * An empty query string value is equivalent to removing the query string.
393
     *
394
     * @param string $query The query string to use with the new instance.
395
     * @return static A new instance with the specified query string.
396
     * @throws \InvalidArgumentException for invalid query strings.
397
     */
398 1
    public function withQuery($query)
399
    {
400 1
        throw new \BadMethodCallException("This method is not supported");
401
    }
402
403
    /**
404
     * Return an instance with the specified URI fragment.
405
     *
406
     * This method MUST retain the state of the current instance, and return
407
     * an instance that contains the specified URI fragment.
408
     *
409
     * Users can provide both encoded and decoded fragment characters.
410
     * Implementations ensure the correct encoding as outlined in getFragment().
411
     *
412
     * An empty fragment value is equivalent to removing the fragment.
413
     *
414
     * @param string $fragment The fragment to use with the new instance.
415
     * @return static A new instance with the specified fragment.
416
     */
417 1
    public function withFragment($fragment)
418
    {
419 1
        return (new TagUri())
420 1
            ->setAuthorityName($this->getAuthorityName())
421 1
            ->setDate($this->getDate())
422 1
            ->setSpecific($this->getSpecific())
423 1
            ->setFragment($fragment);
424
    }
425
426
    /**
427
     * Return the string representation as a URI reference.
428
     *
429
     * Depending on which components of the URI are present, the resulting
430
     * string is either a full URI or relative reference according to RFC 3986,
431
     * Section 4.1. The method concatenates the various components of the URI,
432
     * using the appropriate delimiters:
433
     *
434
     * - If a scheme is present, it MUST be suffixed by ":".
435
     * - If an authority is present, it MUST be prefixed by "//".
436
     * - The path can be concatenated without delimiters. But there are two
437
     *   cases where the path has to be adjusted to make the URI reference
438
     *   valid as PHP does not allow to throw an exception in __toString():
439
     *     - If the path is rootless and an authority is present, the path MUST
440
     *       be prefixed by "/".
441
     *     - If the path is starting with more than one "/" and no authority is
442
     *       present, the starting slashes MUST be reduced to one.
443
     * - If a query is present, it MUST be prefixed by "?".
444
     * - If a fragment is present, it MUST be prefixed by "#".
445
     *
446
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
447
     * @return string
448
     */
449 13
    public function __toString()
450
    {
451 13
        $tagURI = sprintf("tag:%s:%s", $this->getTaggingEntity(), $this->getSpecific());
452
453 13
        if ('' !== $fragment = $this->getFragment()) {
454 1
            $tagURI = sprintf("%s#%s", $tagURI, $fragment);
455
        }
456
457 13
        return $tagURI;
458
    }
459
460 36
    public function setAuthorityName($authorityName)
461
    {
462 36
        if (strstr($authorityName, '@') !== false) {
463 2
            $this->checkEmail($authorityName);
464
        }
465
466 35
        $this->authorityName = $authorityName;
467
468 35
        return $this;
469
    }
470
471 35
    public function setDate($date)
472
    {
473 35
        $dateRegex = self::getDateRegex();
474 35
        if (!preg_match("/^$dateRegex$/", $date, $m)) {
475 1
            throw new \InvalidArgumentException('Invalid date: '.$date);
476
        }
477
478 34
        $parts = array_merge([
479 34
            'year' => $m['year'],
480 34
            'month' => '01',
481 34
            'day' => '01',
482 34
        ], $m);
483
484 34
        if (!checkdate($parts['month'], $parts['day'], $parts['year'])) {
485 1
            throw new \InvalidArgumentException('Invalid date: '.$date);
486
        }
487 33
        $this->date = $date;
488
489 33
        return $this;
490
    }
491
492 33
    public function setSpecific($specific)
493
    {
494 33
        $this->specific = $specific;
495
496 33
        return $this;
497
    }
498
499 31
    public function setFragment($fragment)
500
    {
501 31
        $this->fragment = $fragment ?: '';
502
503 31
        return $this;
504
    }
505
506 1
    private function extractHost($authorityName)
507
    {
508 1
        $parts = explode('@', $authorityName);
509
510 1
        return end($parts);
511
    }
512
513 38
    public static function createFromString($string)
514
    {
515 38
        $overallRegex = self::REGEX_OVERALL;
516 38
        if (!preg_match("/{$overallRegex}/", $string, $overall)) {
517 6
            throw new \InvalidArgumentException("The provided tag URI doesn't seem to be valid: {$string}");
518
        }
519 35
        $overall = array_merge(['fragment' => ''], $overall);
520
521 35
        $taggingEntity = self::getTaggingEntityRegex();
522 35
        if (preg_match("/^{$taggingEntity}/", $overall['taggingEntity'], $m) !== 1) {
523 2
            throw new \InvalidArgumentException('Invalid taggingEntity: '.$overall['taggingEntity']);
524
        }
525
526 33
        $tagUri = (new TagUri())
527 33
            ->setAuthorityName($m['authorityName'])
528 33
            ->setDate($m['date'])
529 31
            ->setSpecific($overall['specific'])
530 31
            ->setFragment($overall['fragment']);
531
532 31
        return $tagUri;
533
    }
534
535 2
    private function checkEmail($email)
536
    {
537 2
        $validator = new EmailValidator();
538 2
        if (!$validator->isValid($email)) {
539 1
            throw new \InvalidArgumentException("Invalid authority name: '{$email}'. It doesn't seem to be a valid email address.");
540
        }
541
542 1
        return true;
543
    }
544
}
545