Completed
Push — director-middleware ( 059969...3cff84 )
by Sam
08:52
created

HTTPRequest::setIP()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control;
4
5
use ArrayAccess;
6
use BadMethodCallException;
7
use SilverStripe\Core\ClassInfo;
8
use SilverStripe\ORM\ArrayLib;
9
10
/**
11
 * Represents a HTTP-request, including a URL that is tokenised for parsing, and a request method
12
 * (GET/POST/PUT/DELETE). This is used by {@link RequestHandler} objects to decide what to do.
13
 *
14
 * Caution: objects of this class are immutable, e.g. echo $request['a']; works as expected,
15
 * but $request['a'] = '1'; has no effect.
16
 *
17
 * The intention is that a single HTTPRequest object can be passed from one object to another, each object calling
18
 * match() to get the information that they need out of the URL.  This is generally handled by
19
 * {@link RequestHandler::handleRequest()}.
20
 *
21
 * @todo Accept X_HTTP_METHOD_OVERRIDE http header and $_REQUEST['_method'] to override request types (useful for
22
 *       webclients not supporting PUT and DELETE)
23
 */
24
class HTTPRequest implements ArrayAccess
25
{
26
    /**
27
     * @var string
28
     */
29
    protected $url;
30
31
    /**
32
     * The non-extension parts of the passed URL as an array, originally exploded by the "/" separator.
33
     * All elements of the URL are loaded in here,
34
     * and subsequently popped out of the array by {@link shift()}.
35
     * Only use this structure for internal request handling purposes.
36
     *
37
     * @var array
38
     */
39
    protected $dirParts;
40
41
    /**
42
     * The URL extension (if present)
43
     *
44
     * @var string
45
     */
46
    protected $extension;
47
48
    /**
49
     * The HTTP method in all uppercase: GET/PUT/POST/DELETE/HEAD
50
     *
51
     * @var string
52
     */
53
    protected $httpMethod;
54
55
    /**
56
     * The URL scheme in lowercase: http or https
57
     *
58
     * @var string
59
     */
60
    protected $scheme;
61
62
    /**
63
     * The client IP address
64
     *
65
     * @var string
66
     */
67
    protected $ip;
68
69
    /**
70
     * Contains alls HTTP GET parameters passed into this request.
71
     *
72
     * @var array
73
     */
74
    protected $getVars = array();
75
76
    /**
77
     * Contains alls HTTP POST parameters passed into this request.
78
     *
79
     * @var array
80
     */
81
    protected $postVars = array();
82
83
    /**
84
     * HTTP Headers like "Content-Type: text/xml"
85
     *
86
     * @see http://en.wikipedia.org/wiki/List_of_HTTP_headers
87
     * @var array
88
     */
89
    protected $headers = array();
90
91
    /**
92
     * Raw HTTP body, used by PUT and POST requests.
93
     *
94
     * @var string
95
     */
96
    protected $body;
97
98
    /**
99
     * Contains an associative array of all
100
     * arguments matched in all calls to {@link RequestHandler->handleRequest()}.
101
     * It's a "historical record" that's specific to the current call of
102
     * {@link handleRequest()}, and is only complete once the "last call" to that method is made.
103
     *
104
     * @var array
105
     */
106
    protected $allParams = array();
107
108
    /**
109
     * Contains an associative array of all
110
     * arguments matched in the current call from {@link RequestHandler->handleRequest()},
111
     * as denoted with a "$"-prefix in the $url_handlers definitions.
112
     * Contains different states throughout its lifespan, so just useful
113
     * while processed in {@link RequestHandler} and to get the last
114
     * processes arguments.
115
     *
116
     * @var array
117
     */
118
    protected $latestParams = array();
119
120
    /**
121
     * Contains an associative array of all arguments
122
     * explicitly set in the route table for the current request.
123
     * Useful for passing generic arguments via custom routes.
124
     *
125
     * E.g. The "Locale" parameter would be assigned "en_NZ" below
126
     *
127
     * Director:
128
     *   rules:
129
     *     'en_NZ/$URLSegment!//$Action/$ID/$OtherID':
130
     *       Controller: 'ModelAsController'
131
     *       Locale: 'en_NZ'
132
     *
133
     * @var array
134
     */
135
    protected $routeParams = array();
136
137
    /**
138
     * @var int
139
     */
140
    protected $unshiftedButParsedParts = 0;
141
142
    /**
143
     * @var Session
144
     */
145
    protected $session;
146
147
    /**
148
     * Construct a HTTPRequest from a URL relative to the site root.
149
     *
150
     * @param string $httpMethod
151
     * @param string $url
152
     * @param array $getVars
153
     * @param array $postVars
154
     * @param string $body
155
     */
156
    public function __construct($httpMethod, $url, $getVars = array(), $postVars = array(), $body = null)
157
    {
158
        $this->httpMethod = strtoupper(self::detect_method($httpMethod, $postVars));
159
        $this->setUrl($url);
160
        $this->getVars = (array) $getVars;
161
        $this->postVars = (array) $postVars;
162
        $this->body = $body;
163
        $this->scheme = "http";
164
    }
165
166
    /**
167
     * Allow the setting of a URL
168
     *
169
     * This is here so that RootURLController can change the URL of the request
170
     * without us loosing all the other info attached (like headers)
171
     *
172
     * @param string $url The new URL
173
     * @return HTTPRequest The updated request
174
     */
175
    public function setUrl($url)
176
    {
177
        $this->url = $url;
178
179
        // Normalize URL if its relative (strictly speaking), or has leading slashes
180
        if (Director::is_relative_url($url) || preg_match('/^\//', $url)) {
181
            $this->url = preg_replace(array('/\/+/','/^\//', '/\/$/'), array('/','',''), $this->url);
182
        }
183
        if (preg_match('/^(.*)\.([A-Za-z][A-Za-z0-9]*)$/', $this->url, $matches)) {
184
            $this->url = $matches[1];
185
            $this->extension = $matches[2];
186
        }
187
        if ($this->url) {
188
            $this->dirParts = preg_split('|/+|', $this->url);
189
        } else {
190
            $this->dirParts = array();
191
        }
192
193
        return $this;
194
    }
195
196
    /**
197
     * @return bool
198
     */
199
    public function isGET()
200
    {
201
        return $this->httpMethod == 'GET';
202
    }
203
204
    /**
205
     * @return bool
206
     */
207
    public function isPOST()
208
    {
209
        return $this->httpMethod == 'POST';
210
    }
211
212
    /**
213
     * @return bool
214
     */
215
    public function isPUT()
216
    {
217
        return $this->httpMethod == 'PUT';
218
    }
219
220
    /**
221
     * @return bool
222
     */
223
    public function isDELETE()
224
    {
225
        return $this->httpMethod == 'DELETE';
226
    }
227
228
    /**
229
     * @return bool
230
     */
231
    public function isHEAD()
232
    {
233
        return $this->httpMethod == 'HEAD';
234
    }
235
236
    /**
237
     * @param string $body
238
     * @return HTTPRequest $this
239
     */
240
    public function setBody($body)
241
    {
242
        $this->body = $body;
243
        return $this;
244
    }
245
246
    /**
247
     * @return null|string
248
     */
249
    public function getBody()
250
    {
251
        return $this->body;
252
    }
253
254
    /**
255
     * @return array
256
     */
257
    public function getVars()
258
    {
259
        return $this->getVars;
260
    }
261
262
    /**
263
     * @return array
264
     */
265
    public function postVars()
266
    {
267
        return $this->postVars;
268
    }
269
270
    /**
271
     * Returns all combined HTTP GET and POST parameters
272
     * passed into this request. If a parameter with the same
273
     * name exists in both arrays, the POST value is returned.
274
     *
275
     * @return array
276
     */
277
    public function requestVars()
278
    {
279
        return ArrayLib::array_merge_recursive($this->getVars, $this->postVars);
280
    }
281
282
    /**
283
     * @param string $name
284
     * @return mixed
285
     */
286
    public function getVar($name)
287
    {
288
        if (isset($this->getVars[$name])) {
289
            return $this->getVars[$name];
290
        }
291
        return null;
292
    }
293
294
    /**
295
     * @param string $name
296
     * @return mixed
297
     */
298
    public function postVar($name)
299
    {
300
        if (isset($this->postVars[$name])) {
301
            return $this->postVars[$name];
302
        }
303
        return null;
304
    }
305
306
    /**
307
     * @param string $name
308
     * @return mixed
309
     */
310
    public function requestVar($name)
311
    {
312
        if (isset($this->postVars[$name])) {
313
            return $this->postVars[$name];
314
        }
315
        if (isset($this->getVars[$name])) {
316
            return $this->getVars[$name];
317
        }
318
        return null;
319
    }
320
321
    /**
322
     * Returns a possible file extension found in parsing the URL
323
     * as denoted by a "."-character near the end of the URL.
324
     * Doesn't necessarily have to belong to an existing file,
325
     * as extensions can be also used for content-type-switching.
326
     *
327
     * @return string
328
     */
329
    public function getExtension()
330
    {
331
        return $this->extension;
332
    }
333
334
    /**
335
     * Checks if the {@link HTTPRequest->getExtension()} on this request matches one of the more common media types
336
     * embedded into a webpage - e.g. css, png.
337
     *
338
     * This is useful for things like determining whether to display a fully rendered error page or not. Note that the
339
     * media file types is not at all comprehensive.
340
     *
341
     * @return bool
342
     */
343
    public function isMedia()
344
    {
345
        return in_array($this->getExtension(), array('css', 'js', 'jpg', 'jpeg', 'gif', 'png', 'bmp', 'ico'));
346
    }
347
348
    /**
349
     * Add a HTTP header to the response, replacing any header of the same name.
350
     *
351
     * @param string $header Example: "Content-Type"
352
     * @param string $value Example: "text/xml"
353
     */
354
    public function addHeader($header, $value)
355
    {
356
        $this->headers[$header] = $value;
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
        return (isset($this->headers[$header])) ? $this->headers[$header] : null;
376
    }
377
378
    /**
379
     * Remove an existing HTTP header by its name,
380
     * e.g. "Content-Type".
381
     *
382
     * @param string $header
383
     * @return HTTPRequest $this
384
     */
385
    public function removeHeader($header)
386
    {
387
        if (isset($this->headers[$header])) {
388
            unset($this->headers[$header]);
389
        }
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
            // if we don't unset $vars['url'] we end up with /my/url?url=my/url&foo=bar etc
405
406
            $vars = $this->getVars();
407
            unset($vars['url']);
408
409
            if (count($vars)) {
410
                $url .= '?' . http_build_query($vars);
411
            }
412 View Code Duplication
        } elseif (strpos($url, "?") !== false) {
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...
413
            $url = substr($url, 0, strpos($url, "?"));
414
        }
415
416
        return $url;
417
    }
