Completed
Pull Request — master (#5408)
by Damian
23:40 queued 12:41
created

SS_HTTPRequest::getIPFromHeaderValue()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 14
rs 9.2
cc 4
eloc 8
nc 3
nop 1
1
<?php
2
3
/**
4
 * Represents a HTTP-request, including a URL that is tokenised for parsing, and a request method
5
 * (GET/POST/PUT/DELETE). This is used by {@link RequestHandler} objects to decide what to do.
6
 *
7
 * Caution: objects of this class are immutable, e.g. echo $request['a']; works as expected,
8
 * but $request['a'] = '1'; has no effect.
9
 *
10
 * The intention is that a single SS_HTTPRequest object can be passed from one object to another, each object calling
11
 * match() to get the information that they need out of the URL.  This is generally handled by
12
 * {@link RequestHandler::handleRequest()}.
13
 *
14
 * @todo Accept X_HTTP_METHOD_OVERRIDE http header and $_REQUEST['_method'] to override request types (useful for
15
 *       webclients not supporting PUT and DELETE)
16
 *
17
 * @package framework
18
 * @subpackage control
19
 */
20
class SS_HTTPRequest implements ArrayAccess {
21
22
	/**
23
	 * @var string $url
24
	 */
25
	protected $url;
26
27
	/**
28
	 * The non-extension parts of the passed URL as an array, originally exploded by the "/" separator.
29
	 * All elements of the URL are loaded in here,
30
	 * and subsequently popped out of the array by {@link shift()}.
31
	 * Only use this structure for internal request handling purposes.
32
	 */
33
	protected $dirParts;
34
35
	/**
36
	 * @var string $extension The URL extension (if present)
37
	 */
38
	protected $extension;
39
40
	/**
41
	 * @var string $httpMethod The HTTP method in all uppercase: GET/PUT/POST/DELETE/HEAD
42
	 */
43
	protected $httpMethod;
44
45
	/**
46
	 * @var array $getVars Contains alls HTTP GET parameters passed into this request.
47
	 */
48
	protected $getVars = array();
49
50
	/**
51
	 * @var array $postVars Contains alls HTTP POST parameters passed into this request.
52
	 */
53
	protected $postVars = array();
54
55
	/**
56
	 * HTTP Headers like "Content-Type: text/xml"
57
	 *
58
	 * @see http://en.wikipedia.org/wiki/List_of_HTTP_headers
59
	 * @var array
60
	 */
61
	protected $headers = array();
62
63
	/**
64
	 * Raw HTTP body, used by PUT and POST requests.
65
	 *
66
	 * @var string
67
	 */
68
	protected $body;
69
70
	/**
71
	 * @var array $allParams Contains an associative array of all
72
	 * arguments matched in all calls to {@link RequestHandler->handleRequest()}.
73
	 * It's a "historical record" that's specific to the current call of
74
	 * {@link handleRequest()}, and is only complete once the "last call" to that method is made.
75
	 */
76
	protected $allParams = array();
77
78
	/**
79
	 * @var array $latestParams Contains an associative array of all
80
	 * arguments matched in the current call from {@link RequestHandler->handleRequest()},
81
	 * as denoted with a "$"-prefix in the $url_handlers definitions.
82
	 * Contains different states throughout its lifespan, so just useful
83
	 * while processed in {@link RequestHandler} and to get the last
84
	 * processes arguments.
85
	 */
86
	protected $latestParams = array();
87
88
	/**
89
	 * @var array $routeParams Contains an associative array of all arguments
90
	 * explicitly set in the route table for the current request.
91
	 * Useful for passing generic arguments via custom routes.
92
	 *
93
	 * E.g. The "Locale" parameter would be assigned "en_NZ" below
94
	 *
95
	 * Director:
96
	 *   rules:
97
	 *     'en_NZ/$URLSegment!//$Action/$ID/$OtherID':
98
	 *       Controller: 'ModelAsController'
99
	 *       Locale: 'en_NZ'
100
	 */
101
	protected $routeParams = array();
102
103
	protected $unshiftedButParsedParts = 0;
104
105
	/**
106
	 * Construct a SS_HTTPRequest from a URL relative to the site root.
107
	 */
108
	public function __construct($httpMethod, $url, $getVars = array(), $postVars = array(), $body = null) {
109
		$this->httpMethod = strtoupper(self::detect_method($httpMethod, $postVars));
110
		$this->setUrl($url);
111
112
		$this->getVars = (array) $getVars;
113
		$this->postVars = (array) $postVars;
114
		$this->body = $body;
115
	}
116
117
	/**
118
	 * Allow the setting of a URL
119
	 *
120
	 * This is here so that RootURLController can change the URL of the request
121
	 * without us loosing all the other info attached (like headers)
122
	 *
123
	 * @param string The new URL
124
	 *
125
	 * @return SS_HTTPRequest The updated request
126
	 */
127
	public function setUrl($url) {
128
		$this->url = $url;
129
130
		// Normalize URL if its relative (strictly speaking), or has leading slashes
131
		if(Director::is_relative_url($url) || preg_match('/^\//', $url)) {
132
			$this->url = preg_replace(array('/\/+/','/^\//', '/\/$/'),array('/','',''), $this->url);
133
		}
134
		if(preg_match('/^(.*)\.([A-Za-z][A-Za-z0-9]*)$/', $this->url, $matches)) {
135
			$this->url = $matches[1];
136
			$this->extension = $matches[2];
137
		}
138
		if($this->url) $this->dirParts = preg_split('|/+|', $this->url);
139
		else $this->dirParts = array();
140
141
		return $this;
142
	}
143
144
	/**
145
	 * @return bool
146
	 */
147
	public function isGET() {
148
		return $this->httpMethod == 'GET';
149
	}
150
151
	/**
152
	 * @return bool
153
	 */
154
	public function isPOST() {
155
		return $this->httpMethod == 'POST';
156
	}
157
158
	/**
159
	 * @return bool
160
	 */
161
	public function isPUT() {
162
		return $this->httpMethod == 'PUT';
163
	}
164
165
	/**
166
	 * @return bool
167
	 */
168
	public function isDELETE() {
169
		return $this->httpMethod == 'DELETE';
170
	}
171
172
	/**
173
	 * @return bool
174
	 */
175
	public function isHEAD() {
176
		return $this->httpMethod == 'HEAD';
177
	}
178
179
	/**
180
	 * @param string $body
181
	 * @return SS_HTTPRequest $this
182
	 */
183
	public function setBody($body) {
184
		$this->body = $body;
185
		return $this;
186
	}
187
188
	/**
189
	 * @return null|string
190
	 */
191
	public function getBody() {
192
		return $this->body;
193
	}
194
195
	/**
196
	 * @return array
197
	 */
198
	public function getVars() {
199
		return $this->getVars;
200
	}
201
202
	/**
203
	 * @return array
204
	 */
205
	public function postVars() {
206
		return $this->postVars;
207
	}
208
209
	/**
210
	 * Returns all combined HTTP GET and POST parameters
211
	 * passed into this request. If a parameter with the same
212
	 * name exists in both arrays, the POST value is returned.
213
	 *
214
	 * @return array
215
	 */
216
	public function requestVars() {
217
		return ArrayLib::array_merge_recursive($this->getVars, $this->postVars);
218
	}
219
220
	/**
221
	 * @param string $name
222
	 * @return mixed
223
	 */
224
	public function getVar($name) {
225
		if(isset($this->getVars[$name])) return $this->getVars[$name];
226
	}
227
228
	/**
229
	 * @param string $name
230
	 * @return mixed
231
	 */
232
	public function postVar($name) {
233
		if(isset($this->postVars[$name])) return $this->postVars[$name];
234
	}
235
236
	/**
237
	 * @param string $name
238
	 * @return mixed
239
	 */
240
	public function requestVar($name) {
241
		if(isset($this->postVars[$name])) return $this->postVars[$name];
242
		if(isset($this->getVars[$name])) return $this->getVars[$name];
243
	}
244
245
	/**
246
	 * Returns a possible file extension found in parsing the URL
247
	 * as denoted by a "."-character near the end of the URL.
248
	 * Doesn't necessarily have to belong to an existing file,
249
	 * as extensions can be also used for content-type-switching.
250
	 *
251
	 * @return string
252
	 */
253
	public function getExtension() {
254
		return $this->extension;
255
	}
256
257
	/**
258
	 * Checks if the {@link SS_HTTPRequest->getExtension()} on this request matches one of the more common media types
259
	 * embedded into a webpage - e.g. css, png.
260
	 *
261
	 * This is useful for things like determining wether to display a fully rendered error page or not. Note that the
262
	 * media file types is not at all comprehensive.
263
	 *
264
	 * @return bool
265
	 */
266
	public function isMedia() {
267
		return in_array($this->getExtension(), array('css', 'js', 'jpg', 'jpeg', 'gif', 'png', 'bmp', 'ico'));
268
	}
269
270
	/**
271
	 * Add a HTTP header to the response, replacing any header of the same name.
272
	 *
273
	 * @param string $header Example: "Content-Type"
274
	 * @param string $value Example: "text/xml"
275
	 */
276
	public function addHeader($header, $value) {
277
		$this->headers[$header] = $value;
278
	}
279
280
	/**
281
	 * @return array
282
	 */
283
	public function getHeaders() {
284
		return $this->headers;
285
	}
286
287
	/**
288
	 * Remove an existing HTTP header
289
	 *
290
	 * @param string $header
291
	 * @return mixed
292
	 */
293
	public function getHeader($header) {
294
		return (isset($this->headers[$header])) ? $this->headers[$header] : null;
295
	}
296
297
	/**
298
	 * Remove an existing HTTP header by its name,
299
	 * e.g. "Content-Type".
300
	 *
301
	 * @param string $header
302
	 * @return SS_HTTPRequest $this
303
	 */
304
	public function removeHeader($header) {
305
		if(isset($this->headers[$header])) unset($this->headers[$header]);
306
		return $this;
307
	}
308
309
	/**
310
	 * Returns the URL used to generate the page
311
	 *
312
	 * @param bool $includeGetVars whether or not to include the get parameters\
313
	 * @return string
314
	 */
315
	public function getURL($includeGetVars = false) {
316
		$url = ($this->getExtension()) ? $this->url . '.' . $this->getExtension() : $this->url;
317
318
		if ($includeGetVars) {
319
			// if we don't unset $vars['url'] we end up with /my/url?url=my/url&foo=bar etc
320
321
			$vars = $this->getVars();
322
			unset($vars['url']);
323
324
			if (count($vars)) {
325
				$url .= '?' . http_build_query($vars);
326
			}
327
		}
328
		else if(strpos($url, "?") !== false) {
329
			$url = substr($url, 0, strpos($url, "?"));
330
		}
331
332
		return $url;
333
	}
334
335
	/**
336
	 * Returns true if this request an ajax request,
337
	 * based on custom HTTP ajax added by common JavaScript libraries,
338
	 * or based on an explicit "ajax" request parameter.
339
	 *
340
	 * @return boolean
341
	 */
342
	public function isAjax() {
343
		return (
344
			$this->requestVar('ajax') ||
345
			$this->getHeader('X-Requested-With') && $this->getHeader('X-Requested-With') == "XMLHttpRequest"
346
		);
347
	}
348
349
	/**
350
	 * Enables the existence of a key-value pair in the request to be checked using
351
	 * array syntax, so isset($request['title']) will check for $_POST['title'] and $_GET['title']
352
	 *
353
	 * @param unknown_type $offset
354
	 * @return boolean
355
	 */
356
	public function offsetExists($offset) {
357
		if(isset($this->postVars[$offset])) return true;
358
		if(isset($this->getVars[$offset])) return true;
359
		return false;
360
	}
361
362
	/**
363
	 * Access a request variable using array syntax. eg: $request['title'] instead of $request->postVar('title')
364
	 *
365
	 * @param unknown_type $offset
366
	 * @return unknown
367
	 */
368
	public function offsetGet($offset) {
369
		return $this->requestVar($offset);
370
	}
371
372
	/**
373
	 * @ignore
374
	 */
375
	public function offsetSet($offset, $value) {}
376
377
	/**
378
	 * @ignore
379
	 */
380
	public function offsetUnset($offset) {}
381
382
	/**
383
	 * Construct an SS_HTTPResponse that will deliver a file to the client.
384
	 * Caution: Since it requires $fileData to be passed as binary data (no stream support),
385
	 * it's only advisable to send small files through this method.
386
	 *
387
	 * @static
388
	 * @param $fileData
389
	 * @param $fileName
390
	 * @param null $mimeType
391
	 * @return SS_HTTPResponse
392
	 */
393
	public static function send_file($fileData, $fileName, $mimeType = null) {
394
		if(!$mimeType) {
395
			$mimeType = HTTP::get_mime_type($fileName);
396
		}
397
		$response = new SS_HTTPResponse($fileData);
398
		$response->addHeader("Content-Type", "$mimeType; name=\"" . addslashes($fileName) . "\"");
399
		// Note a IE-only fix that inspects this header in HTTP::add_cache_headers().
400
		$response->addHeader("Content-Disposition", "attachment; filename=" . addslashes($fileName));
401
		$response->addHeader("Content-Length", strlen($fileData));
402
403
		return $response;
404
	}
405
406
	/**
407
	 * Matches a URL pattern
408
	 * The pattern can contain a number of segments, separated by / (and an extension indicated by a .)
409
	 *
410
	 * The parts can be either literals, or, if they start with a $ they are interpreted as variables.
411
	 *  - Literals must be provided in order to match
412
	 *  - $Variables are optional
413
	 *  - However, if you put ! at the end of a variable, then it becomes mandatory.
414
	 *
415
	 * For example:
416
	 *  - admin/crm/list will match admin/crm/$Action/$ID/$OtherID, but it won't match admin/crm/$Action!/$ClassName!
417
	 *
418
	 * The pattern can optionally start with an HTTP method and a space.  For example, "POST $Controller/$Action".
419
	 * This is used to define a rule that only matches on a specific HTTP method.
420
	 *
421
	 * @param $pattern
422
	 * @param bool $shiftOnSuccess
423
	 * @return array|bool
424
	 */
425
	public function match($pattern, $shiftOnSuccess = false) {
426
		// Check if a specific method is required
427
		if(preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
428
			$requiredMethod = $matches[1];
429
			if($requiredMethod != $this->httpMethod) return false;
430
431
			// If we get this far, we can match the URL pattern as usual.
432
			$pattern = $matches[2];
433
		}
434
435
		// Special case for the root URL controller
436
		if(!$pattern) {
437
			return ($this->dirParts == array()) ? array('Matched' => true) : false;
438
		}
439
440
		// Check for the '//' marker that represents the "shifting point"
441
		$doubleSlashPoint = strpos($pattern, '//');
442
		if($doubleSlashPoint !== false) {
443
			$shiftCount = substr_count(substr($pattern,0,$doubleSlashPoint), '/') + 1;
444
			$pattern = str_replace('//', '/', $pattern);
445
			$patternParts = explode('/', $pattern);
446
447
448
		} else {
449
			$patternParts = explode('/', $pattern);
450
			$shiftCount = sizeof($patternParts);
451
		}
452
453
		// Filter out any "empty" matching parts - either from an initial / or a trailing /
454
		$patternParts = array_values(array_filter($patternParts));
455
456
		$arguments = array();
457
		foreach($patternParts as $i => $part) {
458
			$part = trim($part);
459
460
			// Match a variable
461
			if(isset($part[0]) && $part[0] == '$') {
462
				// A variable ending in ! is required
463
				if(substr($part,-1) == '!') {
464
					$varRequired = true;
465
					$varName = substr($part,1,-1);
466
				} else {
467
					$varRequired = false;
468
					$varName = substr($part,1);
469
				}
470
471
				// Fail if a required variable isn't populated
472
				if($varRequired && !isset($this->dirParts[$i])) return false;
473
474
				$arguments[$varName] = isset($this->dirParts[$i]) ? $this->dirParts[$i] : null;
475
				if($part == '$Controller' && (!ClassInfo::exists($arguments['Controller'])
476
						|| !is_subclass_of($arguments['Controller'], 'Controller'))) {
477
478
					return false;
479
				}
480
481
			// Literal parts with extension
482
			} else if(isset($this->dirParts[$i]) && $this->dirParts[$i] . '.' . $this->extension == $part) {
483
				continue;
484
485
			// Literal parts must always be there
486
			} else if(!isset($this->dirParts[$i]) || $this->dirParts[$i] != $part) {
487
				return false;
488
			}
489
490
		}
491
492
		if($shiftOnSuccess) {
493
			$this->shift($shiftCount);
494
			// We keep track of pattern parts that we looked at but didn't shift off.
495
			// This lets us say that we have *parsed* the whole URL even when we haven't *shifted* it all
496
			$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...
497
		}
498
499
		$this->latestParams = $arguments;
500
501
		// Load the arguments that actually have a value into $this->allParams
502
		// This ensures that previous values aren't overridden with blanks
503
		foreach($arguments as $k => $v) {
504
			if($v || !isset($this->allParams[$k])) $this->allParams[$k] = $v;
505
		}
506
507
		if($arguments === array()) $arguments['_matched'] = true;
508
		return $arguments;
509
	}
510
511
	/**
512
	 * @return array
513
	 */
514
	public function allParams() {
515
		return $this->allParams;
516
	}
517
518
	/**
519
	 * Shift all the parameter values down a key space, and return the shifted value.
520
	 *
521
	 * @return string
522
	 */
523
	public function shiftAllParams() {
524
		$keys    = array_keys($this->allParams);
525
		$values  = array_values($this->allParams);
526
		$value   = array_shift($values);
527
528
		// push additional unparsed URL parts onto the parameter stack
529
		if(array_key_exists($this->unshiftedButParsedParts, $this->dirParts)) {
530
			$values[] = $this->dirParts[$this->unshiftedButParsedParts];
531
		}
532
533
		foreach($keys as $position => $key) {
534
			$this->allParams[$key] = isset($values[$position]) ? $values[$position] : null;
535
		}
536
537
		return $value;
538
	}
539
540
	/**
541
	 * @return array
542
	 */
543
	public function latestParams() {
544
		return $this->latestParams;
545
	}
546
547
	/**
548
	 * @param string $name
549
	 * @return string|null
550
	 */
551
	public function latestParam($name) {
552
		if(isset($this->latestParams[$name])) return $this->latestParams[$name];
553
		else return null;
554
	}
555
556
	/**
557
	 * @return array
558
	 */
559
	public function routeParams() {
560
		return $this->routeParams;
561
	}
562
563
	/**
564
	 * @param $params
565
	 * @return SS_HTTPRequest $this
566
	 */
567
	public function setRouteParams($params) {
568
		$this->routeParams = $params;
569
		return $this;
570
	}
571
572
	/**
573
	 * @return array
574
	 */
575
	public function params() {
576
		return array_merge($this->allParams, $this->routeParams);
577
	}
578
579
	/**
580
	 * Finds a named URL parameter (denoted by "$"-prefix in $url_handlers)
581
	 * from the full URL, or a parameter specified in the route table
582
	 *
583
	 * @param string $name
584
	 * @return string Value of the URL parameter (if found)
585
	 */
586
	public function param($name) {
587
		$params = $this->params();
588
		if(isset($params[$name])) return $params[$name];
589
		else return null;
590
	}
591
592
	/**
593
	 * Returns the unparsed part of the original URL
594
	 * separated by commas. This is used by {@link RequestHandler->handleRequest()}
595
	 * to determine if further URL processing is necessary.
596
	 *
597
	 * @return string Partial URL
598
	 */
599
	public function remaining() {
600
		return implode("/", $this->dirParts);
601
	}
602
603
	/**
604
	 * Returns true if this is a URL that will match without shifting off any of the URL.
605
	 * This is used by the request handler to prevent infinite parsing loops.
606
	 *
607
	 * @param $pattern
608
	 * @return bool
609
	 */
610
	public function isEmptyPattern($pattern) {
611
		if(preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
612
			$pattern = $matches[2];
613
		}
614
615
		if(trim($pattern) == "") return true;
616
	}
617
618
	/**
619
	 * Shift one or more parts off the beginning of the URL.
620
	 * If you specify shifting more than 1 item off, then the items will be returned as an array
621
	 *
622
	 * @param int $count Shift Count
623
	 * @return string|Array
624
	 */
625
	public function shift($count = 1) {
626
		$return = array();
627
628
		if($count == 1) return array_shift($this->dirParts);
629
630
		for($i=0;$i<$count;$i++) {
631
			$value = array_shift($this->dirParts);
632
633
			if($value === null) break;
634
635
			$return[] = $value;
636
		}
637
638
		return $return;
639
	}
640
641
	/**
642
	 * Returns true if the URL has been completely parsed.
643
	 * This will respect parsed but unshifted directory parts.
644
	 *
645
	 * @return bool
646
	 */
647
	public function allParsed() {
648
		return sizeof($this->dirParts) <= $this->unshiftedButParsedParts;
649
	}
650
651
	/**
652
	 * Returns the client IP address which
653
	 * originated this request.
654
	 *
655
	 * @return string
656
	 */
657
	public function getIP() {
0 ignored issues
show
Coding Style introduced by
getIP uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
658
		$headerOverrideIP = null;
659
		if(TRUSTED_PROXY) {
660
			$headers = (defined('SS_TRUSTED_PROXY_IP_HEADER')) ? array(SS_TRUSTED_PROXY_IP_HEADER) : null;
661
			if(!$headers) {
662
				// Backwards compatible defaults
663
				$headers = array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR');
664
			}
665
			foreach($headers as $header) {
666
				if(!empty($_SERVER[$header])) {
667
					$headerOverrideIP = $_SERVER[$header];
668
					break;
669
				}
670
			}
671
		}
672
673
		if ($headerOverrideIP && filter_var($headerOverrideIP, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
674
			return $this->getIPFromHeaderValue($headerOverrideIP);
675
		} elseif(isset($_SERVER['REMOTE_ADDR'])) {
676
			return $_SERVER['REMOTE_ADDR'];
677
		} else {
678
			return null;
679
		}
680
	}
681
682
	/**
683
	 * Extract an IP address from a header value that has been obtained. Accepts single IP or comma separated string of
684
	 * IPs
685
	 *
686
	 * @param string $headerValue The value from a trusted header
687
	 * @return string The IP address
688
	 */
689
	protected function getIPFromHeaderValue($headerValue) {
690
		if (strpos($headerValue, ',') !== false) {
691
			//sometimes the IP from a load balancer could be "x.x.x.x, y.y.y.y, z.z.z.z" so we need to find the most
692
			// likely candidate
693
			$ips = explode(',', $headerValue);
694
			foreach ($ips as $ip) {
695
				$ip = trim($ip);
696
				if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {
697
					return $ip;
698
				}
699
			}
700
		}
701
		return $headerValue;
702
	}
703
704
	/**
705
	 * Returns all mimetypes from the HTTP "Accept" header
706
	 * as an array.
707
	 *
708
	 * @param boolean $includeQuality Don't strip away optional "quality indicators", e.g. "application/xml;q=0.9"
709
	 *                                (Default: false)
710
	 * @return array
711
	 */
712
	public function getAcceptMimetypes($includeQuality = false) {
713
		$mimetypes = array();
714
		$mimetypesWithQuality = explode(',',$this->getHeader('Accept'));
715
		foreach($mimetypesWithQuality as $mimetypeWithQuality) {
716
			$mimetypes[] = ($includeQuality) ? $mimetypeWithQuality : preg_replace('/;.*/', '', $mimetypeWithQuality);
717
		}
718
		return $mimetypes;
719
	}
720
721
	/**
722
	 * @return string HTTP method (all uppercase)
723
	 */
724
	public function httpMethod() {
725
		return $this->httpMethod;
726
	}
727
728
	/**
729
	 * Gets the "real" HTTP method for a request.
730
	 *
731
	 * Used to work around browser limitations of form
732
	 * submissions to GET and POST, by overriding the HTTP method
733
	 * with a POST parameter called "_method" for PUT, DELETE, HEAD.
734
	 * Using GET for the "_method" override is not supported,
735
	 * as GET should never carry out state changes.
736
	 * Alternatively you can use a custom HTTP header 'X-HTTP-Method-Override'
737
	 * to override the original method in {@link Director::direct()}.
738
	 * The '_method' POST parameter overrules the custom HTTP header.
739
	 *
740
	 * @param string $origMethod Original HTTP method from the browser request
741
	 * @param array $postVars
742
	 * @return string HTTP method (all uppercase)
743
	 */
744
	public static function detect_method($origMethod, $postVars) {
745
		if(isset($postVars['_method'])) {
746
			if(!in_array(strtoupper($postVars['_method']), array('GET','POST','PUT','DELETE','HEAD'))) {
747
				user_error('Director::direct(): Invalid "_method" parameter', E_USER_ERROR);
748
			}
749
			return strtoupper($postVars['_method']);
750
		} else {
751
			return $origMethod;
752
		}
753
	}
754
}
755