Passed
Branch master (950424)
by Christopher
11:06
created

HttpProcessUtility::isHttpSeparator()   B

Complexity

Conditions 19
Paths 19

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 6
nc 19
nop 1
dl 0
loc 8
rs 7.238
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace POData;
4
5
use POData\Common\HttpHeaderFailure;
6
use POData\Common\Messages;
7
use POData\Providers\Metadata\Type\Char;
0 ignored issues
show
Bug introduced by
The type POData\Providers\Metadata\Type\Char was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
9
/**
10
 * Class HttpProcessUtility.
11
 */
12
class HttpProcessUtility
13
{
14
    /**
15
     * Gets the appropriate MIME type for the request, throwing if there is none.
16
     *
17
     * @param string   $acceptTypesText    Text as it appears in an HTTP
18
     *                                     Accepts header
19
     * @param string[] $exactContentTypes  Preferred content type to match if an exact media type is given - this is in
20
     *                                     descending order of preference
21
     * @param string   $inexactContentType Preferred fallback content type for inexact matches
22
     *
23
     * @throws HttpHeaderFailure
24
     * @return string|null       One of exactContentType or inexactContentType
25
     */
26
    public static function selectRequiredMimeType(
27
        $acceptTypesText,
28
        $exactContentTypes,
29
        $inexactContentType
30
    ) {
31
        $selectedContentType = null;
32
        $selectedMatchingParts = -1;
33
        $selectedQualityValue = 0;
34
        $acceptable = false;
35
        $acceptTypesEmpty = true;
36
        $foundExactMatch = false;
37
38
        if (null !== $acceptTypesText) {
39
            $acceptTypes = self::mimeTypesFromAcceptHeaders($acceptTypesText);
40
            foreach ($acceptTypes as $acceptType) {
41
                $acceptTypesEmpty = false;
42
                foreach ($exactContentTypes as $exactContentType) {
43
                    if (strcasecmp($acceptType->getMimeType(), $exactContentType) == 0) {
44
                        $selectedContentType = $exactContentType;
45
                        $selectedQualityValue = $acceptType->getQualityValue();
46
                        $acceptable = $selectedQualityValue != 0;
47
                        $foundExactMatch = true;
48
                        break;
49
                    }
50
                }
51
52
                if ($foundExactMatch) {
53
                    break;
54
                }
55
56
                $matchingParts = $acceptType->getMatchingRating($inexactContentType);
57
                if ($matchingParts < 0) {
58
                    continue;
59
                }
60
61 View Code Duplication
                if ($matchingParts > $selectedMatchingParts) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
62
                    // A more specific type wins.
63
                    $selectedContentType = $inexactContentType;
64
                    $selectedMatchingParts = $matchingParts;
65
                    $selectedQualityValue = $acceptType->getQualityValue();
66
                    $acceptable = $selectedQualityValue != 0;
67
                } elseif ($matchingParts == $selectedMatchingParts) {
68
                    // A type with a higher q-value wins.
69
                    $candidateQualityValue = $acceptType->getQualityValue();
70
                    if ($candidateQualityValue > $selectedQualityValue) {
71
                        $selectedContentType = $inexactContentType;
72
                        $selectedQualityValue = $candidateQualityValue;
73
                        $acceptable = $selectedQualityValue != 0;
74
                    }
75
                }
76
            }
77
        }
78
79
        if (!$acceptable && !$acceptTypesEmpty) {
80
            throw new HttpHeaderFailure(
81
                Messages::unsupportedMediaType(),
82
                415
83
            );
84
        }
85
86
        if ($acceptTypesEmpty) {
87
            $selectedContentType = $inexactContentType;
88
        }
89
90
        return $selectedContentType;
91
    }
92
93
    /**
94
     * Selects an acceptable MIME type that satisfies the Accepts header.
95
     *
96
     * @param string   $acceptTypesText Text for Accepts header
97
     * @param string[] $availableTypes  Types that the server is willing to return, in descending order of preference
98
     *
99
     * @throws HttpHeaderFailure
100
     *
101
     * @return string|null The best MIME type for the client
102
     */