418
419
    /**
420
     * Returns true if this request an ajax request,
421
     * based on custom HTTP ajax added by common JavaScript libraries,
422
     * or based on an explicit "ajax" request parameter.
423
     *
424
     * @return boolean
425
     */
426
    public function isAjax()
427
    {
428
        return (
429
            $this->requestVar('ajax') ||
430
            $this->getHeader('X-Requested-With') && $this->getHeader('X-Requested-With') == "XMLHttpRequest"
431
        );
432
    }
433
434
    /**
435
     * Enables the existence of a key-value pair in the request to be checked using
436
     * array syntax, so isset($request['title']) will check for $_POST['title'] and $_GET['title']
437
     *
438
     * @param string $offset
439
     * @return bool
440
     */
441
    public function offsetExists($offset)
442
    {
443
        return isset($this->postVars[$offset]) || isset($this->getVars[$offset]);
444
    }
445
446
    /**
447
     * Access a request variable using array syntax. eg: $request['title'] instead of $request->postVar('title')
448
     *
449
     * @param string $offset
450
     * @return mixed
451
     */
452
    public function offsetGet($offset)
453
    {
454
        return $this->requestVar($offset);
455
    }
456
457
    public function offsetSet($offset, $value)
458
    {
459
        $this->getVars[$offset] = $value;
460
    }
