Completed
Pull Request — 3.1 (#5200)
by Daniel
12:18
created

SS_HTTPRequest::postVar()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 3
rs 10
cc 2
eloc 2
nc 2
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
	 *
385
	 * @static
386
	 * @param $fileData
387
	 * @param $fileName
388
	 * @param null $mimeType
389
	 * @return SS_HTTPResponse
390
	 */
391
	public static function send_file($fileData, $fileName, $mimeType = null) {
392
		if(!$mimeType) {
393
			$mimeType = HTTP::get_mime_type($fileName);
394
		}
395
		$response = new SS_HTTPResponse($fileData);
396
		$response->addHeader("Content-Type", "$mimeType; name=\"" . addslashes($fileName) . "\"");
397
		// Note a IE-only fix that inspects this header in HTTP::add_cache_headers().
398
		$response->addHeader("Content-Disposition", "attachment; filename=" . addslashes($fileName));
399
		$response->addHeader("Content-Length", strlen($fileData));
400
		
401
		return $response;
402
	}
403
	
404
	/**
405
	 * Matches a URL pattern
406
	 * The pattern can contain a number of segments, separated by / (and an extension indicated by a .)
407
	 * 
408
	 * The parts can be either literals, or, if they start with a $ they are interpreted as variables.
409
	 *  - Literals must be provided in order to match
410
	 *  - $Variables are optional
411
	 *  - However, if you put ! at the end of a variable, then it becomes mandatory.
412
	 * 
413
	 * For example:
414
	 *  - admin/crm/list will match admin/crm/$Action/$ID/$OtherID, but it won't match admin/crm/$Action!/$ClassName!
415
	 * 
416
	 * The pattern can optionally start with an HTTP method and a space.  For example, "POST $Controller/$Action".
417
	 * This is used to define a rule that only matches on a specific HTTP method.
418
	 *
419
	 * @param $pattern
420
	 * @param bool $shiftOnSuccess
421
	 * @return array|bool
422
	 */
423
	public function match($pattern, $shiftOnSuccess = false) {
424
		// Check if a specific method is required
425
		if(preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
426
			$requiredMethod = $matches[1];
427
			if($requiredMethod != $this->httpMethod) return false;
428
			
429
			// If we get this far, we can match the URL pattern as usual.
430
			$pattern = $matches[2];
431
		}
432
		
433
		// Special case for the root URL controller
434
		if(!$pattern) {
435
			return ($this->dirParts == array()) ? array('Matched' => true) : false;
436
		}
437
438
		// Check for the '//' marker that represents the "shifting point"
439
		$doubleSlashPoint = strpos($pattern, '//');
440
		if($doubleSlashPoint !== false) {
441
			$shiftCount = substr_count(substr($pattern,0,$doubleSlashPoint), '/') + 1;
442
			$pattern = str_replace('//', '/', $pattern);
443
			$patternParts = explode('/', $pattern);
444
			
445
			
446
		} else {
447
			$patternParts = explode('/', $pattern);
448
			$shiftCount = sizeof($patternParts);
449
		}
450
451
		// Filter out any "empty" matching parts - either from an initial / or a trailing /
452
		$patternParts = array_values(array_filter($patternParts));
453
454
		$arguments = array();
455
		foreach($patternParts as $i => $part) {
456
			$part = trim($part);
457
458
			// Match a variable
459
			if(isset($part[0]) && $part[0] == '$') {
460
				// A variable ending in ! is required
461
				if(substr($part,-1) == '!') {
462
					$varRequired = true;
463
					$varName = substr($part,1,-1);
464
				} else {
465
					$varRequired = false;
466
					$varName = substr($part,1);
467
				}
468
				
469
				// Fail if a required variable isn't populated
470
				if($varRequired && !isset($this->dirParts[$i])) return false;
471
				
472
				$arguments[$varName] = isset($this->dirParts[$i]) ? $this->dirParts[$i] : null;
473
				if($part == '$Controller' && (!ClassInfo::exists($arguments['Controller'])
474
						|| !is_subclass_of($arguments['Controller'], 'Controller'))) {
475
					
476
					return false;
477
				}
478
				
479
			// Literal parts with extension
480
			} else if(isset($this->dirParts[$i]) && $this->dirParts[$i] . '.' . $this->extension == $part) {
481
				continue;
482
				
483
			// Literal parts must always be there
484
			} else if(!isset($this->dirParts[$i]) || $this->dirParts[$i] != $part) {
485
				return false;
486
			}
487
			
488
		}
489
490
		if($shiftOnSuccess) {
491
			$this->shift($shiftCount);
492
			// We keep track of pattern parts that we looked at but didn't shift off.
493
			// This lets us say that we have *parsed* the whole URL even when we haven't *shifted* it all
494
			$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...
495
		}
496
497
		$this->latestParams = $arguments;
498
		
499
		// Load the arguments that actually have a value into $this->allParams
500
		// This ensures that previous values aren't overridden with blanks
501
		foreach($arguments as $k => $v) {
502
			if($v || !isset($this->allParams[$k])) $this->allParams[$k] = $v;
503
		}
504
505
		if($arguments === array()) $arguments['_matched'] = true;
506
		return $arguments;
507
	}
508
509
	/**
510
	 * @return array
511
	 */
512
	public function allParams() {
513
		return $this->allParams;
514
	}
515
	
516
	/**
517
	 * Shift all the parameter values down a key space, and return the shifted value.
518
	 *
519
	 * @return string
520
	 */
521
	public function shiftAllParams() {
522
		$keys    = array_keys($this->allParams);
523
		$values  = array_values($this->allParams);
524
		$value   = array_shift($values);
525
526
		// push additional unparsed URL parts onto the parameter stack
527
		if(array_key_exists($this->unshiftedButParsedParts, $this->dirParts)) {
528
			$values[] = $this->dirParts[$this->unshiftedButParsedParts];
529
		}
530
531
		foreach($keys as $position => $key) {
532
			$this->allParams[$key] = isset($values[$position]) ? $values[$position] : null;
533
		}
534
535
		return $value;
536
	}
537
538
	/**
539
	 * @return array
540
	 */
541
	public function latestParams() {
542
		return $this->latestParams;
543
	}
544
545
	/**
546
	 * @param string $name
547
	 * @return string|null
548
	 */
549
	public function latestParam($name) {
550
		if(isset($this->latestParams[$name])) return $this->latestParams[$name];
551
		else return null;
552
	}
553
554
	/**
555
	 * @return array
556
	 */
557
	public function routeParams() {
558
		return $this->routeParams;
559
	}
560
561
	/**
562
	 * @param $params
563
	 * @return SS_HTTPRequest $this
564
	 */
565
	public function setRouteParams($params) {
566
		$this->routeParams = $params;
567
		return $this;
568
	}
569
570
	/**
571
	 * @return array
572
	 */
573
	public function params() {
574
		return array_merge($this->allParams, $this->routeParams);
575
	}
576
	
577
	/**
578
	 * Finds a named URL parameter (denoted by "$"-prefix in $url_handlers)
579
	 * from the full URL, or a parameter specified in the route table
580
	 * 
581
	 * @param string $name
582
	 * @return string Value of the URL parameter (if found)
583
	 */
584
	public function param($name) {
585
		$params = $this->params();
586
		if(isset($params[$name])) return $params[$name];
587
		else return null;
588
	}
589
	
590
	/**
591
	 * Returns the unparsed part of the original URL
592
	 * separated by commas. This is used by {@link RequestHandler->handleRequest()}
593
	 * to determine if further URL processing is necessary.
594
	 * 
595
	 * @return string Partial URL
596
	 */
597
	public function remaining() {
598
		return implode("/", $this->dirParts);
599
	}
600
	
601
	/**
602
	 * Returns true if this is a URL that will match without shifting off any of the URL.
603
	 * This is used by the request handler to prevent infinite parsing loops.
604
	 *
605
	 * @param $pattern
606
	 * @return bool
607
	 */
608
	public function isEmptyPattern($pattern) {
609
		if(preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
610
			$pattern = $matches[2];
611
		}
612
		
613
		if(trim($pattern) == "") return true;
614
	}
615
	
616
	/**
617
	 * Shift one or more parts off the beginning of the URL.
618
	 * If you specify shifting more than 1 item off, then the items will be returned as an array
619
	 *
620
	 * @param int $count Shift Count
621
	 * @return String|Array
622
	 */
623
	public function shift($count = 1) {
624
		$return = array();
625
		
626
		if($count == 1) return array_shift($this->dirParts);
627
		
628
		for($i=0;$i<$count;$i++) {
629
			$value = array_shift($this->dirParts);
630
			
631
			if($value === null) break;
632
			
633
			$return[] = $value;
634
		}
635
		
636
		return $return;
637
	}
638
639
	/**
640
	 * Returns true if the URL has been completely parsed.
641
	 * This will respect parsed but unshifted directory parts.
642
	 *
643
	 * @return bool
644
	 */
645
	public function allParsed() {
646
		return sizeof($this->dirParts) <= $this->unshiftedButParsedParts;
647
	}
648
	
649
	/**
650
	 * Returns the client IP address which
651
	 * originated this request.
652
	 *
653
	 * @return string
654
	 */
655
	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...
656
		$headerOverrideIP = null;
657
		if(TRUSTED_PROXY) {
658
			$headers = (defined('SS_TRUSTED_PROXY_IP_HEADER')) ? array(SS_TRUSTED_PROXY_IP_HEADER) : null;
659
			if(!$headers) {
660
				// Backwards compatible defaults
661
				$headers = array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR');
662
			}
663
			foreach($headers as $header) {
664
				if(!empty($_SERVER[$header])) {
665
					$headerOverrideIP = $_SERVER[$header];
666
					break;
667
				}
668
			}
669
		}
670
671
		if ($headerOverrideIP) {
672
			return $this->getIPFromHeaderValue($headerOverrideIP);
673
		} elseif(isset($_SERVER['REMOTE_ADDR'])) {
674
			return $_SERVER['REMOTE_ADDR'];
675
		} else {
676
			return null;
677
		}
678
	}