103
    public static function selectMimeType($acceptTypesText, array $availableTypes)
104
    {
105
        $selectedContentType = null;
106
        $selectedMatchingParts = -1;
107
        $selectedQualityValue = 0;
108
        $selectedPreferenceIndex = PHP_INT_MAX;
109
        $acceptable = false;
110
        $acceptTypesEmpty = true;
111
        if (null !== $acceptTypesText) {
112
            $acceptTypes = self::mimeTypesFromAcceptHeaders($acceptTypesText);
113
            $numAvailable = count($availableTypes);
114
            foreach ($acceptTypes as $acceptType) {
115
                $acceptTypesEmpty = false;
116
                for ($i = 0; $i < $numAvailable; ++$i) {
117
                    $availableType = $availableTypes[$i];
118
                    $matchRating = $acceptType->getMatchingRating($availableType);
119
                    if ($matchRating < 0) {
120
                        continue;
121
                    }
122
123
                    if ($matchRating > $selectedMatchingParts) {
124
                        // A more specific type wins.
125
                        $selectedContentType = $availableType;
126
                        $selectedMatchingParts = $matchRating;
127
                        $selectedQualityValue = $acceptType->getQualityValue();
128
                        $selectedPreferenceIndex = $i;
129
                        $acceptable = $selectedQualityValue != 0;
130 View Code Duplication
                    } elseif ($matchRating == $selectedMatchingParts) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
131
                        // A type with a higher q-value wins.
132
                        $candidateQualityValue = $acceptType->getQualityValue();
133
                        if ($candidateQualityValue > $selectedQualityValue) {
134
                            $selectedContentType = $availableType;
135
                            $selectedQualityValue = $candidateQualityValue;
136
                            $selectedPreferenceIndex = $i;
137
                            $acceptable = $selectedQualityValue != 0;
138
                        } elseif ($candidateQualityValue == $selectedQualityValue) {
139
                            // A type that is earlier in the availableTypes array wins.
140
                            if ($i < $selectedPreferenceIndex) {
141
                                $selectedContentType = $availableType;
142
                                $selectedPreferenceIndex = $i;
143
                            }
144
                        }
145
                    }
146
                }
147
            }
148
        }
149
150
        if ($acceptTypesEmpty) {
151
            $selectedContentType = $availableTypes[0];
152
        } elseif (!$acceptable) {
153
            $selectedContentType = null;
154
        }
155
156
        return $selectedContentType;
157
    }
158
159
    /**
160
     * Returns all MIME types from the $text.
161
     *
162
     * @param string $text Text as it appears on an HTTP Accepts header
163
     *
164
     * @throws HttpHeaderFailure If found any syntax error in the given text
165
     *
166
     * @return MediaType[] Array of media (MIME) type description
167
     */
168
    public static function mimeTypesFromAcceptHeaders($text)
169
    {
170
        $mediaTypes = [];
171
        $textIndex = 0;
172
        while (!self::skipWhiteSpace($text, $textIndex)) {
173
            $type = null;
174
            $subType = null;
175
            self::readMediaTypeAndSubtype($text, $textIndex, $type, $subType);
176
177
            $parameters = [];
178
            while (!self::skipWhiteSpace($text, $textIndex)) {
179
                if ($text[$textIndex] == ',') {
180
                    ++$textIndex;
181
                    break;
182
                }
183
184
                if ($text[$textIndex] != ';') {
185
                    throw new HttpHeaderFailure(
186
                        Messages::httpProcessUtilityMediaTypeRequiresSemicolonBeforeParameter(),
187
                        400
188
                    );
189
                }
190
191
                ++$textIndex;
192
                if (self::skipWhiteSpace($text, $textIndex)) {
193
                    break;
194
                }
195
196
                self::readMediaTypeParameter($text, $textIndex, $parameters);
197
            }
198
199
            $mediaTypes[] = new MediaType($type, $subType, $parameters);
200
        }
201
202
        return $mediaTypes;
203
    }