461
462
    public function offsetUnset($offset)
463
    {
464
        unset($this->getVars[$offset]);
465
        unset($this->postVars[$offset]);
466
    }
467
468
    /**
469
     * Construct an HTTPResponse that will deliver a file to the client.
470
     * Caution: Since it requires $fileData to be passed as binary data (no stream support),
471
     * it's only advisable to send small files through this method.
472
     *
473
     * @static
474
     * @param $fileData
475
     * @param $fileName
476
     * @param null $mimeType
477
     * @return HTTPResponse
478
     */
479
    public static function send_file($fileData, $fileName, $mimeType = null)
480
    {
481
        if (!$mimeType) {
482
            $mimeType = HTTP::get_mime_type($fileName);
483
        }
484
        $response = new HTTPResponse($fileData);
485
        $response->addHeader("Content-Type", "$mimeType; name=\"" . addslashes($fileName) . "\"");
486
        // Note a IE-only fix that inspects this header in HTTP::add_cache_headers().
487
        $response->addHeader("Content-Disposition", "attachment; filename=\"" . addslashes($fileName) . "\"");
488
        $response->addHeader("Content-Length", strlen($fileData));
489
490
        return $response;
491
    }
492
493
    /**
494
     * Matches a URL pattern
495
     * The pattern can contain a number of segments, separated by / (and an extension indicated by a .)
496
     *
497
     * The parts can be either literals, or, if they start with a $ they are interpreted as variables.
498
     *  - Literals must be provided in order to match
499
     *  - $Variables are optional
500
     *  - However, if you put ! at the end of a variable, then it becomes mandatory.
501
     *
502
     * For example:
503
     *  - admin/crm/list will match admin/crm/$Action/$ID/$OtherID, but it won't match admin/crm/$Action!/$ClassName!
504
     *
505
     * The pattern can optionally start with an HTTP method and a space.  For example, "POST $Controller/$Action".
506
     * This is used to define a rule that only matches on a specific HTTP method.
507
     *
508
     * @param $pattern
509
     * @param bool $shiftOnSuccess
510
     * @return array|bool
511
     */