679
680
	/**
681
	 * Extract an IP address from a header value that has been obtained. Accepts single IP or comma separated string of
682
	 * IPs
683
	 *
684
	 * @param string $headerValue The value from a trusted header
685
	 * @return string The IP address
686
	 */
687
	protected function getIPFromHeaderValue($headerValue) {
688
		if (strpos($headerValue, ',') !== false) {
689
			//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
690
			// likely candidate
691
			$ips = explode(',', $headerValue);
692
			foreach ($ips as $ip) {
693
				$ip = trim($ip);
694
				if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {
695
					return $ip;
696
				}
697
			}
698
		}
699
		return $headerValue;
700
	}
701
702
	/**
703
	 * Returns all mimetypes from the HTTP "Accept" header
704
	 * as an array.
705
	 * 
706
	 * @param boolean $includeQuality Don't strip away optional "quality indicators", e.g. "application/xml;q=0.9"
707
	 *                                (Default: false)
708
	 * @return array
709
	 */
710
	public function getAcceptMimetypes($includeQuality = false) {
711
		$mimetypes = array();
712
		$mimetypesWithQuality = explode(',',$this->getHeader('Accept'));
713
		foreach($mimetypesWithQuality as $mimetypeWithQuality) {
714
			$mimetypes[] = ($includeQuality) ? $mimetypeWithQuality : preg_replace('/;.*/', '', $mimetypeWithQuality);
715
		}
716
		return $mimetypes;
717
	}
