Completed
Push — master ( daed8c...cf758d )
by Damian
08:03
created

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