512
    public function match($pattern, $shiftOnSuccess = false)
513
    {
514
        // Check if a specific method is required
515
        if (preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
516
            $requiredMethod = $matches[1];
517
            if ($requiredMethod != $this->httpMethod) {
518
                return false;
519
            }
520
521
            // If we get this far, we can match the URL pattern as usual.
522
            $pattern = $matches[2];
523
        }
524
525
        // Special case for the root URL controller
526
        if (!$pattern) {
527
            return ($this->dirParts == array()) ? array('Matched' => true) : false;
528
        }
529
530
        // Check for the '//' marker that represents the "shifting point"
531
        $doubleSlashPoint = strpos($pattern, '//');
532
        if ($doubleSlashPoint !== false) {
533
            $shiftCount = substr_count(substr($pattern, 0, $doubleSlashPoint), '/') + 1;
534
            $pattern = str_replace('//', '/', $pattern);
535
            $patternParts = explode('/', $pattern);
536
        } else {
537
            $patternParts = explode('/', $pattern);
538
            $shiftCount = sizeof($patternParts);
539
        }
540
541
        // Filter out any "empty" matching parts - either from an initial / or a trailing /
542
        $patternParts = array_values(array_filter($patternParts));
543
544
        $arguments = array();
545
        foreach ($patternParts as $i => $part) {
546
            $part = trim($part);
547
548
            // Match a variable
549
            if (isset($part[0]) && $part[0] == '$') {
550
                // A variable ending in ! is required
551
                if (substr($part, -1) == '!') {
552
                    $varRequired = true;
553
                    $varName = substr($part, 1, -1);
554
                } else {
555
                    $varRequired = false;
556
                    $varName = substr($part, 1);
557
                }
558
559
                // Fail if a required variable isn't populated
560
                if ($varRequired && !isset($this->dirParts[$i])) {
561
                    return false;
562
                }
563
564
                /** @skipUpgrade */
565
                $key = "Controller";
566
                $arguments[$varName] = isset($this->dirParts[$i]) ? $this->dirParts[$i] : null;
567
                if ($part == '$Controller'
568
                    && (
569
                        !ClassInfo::exists($arguments[$key])
570
                        || !is_subclass_of($arguments[$key], 'SilverStripe\\Control\\Controller')
571
                    )
572
                ) {
573
                    return false;
574
                }
575
576
            // Literal parts with extension
577
            } elseif (isset($this->dirParts[$i]) && $this->dirParts[$i] . '.' . $this->extension == $part) {
578
                continue;
579
580
            // Literal parts must always be there
581
            } elseif (!isset($this->dirParts[$i]) || $this->dirParts[$i] != $part) {
582
                return false;
583
            }
584
        }
585
586
        if ($shiftOnSuccess) {
587
            $this->shift($shiftCount);
588
            // We keep track of pattern parts that we looked at but didn't shift off.
589
            // This lets us say that we have *parsed* the whole URL even when we haven't *shifted* it all
590
            $this->unshiftedButParsedParts = sizeof($patternParts) - $shiftCount;
0 ignored issues
show
Documentation Bug introduced by
It seems like sizeof($patternParts) - $shiftCount can also be of type double. However, the property $unshiftedButParsedParts is declared as type integer. 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...
591
        }
592
593
        $this->latestParams = $arguments;
594
595
        // Load the arguments that actually have a value into $this->allParams
596
        // This ensures that previous values aren't overridden with blanks
597
        foreach ($arguments as $k => $v) {
598
            if ($v || !isset($this->allParams[$k])) {
599
                $this->allParams[$k] = $v;
600
            }
601
        }
602
603
        if ($arguments === array()) {
604
            $arguments['_matched'] = true;
605
        }
606
        return $arguments;
607
    }