204
205
    /**
206
     * Skips whitespace in the specified text by advancing an index to
207
     * the next non-whitespace character.
208
     *
209
     * @param string $text       Text to scan
210
     * @param int    &$textIndex Index to begin scanning from
211
     *
212
     * @return bool true if the end of the string was reached, false otherwise
213
     */
214 View Code Duplication
    public static function skipWhiteSpace($text, &$textIndex)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
215
    {
216
        $textLen = strlen($text);
217
        while (($textIndex < $textLen) && Char::isWhiteSpace($text[$textIndex])) {
218
            ++$textIndex;
219
        }
220
221
        return $textLen == $textIndex;
222
    }
223
224
    /**
225
     * Reads the type and subtype specifications for a MIME type.
226
     *
227
     * @param string $text       Text in which specification exists
228
     * @param int    &$textIndex Pointer into text
229
     * @param string &$type      Type of media found
230
     * @param string &$subType   Subtype of media found
231
     *
232
     * @throws HttpHeaderFailure If failed to read type and sub-type
233
     */
234
    public static function readMediaTypeAndSubtype(
235
        $text,
236
        &$textIndex,
237
        &$type,
238
        &$subType
239
    ) {
240
        $textStart = $textIndex;
241
        if (self::readToken($text, $textIndex)) {
242
            throw new HttpHeaderFailure(
243
                Messages::httpProcessUtilityMediaTypeUnspecified(),
244
                400
245
            );
246
        }
247
248
        if ($text[$textIndex] != '/') {
249
            throw new HttpHeaderFailure(
250
                Messages::httpProcessUtilityMediaTypeRequiresSlash(),
251
                400
252
            );
253
        }
254
255
        $type = substr($text, $textStart, $textIndex - $textStart);
256
        ++$textIndex;
257
258
        $subTypeStart = $textIndex;
259
        self::readToken($text, $textIndex);
260
        if ($textIndex == $subTypeStart) {
261
            throw new HttpHeaderFailure(
262
                Messages::httpProcessUtilityMediaTypeRequiresSubType(),
263
                400
264
            );
265
        }
266
267
        $subType = substr($text, $subTypeStart, $textIndex - $subTypeStart);
268
    }
269
270
    /**
271
     * Reads a token on the specified text by advancing an index on it.
272
     *
273
     * @param string $text       Text to read token from
274
     * @param int    &$textIndex Index for the position being scanned on text
275
     *
276
     * @return bool true if the end of the text was reached; false otherwise
277
     */
278 View Code Duplication
    public static function readToken($text, &$textIndex)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
279
    {
280
        $textLen = strlen($text);
281
        while (($textIndex < $textLen) && self::isHttpTokenChar($text[$textIndex])) {
282
            ++$textIndex;
283
        }
284
285
        return $textLen == $textIndex;
286
    }
287
288
    /**
289
     * To check whether the given character is a HTTP token character
290
     * or not.
291
     *
292
     * @param string $char The character to inspect
293
     *
294
     * @return bool True if the given character is a valid HTTP token
295
     *              character, False otherwise
296
     */
297
    public static function isHttpTokenChar($char)
298
    {
299
        return ord($char) < 126 && ord($char) > 31
300
            && !self::isHttpSeparator($char);
301
    }
302
303
    /**
304
     * To check whether the given character is a HTTP separator character.
305
     *
306
     * @param string $char The character to inspect
307
     *
308
     * @return bool True if the given character is a valid HTTP separator
309
     *              character, False otherwise
310
     */
311
    public static function isHttpSeparator($char)
312
    {
313
        return
314
            $char == '(' || $char == ')' || $char == '<' || $char == '>' ||
315
            $char == '@' || $char == ',' || $char == ';' || $char == ':' ||
316
            $char == '\\' || $char == '"' || $char == '/' || $char == '[' ||
317
            $char == ']' || $char == '?' || $char == '=' || $char == '{' ||
318
            $char == '}' || $char == ' ' || ord($char) == Char::TAB;
319
    }
