2008-08-09 05:19:54 +02:00
|
|
|
<?php
|
|
|
|
|
2016-08-19 00:51:35 +02:00
|
|
|
namespace SilverStripe\Control;
|
|
|
|
|
2017-06-22 12:50:45 +02:00
|
|
|
use ArrayAccess;
|
|
|
|
use BadMethodCallException;
|
2017-06-25 05:12:29 +02:00
|
|
|
use InvalidArgumentException;
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\Core\ClassInfo;
|
|
|
|
use SilverStripe\ORM\ArrayLib;
|
|
|
|
|
2008-08-09 05:19:54 +02:00
|
|
|
/**
|
2012-09-26 23:34:00 +02:00
|
|
|
* Represents a HTTP-request, including a URL that is tokenised for parsing, and a request method
|
|
|
|
* (GET/POST/PUT/DELETE). This is used by {@link RequestHandler} objects to decide what to do.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
|
|
|
* Caution: objects of this class are immutable, e.g. echo $request['a']; works as expected,
|
2014-03-05 01:21:54 +01:00
|
|
|
* but $request['a'] = '1'; has no effect.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2016-09-09 08:43:05 +02:00
|
|
|
* The intention is that a single HTTPRequest object can be passed from one object to another, each object calling
|
2014-08-15 08:53:05 +02:00
|
|
|
* match() to get the information that they need out of the URL. This is generally handled by
|
2008-10-30 23:03:21 +01:00
|
|
|
* {@link RequestHandler::handleRequest()}.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2012-09-26 23:34:00 +02:00
|
|
|
* @todo Accept X_HTTP_METHOD_OVERRIDE http header and $_REQUEST['_method'] to override request types (useful for
|
|
|
|
* webclients not supporting PUT and DELETE)
|
2008-08-09 05:19:54 +02:00
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
class HTTPRequest implements ArrayAccess
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $url;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The non-extension parts of the passed URL as an array, originally exploded by the "/" separator.
|
|
|
|
* All elements of the URL are loaded in here,
|
|
|
|
* and subsequently popped out of the array by {@link shift()}.
|
|
|
|
* Only use this structure for internal request handling purposes.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $dirParts;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The URL extension (if present)
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $extension;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The HTTP method in all uppercase: GET/PUT/POST/DELETE/HEAD
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $httpMethod;
|
|
|
|
|
2017-06-23 05:47:40 +02:00
|
|
|
/**
|
|
|
|
* The URL scheme in lowercase: http or https
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $scheme;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The client IP address
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $ip;
|
|
|
|
|
2016-11-29 00:31:16 +01:00
|
|
|
/**
|
|
|
|
* Contains alls HTTP GET parameters passed into this request.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $getVars = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Contains alls HTTP POST parameters passed into this request.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $postVars = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* HTTP Headers like "Content-Type: text/xml"
|
|
|
|
*
|
|
|
|
* @see http://en.wikipedia.org/wiki/List_of_HTTP_headers
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $headers = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Raw HTTP body, used by PUT and POST requests.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $body;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Contains an associative array of all
|
|
|
|
* arguments matched in all calls to {@link RequestHandler->handleRequest()}.
|
|
|
|
* It's a "historical record" that's specific to the current call of
|
|
|
|
* {@link handleRequest()}, and is only complete once the "last call" to that method is made.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $allParams = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Contains an associative array of all
|
|
|
|
* arguments matched in the current call from {@link RequestHandler->handleRequest()},
|
|
|
|
* as denoted with a "$"-prefix in the $url_handlers definitions.
|
|
|
|
* Contains different states throughout its lifespan, so just useful
|
|
|
|
* while processed in {@link RequestHandler} and to get the last
|
|
|
|
* processes arguments.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $latestParams = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Contains an associative array of all arguments
|
|
|
|
* explicitly set in the route table for the current request.
|
|
|
|
* Useful for passing generic arguments via custom routes.
|
|
|
|
*
|
|
|
|
* E.g. The "Locale" parameter would be assigned "en_NZ" below
|
|
|
|
*
|
|
|
|
* Director:
|
|
|
|
* rules:
|
|
|
|
* 'en_NZ/$URLSegment!//$Action/$ID/$OtherID':
|
|
|
|
* Controller: 'ModelAsController'
|
|
|
|
* Locale: 'en_NZ'
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $routeParams = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var int
|
|
|
|
*/
|
|
|
|
protected $unshiftedButParsedParts = 0;
|
|
|
|
|
2017-06-22 12:50:45 +02:00
|
|
|
/**
|
|
|
|
* @var Session
|
|
|
|
*/
|
|
|
|
protected $session;
|
|
|
|
|
2016-11-29 00:31:16 +01:00
|
|
|
/**
|
|
|
|
* Construct a HTTPRequest from a URL relative to the site root.
|
|
|
|
*
|
|
|
|
* @param string $httpMethod
|
|
|
|
* @param string $url
|
|
|
|
* @param array $getVars
|
|
|
|
* @param array $postVars
|
|
|
|
* @param string $body
|
|
|
|
*/
|
|
|
|
public function __construct($httpMethod, $url, $getVars = array(), $postVars = array(), $body = null)
|
|
|
|
{
|
|
|
|
$this->httpMethod = strtoupper(self::detect_method($httpMethod, $postVars));
|
|
|
|
$this->setUrl($url);
|
|
|
|
$this->getVars = (array) $getVars;
|
|
|
|
$this->postVars = (array) $postVars;
|
|
|
|
$this->body = $body;
|
2017-06-23 05:47:40 +02:00
|
|
|
$this->scheme = "http";
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Allow the setting of a URL
|
|
|
|
*
|
|
|
|
* This is here so that RootURLController can change the URL of the request
|
|
|
|
* without us loosing all the other info attached (like headers)
|
|
|
|
*
|
|
|
|
* @param string $url The new URL
|
|
|
|
* @return HTTPRequest The updated request
|
|
|
|
*/
|
|
|
|
public function setUrl($url)
|
|
|
|
{
|
|
|
|
$this->url = $url;
|
|
|
|
|
|
|
|
// Normalize URL if its relative (strictly speaking), or has leading slashes
|
|
|
|
if (Director::is_relative_url($url) || preg_match('/^\//', $url)) {
|
|
|
|
$this->url = preg_replace(array('/\/+/','/^\//', '/\/$/'), array('/','',''), $this->url);
|
|
|
|
}
|
|
|
|
if (preg_match('/^(.*)\.([A-Za-z][A-Za-z0-9]*)$/', $this->url, $matches)) {
|
|
|
|
$this->url = $matches[1];
|
|
|
|
$this->extension = $matches[2];
|
|
|
|
}
|
|
|
|
if ($this->url) {
|
|
|
|
$this->dirParts = preg_split('|/+|', $this->url);
|
|
|
|
} else {
|
|
|
|
$this->dirParts = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isGET()
|
|
|
|
{
|
|
|
|
return $this->httpMethod == 'GET';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isPOST()
|
|
|
|
{
|
|
|
|
return $this->httpMethod == 'POST';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isPUT()
|
|
|
|
{
|
|
|
|
return $this->httpMethod == 'PUT';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isDELETE()
|
|
|
|
{
|
|
|
|
return $this->httpMethod == 'DELETE';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isHEAD()
|
|
|
|
{
|
|
|
|
return $this->httpMethod == 'HEAD';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $body
|
|
|
|
* @return HTTPRequest $this
|
|
|
|
*/
|
|
|
|
public function setBody($body)
|
|
|
|
{
|
|
|
|
$this->body = $body;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return null|string
|
|
|
|
*/
|
|
|
|
public function getBody()
|
|
|
|
{
|
|
|
|
return $this->body;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getVars()
|
|
|
|
{
|
|
|
|
return $this->getVars;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function postVars()
|
|
|
|
{
|
|
|
|
return $this->postVars;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns all combined HTTP GET and POST parameters
|
|
|
|
* passed into this request. If a parameter with the same
|
|
|
|
* name exists in both arrays, the POST value is returned.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function requestVars()
|
|
|
|
{
|
|
|
|
return ArrayLib::array_merge_recursive($this->getVars, $this->postVars);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $name
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function getVar($name)
|
|
|
|
{
|
|
|
|
if (isset($this->getVars[$name])) {
|
|
|
|
return $this->getVars[$name];
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $name
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function postVar($name)
|
|
|
|
{
|
|
|
|
if (isset($this->postVars[$name])) {
|
|
|
|
return $this->postVars[$name];
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $name
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function requestVar($name)
|
|
|
|
{
|
|
|
|
if (isset($this->postVars[$name])) {
|
|
|
|
return $this->postVars[$name];
|
|
|
|
}
|
|
|
|
if (isset($this->getVars[$name])) {
|
|
|
|
return $this->getVars[$name];
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a possible file extension found in parsing the URL
|
|
|
|
* as denoted by a "."-character near the end of the URL.
|
|
|
|
* Doesn't necessarily have to belong to an existing file,
|
|
|
|
* as extensions can be also used for content-type-switching.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getExtension()
|
|
|
|
{
|
|
|
|
return $this->extension;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if the {@link HTTPRequest->getExtension()} on this request matches one of the more common media types
|
|
|
|
* embedded into a webpage - e.g. css, png.
|
|
|
|
*
|
2017-05-17 12:09:07 +02:00
|
|
|
* This is useful for things like determining whether to display a fully rendered error page or not. Note that the
|
2016-11-29 00:31:16 +01:00
|
|
|
* media file types is not at all comprehensive.
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isMedia()
|
|
|
|
{
|
|
|
|
return in_array($this->getExtension(), array('css', 'js', 'jpg', 'jpeg', 'gif', 'png', 'bmp', 'ico'));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a HTTP header to the response, replacing any header of the same name.
|
|
|
|
*
|
2017-09-13 17:02:12 +02:00
|
|
|
* @param string $header Example: "content-type"
|
2016-11-29 00:31:16 +01:00
|
|
|
* @param string $value Example: "text/xml"
|
|
|
|
*/
|
|
|
|
public function addHeader($header, $value)
|
|
|
|
{
|
2017-09-13 17:02:12 +02:00
|
|
|
$header = strtolower($header);
|
2016-11-29 00:31:16 +01:00
|
|
|
$this->headers[$header] = $value;
|
2017-09-13 17:02:12 +02:00
|
|
|
return $this;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getHeaders()
|
|
|
|
{
|
|
|
|
return $this->headers;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove an existing HTTP header
|
|
|
|
*
|
|
|
|
* @param string $header
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function getHeader($header)
|
|
|
|
{
|
2017-09-13 17:02:12 +02:00
|
|
|
$header = strtolower($header);
|
2016-11-29 00:31:16 +01:00
|
|
|
return (isset($this->headers[$header])) ? $this->headers[$header] : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove an existing HTTP header by its name,
|
|
|
|
* e.g. "Content-Type".
|
|
|
|
*
|
|
|
|
* @param string $header
|
|
|
|
* @return HTTPRequest $this
|
|
|
|
*/
|
|
|
|
public function removeHeader($header)
|
|
|
|
{
|
2017-09-13 17:02:12 +02:00
|
|
|
$header = strtolower($header);
|
|
|
|
unset($this->headers[$header]);
|
2016-11-29 00:31:16 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the URL used to generate the page
|
|
|
|
*
|
|
|
|
* @param bool $includeGetVars whether or not to include the get parameters\
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getURL($includeGetVars = false)
|
|
|
|
{
|
|
|
|
$url = ($this->getExtension()) ? $this->url . '.' . $this->getExtension() : $this->url;
|
|
|
|
|
|
|
|
if ($includeGetVars) {
|
|
|
|
$vars = $this->getVars();
|
|
|
|
if (count($vars)) {
|
|
|
|
$url .= '?' . http_build_query($vars);
|
|
|
|
}
|
|
|
|
} elseif (strpos($url, "?") !== false) {
|
|
|
|
$url = substr($url, 0, strpos($url, "?"));
|
|
|
|
}
|
|
|
|
|
|
|
|
return $url;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if this request an ajax request,
|
|
|
|
* based on custom HTTP ajax added by common JavaScript libraries,
|
|
|
|
* or based on an explicit "ajax" request parameter.
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function isAjax()
|
|
|
|
{
|
|
|
|
return (
|
|
|
|
$this->requestVar('ajax') ||
|
2017-09-13 17:02:12 +02:00
|
|
|
$this->getHeader('x-requested-with') === "XMLHttpRequest"
|
2016-11-29 00:31:16 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enables the existence of a key-value pair in the request to be checked using
|
|
|
|
* array syntax, so isset($request['title']) will check for $_POST['title'] and $_GET['title']
|
|
|
|
*
|
|
|
|
* @param string $offset
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function offsetExists($offset)
|
|
|
|
{
|
|
|
|
return isset($this->postVars[$offset]) || isset($this->getVars[$offset]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Access a request variable using array syntax. eg: $request['title'] instead of $request->postVar('title')
|
|
|
|
*
|
|
|
|
* @param string $offset
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function offsetGet($offset)
|
|
|
|
{
|
|
|
|
return $this->requestVar($offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function offsetSet($offset, $value)
|
|
|
|
{
|
2017-06-22 12:50:45 +02:00
|
|
|
$this->getVars[$offset] = $value;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function offsetUnset($offset)
|
|
|
|
{
|
2017-06-22 12:50:45 +02:00
|
|
|
unset($this->getVars[$offset]);
|
|
|
|
unset($this->postVars[$offset]);
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct an HTTPResponse that will deliver a file to the client.
|
|
|
|
* Caution: Since it requires $fileData to be passed as binary data (no stream support),
|
|
|
|
* it's only advisable to send small files through this method.
|
2019-10-22 04:18:03 +02:00
|
|
|
* this function needs to called inside the controllers response, eg
|
|
|
|
* $this->setResponse(HTTPRequest::send_file('the content', 'filename.txt'));
|
2016-11-29 00:31:16 +01:00
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @param $fileData
|
|
|
|
* @param $fileName
|
|
|
|
* @param null $mimeType
|
|
|
|
* @return HTTPResponse
|
|
|
|
*/
|
|
|
|
public static function send_file($fileData, $fileName, $mimeType = null)
|
|
|
|
{
|
|
|
|
if (!$mimeType) {
|
|
|
|
$mimeType = HTTP::get_mime_type($fileName);
|
|
|
|
}
|
|
|
|
$response = new HTTPResponse($fileData);
|
2017-09-13 17:02:12 +02:00
|
|
|
$response->addHeader("content-type", "$mimeType; name=\"" . addslashes($fileName) . "\"");
|
2016-11-29 00:31:16 +01:00
|
|
|
// Note a IE-only fix that inspects this header in HTTP::add_cache_headers().
|
2017-09-13 17:02:12 +02:00
|
|
|
$response->addHeader("content-disposition", "attachment; filename=\"" . addslashes($fileName) . "\"");
|
|
|
|
$response->addHeader("content-length", strlen($fileData));
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Matches a URL pattern
|
|
|
|
* The pattern can contain a number of segments, separated by / (and an extension indicated by a .)
|
|
|
|
*
|
|
|
|
* The parts can be either literals, or, if they start with a $ they are interpreted as variables.
|
|
|
|
* - Literals must be provided in order to match
|
|
|
|
* - $Variables are optional
|
|
|
|
* - However, if you put ! at the end of a variable, then it becomes mandatory.
|
|
|
|
*
|
|
|
|
* For example:
|
|
|
|
* - admin/crm/list will match admin/crm/$Action/$ID/$OtherID, but it won't match admin/crm/$Action!/$ClassName!
|
|
|
|
*
|
|
|
|
* The pattern can optionally start with an HTTP method and a space. For example, "POST $Controller/$Action".
|
|
|
|
* This is used to define a rule that only matches on a specific HTTP method.
|
|
|
|
*
|
|
|
|
* @param $pattern
|
|
|
|
* @param bool $shiftOnSuccess
|
|
|
|
* @return array|bool
|
|
|
|
*/
|
|
|
|
public function match($pattern, $shiftOnSuccess = false)
|
|
|
|
{
|
|
|
|
// Check if a specific method is required
|
|
|
|
if (preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
|
|
|
|
$requiredMethod = $matches[1];
|
|
|
|
if ($requiredMethod != $this->httpMethod) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we get this far, we can match the URL pattern as usual.
|
|
|
|
$pattern = $matches[2];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Special case for the root URL controller
|
|
|
|
if (!$pattern) {
|
|
|
|
return ($this->dirParts == array()) ? array('Matched' => true) : false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for the '//' marker that represents the "shifting point"
|
|
|
|
$doubleSlashPoint = strpos($pattern, '//');
|
|
|
|
if ($doubleSlashPoint !== false) {
|
|
|
|
$shiftCount = substr_count(substr($pattern, 0, $doubleSlashPoint), '/') + 1;
|
|
|
|
$pattern = str_replace('//', '/', $pattern);
|
|
|
|
$patternParts = explode('/', $pattern);
|
|
|
|
} else {
|
|
|
|
$patternParts = explode('/', $pattern);
|
|
|
|
$shiftCount = sizeof($patternParts);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filter out any "empty" matching parts - either from an initial / or a trailing /
|
|
|
|
$patternParts = array_values(array_filter($patternParts));
|
|
|
|
|
|
|
|
$arguments = array();
|
|
|
|
foreach ($patternParts as $i => $part) {
|
|
|
|
$part = trim($part);
|
|
|
|
|
|
|
|
// Match a variable
|
|
|
|
if (isset($part[0]) && $part[0] == '$') {
|
|
|
|
// A variable ending in ! is required
|
|
|
|
if (substr($part, -1) == '!') {
|
|
|
|
$varRequired = true;
|
|
|
|
$varName = substr($part, 1, -1);
|
|
|
|
} else {
|
|
|
|
$varRequired = false;
|
|
|
|
$varName = substr($part, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fail if a required variable isn't populated
|
|
|
|
if ($varRequired && !isset($this->dirParts[$i])) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @skipUpgrade */
|
|
|
|
$key = "Controller";
|
|
|
|
$arguments[$varName] = isset($this->dirParts[$i]) ? $this->dirParts[$i] : null;
|
|
|
|
if ($part == '$Controller'
|
|
|
|
&& (
|
|
|
|
!ClassInfo::exists($arguments[$key])
|
|
|
|
|| !is_subclass_of($arguments[$key], 'SilverStripe\\Control\\Controller')
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Literal parts with extension
|
|
|
|
} elseif (isset($this->dirParts[$i]) && $this->dirParts[$i] . '.' . $this->extension == $part) {
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Literal parts must always be there
|
|
|
|
} elseif (!isset($this->dirParts[$i]) || $this->dirParts[$i] != $part) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($shiftOnSuccess) {
|
|
|
|
$this->shift($shiftCount);
|
|
|
|
// We keep track of pattern parts that we looked at but didn't shift off.
|
|
|
|
// This lets us say that we have *parsed* the whole URL even when we haven't *shifted* it all
|
|
|
|
$this->unshiftedButParsedParts = sizeof($patternParts) - $shiftCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->latestParams = $arguments;
|
|
|
|
|
|
|
|
// Load the arguments that actually have a value into $this->allParams
|
|
|
|
// This ensures that previous values aren't overridden with blanks
|
|
|
|
foreach ($arguments as $k => $v) {
|
|
|
|
if ($v || !isset($this->allParams[$k])) {
|
|
|
|
$this->allParams[$k] = $v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($arguments === array()) {
|
|
|
|
$arguments['_matched'] = true;
|
|
|
|
}
|
|
|
|
return $arguments;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function allParams()
|
|
|
|
{
|
|
|
|
return $this->allParams;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shift all the parameter values down a key space, and return the shifted value.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function shiftAllParams()
|
|
|
|
{
|
|
|
|
$keys = array_keys($this->allParams);
|
|
|
|
$values = array_values($this->allParams);
|
|
|
|
$value = array_shift($values);
|
|
|
|
|
|
|
|
// push additional unparsed URL parts onto the parameter stack
|
|
|
|
if (array_key_exists($this->unshiftedButParsedParts, $this->dirParts)) {
|
|
|
|
$values[] = $this->dirParts[$this->unshiftedButParsedParts];
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($keys as $position => $key) {
|
|
|
|
$this->allParams[$key] = isset($values[$position]) ? $values[$position] : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function latestParams()
|
|
|
|
{
|
|
|
|
return $this->latestParams;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $name
|
|
|
|
* @return string|null
|
|
|
|
*/
|
|
|
|
public function latestParam($name)
|
|
|
|
{
|
|
|
|
if (isset($this->latestParams[$name])) {
|
|
|
|
return $this->latestParams[$name];
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function routeParams()
|
|
|
|
{
|
|
|
|
return $this->routeParams;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $params
|
|
|
|
* @return HTTPRequest $this
|
|
|
|
*/
|
|
|
|
public function setRouteParams($params)
|
|
|
|
{
|
|
|
|
$this->routeParams = $params;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function params()
|
|
|
|
{
|
|
|
|
return array_merge($this->allParams, $this->routeParams);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Finds a named URL parameter (denoted by "$"-prefix in $url_handlers)
|
|
|
|
* from the full URL, or a parameter specified in the route table
|
|
|
|
*
|
|
|
|
* @param string $name
|
|
|
|
* @return string Value of the URL parameter (if found)
|
|
|
|
*/
|
|
|
|
public function param($name)
|
|
|
|
{
|
|
|
|
$params = $this->params();
|
|
|
|
if (isset($params[$name])) {
|
|
|
|
return $params[$name];
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the unparsed part of the original URL
|
|
|
|
* separated by commas. This is used by {@link RequestHandler->handleRequest()}
|
|
|
|
* to determine if further URL processing is necessary.
|
|
|
|
*
|
|
|
|
* @return string Partial URL
|
|
|
|
*/
|
|
|
|
public function remaining()
|
|
|
|
{
|
|
|
|
return implode("/", $this->dirParts);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if this is a URL that will match without shifting off any of the URL.
|
|
|
|
* This is used by the request handler to prevent infinite parsing loops.
|
|
|
|
*
|
|
|
|
* @param string $pattern
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isEmptyPattern($pattern)
|
|
|
|
{
|
|
|
|
if (preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
|
|
|
|
$pattern = $matches[2];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (trim($pattern) == "") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shift one or more parts off the beginning of the URL.
|
|
|
|
* If you specify shifting more than 1 item off, then the items will be returned as an array
|
|
|
|
*
|
|
|
|
* @param int $count Shift Count
|
|
|
|
* @return string|array
|
|
|
|
*/
|
|
|
|
public function shift($count = 1)
|
|
|
|
{
|
|
|
|
$return = array();
|
|
|
|
|
|
|
|
if ($count == 1) {
|
|
|
|
return array_shift($this->dirParts);
|
|
|
|
}
|
|
|
|
|
|
|
|
for ($i=0; $i<$count; $i++) {
|
|
|
|
$value = array_shift($this->dirParts);
|
|
|
|
|
|
|
|
if ($value === null) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
$return[] = $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if the URL has been completely parsed.
|
|
|
|
* This will respect parsed but unshifted directory parts.
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function allParsed()
|
|
|
|
{
|
|
|
|
return sizeof($this->dirParts) <= $this->unshiftedButParsedParts;
|
|
|
|
}
|
|
|
|
|
2017-09-27 15:44:38 +02:00
|
|
|
/**
|
|
|
|
* @return string Return the host from the request
|
|
|
|
*/
|
|
|
|
public function getHost()
|
|
|
|
{
|
|
|
|
return $this->getHeader('host');
|
|
|
|
}
|
|
|
|
|
2016-11-29 00:31:16 +01:00
|
|
|
/**
|
2017-06-23 05:47:40 +02:00
|
|
|
* Returns the client IP address which originated this request.
|
2016-11-29 00:31:16 +01:00
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getIP()
|
|
|
|
{
|
2017-06-23 05:47:40 +02:00
|
|
|
return $this->ip;
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2017-06-23 05:47:40 +02:00
|
|
|
/**
|
|
|
|
* Sets the client IP address which originated this request.
|
2017-06-25 05:12:29 +02:00
|
|
|
* Use setIPFromHeaderValue if assigning from header value.
|
2017-06-23 05:47:40 +02:00
|
|
|
*
|
|
|
|
* @param $ip string
|
2017-06-25 05:12:29 +02:00
|
|
|
* @return $this
|
2017-06-23 05:47:40 +02:00
|
|
|
*/
|
|
|
|
public function setIP($ip)
|
|
|
|
{
|
2017-06-25 05:12:29 +02:00
|
|
|
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
|
|
|
|
throw new InvalidArgumentException("Invalid ip $ip");
|
|
|
|
}
|
2017-06-23 05:47:40 +02:00
|
|
|
$this->ip = $ip;
|
2017-06-25 05:12:29 +02:00
|
|
|
return $this;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns all mimetypes from the HTTP "Accept" header
|
|
|
|
* as an array.
|
|
|
|
*
|
|
|
|
* @param boolean $includeQuality Don't strip away optional "quality indicators", e.g. "application/xml;q=0.9"
|
|
|
|
* (Default: false)
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getAcceptMimetypes($includeQuality = false)
|
|
|
|
{
|
|
|
|
$mimetypes = array();
|
2017-09-13 17:02:12 +02:00
|
|
|
$mimetypesWithQuality = preg_split('#\s*,\s*#', $this->getHeader('accept'));
|
2016-11-29 00:31:16 +01:00
|
|
|
foreach ($mimetypesWithQuality as $mimetypeWithQuality) {
|
|
|
|
$mimetypes[] = ($includeQuality) ? $mimetypeWithQuality : preg_replace('/;.*/', '', $mimetypeWithQuality);
|
|
|
|
}
|
|
|
|
return $mimetypes;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string HTTP method (all uppercase)
|
|
|
|
*/
|
|
|
|
public function httpMethod()
|
|
|
|
{
|
|
|
|
return $this->httpMethod;
|
|
|
|
}
|
|
|
|
|
2017-06-23 05:47:40 +02:00
|
|
|
/**
|
|
|
|
* Return the URL scheme (e.g. "http" or "https").
|
|
|
|
* Equivalent to PSR-7 getUri()->getScheme()
|
2017-06-25 05:12:29 +02:00
|
|
|
*
|
2017-06-23 05:47:40 +02:00
|
|
|
* @return string
|
|
|
|
*/
|
2017-06-25 05:12:29 +02:00
|
|
|
public function getScheme()
|
|
|
|
{
|
2017-06-23 05:47:40 +02:00
|
|
|
return $this->scheme;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the URL scheme (e.g. "http" or "https").
|
|
|
|
* Equivalent to PSR-7 getUri()->getScheme(),
|
|
|
|
*
|
|
|
|
* @param string $scheme
|
2017-06-25 05:12:29 +02:00
|
|
|
* @return $this
|
2017-06-23 05:47:40 +02:00
|
|
|
*/
|
2017-06-25 05:12:29 +02:00
|
|
|
public function setScheme($scheme)
|
|
|
|
{
|
2017-06-23 05:47:40 +02:00
|
|
|
$this->scheme = $scheme;
|
2017-06-25 05:12:29 +02:00
|
|
|
return $this;
|
2017-06-23 05:47:40 +02:00
|
|
|
}
|
|
|
|
|
2016-11-29 00:31:16 +01:00
|
|
|
/**
|
|
|
|
* Gets the "real" HTTP method for a request.
|
|
|
|
*
|
|
|
|
* Used to work around browser limitations of form
|
|
|
|
* submissions to GET and POST, by overriding the HTTP method
|
|
|
|
* with a POST parameter called "_method" for PUT, DELETE, HEAD.
|
|
|
|
* Using GET for the "_method" override is not supported,
|
|
|
|
* as GET should never carry out state changes.
|
|
|
|
* Alternatively you can use a custom HTTP header 'X-HTTP-Method-Override'
|
2017-06-23 05:12:15 +02:00
|
|
|
* to override the original method.
|
2016-11-29 00:31:16 +01:00
|
|
|
* The '_method' POST parameter overrules the custom HTTP header.
|
|
|
|
*
|
|
|
|
* @param string $origMethod Original HTTP method from the browser request
|
|
|
|
* @param array $postVars
|
|
|
|
* @return string HTTP method (all uppercase)
|
|
|
|
*/
|
|
|
|
public static function detect_method($origMethod, $postVars)
|
|
|
|
{
|
|
|
|
if (isset($postVars['_method'])) {
|
|
|
|
if (!in_array(strtoupper($postVars['_method']), array('GET','POST','PUT','DELETE','HEAD'))) {
|
2017-06-23 05:12:15 +02:00
|
|
|
user_error('HTTPRequest::detect_method(): Invalid "_method" parameter', E_USER_ERROR);
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
return strtoupper($postVars['_method']);
|
|
|
|
} else {
|
|
|
|
return $origMethod;
|
|
|
|
}
|
|
|
|
}
|
2017-06-22 12:50:45 +02:00
|
|
|
|
2019-08-02 01:29:23 +02:00
|
|
|
/**
|
|
|
|
* Determines whether the request has a session
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function hasSession(): bool
|
|
|
|
{
|
|
|
|
return !empty($this->session);
|
|
|
|
}
|
|
|
|
|
2017-06-22 12:50:45 +02:00
|
|
|
/**
|
|
|
|
* @return Session
|
|
|
|
*/
|
|
|
|
public function getSession()
|
|
|
|
{
|
2019-08-02 01:29:23 +02:00
|
|
|
if (!$this->hasSession()) {
|
2017-06-22 12:50:45 +02:00
|
|
|
throw new BadMethodCallException("No session available for this HTTPRequest");
|
|
|
|
}
|
|
|
|
return $this->session;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Session $session
|
|
|
|
* @return $this
|
|
|
|
*/
|
|
|
|
public function setSession(Session $session)
|
|
|
|
{
|
|
|
|
$this->session = $session;
|
|
|
|
return $this;
|
|
|
|
}
|
2009-10-11 02:07:02 +02:00
|
|
|
}
|