608
609
    /**
610
     * @return array
611
     */
612
    public function allParams()
613
    {
614
        return $this->allParams;
615
    }
616
617
    /**
618
     * Shift all the parameter values down a key space, and return the shifted value.
619
     *
620
     * @return string
621
     */
622
    public function shiftAllParams()
623
    {
624
        $keys    = array_keys($this->allParams);
625
        $values  = array_values($this->allParams);
626
        $value   = array_shift($values);
627
628
        // push additional unparsed URL parts onto the parameter stack
629
        if (array_key_exists($this->unshiftedButParsedParts, $this->dirParts)) {
630
            $values[] = $this->dirParts[$this->unshiftedButParsedParts];
631
        }
632
633
        foreach ($keys as $position => $key) {
634
            $this->allParams[$key] = isset($values[$position]) ? $values[$position] : null;
635
        }
636
637
        return $value;
638
    }
639
640
    /**
641
     * @return array
642
     */
643
    public function latestParams()
644
    {
645
        return $this->latestParams;
646
    }
647
648
    /**
649
     * @param string $name
650
     * @return string|null
651
     */
652
    public function latestParam($name)
653
    {
654
        if (isset($this->latestParams[$name])) {
655
            return $this->latestParams[$name];
656
        } else {
657
            return null;
658
        }
659
    }
660
661
    /**
662
     * @return array
663
     */
664
    public function routeParams()
665
    {
666
        return $this->routeParams;
667
    }
668
669
    /**
670
     * @param $params
671
     * @return HTTPRequest $this
672
     */
673
    public function setRouteParams($params)
674
    {
675
        $this->routeParams = $params;
676
        return $this;
677
    }
678
679
    /**
680
     * @return array
681
     */
682
    public function params()
683
    {
684
        return array_merge($this->allParams, $this->routeParams);
685
    }
686
687
    /**
688
     * Finds a named URL parameter (denoted by "$"-prefix in $url_handlers)
689
     * from the full URL, or a parameter specified in the route table
690
     *
691
     * @param string $name
692
     * @return string Value of the URL parameter (if found)
693
     */
694
    public function param($name)
695
    {
696
        $params = $this->params();
697
        if (isset($params[$name])) {
698
            return $params[$name];
699
        } else {
700
            return null;
701
        }
702
    }
703
704
    /**
705
     * Returns the unparsed part of the original URL
706
     * separated by commas. This is used by {@link RequestHandler->handleRequest()}
707
     * to determine if further URL processing is necessary.
708
     *
709
     * @return string Partial URL
710
     */
711
    public function remaining()
712
    {
713
        return implode("/", $this->dirParts);
714
    }
715
716
    /**
717
     * Returns true if this is a URL that will match without shifting off any of the URL.
718
     * This is used by the request handler to prevent infinite parsing loops.
719
     *
720
     * @param string $pattern
721
     * @return bool
722
     */
723
    public function isEmptyPattern($pattern)
724
    {
725
        if (preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
726
            $pattern = $matches[2];
727
        }
728
729
        if (trim($pattern) == "") {
730
            return true;
731
        }
732
        return false;
733
    }
734
735
    /**
736
     * Shift one or more parts off the beginning of the URL.
737
     * If you specify shifting more than 1 item off, then the items will be returned as an array
738
     *
739
     * @param int $count Shift Count
740
     * @return string|array
741
     */
742
    public function shift($count = 1)