320
321
    /**
322
     * Read a parameter for a media type/range.
323
     *
324
     * @param string $text        Text to read from
325
     * @param int    &$textIndex  Pointer in text
326
     * @param array  &$parameters Array with parameters
327
     *
328
     * @throws HttpHeaderFailure If found parameter value missing
329
     */
330
    public static function readMediaTypeParameter($text, &$textIndex, &$parameters)
331
    {
332
        $textStart = $textIndex;
333
        if (self::readToken($text, $textIndex)) {
334
            throw new HttpHeaderFailure(
335
                Messages::httpProcessUtilityMediaTypeMissingValue(),
336
                400
337
            );
338
        }
339
340
        $parameterName = substr($text, $textStart, $textIndex - $textStart);
341
        if ($text[$textIndex] != '=') {
342
            throw new HttpHeaderFailure(
343
                Messages::httpProcessUtilityMediaTypeMissingValue(),
344
                400
345
            );
346
        }
347
348
        ++$textIndex;
349
        $parameterValue
350
            = self::readQuotedParameterValue($parameterName, $text, $textIndex);
0 ignored issues
show
Bug introduced by
It seems like $parameterName can also be of type false; however, parameter $parameterName of POData\HttpProcessUtilit...dQuotedParameterValue() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

350
            = self::readQuotedParameterValue(/** @scrutinizer ignore-type */ $parameterName, $text, $textIndex);
Loading history...
351
        $parameters[] = [$parameterName => $parameterValue];
352
    }
353
354
    /**
355
     * Reads Mime type parameter value for a particular parameter in the
356
     * Content-Type/Accept headers.
357
     *
358
     * @param string $parameterName Name of parameter
359
     * @param string $text          Header text
360
     * @param int    &$textIndex    Parsing index in $text
361
     *
362
     * @throws HttpHeaderFailure
363
     *
364
     * @return string String representing the value of the $parameterName parameter
365
     */
366
    public static function readQuotedParameterValue(
367
        $parameterName,
368
        $text,
369
        &$textIndex
370
    ) {
371
        $parameterValue = [];
372
        $textLen = strlen($text);
373
        $valueIsQuoted = false;
374
        if ($textIndex < $textLen) {
375
            if ($text[$textIndex] == '"') {
376
                ++$textIndex;
377
                $valueIsQuoted = true;
378
            }
379
        }
380
381
        while ($textIndex < $textLen) {
382
            $currentChar = $text[$textIndex];
383
384
            if ($currentChar == '\\' || $currentChar == '"') {
385
                if (!$valueIsQuoted) {
386
                    throw new HttpHeaderFailure(
387
                        Messages::httpProcessUtilityEscapeCharWithoutQuotes(
388
                            $parameterName
389
                        ),
390
                        400
391
                    );
392
                }
393
394
                ++$textIndex;
395
396
                // End of quoted parameter value.
397
                if ($currentChar == '"') {
398
                    $valueIsQuoted = false;
399
                    break;
400
                }
401
402
                if ($textIndex >= $textLen) {
403
                    throw new HttpHeaderFailure(
404
                        Messages::httpProcessUtilityEscapeCharAtEnd($parameterName),
405
                        400
406
                    );
407
                }
408
409
                $currentChar = $text[$textIndex];
410
            } elseif (!self::isHttpTokenChar($currentChar)) {
411
                // If the given character is special, we stop processing.
412
                break;
413
            }
414
415
            $parameterValue[] = $currentChar;
416
            ++$textIndex;
417
        }
418
419
        if ($valueIsQuoted) {
420
            throw new HttpHeaderFailure(
421
                Messages::httpProcessUtilityClosingQuoteNotFound($parameterName),
422
                400
423
            );
424
        }
425
426
        return empty($parameterValue) ? null : implode('', $parameterValue);
427
    }
