TagUri::setFragment()   A
last analyzed

Complexity

Conditions 2
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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