743
    {
744
        $return = array();
745
746
        if ($count == 1) {
747
            return array_shift($this->dirParts);
748
        }
749
750
        for ($i=0; $i<$count; $i++) {
751
            $value = array_shift($this->dirParts);
752
753
            if ($value === null) {
754
                break;
755
            }
756
757
            $return[] = $value;
758
        }
759
760
        return $return;
761
    }
762
763
    /**
764
     * Returns true if the URL has been completely parsed.
765
     * This will respect parsed but unshifted directory parts.
766
     *
767
     * @return bool
768
     */
769
    public function allParsed()
770
    {
771
        return sizeof($this->dirParts) <= $this->unshiftedButParsedParts;
772
    }
773
774
    /**
775
     * Returns the client IP address which originated this request.
776
     *
777
     * @return string
778
     */
779
    public function getIP()
780
    {
781
        return $this->ip;
782
    }
783
784
    /**
785
     * Sets the client IP address which originated this request.
786
     *
787
     * @param $ip string
788
     */
789
    public function setIP($ip)
790
    {
791
        $this->ip = $ip;
792
    }
793
794
    /**
795
     * Returns all mimetypes from the HTTP "Accept" header
796
     * as an array.
797
     *
798
     * @param boolean $includeQuality Don't strip away optional "quality indicators", e.g. "application/xml;q=0.9"
799
     *                                (Default: false)
800
     * @return array
801
     */
802
    public function getAcceptMimetypes($includeQuality = false)
803
    {
804
        $mimetypes = array();
805
        $mimetypesWithQuality = explode(',', $this->getHeader('Accept'));
806
        foreach ($mimetypesWithQuality as $mimetypeWithQuality) {
807
            $mimetypes[] = ($includeQuality) ? $mimetypeWithQuality : preg_replace('/;.*/', '', $mimetypeWithQuality);
808
        }
809
        return $mimetypes;
810
    }
811
812
    /**
813
     * @return string HTTP method (all uppercase)
814
     */
815
    public function httpMethod()
816
    {
817
        return $this->httpMethod;
818
    }
819
820
    /**
821
     * Return the URL scheme (e.g. "http" or "https").
822
     * Equivalent to PSR-7 getUri()->getScheme()
823
     * @return string
824
     */
825
    public function getScheme() {
826
        return $this->scheme;
827
    }
828
829
    /**
830
     * Set the URL scheme (e.g. "http" or "https").
831
     * Equivalent to PSR-7 getUri()->getScheme(),
832
     *
833
     * @param string $scheme
834
     */
835
    public function setScheme($scheme) {
836
        $this->scheme = $scheme;
837
    }
838
839
    /**
840
     * Gets the "real" HTTP method for a request.
841
     *
842
     * Used to work around browser limitations of form
843
     * submissions to GET and POST, by overriding the HTTP method
844
     * with a POST parameter called "_method" for PUT, DELETE, HEAD.
845
     * Using GET for the "_method" override is not supported,
846
     * as GET should never carry out state changes.
847
     * Alternatively you can use a custom HTTP header 'X-HTTP-Method-Override'
848
     * to override the original method.
849
     * The '_method' POST parameter overrules the custom HTTP header.
850
     *
851
     * @param string $origMethod Original HTTP method from the browser request
852
     * @param array $postVars
853
     * @return string HTTP method (all uppercase)
854
     */
855
    public static function detect_method($origMethod, $postVars)
856
    {
857
        if (isset($postVars['_method'])) {
858
            if (!in_array(strtoupper($postVars['_method']), array('GET','POST','PUT','DELETE','HEAD'))) {
859
                user_error('HTTPRequest::detect_method(): Invalid "_method" parameter', E_USER_ERROR);
860
            }
861
            return strtoupper($postVars['_method']);
862
        } else {
863
            return $origMethod;
864
        }
865
    }
866
867
    /**
868
     * @return Session
869
     */
870
    public function getSession()
871
    {
872
        if (empty($this->session)) {
873
            throw new BadMethodCallException("No session available for this HTTPRequest");
874
        }
875
        return $this->session;
876
    }
877
878
    /**
879
     * @param Session $session
880
     * @return $this
881
     */
882
    public function setSession(Session $session)
883
    {
884
        $this->session = $session;
885
        return $this;
886
    }
887
}
888