428
429
    /**
430
     * Reads the numeric part of a quality value substring, normalizing it to 0-1000.
431
     rather than the standard 0.000-1.000 ranges.
432
     *
433
     * @param string $text          Text to read qvalue from
434
     * @param int    &$textIndex    Index into text where the qvalue starts
435
     * @param int    &$qualityValue After the method executes, the normalized qvalue
436
     * @param int    $textIndex
437
     *
438
     * @throws HttpHeaderFailure If any error occurred while reading and processing
439
     *                           the quality factor
440
     */
441
    public static function readQualityValue($text, &$textIndex, &$qualityValue)
442
    {
443
        $digit = $text[$textIndex++];
444
        if ($digit == '0') {
445
            $qualityValue = 0;
446
        } elseif ($digit == '1') {
447
            $qualityValue = 1;
448
        } else {
449
            throw new HttpHeaderFailure(
450
                Messages::httpProcessUtilityMalformedHeaderValue(),
451
                400
452
            );
453
        }
454
455
        $textLen = strlen($text);
456
        if ($textIndex < $textLen && $text[$textIndex] == '.') {
457
            ++$textIndex;
458
459
            $adjustFactor = 1000;
460
            while ($adjustFactor > 1 && $textIndex < $textLen) {
461
                $c = $text[$textIndex];
462
                $charValue = self::digitToInt32($c);
463
                if ($charValue >= 0) {
464
                    ++$textIndex;
465
                    $adjustFactor /= 10;
466
                    $qualityValue *= 10;
467
                    $qualityValue += $charValue;
468
                } else {
469
                    break;
470
                }
471
            }
472
473
            $qualityValue = $qualityValue *= $adjustFactor;
474
            if ($qualityValue > 1000) {
475
                // Too high of a value in qvalue.
476
                throw new HttpHeaderFailure(
477
                    Messages::httpProcessUtilityMalformedHeaderValue(),
478
                    400
479
                );
480
            }
481
        } else {
482
            $qualityValue *= 1000;
483
        }
484
    }
485
486
    /**
487
     * Converts the specified character from the ASCII range to a digit.
488
     *
489
     * @param string $c Character to convert
490
     *
491
     * @throws HttpHeaderFailure If $c is not ASCII value for digit or element separator
492
     *
493
     * @return int The Int32 value for $c, or -1 if it is an element separator
494
     */
495
    public static function digitToInt32($c)
496
    {
497
        if ($c >= '0' && $c <= '9') {
498
            return intval($c);
499
        } else {
500
            if (self::isHttpElementSeparator($c)) {
501
                return -1;
502
            } else {
503
                throw new HttpHeaderFailure(
504
                    Messages::httpProcessUtilityMalformedHeaderValue(),
505
                    400
506
                );
507
            }
508
        }
509
    }
510
511
    /**
512
     * Verifies whether the specified character is a valid separator in.
513
     an HTTP header list of element.
514
     *
515
     * @param string $c Character to verify
516
     *
517
     * @return bool true if c is a valid character for separating elements;
518
     *              false otherwise
519
     */
520
    public static function isHttpElementSeparator($c)
521
    {
522
        return $c == ',' || $c == ' ' || $c == '\t';
523
    }
524
525
    /**
526
     * Get server key by header.
527
     *
528
     * @param  string $headerName Name of header
529
     * @return string
530
     */
531
    public static function headerToServerKey($headerName)
532
    {
533
        $name = strtoupper(str_replace('-', '_', $headerName));
534
        switch ($name) {
535
            case 'HOST':
536
            case 'CONNECTION':
537
            case 'CACHE_CONTROL':
538
            case 'ORIGIN':
539
            case 'USER_AGENT':
540
            case 'POSTMAN_TOKEN':
541
            case 'ACCEPT':
542
            case 'ACCEPT_ENCODING':
543
            case 'ACCEPT_LANGUAGE':
544
            case 'DATASERVICEVERSION':
545
            case 'MAXDATASERVICEVERSION':
546
                return 'HTTP_' . $name;
547
        }
548
549
        return $name;
550
    }
551
}
552