Completed
Push — 4.5 ( 0ed340...1726cd )
by Garion
20:10 queued 11:52
created

HTTPRequest::isValidHttpMethod()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control;
4
5
use ArrayAccess;
6
use BadMethodCallException;
7
use InvalidArgumentException;
8
use SilverStripe\Core\ClassInfo;
9
use SilverStripe\ORM\ArrayLib;
10
11
/**
12
 * Represents a HTTP-request, including a URL that is tokenised for parsing, and a request method
13
 * (GET/POST/PUT/DELETE). This is used by {@link RequestHandler} objects to decide what to do.
14
 *
15
 * Caution: objects of this class are immutable, e.g. echo $request['a']; works as expected,
16
 * but $request['a'] = '1'; has no effect.
17
 *
18
 * The intention is that a single HTTPRequest object can be passed from one object to another, each object calling
19
 * match() to get the information that they need out of the URL.  This is generally handled by
20
 * {@link RequestHandler::handleRequest()}.
21
 */
22
class HTTPRequest implements ArrayAccess
23
{
24
    /**
25
     * @var string
26
     */
27
    protected $url;
28
29
    /**
30
     * The non-extension parts of the passed URL as an array, originally exploded by the "/" separator.
31
     * All elements of the URL are loaded in here,
32
     * and subsequently popped out of the array by {@link shift()}.
33
     * Only use this structure for internal request handling purposes.
34
     *
35
     * @var array
36
     */
37
    protected $dirParts;
38
39
    /**
40
     * The URL extension (if present)
41
     *
42
     * @var string
43
     */
44
    protected $extension;
45
46
    /**
47
     * The HTTP method in all uppercase: GET/PUT/POST/DELETE/HEAD
48
     *
49
     * @var string
50
     */
51
    protected $httpMethod;
52
53
    /**
54
     * The URL scheme in lowercase: http or https
55
     *
56
     * @var string
57
     */
58
    protected $scheme;
59
60
    /**
61
     * The client IP address
62
     *
63
     * @var string
64
     */
65
    protected $ip;
66
67
    /**
68
     * Contains alls HTTP GET parameters passed into this request.
69
     *
70
     * @var array
71
     */
72
    protected $getVars = array();
73
74
    /**
75
     * Contains alls HTTP POST parameters passed into this request.
76
     *
77
     * @var array
78
     */
79
    protected $postVars = array();
80
81
    /**
82
     * HTTP Headers like "Content-Type: text/xml"
83
     *
84
     * @see http://en.wikipedia.org/wiki/List_of_HTTP_headers
85
     * @var array
86
     */
87
    protected $headers = array();
88
89
    /**
90
     * Raw HTTP body, used by PUT and POST requests.
91
     *
92
     * @var string
93
     */
94
    protected $body;
95
96
    /**
97
     * Contains an associative array of all
98
     * arguments matched in all calls to {@link RequestHandler->handleRequest()}.
99
     * It's a "historical record" that's specific to the current call of
100
     * {@link handleRequest()}, and is only complete once the "last call" to that method is made.
101
     *
102
     * @var array
103
     */
104
    protected $allParams = array();
105
106
    /**
107
     * Contains an associative array of all
108
     * arguments matched in the current call from {@link RequestHandler->handleRequest()},
109
     * as denoted with a "$"-prefix in the $url_handlers definitions.
110
     * Contains different states throughout its lifespan, so just useful
111
     * while processed in {@link RequestHandler} and to get the last
112
     * processes arguments.
113
     *
114
     * @var array
115
     */
116
    protected $latestParams = array();
117
118
    /**
119
     * Contains an associative array of all arguments
120
     * explicitly set in the route table for the current request.
121
     * Useful for passing generic arguments via custom routes.
122
     *
123
     * E.g. The "Locale" parameter would be assigned "en_NZ" below
124
     *
125
     * Director:
126
     *   rules:
127
     *     'en_NZ/$URLSegment!//$Action/$ID/$OtherID':
128
     *       Controller: 'ModelAsController'
129
     *       Locale: 'en_NZ'
130
     *
131
     * @var array
132
     */
133
    protected $routeParams = array();
134
135
    /**
136
     * @var int
137
     */
138
    protected $unshiftedButParsedParts = 0;
139
140
    /**
141
     * @var Session
142
     */
143
    protected $session;
144
145
    /**
146
     * Construct a HTTPRequest from a URL relative to the site root.
147
     *
148
     * @param string $httpMethod
149
     * @param string $url
150
     * @param array $getVars
151
     * @param array $postVars
152
     * @param string $body
153
     */
154
    public function __construct($httpMethod, $url, $getVars = array(), $postVars = array(), $body = null)
155
    {
156
        $this->httpMethod = strtoupper($httpMethod);
157
        $this->setUrl($url);
158
        $this->getVars = (array) $getVars;
159
        $this->postVars = (array) $postVars;
160
        $this->body = $body;
161
        $this->scheme = "http";
162
    }
163
164
    /**
165
     * Allow the setting of a URL
166
     *
167
     * This is here so that RootURLController can change the URL of the request
168
     * without us loosing all the other info attached (like headers)
169
     *
170
     * @param string $url The new URL
171
     * @return HTTPRequest The updated request
172
     */
173
    public function setUrl($url)
174
    {
175
        $this->url = $url;
176
177
        // Normalize URL if its relative (strictly speaking), or has leading slashes
178
        if (Director::is_relative_url($url) || preg_match('/^\//', $url)) {
179
            $this->url = preg_replace(array('/\/+/','/^\//', '/\/$/'), array('/','',''), $this->url);
180
        }
181
        if (preg_match('/^(.*)\.([A-Za-z][A-Za-z0-9]*)$/', $this->url, $matches)) {
182
            $this->url = $matches[1];
183
            $this->extension = $matches[2];
184
        }
185
        if ($this->url) {
186
            $this->dirParts = preg_split('|/+|', $this->url);
0 ignored issues
show
Documentation Bug introduced by
It seems like preg_split('|/+|', $this->url) can also be of type false. However, the property $dirParts is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
187
        } else {
188
            $this->dirParts = array();
189
        }
190
191
        return $this;
192
    }
193
194
    /**
195
     * @return bool
196
     */
197
    public function isGET()
198
    {
199
        return $this->httpMethod == 'GET';
200
    }
201
202
    /**
203
     * @return bool
204
     */
205
    public function isPOST()
206
    {
207
        return $this->httpMethod == 'POST';
208
    }
209
210
    /**
211
     * @return bool
212
     */
213
    public function isPUT()
214
    {
215
        return $this->httpMethod == 'PUT';
216
    }
217
218
    /**
219
     * @return bool
220
     */
221
    public function isDELETE()
222
    {
223
        return $this->httpMethod == 'DELETE';
224
    }
225
226
    /**
227
     * @return bool
228
     */
229
    public function isHEAD()
230
    {
231
        return $this->httpMethod == 'HEAD';
232
    }
233
234
    /**
235
     * @param string $body
236
     * @return HTTPRequest $this
237
     */
238
    public function setBody($body)
239
    {
240
        $this->body = $body;
241
        return $this;
242
    }
243
244
    /**
245
     * @return null|string
246
     */
247
    public function getBody()
248
    {
249
        return $this->body;
250
    }
251
252
    /**
253
     * @return array
254
     */
255
    public function getVars()
256
    {
257
        return $this->getVars;
258
    }
259
260
    /**
261
     * @return array
262
     */
263
    public function postVars()
264
    {
265
        return $this->postVars;
266
    }
267
268
    /**
269
     * Returns all combined HTTP GET and POST parameters
270
     * passed into this request. If a parameter with the same
271
     * name exists in both arrays, the POST value is returned.
272
     *
273
     * @return array
274
     */
275
    public function requestVars()
276
    {
277
        return ArrayLib::array_merge_recursive($this->getVars, $this->postVars);
278
    }
279
280
    /**
281
     * @param string $name
282
     * @return mixed
283
     */
284
    public function getVar($name)
285
    {
286
        if (isset($this->getVars[$name])) {
287
            return $this->getVars[$name];
288
        }
289
        return null;
290
    }
291
292
    /**
293
     * @param string $name
294
     * @return mixed
295
     */
296
    public function postVar($name)
297
    {
298
        if (isset($this->postVars[$name])) {
299
            return $this->postVars[$name];
300
        }
301
        return null;
302
    }
303
304
    /**
305
     * @param string $name
306
     * @return mixed
307
     */
308
    public function requestVar($name)
309
    {
310
        if (isset($this->postVars[$name])) {
311
            return $this->postVars[$name];
312
        }
313
        if (isset($this->getVars[$name])) {
314
            return $this->getVars[$name];
315
        }
316
        return null;
317
    }
318
319
    /**
320
     * Returns a possible file extension found in parsing the URL
321
     * as denoted by a "."-character near the end of the URL.
322
     * Doesn't necessarily have to belong to an existing file,
323
     * as extensions can be also used for content-type-switching.
324
     *
325
     * @return string
326
     */
327
    public function getExtension()
328
    {
329
        return $this->extension;
330
    }
331
332
    /**
333
     * Checks if the {@link HTTPRequest->getExtension()} on this request matches one of the more common media types
334
     * embedded into a webpage - e.g. css, png.
335
     *
336
     * This is useful for things like determining whether to display a fully rendered error page or not. Note that the
337
     * media file types is not at all comprehensive.
338
     *
339
     * @return bool
340
     */
341
    public function isMedia()
342
    {
343
        return in_array($this->getExtension(), array('css', 'js', 'jpg', 'jpeg', 'gif', 'png', 'bmp', 'ico'));
344
    }
345
346
    /**
347
     * Add a HTTP header to the response, replacing any header of the same name.
348
     *
349
     * @param string $header Example: "content-type"
350
     * @param string $value Example: "text/xml"
351
     */
352
    public function addHeader($header, $value)
353
    {
354
        $header = strtolower($header);
355
        $this->headers[$header] = $value;
356
        return $this;
357
    }
358
359
    /**
360
     * @return array
361
     */
362
    public function getHeaders()
363
    {
364
        return $this->headers;
365
    }
366
367
    /**
368
     * Remove an existing HTTP header
369
     *
370
     * @param string $header
371
     * @return mixed
372
     */
373
    public function getHeader($header)
374
    {
375
        $header = strtolower($header);
376
        return (isset($this->headers[$header])) ? $this->headers[$header] : null;
377
    }
378
379
    /**
380
     * Remove an existing HTTP header by its name,
381
     * e.g. "Content-Type".
382
     *
383
     * @param string $header
384
     * @return HTTPRequest $this
385
     */
386
    public function removeHeader($header)
387
    {
388
        $header = strtolower($header);
389
        unset($this->headers[$header]);
390
        return $this;
391
    }
392
393
    /**
394
     * Returns the URL used to generate the page
395
     *
396
     * @param bool $includeGetVars whether or not to include the get parameters\
397
     * @return string
398
     */
399
    public function getURL($includeGetVars = false)
400
    {
401
        $url = ($this->getExtension()) ? $this->url . '.' . $this->getExtension() : $this->url;
402
403
        if ($includeGetVars) {
404
            $vars = $this->getVars();
405
            if (count($vars)) {
406
                $url .= '?' . http_build_query($vars);
407
            }
408
        } elseif (strpos($url, "?") !== false) {
409
            $url = substr($url, 0, strpos($url, "?"));
410
        }
411
412
        return $url;
413
    }
414
415
    /**
416
     * Returns true if this request an ajax request,
417
     * based on custom HTTP ajax added by common JavaScript libraries,
418
     * or based on an explicit "ajax" request parameter.
419
     *
420
     * @return boolean
421
     */
422
    public function isAjax()
423
    {
424
        return (
425
            $this->requestVar('ajax') ||
426
            $this->getHeader('x-requested-with') === "XMLHttpRequest"
427
        );
428
    }
429
430
    /**
431
     * Enables the existence of a key-value pair in the request to be checked using
432
     * array syntax, so isset($request['title']) will check for $_POST['title'] and $_GET['title']
433
     *
434
     * @param string $offset
435
     * @return bool
436
     */
437
    public function offsetExists($offset)
438
    {
439
        return isset($this->postVars[$offset]) || isset($this->getVars[$offset]);
440
    }
441
442
    /**
443
     * Access a request variable using array syntax. eg: $request['title'] instead of $request->postVar('title')
444
     *
445
     * @param string $offset
446
     * @return mixed
447
     */
448
    public function offsetGet($offset)
449
    {
450
        return $this->requestVar($offset);
451
    }
452
453
    public function offsetSet($offset, $value)
454
    {
455
        $this->getVars[$offset] = $value;
456
    }
457
458
    public function offsetUnset($offset)
459
    {
460
        unset($this->getVars[$offset]);
461
        unset($this->postVars[$offset]);
462
    }
463
464
    /**
465
     * Construct an HTTPResponse that will deliver a file to the client.
466
     * Caution: Since it requires $fileData to be passed as binary data (no stream support),
467
     * it's only advisable to send small files through this method.
468
     *
469
     * @static
470
     * @param $fileData
471
     * @param $fileName
472
     * @param null $mimeType
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $mimeType is correct as it would always require null to be passed?
Loading history...
473
     * @return HTTPResponse
474
     */
475
    public static function send_file($fileData, $fileName, $mimeType = null)
476
    {
477
        if (!$mimeType) {
0 ignored issues
show
introduced by
$mimeType is of type null, thus it always evaluated to false.
Loading history...
478
            $mimeType = HTTP::get_mime_type($fileName);
479
        }
480
        $response = new HTTPResponse($fileData);
481
        $response->addHeader("content-type", "$mimeType; name=\"" . addslashes($fileName) . "\"");
482
        // Note a IE-only fix that inspects this header in HTTP::add_cache_headers().
483
        $response->addHeader("content-disposition", "attachment; filename=\"" . addslashes($fileName) . "\"");
484
        $response->addHeader("content-length", strlen($fileData));
485
486
        return $response;
487
    }
488
489
    /**
490
     * Matches a URL pattern
491
     * The pattern can contain a number of segments, separated by / (and an extension indicated by a .)
492
     *
493
     * The parts can be either literals, or, if they start with a $ they are interpreted as variables.
494
     *  - Literals must be provided in order to match
495
     *  - $Variables are optional
496
     *  - However, if you put ! at the end of a variable, then it becomes mandatory.
497
     *
498
     * For example:
499
     *  - admin/crm/list will match admin/crm/$Action/$ID/$OtherID, but it won't match admin/crm/$Action!/$ClassName!
500
     *
501
     * The pattern can optionally start with an HTTP method and a space.  For example, "POST $Controller/$Action".
502
     * This is used to define a rule that only matches on a specific HTTP method.
503
     *
504
     * @param $pattern
505
     * @param bool $shiftOnSuccess
506
     * @return array|bool
507
     */
508
    public function match($pattern, $shiftOnSuccess = false)
509
    {
510
        // Check if a specific method is required
511
        if (preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
512
            $requiredMethod = $matches[1];
513
            if ($requiredMethod != $this->httpMethod) {
514
                return false;
515
            }
516
517
            // If we get this far, we can match the URL pattern as usual.
518
            $pattern = $matches[2];
519
        }
520
521
        // Special case for the root URL controller
522
        if (!$pattern) {
523
            return ($this->dirParts == array()) ? array('Matched' => true) : false;
524
        }
525
526
        // Check for the '//' marker that represents the "shifting point"
527
        $doubleSlashPoint = strpos($pattern, '//');
528
        if ($doubleSlashPoint !== false) {
529
            $shiftCount = substr_count(substr($pattern, 0, $doubleSlashPoint), '/') + 1;
530
            $pattern = str_replace('//', '/', $pattern);
531
            $patternParts = explode('/', $pattern);
532
        } else {
533
            $patternParts = explode('/', $pattern);
534
            $shiftCount = sizeof($patternParts);
535
        }
536
537
        // Filter out any "empty" matching parts - either from an initial / or a trailing /
538
        $patternParts = array_values(array_filter($patternParts));
539
540
        $arguments = array();
541
        foreach ($patternParts as $i => $part) {
542
            $part = trim($part);
543
544
            // Match a variable
545
            if (isset($part[0]) && $part[0] == '$') {
546
                // A variable ending in ! is required
547
                if (substr($part, -1) == '!') {
548
                    $varRequired = true;
549
                    $varName = substr($part, 1, -1);
550
                } else {
551
                    $varRequired = false;
552
                    $varName = substr($part, 1);
553
                }
554
555
                // Fail if a required variable isn't populated
556
                if ($varRequired && !isset($this->dirParts[$i])) {
557
                    return false;
558
                }
559
560
                /** @skipUpgrade */
561
                $key = "Controller";
562
                $arguments[$varName] = isset($this->dirParts[$i]) ? $this->dirParts[$i] : null;
563
                if ($part == '$Controller'
564
                    && (
565
                        !ClassInfo::exists($arguments[$key])
566
                        || !is_subclass_of($arguments[$key], 'SilverStripe\\Control\\Controller')
567
                    )
568
                ) {
569
                    return false;
570
                }
571
572
            // Literal parts with extension
573
            } elseif (isset($this->dirParts[$i]) && $this->dirParts[$i] . '.' . $this->extension == $part) {
574
                continue;
575
576
            // Literal parts must always be there
577
            } elseif (!isset($this->dirParts[$i]) || $this->dirParts[$i] != $part) {
578
                return false;
579
            }
580
        }
581
582
        if ($shiftOnSuccess) {
583
            $this->shift($shiftCount);
584
            // We keep track of pattern parts that we looked at but didn't shift off.
585
            // This lets us say that we have *parsed* the whole URL even when we haven't *shifted* it all
586
            $this->unshiftedButParsedParts = sizeof($patternParts) - $shiftCount;
587
        }
588
589
        $this->latestParams = $arguments;
590
591
        // Load the arguments that actually have a value into $this->allParams
592
        // This ensures that previous values aren't overridden with blanks
593
        foreach ($arguments as $k => $v) {
594
            if ($v || !isset($this->allParams[$k])) {
595
                $this->allParams[$k] = $v;
596
            }
597
        }
598
599
        if ($arguments === array()) {
600
            $arguments['_matched'] = true;
601
        }
602
        return $arguments;
603
    }
604
605
    /**
606
     * @return array
607
     */
608
    public function allParams()
609
    {
610
        return $this->allParams;
611
    }
612
613
    /**
614
     * Shift all the parameter values down a key space, and return the shifted value.
615
     *
616
     * @return string
617
     */
618
    public function shiftAllParams()
619
    {
620
        $keys    = array_keys($this->allParams);
621
        $values  = array_values($this->allParams);
622
        $value   = array_shift($values);
623
624
        // push additional unparsed URL parts onto the parameter stack
625
        if (array_key_exists($this->unshiftedButParsedParts, $this->dirParts)) {
626
            $values[] = $this->dirParts[$this->unshiftedButParsedParts];
627
        }
628
629
        foreach ($keys as $position => $key) {
630
            $this->allParams[$key] = isset($values[$position]) ? $values[$position] : null;
631
        }
632
633
        return $value;
634
    }
635
636
    /**
637
     * @return array
638
     */
639
    public function latestParams()
640
    {
641
        return $this->latestParams;
642
    }
643
644
    /**
645
     * @param string $name
646
     * @return string|null
647
     */
648
    public function latestParam($name)
649
    {
650
        if (isset($this->latestParams[$name])) {
651
            return $this->latestParams[$name];
652
        } else {
653
            return null;
654
        }
655
    }
656
657
    /**
658
     * @return array
659
     */
660
    public function routeParams()
661
    {
662
        return $this->routeParams;
663
    }
664
665
    /**
666
     * @param $params
667
     * @return HTTPRequest $this
668
     */
669
    public function setRouteParams($params)
670
    {
671
        $this->routeParams = $params;
672
        return $this;
673
    }
674
675
    /**
676
     * @return array
677
     */
678
    public function params()
679
    {
680
        return array_merge($this->allParams, $this->routeParams);
681
    }
682
683
    /**
684
     * Finds a named URL parameter (denoted by "$"-prefix in $url_handlers)
685
     * from the full URL, or a parameter specified in the route table
686
     *
687
     * @param string $name
688
     * @return string Value of the URL parameter (if found)
689
     */
690
    public function param($name)
691
    {
692
        $params = $this->params();
693
        if (isset($params[$name])) {
694
            return $params[$name];
695
        } else {
696
            return null;
697
        }
698
    }
699
700
    /**
701
     * Returns the unparsed part of the original URL
702
     * separated by commas. This is used by {@link RequestHandler->handleRequest()}
703
     * to determine if further URL processing is necessary.
704
     *
705
     * @return string Partial URL
706
     */
707
    public function remaining()
708
    {
709
        return implode("/", $this->dirParts);
710
    }
711
712
    /**
713
     * Returns true if this is a URL that will match without shifting off any of the URL.
714
     * This is used by the request handler to prevent infinite parsing loops.
715
     *
716
     * @param string $pattern
717
     * @return bool
718
     */
719
    public function isEmptyPattern($pattern)
720
    {
721
        if (preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
722
            $pattern = $matches[2];
723
        }
724
725
        if (trim($pattern) == "") {
726
            return true;
727
        }
728
        return false;
729
    }
730
731
    /**
732
     * Shift one or more parts off the beginning of the URL.
733
     * If you specify shifting more than 1 item off, then the items will be returned as an array
734
     *
735
     * @param int $count Shift Count
736
     * @return string|array
737
     */
738
    public function shift($count = 1)
739
    {
740
        $return = array();
741
742
        if ($count == 1) {
743
            return array_shift($this->dirParts);
744
        }
745
746
        for ($i=0; $i<$count; $i++) {
747
            $value = array_shift($this->dirParts);
748
749
            if ($value === null) {
750
                break;
751
            }
752
753
            $return[] = $value;
754
        }
755
756
        return $return;
757
    }
758
759
    /**
760
     * Returns true if the URL has been completely parsed.
761
     * This will respect parsed but unshifted directory parts.
762
     *
763
     * @return bool
764
     */
765
    public function allParsed()
766
    {
767
        return sizeof($this->dirParts) <= $this->unshiftedButParsedParts;
768
    }
769
770
    /**
771
     * @return string Return the host from the request
772
     */
773
    public function getHost()
774
    {
775
        return $this->getHeader('host');
776
    }
777
778
    /**
779
     * Returns the client IP address which originated this request.
780
     *
781
     * @return string
782
     */
783
    public function getIP()
784
    {
785
        return $this->ip;
786
    }
787
788
    /**
789
     * Sets the client IP address which originated this request.
790
     * Use setIPFromHeaderValue if assigning from header value.
791
     *
792
     * @param $ip string
793
     * @return $this
794
     */
795
    public function setIP($ip)
796
    {
797
        if (!filter_var($ip, FILTER_VALIDATE_IP)) {
798
            throw new InvalidArgumentException("Invalid ip $ip");
799
        }
800
        $this->ip = $ip;
801
        return $this;
802
    }
803
804
    /**
805
     * Returns all mimetypes from the HTTP "Accept" header
806
     * as an array.
807
     *
808
     * @param boolean $includeQuality Don't strip away optional "quality indicators", e.g. "application/xml;q=0.9"
809
     *                                (Default: false)
810
     * @return array
811
     */
812
    public function getAcceptMimetypes($includeQuality = false)
813
    {
814
        $mimetypes = array();
815
        $mimetypesWithQuality = preg_split('#\s*,\s*#', $this->getHeader('accept'));
816
        foreach ($mimetypesWithQuality as $mimetypeWithQuality) {
817
            $mimetypes[] = ($includeQuality) ? $mimetypeWithQuality : preg_replace('/;.*/', '', $mimetypeWithQuality);
818
        }
819
        return $mimetypes;
820
    }
821
822
    /**
823
     * @return string HTTP method (all uppercase)
824
     */
825
    public function httpMethod()
826
    {
827
        return $this->httpMethod;
828
    }
829
830
    /**
831
     * Explicitly set the HTTP method for this request.
832
     * @param string $method
833
     * @return $this
834
     */
835
    public function setHttpMethod($method)
836
    {
837
        if (!self::isValidHttpMethod($method)) {
838
            user_error('HTTPRequest::setHttpMethod: Invalid HTTP method', E_USER_ERROR);
839
        }
840
841
        $this->httpMethod = strtoupper($method);
842
        return $this;
843
    }
844
845
    /**
846
     * Return the URL scheme (e.g. "http" or "https").
847
     * Equivalent to PSR-7 getUri()->getScheme()
848
     *
849
     * @return string
850
     */
851
    public function getScheme()
852
    {
853
        return $this->scheme;
854
    }
855
856
    /**
857
     * Set the URL scheme (e.g. "http" or "https").
858
     * Equivalent to PSR-7 getUri()->getScheme(),
859
     *
860
     * @param string $scheme
861
     * @return $this
862
     */
863
    public function setScheme($scheme)
864
    {
865
        $this->scheme = $scheme;
866
        return $this;
867
    }
868
869
    /**
870
     * @param string $method
871
     * @return bool
872
     */
873
    private static function isValidHttpMethod($method)
874
    {
875
        return in_array(strtoupper($method), ['GET','POST','PUT','DELETE','HEAD']);
876
    }
877
878
    /**
879
     * Gets the "real" HTTP method for a request. This method is no longer used to mitigate the risk of web cache
880
     * poisoning.
881
     *
882
     * @see https://www.silverstripe.org/download/security-releases/CVE-2019-19326
883
     * @param string $origMethod Original HTTP method from the browser request
884
     * @param array $postVars
885
     * @return string HTTP method (all uppercase)
886
     * @deprecated 4.4.7
887
     */
888
    public static function detect_method($origMethod, $postVars)
889
    {
890
        if (isset($postVars['_method'])) {
891
            if (!self::isValidHttpMethod($postVars['_method'])) {
892
                user_error('HTTPRequest::detect_method(): Invalid "_method" parameter', E_USER_ERROR);
893
            }
894
            return strtoupper($postVars['_method']);
895
        } else {
896
            return $origMethod;
897
        }
898
    }
899
900
    /**
901
     * Determines whether the request has a session
902
     *
903
     * @return bool
904
     */
905
    public function hasSession(): bool
906
    {
907
        return !empty($this->session);
908
    }
909
910
    /**
911
     * @return Session
912
     */
913
    public function getSession()
914
    {
915
        if (!$this->hasSession()) {
916
            throw new BadMethodCallException("No session available for this HTTPRequest");
917
        }
918
        return $this->session;
919
    }
920
921
    /**
922
     * @param Session $session
923
     * @return $this
924
     */
925
    public function setSession(Session $session)
926
    {
927
        $this->session = $session;
928
        return $this;
929
    }
930
}
931