718
	
719
	/**
720
	 * @return string HTTP method (all uppercase)
721
	 */
722
	public function httpMethod() {
723
		return $this->httpMethod;
724
	}
725
	
726
	/**
727
	 * Gets the "real" HTTP method for a request.
728
	 * 
729
	 * Used to work around browser limitations of form
730
	 * submissions to GET and POST, by overriding the HTTP method
731
	 * with a POST parameter called "_method" for PUT, DELETE, HEAD.
732
	 * Using GET for the "_method" override is not supported,
733
	 * as GET should never carry out state changes.
734
	 * Alternatively you can use a custom HTTP header 'X-HTTP-Method-Override'
735
	 * to override the original method in {@link Director::direct()}. 
736
	 * The '_method' POST parameter overrules the custom HTTP header.
737
	 *
738
	 * @param string $origMethod Original HTTP method from the browser request
739
	 * @param array $postVars
740
	 * @return string HTTP method (all uppercase)
741
	 */
742
	public static function detect_method($origMethod, $postVars) {
743
		if(isset($postVars['_method'])) {
744
			if(!in_array(strtoupper($postVars['_method']), array('GET','POST','PUT','DELETE','HEAD'))) {
745
				user_error('Director::direct(): Invalid "_method" parameter', E_USER_ERROR);
746
			}
747
			return strtoupper($postVars['_method']);
748
		} else {
749
			return $origMethod;
750
		}
751
	}
752
}
753