First Local Commit - After Clean up.

Signed-off-by: Rick Hays <rhays@haysgang.com>
This commit is contained in:
2019-12-02 14:54:38 -06:00
commit 10412ab7f6
486 changed files with 123242 additions and 0 deletions

254
system/HTTP/CLIRequest.php Normal file
View File

@@ -0,0 +1,254 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP;
use Config\App;
/**
* Class CLIRequest
*
* Represents a request from the command-line. Provides additional
* tools to interact with that request since CLI requests are not
* static like HTTP requests might be.
*
* Portions of this code were initially from the FuelPHP Framework,
* version 1.7.x, and used here under the MIT license they were
* originally made available under.
*
* http://fuelphp.com
*
* @package CodeIgniter\HTTP
*/
class CLIRequest extends Request
{
/**
* Stores the segments of our cli "URI" command.
*
* @var array
*/
protected $segments = [];
/**
* Command line options and their values.
*
* @var array
*/
protected $options = [];
/**
* Set the expected HTTP verb
*
* @var string
*/
protected $method = 'cli';
//--------------------------------------------------------------------
/**
* Constructor
*
* @param App $config
*/
public function __construct(App $config)
{
parent::__construct($config);
// Don't terminate the script when the cli's tty goes away
ignore_user_abort(true);
$this->parseCommand();
}
//--------------------------------------------------------------------
/**
* Returns the "path" of the request script so that it can be used
* in routing to the appropriate controller/method.
*
* The path is determined by treating the command line arguments
* as if it were a URL - up until we hit our first option.
*
* Example:
* php index.php users 21 profile -foo bar
*
* // Routes to /users/21/profile (index is removed for routing sake)
* // with the option foo = bar.
*
* @return string
*/
public function getPath(): string
{
$path = implode('/', $this->segments);
return empty($path) ? '' : $path;
}
//--------------------------------------------------------------------
/**
* Returns an associative array of all CLI options found, with
* their values.
*
* @return array
*/
public function getOptions(): array
{
return $this->options;
}
//--------------------------------------------------------------------
/**
* Returns the path segments.
*
* @return array
*/
public function getSegments(): array
{
return $this->segments;
}
//--------------------------------------------------------------------
/**
* Returns the value for a single CLI option that was passed in.
*
* @param string $key
*
* @return string|null
*/
public function getOption(string $key)
{
return $this->options[$key] ?? null;
}
//--------------------------------------------------------------------
/**
* Returns the options as a string, suitable for passing along on
* the CLI to other commands.
*
* Example:
* $options = [
* 'foo' => 'bar',
* 'baz' => 'queue some stuff'
* ];
*
* getOptionString() = '-foo bar -baz "queue some stuff"'
*
* @return string
*/
public function getOptionString(): string
{
if (empty($this->options))
{
return '';
}
$out = '';
foreach ($this->options as $name => $value)
{
// If there's a space, we need to group
// so it will pass correctly.
if (strpos($value, ' ') !== false)
{
$value = '"' . $value . '"';
}
$out .= "-{$name} $value ";
}
return trim($out);
}
//--------------------------------------------------------------------
/**
* Parses the command line it was called from and collects all options
* and valid segments.
*
* NOTE: I tried to use getopt but had it fail occasionally to find
* any options, where argv has always had our back.
*/
protected function parseCommand()
{
// Since we're building the options ourselves,
// we stop adding it to the segments array once
// we have found the first dash.
$options_found = false;
$argc = $this->getServer('argc', FILTER_SANITIZE_NUMBER_INT);
$argv = $this->getServer('argv');
// We start at 1 since we never want to include index.php
for ($i = 1; $i < $argc; $i ++)
{
// If there's no '-' at the beginning of the argument
// then add it to our segments.
if (! $options_found && strpos($argv[$i], '-') === false)
{
$this->segments[] = filter_var($argv[$i], FILTER_SANITIZE_STRING);
continue;
}
$options_found = true;
if (strpos($argv[$i], '-') !== 0)
{
continue;
}
$arg = filter_var(str_replace('-', '', $argv[$i]), FILTER_SANITIZE_STRING);
$value = null;
// If the next item starts with a dash it's a value
if (isset($argv[$i + 1]) && strpos($argv[$i + 1], '-') !== 0)
{
$value = filter_var($argv[$i + 1], FILTER_SANITIZE_STRING);
$i ++;
}
$this->options[$arg] = $value;
}
}
//--------------------------------------------------------------------
}

832
system/HTTP/CURLRequest.php Normal file
View File

@@ -0,0 +1,832 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP;
use CodeIgniter\HTTP\Exceptions\HTTPException;
use Config\App;
/**
* Class OutgoingRequest
*
* A lightweight HTTP client for sending synchronous HTTP requests
* via cURL.
*
* @package CodeIgniter\HTTP
*/
class CURLRequest extends Request
{
/**
* The response object associated with this request
*
* @var \CodeIgniter\HTTP\Response
*/
protected $response;
/**
* The URI associated with this request
*
* @var URI
*/
protected $baseURI;
/**
* The setting values
*
* @var array
*/
protected $config = [
'timeout' => 0.0,
'connect_timeout' => 150,
'debug' => false,
'verify' => true,
];
/**
* Default values for when 'allow_redirects'
* option is true.
*
* @var array
*/
protected $redirectDefaults = [
'max' => 5,
'strict' => true,
'protocols' => [
'http',
'https',
],
];
/**
* The number of milliseconds to delay before
* sending the request.
*
* @var float
*/
protected $delay = 0.0;
//--------------------------------------------------------------------
/**
* Takes an array of options to set the following possible class properties:
*
* - baseURI
* - timeout
* - any other request options to use as defaults.
*
* @param App $config
* @param URI $uri
* @param ResponseInterface $response
* @param array $options
*/
public function __construct(App $config, URI $uri, ResponseInterface $response = null, array $options = [])
{
if (! function_exists('curl_version'))
{
// we won't see this during travis-CI
// @codeCoverageIgnoreStart
throw HTTPException::forMissingCurl();
// @codeCoverageIgnoreEnd
}
parent::__construct($config);
$this->response = $response;
$this->baseURI = $uri;
$this->parseOptions($options);
}
//--------------------------------------------------------------------
/**
* Sends an HTTP request to the specified $url. If this is a relative
* URL, it will be merged with $this->baseURI to form a complete URL.
*
* @param $method
* @param string $url
* @param array $options
*
* @return \CodeIgniter\HTTP\ResponseInterface
*/
public function request($method, string $url, array $options = []): ResponseInterface
{
$this->parseOptions($options);
$url = $this->prepareURL($url);
$method = filter_var($method, FILTER_SANITIZE_STRING);
$this->send($method, $url);
return $this->response;
}
//--------------------------------------------------------------------
/**
* Convenience method for sending a GET request.
*
* @param string $url
* @param array $options
*
* @return \CodeIgniter\HTTP\ResponseInterface
*/
public function get(string $url, array $options = []): ResponseInterface
{
return $this->request('get', $url, $options);
}
//--------------------------------------------------------------------
/**
* Convenience method for sending a DELETE request.
*
* @param string $url
* @param array $options
*
* @return \CodeIgniter\HTTP\ResponseInterface
*/
public function delete(string $url, array $options = []): ResponseInterface
{
return $this->request('delete', $url, $options);
}
//--------------------------------------------------------------------
/**
* Convenience method for sending a HEAD request.
*
* @param string $url
* @param array $options
*
* @return ResponseInterface
*/
public function head(string $url, array $options = []): ResponseInterface
{
return $this->request('head', $url, $options);
}
//--------------------------------------------------------------------
/**
* Convenience method for sending an OPTIONS request.
*
* @param string $url
* @param array $options
*
* @return \CodeIgniter\HTTP\ResponseInterface
*/
public function options(string $url, array $options = []): ResponseInterface
{
return $this->request('options', $url, $options);
}
//--------------------------------------------------------------------
/**
* Convenience method for sending a PATCH request.
*
* @param string $url
* @param array $options
*
* @return \CodeIgniter\HTTP\ResponseInterface
*/
public function patch(string $url, array $options = []): ResponseInterface
{
return $this->request('patch', $url, $options);
}
//--------------------------------------------------------------------
/**
* Convenience method for sending a POST request.
*
* @param string $url
* @param array $options
*
* @return \CodeIgniter\HTTP\ResponseInterface
*/
public function post(string $url, array $options = []): ResponseInterface
{
return $this->request('post', $url, $options);
}
//--------------------------------------------------------------------
/**
* Convenience method for sending a PUT request.
*
* @param string $url
* @param array $options
*
* @return \CodeIgniter\HTTP\ResponseInterface
*/
public function put(string $url, array $options = []): ResponseInterface
{
return $this->request('put', $url, $options);
}
//--------------------------------------------------------------------
/**
* Set the HTTP Authentication.
*
* @param string $username
* @param string $password
* @param string $type basic or digest
*
* @return $this
*/
public function setAuth(string $username, string $password, string $type = 'basic')
{
$this->config['auth'] = [
$username,
$password,
$type,
];
return $this;
}
//--------------------------------------------------------------------
/**
* Set form data to be sent.
*
* @param array $params
* @param boolean $multipart Set TRUE if you are sending CURLFiles
*
* @return $this
*/
public function setForm(array $params, bool $multipart = false)
{
if ($multipart)
{
$this->config['multipart'] = $params;
}
else
{
$this->config['form_params'] = $params;
}
return $this;
}
//--------------------------------------------------------------------
/**
* Set JSON data to be sent.
*
* @param mixed $data
*
* @return $this
*/
public function setJSON($data)
{
$this->config['json'] = $data;
return $this;
}
//--------------------------------------------------------------------
/**
* Sets the correct settings based on the options array
* passed in.
*
* @param array $options
*/
protected function parseOptions(array $options)
{
if (array_key_exists('baseURI', $options))
{
$this->baseURI = $this->baseURI->setURI($options['baseURI']);
unset($options['baseURI']);
}
if (array_key_exists('headers', $options) && is_array($options['headers']))
{
foreach ($options['headers'] as $name => $value)
{
$this->setHeader($name, $value);
}
unset($options['headers']);
}
if (array_key_exists('delay', $options))
{
// Convert from the milliseconds passed in
// to the seconds that sleep requires.
$this->delay = (float) $options['delay'] / 1000;
unset($options['delay']);
}
foreach ($options as $key => $value)
{
$this->config[$key] = $value;
}
}
//--------------------------------------------------------------------
/**
* If the $url is a relative URL, will attempt to create
* a full URL by prepending $this->baseURI to it.
*
* @param string $url
*
* @return string
*/
protected function prepareURL(string $url): string
{
// If it's a full URI, then we have nothing to do here...
if (strpos($url, '://') !== false)
{
return $url;
}
$uri = $this->baseURI->resolveRelativeURI($url);
return (string) $uri;
}
//--------------------------------------------------------------------
/**
* Get the request method. Overrides the Request class' method
* since users expect a different answer here.
*
* @param boolean|false $upper Whether to return in upper or lower case.
*
* @return string
*/
public function getMethod(bool $upper = false): string
{
return ($upper) ? strtoupper($this->method) : strtolower($this->method);
}
//--------------------------------------------------------------------
/**
* Fires the actual cURL request.
*
* @param string $method
* @param string $url
*
* @return \CodeIgniter\HTTP\ResponseInterface
*/
public function send(string $method, string $url)
{
// Reset our curl options so we're on a fresh slate.
$curl_options = [];
if (! empty($this->config['query']) && is_array($this->config['query']))
{
// This is likely too naive a solution.
// Should look into handling when $url already
// has query vars on it.
$url .= '?' . http_build_query($this->config['query']);
unset($this->config['query']);
}
$curl_options[CURLOPT_URL] = $url;
$curl_options[CURLOPT_RETURNTRANSFER] = true;
$curl_options[CURLOPT_HEADER] = true;
$curl_options[CURLOPT_FRESH_CONNECT] = true;
// Disable @file uploads in post data.
$curl_options[CURLOPT_SAFE_UPLOAD] = true;
$curl_options = $this->setCURLOptions($curl_options, $this->config);
$curl_options = $this->applyMethod($method, $curl_options);
$curl_options = $this->applyRequestHeaders($curl_options);
// Do we need to delay this request?
if ($this->delay > 0)
{
sleep($this->delay);
}
$output = $this->sendRequest($curl_options);
$continueStr = "HTTP/1.1 100 Continue\x0d\x0a\x0d\x0a";
if (strpos($output, $continueStr) === 0)
{
$output = substr($output, strlen($continueStr));
}
// Split out our headers and body
$break = strpos($output, "\r\n\r\n");
if ($break !== false)
{
// Our headers
$headers = explode("\n", substr($output, 0, $break));
$this->setResponseHeaders($headers);
// Our body
$body = substr($output, $break + 4);
$this->response->setBody($body);
}
else
{
$this->response->setBody($output);
}
return $this->response;
}
//--------------------------------------------------------------------
/**
* Takes all headers current part of this request and adds them
* to the cURL request.
*
* @param array $curl_options
*
* @return array
*/
protected function applyRequestHeaders(array $curl_options = []): array
{
if (empty($this->headers))
{
$this->populateHeaders();
// Otherwise, it will corrupt the request
$this->removeHeader('Host');
}
$headers = $this->getHeaders();
if (empty($headers))
{
return $curl_options;
}
$set = [];
foreach ($headers as $name => $value)
{
$set[] = $name . ': ' . $this->getHeaderLine($name);
}
$curl_options[CURLOPT_HTTPHEADER] = $set;
return $curl_options;
}
//--------------------------------------------------------------------
/**
* Apply method
*
* @param string $method
* @param array $curl_options
*
* @return array
*/
protected function applyMethod(string $method, array $curl_options): array
{
$method = strtoupper($method);
$this->method = $method;
$curl_options[CURLOPT_CUSTOMREQUEST] = $method;
$size = strlen($this->body);
// Have content?
if ($size === null || $size > 0)
{
$curl_options = $this->applyBody($curl_options);
return $curl_options;
}
if ($method === 'PUT' || $method === 'POST')
{
// See http://tools.ietf.org/html/rfc7230#section-3.3.2
if (is_null($this->getHeader('content-length')))
{
$this->setHeader('Content-Length', '0');
}
}
else if ($method === 'HEAD')
{
$curl_options[CURLOPT_NOBODY] = 1;
}
return $curl_options;
}
//--------------------------------------------------------------------
/**
* Apply body
*
* @param array $curl_options
*
* @return array
*/
protected function applyBody(array $curl_options = []): array
{
if (! empty($this->body))
{
$curl_options[CURLOPT_POSTFIELDS] = (string) $this->getBody();
}
return $curl_options;
}
//--------------------------------------------------------------------
/**
* Parses the header retrieved from the cURL response into
* our Response object.
*
* @param array $headers
*/
protected function setResponseHeaders(array $headers = [])
{
foreach ($headers as $header)
{
if (($pos = strpos($header, ':')) !== false)
{
$title = substr($header, 0, $pos);
$value = substr($header, $pos + 1);
$this->response->setHeader($title, $value);
}
else if (strpos($header, 'HTTP') === 0)
{
preg_match('#^HTTP\/([12]\.[01]) ([0-9]+) (.+)#', $header, $matches);
if (isset($matches[1]))
{
$this->response->setProtocolVersion($matches[1]);
}
if (isset($matches[2]))
{
$this->response->setStatusCode($matches[2], $matches[3] ?? null);
}
}
}
}
//--------------------------------------------------------------------
/**
* Set CURL options
*
* @param array $curl_options
* @param array $config
* @return array
* @throws \InvalidArgumentException
*/
protected function setCURLOptions(array $curl_options = [], array $config = [])
{
// Auth Headers
if (! empty($config['auth']))
{
$curl_options[CURLOPT_USERPWD] = $config['auth'][0] . ':' . $config['auth'][1];
if (! empty($config['auth'][2]) && strtolower($config['auth'][2]) === 'digest')
{
$curl_options[CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST;
}
else
{
$curl_options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
}
}
// Certificate
if (! empty($config['cert']))
{
$cert = $config['cert'];
if (is_array($cert))
{
$curl_options[CURLOPT_SSLCERTPASSWD] = $cert[1];
$cert = $cert[0];
}
if (! is_file($cert))
{
throw HTTPException::forSSLCertNotFound($cert);
}
$curl_options[CURLOPT_SSLCERT] = $cert;
}
// SSL Verification
if (isset($config['verify']))
{
if (is_string($config['verify']))
{
$file = realpath($config['ssl_key']);
if (! $file)
{
throw HTTPException::forInvalidSSLKey($config['ssl_key']);
}
$curl_options[CURLOPT_CAINFO] = $file;
$curl_options[CURLOPT_SSL_VERIFYPEER] = 1;
}
else if (is_bool($config['verify']))
{
$curl_options[CURLOPT_SSL_VERIFYPEER] = $config['verify'];
}
}
// Debug
if ($config['debug'])
{
$curl_options[CURLOPT_VERBOSE] = 1;
$curl_options[CURLOPT_STDERR] = is_string($config['debug']) ? fopen($config['debug'], 'a+') : fopen('php://stderr', 'w');
}
// Decode Content
if (! empty($config['decode_content']))
{
$accept = $this->getHeaderLine('Accept-Encoding');
if ($accept)
{
$curl_options[CURLOPT_ENCODING] = $accept;
}
else
{
$curl_options[CURLOPT_ENCODING] = '';
$curl_options[CURLOPT_HTTPHEADER] = 'Accept-Encoding';
}
}
// Allow Redirects
if (array_key_exists('allow_redirects', $config))
{
$settings = $this->redirectDefaults;
if (is_array($config['allow_redirects']))
{
$settings = array_merge($settings, $config['allow_redirects']);
}
if ($config['allow_redirects'] === false)
{
$curl_options[CURLOPT_FOLLOWLOCATION] = 0;
}
else
{
$curl_options[CURLOPT_FOLLOWLOCATION] = 1;
$curl_options[CURLOPT_MAXREDIRS] = $settings['max'];
if ($settings['strict'] === true)
{
$curl_options[CURLOPT_POSTREDIR] = 1 | 2 | 4;
}
$protocols = 0;
foreach ($settings['protocols'] as $proto)
{
$protocols += constant('CURLPROTO_' . strtoupper($proto));
}
$curl_options[CURLOPT_REDIR_PROTOCOLS] = $protocols;
}
}
// Timeout
$curl_options[CURLOPT_TIMEOUT_MS] = (float) $config['timeout'] * 1000;
// Connection Timeout
$curl_options[CURLOPT_CONNECTTIMEOUT_MS] = (float) $config['connect_timeout'] * 1000;
// Post Data - application/x-www-form-urlencoded
if (! empty($config['form_params']) && is_array($config['form_params']))
{
$postFields = http_build_query($config['form_params']);
$curl_options[CURLOPT_POSTFIELDS] = $postFields;
// Ensure content-length is set, since CURL doesn't seem to
// calculate it when HTTPHEADER is set.
$this->setHeader('Content-Length', (string) strlen($postFields));
$this->setHeader('Content-Type', 'application/x-www-form-urlencoded');
}
// Post Data - multipart/form-data
if (! empty($config['multipart']) && is_array($config['multipart']))
{
// setting the POSTFIELDS option automatically sets multipart
$curl_options[CURLOPT_POSTFIELDS] = $config['multipart'];
}
// HTTP Errors
$curl_options[CURLOPT_FAILONERROR] = array_key_exists('http_errors', $config) ? (bool) $config['http_errors'] : true;
// JSON
if (isset($config['json']))
{
// Will be set as the body in `applyBody()`
$json = json_encode($config['json']);
$this->setBody($json);
$this->setHeader('Content-Type', 'application/json');
$this->setHeader('Content-Length', (string) strlen($json));
}
// version
if (! empty($config['version']))
{
if ($config['version'] === 1.0)
{
$curl_options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
}
else if ($config['version'] === 1.1)
{
$curl_options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
}
}
// Cookie
if (isset($config['cookie']))
{
$curl_options[CURLOPT_COOKIEJAR] = $config['cookie'];
$curl_options[CURLOPT_COOKIEFILE] = $config['cookie'];
}
return $curl_options;
}
//--------------------------------------------------------------------
/**
* Does the actual work of initializing cURL, setting the options,
* and grabbing the output.
*
* @codeCoverageIgnore
*
* @param array $curl_options
*
* @return string
*/
protected function sendRequest(array $curl_options = []): string
{
$ch = curl_init();
curl_setopt_array($ch, $curl_options);
// Send the request and wait for a response.
$output = curl_exec($ch);
if ($output === false)
{
throw HTTPException::forCurlError(curl_errno($ch), curl_error($ch));
}
curl_close($ch);
return $output;
}
//--------------------------------------------------------------------
}

View File

@@ -0,0 +1,849 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP;
/**
* Class ContentSecurityPolicy
*
* Provides tools for working with the Content-Security-Policy header
* to help defeat XSS attacks.
*
* @see http://www.w3.org/TR/CSP/
* @see http://www.html5rocks.com/en/tutorials/security/content-security-policy/
* @see http://content-security-policy.com/
* @see https://www.owasp.org/index.php/Content_Security_Policy
* @package CodeIgniter\HTTP
*/
class ContentSecurityPolicy
{
/**
* Used for security enforcement
*
* @var array|string
*/
protected $baseURI = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $childSrc = [];
/**
* Used for security enforcement
*
* @var array
*/
protected $connectSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $defaultSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $fontSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $formAction = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $frameAncestors = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $imageSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $mediaSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $objectSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $pluginTypes = [];
/**
* Used for security enforcement
*
* @var string
*/
protected $reportURI = null;
/**
* Used for security enforcement
*
* @var array|string
*/
protected $sandbox = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $scriptSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $styleSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $manifestSrc = [];
/**
* Used for security enforcement
*
* @var boolean
*/
protected $upgradeInsecureRequests = false;
/**
* Used for security enforcement
*
* @var boolean
*/
protected $reportOnly = false;
/**
* Used for security enforcement
*
* @var array
*/
protected $validSources = [
'self',
'none',
'unsafe-inline',
'unsafe-eval',
];
/**
* Used for security enforcement
*
* @var array
*/
protected $nonces = [];
/**
* An array of header info since we have
* to build ourself before passing to Response.
*
* @var array
*/
protected $tempHeaders = [];
/**
* An array of header info to build
* that should only be reported.
*
* @var array
*/
protected $reportOnlyHeaders = [];
//--------------------------------------------------------------------
/**
* ContentSecurityPolicy constructor.
*
* Stores our default values from the Config file.
*
* @param \Config\ContentSecurityPolicy $config
*/
public function __construct(\Config\ContentSecurityPolicy $config)
{
foreach ($config as $setting => $value)
{
if (isset($this->{$setting}))
{
$this->{$setting} = $value;
}
}
}
//--------------------------------------------------------------------
/**
* Compiles and sets the appropriate headers in the request.
*
* Should be called just prior to sending the response to the user agent.
*
* @param ResponseInterface $response
*/
public function finalize(ResponseInterface &$response)
{
$this->generateNonces($response);
$this->buildHeaders($response);
}
//--------------------------------------------------------------------
/**
* If TRUE, nothing will be restricted. Instead all violations will
* be reported to the reportURI for monitoring. This is useful when
* you are just starting to implement the policy, and will help
* determine what errors need to be addressed before you turn on
* all filtering.
*
* @param boolean|true $value
*
* @return $this
*/
public function reportOnly(bool $value = true)
{
$this->reportOnly = $value;
return $this;
}
//--------------------------------------------------------------------
/**
* Adds a new base_uri value. Can be either a URI class or a simple string.
*
* base_uri restricts the URLs that can appear in a pages <base> element.
*
* @see http://www.w3.org/TR/CSP/#directive-base-uri
*
* @param string|array $uri
* @param boolean|null $explicitReporting
*
* @return $this
*/
public function addBaseURI($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'baseURI', $explicitReporting ?? $this->reportOnly);
return $this;
}
//--------------------------------------------------------------------
/**
* Adds a new valid endpoint for a form's action. Can be either
* a URI class or a simple string.
*
* child-src lists the URLs for workers and embedded frame contents.
* For example: child-src https://youtube.com would enable embedding
* videos from YouTube but not from other origins.
*
* @see http://www.w3.org/TR/CSP/#directive-child-src
*
* @param string|array $uri
* @param boolean|null $explicitReporting
*
* @return $this
*/
public function addChildSrc($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'childSrc', $explicitReporting ?? $this->reportOnly);
return $this;
}
//--------------------------------------------------------------------
/**
* Adds a new valid endpoint for a form's action. Can be either
* a URI class or a simple string.
*
* connect-src limits the origins to which you can connect
* (via XHR, WebSockets, and EventSource).
*
* @see http://www.w3.org/TR/CSP/#directive-connect-src
*
* @param string|array $uri
* @param boolean|null $explicitReporting
*
* @return $this
*/
public function addConnectSrc($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'connectSrc', $explicitReporting ?? $this->reportOnly);
return $this;
}
//--------------------------------------------------------------------
/**
* Adds a new valid endpoint for a form's action. Can be either
* a URI class or a simple string.
*
* default_src is the URI that is used for many of the settings when
* no other source has been set.
*
* @see http://www.w3.org/TR/CSP/#directive-default-src
*
* @param string|array $uri
* @param boolean|null $explicitReporting
*
* @return $this
*/
public function setDefaultSrc($uri, ?bool $explicitReporting = null)
{
$this->defaultSrc = [(string) $uri => $explicitReporting ?? $this->reportOnly];
return $this;
}
//--------------------------------------------------------------------
/**
* Adds a new valid endpoint for a form's action. Can be either
* a URI class or a simple string.
*
* font-src specifies the origins that can serve web fonts.
*
* @see http://www.w3.org/TR/CSP/#directive-font-src
*
* @param string|array $uri
* @param boolean|null $explicitReporting
*
* @return $this
*/
public function addFontSrc($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'fontSrc', $explicitReporting ?? $this->reportOnly);
return $this;
}
//--------------------------------------------------------------------
/**
* Adds a new valid endpoint for a form's action. Can be either
* a URI class or a simple string.
*
* @see http://www.w3.org/TR/CSP/#directive-form-action
*
* @param string|array $uri
* @param boolean|null $explicitReporting
*
* @return $this
*/
public function addFormAction($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'formAction', $explicitReporting ?? $this->reportOnly);
return $this;
}
//--------------------------------------------------------------------
/**
* Adds a new resource that should allow embedding the resource using
* <frame>, <iframe>, <object>, <embed>, or <applet>
*
* @see http://www.w3.org/TR/CSP/#directive-frame-ancestors
*
* @param string|array $uri
* @param boolean|null $explicitReporting
*
* @return $this
*/
public function addFrameAncestor($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'frameAncestors', $explicitReporting ?? $this->reportOnly);
return $this;
}
//--------------------------------------------------------------------
/**
* Adds a new valid endpoint for valid image sources. Can be either
* a URI class or a simple string.
*
* @see http://www.w3.org/TR/CSP/#directive-img-src
*
* @param string|array $uri
* @param boolean|null $explicitReporting
*
* @return $this
*/
public function addImageSrc($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'imageSrc', $explicitReporting ?? $this->reportOnly);
return $this;
}
//--------------------------------------------------------------------
/**
* Adds a new valid endpoint for valid video and audio. Can be either
* a URI class or a simple string.
*
* @see http://www.w3.org/TR/CSP/#directive-media-src
*
* @param string|array $uri
* @param boolean|null $explicitReporting
*
* @return $this
*/
public function addMediaSrc($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'mediaSrc', $explicitReporting ?? $this->reportOnly);
return $this;
}
//--------------------------------------------------------------------
/**
* Adds a new valid endpoint for manifest sources. Can be either
* a URI class or simple string.
*
* @see https://www.w3.org/TR/CSP/#directive-manifest-src
*
* @param string|array $uri
* @param boolean|null $explicitReporting
*
* @return $this
*/
public function addManifestSrc($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'manifestSrc', $explicitReporting ?? $this->reportOnly);
return $this;
}
//--------------------------------------------------------------------
/**
* Adds a new valid endpoint for Flash and other plugin sources. Can be either
* a URI class or a simple string.
*
* @see http://www.w3.org/TR/CSP/#directive-object-src
*
* @param string|array $uri
* @param boolean|null $explicitReporting
*
* @return $this
*/
public function addObjectSrc($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'objectSrc', $explicitReporting ?? $this->reportOnly);
return $this;
}
//--------------------------------------------------------------------
/**
* Limits the types of plugins that can be used. Can be either
* a URI class or a simple string.
*
* @see http://www.w3.org/TR/CSP/#directive-plugin-types
*
* @param string|array $mime One or more plugin mime types, separate by spaces
* @param boolean|null $explicitReporting
*
* @return $this
*/
public function addPluginType($mime, ?bool $explicitReporting = null)
{
$this->addOption($mime, 'pluginTypes', $explicitReporting ?? $this->reportOnly);
return $this;
}
//--------------------------------------------------------------------
/**
* Specifies a URL where a browser will send reports when a content
* security policy is violated. Can be either a URI class or a simple string.
*
* @see http://www.w3.org/TR/CSP/#directive-report-uri
*
* @param string $uri
*
* @return $this
*/
public function setReportURI(string $uri)
{
$this->reportURI = $uri;
return $this;
}
//--------------------------------------------------------------------
/**
* specifies an HTML sandbox policy that the user agent applies to
* the protected resource.
*
* @see http://www.w3.org/TR/CSP/#directive-sandbox
*
* @param string|array $flags An array of sandbox flags that can be added to the directive.
* @param boolean|null $explicitReporting
*
* @return $this
*/
public function addSandbox($flags, ?bool $explicitReporting = null)
{
$this->addOption($flags, 'sandbox', $explicitReporting ?? $this->reportOnly);
return $this;
}
//--------------------------------------------------------------------
/**
* Adds a new valid endpoint for javascript file sources. Can be either
* a URI class or a simple string.
*
* @see http://www.w3.org/TR/CSP/#directive-connect-src
*
* @param string|array $uri
* @param boolean|null $explicitReporting
*
* @return $this
*/
public function addScriptSrc($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'scriptSrc', $explicitReporting ?? $this->reportOnly);
return $this;
}
//--------------------------------------------------------------------
/**
* Adds a new valid endpoint for CSS file sources. Can be either
* a URI class or a simple string.
*
* @see http://www.w3.org/TR/CSP/#directive-connect-src
*
* @param string|array $uri
* @param boolean|null $explicitReporting
*
* @return $this
*/
public function addStyleSrc($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'styleSrc', $explicitReporting ?? $this->reportOnly);
return $this;
}
//--------------------------------------------------------------------
/**
* Sets whether the user agents should rewrite URL schemes, changing
* HTTP to HTTPS.
*
* @param boolean $value
*
* @return $this
*/
public function upgradeInsecureRequests(bool $value = true)
{
$this->upgradeInsecureRequests = $value;
return $this;
}
//--------------------------------------------------------------------
// Utility
//--------------------------------------------------------------------
/**
* DRY method to add an string or array to a class property.
*
* @param string|array $options
* @param string $target
* @param boolean|null $explicitReporting
*/
protected function addOption($options, string $target, ?bool $explicitReporting = null)
{
// Ensure we have an array to work with...
if (is_string($this->{$target}))
{
$this->{$target} = [$this->{$target}];
}
if (is_array($options))
{
foreach ($options as $opt)
{
$this->{$target}[$opt] = $explicitReporting ?? $this->reportOnly;
}
}
else
{
$this->{$target}[$options] = $explicitReporting ?? $this->reportOnly;
}
}
//--------------------------------------------------------------------
/**
* Scans the body of the request message and replaces any nonce
* placeholders with actual nonces, that we'll then add to our
* headers.
*
* @param ResponseInterface|\CodeIgniter\HTTP\Response $response
*/
protected function generateNonces(ResponseInterface &$response)
{
$body = $response->getBody();
if (empty($body))
{
return;
}
if (! is_array($this->styleSrc))
{
$this->styleSrc = [$this->styleSrc];
}
if (! is_array($this->scriptSrc))
{
$this->scriptSrc = [$this->scriptSrc];
}
// Replace style placeholders with nonces
$body = preg_replace_callback(
'/{csp-style-nonce}/', function ($matches) {
$nonce = bin2hex(random_bytes(12));
$this->styleSrc[] = 'nonce-' . $nonce;
return "nonce=\"{$nonce}\"";
}, $body
);
// Replace script placeholders with nonces
$body = preg_replace_callback(
'/{csp-script-nonce}/', function ($matches) {
$nonce = bin2hex(random_bytes(12));
$this->scriptSrc[] = 'nonce-' . $nonce;
return "nonce=\"{$nonce}\"";
}, $body
);
$response->setBody($body);
}
//--------------------------------------------------------------------
/**
* Based on the current state of the elements, will add the appropriate
* Content-Security-Policy and Content-Security-Policy-Report-Only headers
* with their values to the response object.
*
* @param ResponseInterface|\CodeIgniter\HTTP\Response $response
*/
protected function buildHeaders(ResponseInterface &$response)
{
// Ensure both headers are available and arrays...
$response->setHeader('Content-Security-Policy', []);
$response->setHeader('Content-Security-Policy-Report-Only', []);
$directives = [
'base-uri' => 'baseURI',
'child-src' => 'childSrc',
'connect-src' => 'connectSrc',
'default-src' => 'defaultSrc',
'font-src' => 'fontSrc',
'form-action' => 'formAction',
'frame-ancestors' => 'frameAncestors',
'img-src' => 'imageSrc',
'media-src' => 'mediaSrc',
'object-src' => 'objectSrc',
'plugin-types' => 'pluginTypes',
'script-src' => 'scriptSrc',
'style-src' => 'styleSrc',
'manifest-src' => 'manifestSrc',
'sandbox' => 'sandbox',
'report-uri' => 'reportURI',
];
// inject default base & default URIs if needed
if (empty($this->baseURI))
{
$this->baseURI = 'self';
}
if (empty($this->defaultSrc))
{
$this->defaultSrc = 'self';
}
foreach ($directives as $name => $property)
{
// base_uri
if (! empty($this->{$property}))
{
$this->addToHeader($name, $this->{$property});
}
}
// Compile our own header strings here since if we just
// append it to the response, it will be joined with
// commas, not semi-colons as we need.
if (! empty($this->tempHeaders))
{
$header = '';
foreach ($this->tempHeaders as $name => $value)
{
$header .= " {$name} {$value};";
}
// add token only if needed
if ($this->upgradeInsecureRequests)
{
$header .= ' upgrade-insecure-requests;';
}
$response->appendHeader('Content-Security-Policy', $header);
}
if (! empty($this->reportOnlyHeaders))
{
$header = '';
foreach ($this->reportOnlyHeaders as $name => $value)
{
$header .= " {$name} {$value};";
}
$response->appendHeader('Content-Security-Policy-Report-Only', $header);
}
$this->tempHeaders = [];
$this->reportOnlyHeaders = [];
}
//--------------------------------------------------------------------
/**
* Adds a directive and it's options to the appropriate header. The $values
* array might have options that are geared toward either the regular or the
* reportOnly header, since it's viable to have both simultaneously.
*
* @param string $name
* @param array|string|null $values
*/
protected function addToHeader(string $name, $values = null)
{
if (is_string($values))
{
$values = [$values => 0];
}
$sources = [];
$reportSources = [];
foreach ($values as $value => $reportOnly)
{
if (is_numeric($value) && is_string($reportOnly) && ! empty($reportOnly))
{
$value = $reportOnly;
$reportOnly = 0;
}
if ($reportOnly === true)
{
$reportSources[] = in_array($value, $this->validSources) ? "'{$value}'" : $value;
}
else
{
if (strpos($value, 'nonce-') === 0)
{
$sources[] = "'{$value}'";
}
else
{
$sources[] = in_array($value, $this->validSources) ? "'{$value}'" : $value;
}
}
}
if (! empty($sources))
{
$this->tempHeaders[$name] = implode(' ', $sources);
}
if (! empty($reportSources))
{
$this->reportOnlyHeaders[$name] = implode(' ', $reportSources);
}
}
//--------------------------------------------------------------------
}

View File

@@ -0,0 +1,499 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP;
use CodeIgniter\Exceptions\DownloadException;
use CodeIgniter\Files\File;
use Config\Mimes;
/**
* HTTP response when a download is requested.
*/
class DownloadResponse extends Message implements ResponseInterface
{
/**
* Download file name
*
* @var string
*/
private $filename;
/**
* Download for file
*
* @var File?
*/
private $file;
/**
* mime set flag
*
* @var boolean
*/
private $setMime;
/**
* Download for binary
*
* @var string
*/
private $binary;
/**
* Download reason
*
* @var string
*/
private $reason = 'OK';
/**
* Download charset
*
* @var string
*/
private $charset = 'UTF-8';
/**
* pretend
*
* @var boolean
*/
private $pretend = false;
/**
* Constructor.
*
* @param string $filename
* @param boolean $setMime
*/
public function __construct(string $filename, bool $setMime)
{
$this->filename = $filename;
$this->setMime = $setMime;
}
/**
* set download for binary string.
*
* @param string $binary
*/
public function setBinary(string $binary)
{
if ($this->file !== null)
{
throw DownloadException::forCannotSetBinary();
}
$this->binary = $binary;
}
/**
* set download for file.
*
* @param string $filepath
*/
public function setFilePath(string $filepath)
{
if ($this->binary !== null)
{
throw DownloadException::forCannotSetFilePath($filepath);
}
$this->file = new File($filepath, true);
}
/**
* set name for the download.
*
* @param string $filename
*
* @return $this
*/
public function setFileName(string $filename)
{
$this->filename = $filename;
return $this;
}
/**
* get content length.
*
* @return integer
*/
public function getContentLength() : int
{
if (is_string($this->binary))
{
return strlen($this->binary);
}
elseif ($this->file instanceof File)
{
return $this->file->getSize();
}
return 0;
}
/**
* Set content type by guessing mime type from file extension
*/
private function setContentTypeByMimeType()
{
$mime = null;
$charset = '';
if ($this->setMime === true)
{
if (($last_dot_position = strrpos($this->filename, '.')) !== false)
{
$mime = Mimes::guessTypeFromExtension(substr($this->filename, $last_dot_position + 1));
$charset = $this->charset;
}
}
if (! is_string($mime))
{
// Set the default MIME type to send
$mime = 'application/octet-stream';
$charset = '';
}
$this->setContentType($mime, $charset);
}
/**
* get download filename.
*
* @return string
*/
private function getDownloadFileName(): string
{
$filename = $this->filename;
$x = explode('.', $this->filename);
$extension = end($x);
/* It was reported that browsers on Android 2.1 (and possibly older as well)
* need to have the filename extension upper-cased in order to be able to
* download it.
*
* Reference: http://digiblog.de/2011/04/19/android-and-the-download-file-headers/
*/
// @todo: depend super global
if (count($x) !== 1 && isset($_SERVER['HTTP_USER_AGENT'])
&& preg_match('/Android\s(1|2\.[01])/', $_SERVER['HTTP_USER_AGENT']))
{
$x[count($x) - 1] = strtoupper($extension);
$filename = implode('.', $x);
}
return $filename;
}
/**
* get Content-Disposition Header string.
*
* @return string
*/
private function getContentDisposition() : string
{
$download_filename = $this->getDownloadFileName();
$utf8_filename = $download_filename;
if (strtoupper($this->charset) !== 'UTF-8')
{
$utf8_filename = mb_convert_encoding($download_filename, 'UTF-8', $this->charset);
}
$result = sprintf('attachment; filename="%s"', $download_filename);
if (isset($utf8_filename))
{
$result .= '; filename*=UTF-8\'\'' . rawurlencode($utf8_filename);
}
return $result;
}
/**
* {@inheritDoc}
*/
public function getStatusCode(): int
{
return 200;
}
//--------------------------------------------------------------------
/**
* {@inheritDoc}
*
* @throws DownloadException
*/
public function setStatusCode(int $code, string $reason = '')
{
throw DownloadException::forCannotSetStatusCode($code, $reason);
}
//--------------------------------------------------------------------
/**
* {@inheritDoc}
*/
public function getReason(): string
{
return $this->reason;
}
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// Convenience Methods
//--------------------------------------------------------------------
/**
* {@inheritDoc}
*/
public function setDate(\DateTime $date)
{
$date->setTimezone(new \DateTimeZone('UTC'));
$this->setHeader('Date', $date->format('D, d M Y H:i:s') . ' GMT');
return $this;
}
//--------------------------------------------------------------------
/**
* {@inheritDoc}
*/
public function setContentType(string $mime, string $charset = 'UTF-8')
{
// add charset attribute if not already there and provided as parm
if ((strpos($mime, 'charset=') < 1) && ! empty($charset))
{
$mime .= '; charset=' . $charset;
}
$this->removeHeader('Content-Type'); // replace existing content type
$this->setHeader('Content-Type', $mime);
if (! empty($charset))
{
$this->charset = $charset;
}
return $this;
}
/**
* {@inheritDoc}
*/
public function noCache(): self
{
$this->removeHeader('Cache-control');
$this->setHeader('Cache-control', ['private', 'no-transform', 'no-store', 'must-revalidate']);
return $this;
}
//--------------------------------------------------------------------
/**
* {@inheritDoc}
*
* @throws DownloadException
*/
public function setCache(array $options = [])
{
throw DownloadException::forCannotSetCache();
}
//--------------------------------------------------------------------
/**
* {@inheritDoc}
*/
public function setLastModified($date)
{
if ($date instanceof \DateTime)
{
$date->setTimezone(new \DateTimeZone('UTC'));
$this->setHeader('Last-Modified', $date->format('D, d M Y H:i:s') . ' GMT');
}
elseif (is_string($date))
{
$this->setHeader('Last-Modified', $date);
}
return $this;
}
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// Output Methods
//--------------------------------------------------------------------
/**
* For unit testing, don't actually send headers.
*
* @param boolean $pretend
* @return $this
*/
public function pretend(bool $pretend = true)
{
$this->pretend = $pretend;
return $this;
}
/**
* {@inheritDoc}
*/
public function send()
{
$this->buildHeaders();
$this->sendHeaders();
$this->sendBody();
return $this;
}
/**
* set header for file download.
*/
public function buildHeaders()
{
if (! $this->hasHeader('Content-Type'))
{
$this->setContentTypeByMimeType();
}
$this->setHeader('Content-Disposition', $this->getContentDisposition());
$this->setHeader('Expires-Disposition', '0');
$this->setHeader('Content-Transfer-Encoding', 'binary');
$this->setHeader('Content-Length', (string)$this->getContentLength());
$this->noCache();
}
/**
* Sends the headers of this HTTP request to the browser.
*
* @return DownloadResponse
*/
public function sendHeaders()
{
// Have the headers already been sent?
if ($this->pretend || headers_sent())
{
return $this;
}
// Per spec, MUST be sent with each request, if possible.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
if (! isset($this->headers['Date']))
{
$this->setDate(\DateTime::createFromFormat('U', time()));
}
// HTTP Status
header(sprintf('HTTP/%s %s %s', $this->protocolVersion, $this->getStatusCode(), $this->getReason()), true,
$this->getStatusCode());
// Send all of our headers
foreach ($this->getHeaders() as $name => $values)
{
header($name . ': ' . $this->getHeaderLine($name), false, $this->getStatusCode());
}
return $this;
}
/**
* output download file text.
*
* @throws DownloadException
*
* @return DownloadResponse
*/
public function sendBody()
{
if ($this->binary !== null)
{
return $this->sendBodyByBinary();
}
elseif ($this->file !== null)
{
return $this->sendBodyByFilePath();
}
throw DownloadException::forNotFoundDownloadSource();
}
/**
* output download text by file.
*
* @return DownloadResponse
*/
private function sendBodyByFilePath()
{
$spl_file_object = $this->file->openFile('rb');
// Flush 1MB chunks of data
while (! $spl_file_object->eof() && ($data = $spl_file_object->fread(1048576)) !== false)
{
echo $data;
}
return $this;
}
/**
* output download text by binary
*
* @return DownloadResponse
*/
private function sendBodyByBinary()
{
echo $this->binary;
return $this;
}
}

View File

@@ -0,0 +1,261 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP\Exceptions;
use CodeIgniter\Exceptions\ExceptionInterface;
use CodeIgniter\Exceptions\FrameworkException;
/**
* Things that can go wrong with HTTP
*/
class HTTPException extends FrameworkException implements ExceptionInterface
{
/**
* For CurlRequest
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*
* Not testable with travis-ci
* @codeCoverageIgnore
*/
public static function forMissingCurl()
{
return new static(lang('HTTP.missingCurl'));
}
/**
* For CurlRequest
*
* @param string $cert
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forSSLCertNotFound(string $cert)
{
return new static(lang('HTTP.sslCertNotFound', [$cert]));
}
/**
* For CurlRequest
*
* @param string $key
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forInvalidSSLKey(string $key)
{
return new static(lang('HTTP.invalidSSLKey', [$key]));
}
/**
* For CurlRequest
*
* @param string $errorNum
* @param string $error
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*
* Not testable with travis-ci; we over-ride the method which triggers it
* @codeCoverageIgnore
*/
public static function forCurlError(string $errorNum, string $error)
{
return new static(lang('HTTP.curlError', [$errorNum, $error]));
}
/**
* For IncomingRequest
*
* @param string $type
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forInvalidNegotiationType(string $type)
{
return new static(lang('HTTP.invalidNegotiationType', [$type]));
}
/**
* For Message
*
* @param string $protocols
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forInvalidHTTPProtocol(string $protocols)
{
return new static(lang('HTTP.invalidHTTPProtocol', [$protocols]));
}
/**
* For Negotiate
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forEmptySupportedNegotiations()
{
return new static(lang('HTTP.emptySupportedNegotiations'));
}
/**
* For RedirectResponse
*
* @param string $route
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forInvalidRedirectRoute(string $route)
{
return new static(lang('HTTP.invalidRoute', [$route]));
}
/**
* For Response
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forMissingResponseStatus()
{
return new static(lang('HTTP.missingResponseStatus'));
}
/**
* For Response
*
* @param integer $code
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forInvalidStatusCode(int $code)
{
return new static(lang('HTTP.invalidStatusCode', [$code]));
}
/**
* For Response
*
* @param integer $code
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forUnkownStatusCode(int $code)
{
return new static(lang('HTTP.unknownStatusCode', [$code]));
}
/**
* For URI
*
* @param string $uri
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forUnableToParseURI(string $uri)
{
return new static(lang('HTTP.cannotParseURI', [$uri]));
}
/**
* For URI
*
* @param integer $segment
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forURISegmentOutOfRange(int $segment)
{
return new static(lang('HTTP.segmentOutOfRange', [$segment]));
}
/**
* For URI
*
* @param integer $port
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forInvalidPort(int $port)
{
return new static(lang('HTTP.invalidPort', [$port]));
}
/**
* For URI
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forMalformedQueryString()
{
return new static(lang('HTTP.malformedQueryString'));
}
/**
* For Uploaded file move
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forAlreadyMoved()
{
return new static(lang('HTTP.alreadyMoved'));
}
/**
* For Uploaded file move
*
* @param string|null $path
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forInvalidFile(string $path = null)
{
return new static(lang('HTTP.invalidFile'));
}
/**
* For Uploaded file move
*
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forMoveFailed(string $source, string $target, string $error)
{
return new static(lang('HTTP.moveFailed', [$source, $target, $error]));
}
}

View File

@@ -0,0 +1,325 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP\Files;
/**
* Class FileCollection
*
* Provides easy access to uploaded files for a request.
*
* @package CodeIgniter\HTTP\Files
*/
class FileCollection
{
/**
* An array of UploadedFile instances for any files
* uploaded as part of this request.
* Populated the first time either files(), file(), or hasFile()
* is called.
*
* @var array|null
*/
protected $files;
//--------------------------------------------------------------------
/**
* Returns an array of all uploaded files that were found.
* Each element in the array will be an instance of UploadedFile.
* The key of each element will be the client filename.
*
* @return array|null
*/
public function all()
{
$this->populateFiles();
return $this->files;
}
//--------------------------------------------------------------------
/**
* Attempts to get a single file from the collection of uploaded files.
*
* @param string $name
*
* @return UploadedFile|null
*/
public function getFile(string $name)
{
$this->populateFiles();
if ($this->hasFile($name))
{
if (strpos($name, '.') !== false)
{
$name = explode('.', $name);
$uploadedFile = $this->getValueDotNotationSyntax($name, $this->files);
return ($uploadedFile instanceof UploadedFile) ?
$uploadedFile : null;
}
if (array_key_exists($name, $this->files))
{
$uploadedFile = $this->files[$name];
return ($uploadedFile instanceof UploadedFile) ?
$uploadedFile : null;
}
}
return null;
}
//--------------------------------------------------------------------
/**
* Verify if a file exist in the collection of uploaded files and is have been uploaded with multiple option.
*
* @param string $name
*
* @return array|null
*/
public function getFileMultiple(string $name)
{
$this->populateFiles();
if ($this->hasFile($name))
{
if (strpos($name, '.') !== false)
{
$name = explode('.', $name);
$uploadedFile = $this->getValueDotNotationSyntax($name, $this->files);
return (is_array($uploadedFile) && ($uploadedFile[0] instanceof UploadedFile)) ?
$uploadedFile : null;
}
if (array_key_exists($name, $this->files))
{
$uploadedFile = $this->files[$name];
return (is_array($uploadedFile) && ($uploadedFile[0] instanceof UploadedFile)) ?
$uploadedFile : null;
}
}
return null;
}
//--------------------------------------------------------------------
/**
* Checks whether an uploaded file with name $fileID exists in
* this request.
*
* @param string $fileID The name of the uploaded file (from the input)
*
* @return boolean
*/
public function hasFile(string $fileID): bool
{
$this->populateFiles();
if (strpos($fileID, '.') !== false)
{
$segments = explode('.', $fileID);
$el = $this->files;
foreach ($segments as $segment)
{
if (! array_key_exists($segment, $el))
{
return false;
}
$el = $el[$segment];
}
return true;
}
return isset($this->files[$fileID]);
}
//--------------------------------------------------------------------
/**
* Taking information from the $_FILES array, it creates an instance
* of UploadedFile for each one, saving the results to this->files.
*
* Called by files(), file(), and hasFile()
*/
protected function populateFiles()
{
if (is_array($this->files))
{
return;
}
$this->files = [];
if (empty($_FILES))
{
return;
}
$files = $this->fixFilesArray($_FILES);
foreach ($files as $name => $file)
{
$this->files[$name] = $this->createFileObject($file);
}
}
//--------------------------------------------------------------------
/**
* Given a file array, will create UploadedFile instances. Will
* loop over an array and create objects for each.
*
* @param array $array
*
* @return array|UploadedFile
*/
protected function createFileObject(array $array)
{
if (! isset($array['name']))
{
$output = [];
foreach ($array as $key => $values)
{
if (! is_array($values))
{
continue;
}
$output[$key] = $this->createFileObject($values);
}
return $output;
}
return new UploadedFile(
$array['tmp_name'] ?? null, $array['name'] ?? null, $array['type'] ?? null, $array['size'] ?? null, $array['error'] ?? null
);
}
//--------------------------------------------------------------------
/**
* Reformats the odd $_FILES array into something much more like
* we would expect, with each object having its own array.
*
* Thanks to Jack Sleight on the PHP Manual page for the basis
* of this method.
*
* @see http://php.net/manual/en/reserved.variables.files.php#118294
*
* @param array $data
*
* @return array
*/
protected function fixFilesArray(array $data): array
{
$output = [];
foreach ($data as $name => $array)
{
foreach ($array as $field => $value)
{
$pointer = &$output[$name];
if (! is_array($value))
{
$pointer[$field] = $value;
continue;
}
$stack = [&$pointer];
$iterator = new \RecursiveIteratorIterator(
new \RecursiveArrayIterator($value), \RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $key => $val)
{
array_splice($stack, $iterator->getDepth() + 1);
$pointer = &$stack[count($stack) - 1];
$pointer = &$pointer[$key];
$stack[] = &$pointer;
if (! $iterator->hasChildren())
{
$pointer[$field] = $val;
}
}
}
}
return $output;
}
//--------------------------------------------------------------------
/**
* Navigate through a array looking for a particular index
*
* @param array $index The index sequence we are navigating down
* @param array $value The portion of the array to process
*
* @return mixed
*/
protected function getValueDotNotationSyntax(array $index, array $value)
{
if (is_array($index) && ! empty($index))
{
$current_index = array_shift($index);
}
if (is_array($index) && $index && is_array($value[$current_index]) && $value[$current_index])
{
return $this->getValueDotNotationSyntax($index, $value[$current_index]);
}
return (isset($value[$current_index])) ? $value[$current_index] : null;
}
}

View File

@@ -0,0 +1,413 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP\Files;
use CodeIgniter\Files\File;
use CodeIgniter\HTTP\Exceptions\HTTPException;
use Config\Mimes;
use Exception;
/**
* Value object representing a single file uploaded through an
* HTTP request. Used by the IncomingRequest class to
* provide files.
*
* Typically, implementors will extend the SplFileInfo class.
*
* @package CodeIgniter\HTTP
*/
class UploadedFile extends File implements UploadedFileInterface
{
/**
* The path to the temporary file.
*
* @var string
*/
protected $path;
/**
* The original filename as provided by the client.
*
* @var string
*/
protected $originalName;
/**
* The filename given to a file during a move.
*
* @var string
*/
protected $name;
/**
* The type of file as provided by PHP
*
* @var string
*/
protected $originalMimeType;
/**
* The error constant of the upload
* (one of PHP's UPLOADERRXXX constants)
*
* @var integer
*/
protected $error;
/**
* Whether the file has been moved already or not.
*
* @var boolean
*/
protected $hasMoved = false;
//--------------------------------------------------------------------
/**
* Accepts the file information as would be filled in from the $_FILES array.
*
* @param string $path The temporary location of the uploaded file.
* @param string $originalName The client-provided filename.
* @param string $mimeType The type of file as provided by PHP
* @param integer $size The size of the file, in bytes
* @param integer $error The error constant of the upload (one of PHP's UPLOADERRXXX constants)
*/
public function __construct(string $path, string $originalName, string $mimeType = null, int $size = null, int $error = null)
{
$this->path = $path;
$this->name = $originalName;
$this->originalName = $originalName;
$this->originalMimeType = $mimeType;
$this->size = $size;
$this->error = $error;
parent::__construct($path, false);
}
//--------------------------------------------------------------------
/**
* Move the uploaded file to a new location.
*
* $targetPath may be an absolute path, or a relative path. If it is a
* relative path, resolution should be the same as used by PHP's rename()
* function.
*
* The original file MUST be removed on completion.
*
* If this method is called more than once, any subsequent calls MUST raise
* an exception.
*
* When used in an SAPI environment where $_FILES is populated, when writing
* files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be
* used to ensure permissions and upload status are verified correctly.
*
* If you wish to move to a stream, use getStream(), as SAPI operations
* cannot guarantee writing to stream destinations.
*
* @see http://php.net/is_uploaded_file
* @see http://php.net/move_uploaded_file
*
* @param string $targetPath Path to which to move the uploaded file.
* @param string $name the name to rename the file to.
* @param boolean $overwrite State for indicating whether to overwrite the previously generated file with the same
* name or not.
*
* @return boolean
*
* @throws \InvalidArgumentException if the $path specified is invalid.
* @throws \RuntimeException on any error during the move operation.
* @throws \RuntimeException on the second or subsequent call to the method.
*/
public function move(string $targetPath, string $name = null, bool $overwrite = false)
{
$targetPath = $this->setPath($targetPath); //set the target path
if ($this->hasMoved)
{
throw HTTPException::forAlreadyMoved();
}
if (! $this->isValid())
{
throw HTTPException::forInvalidFile();
}
$targetPath = rtrim($targetPath, '/') . '/';
$name = is_null($name) ? $this->getName() : $name;
$destination = $overwrite ? $targetPath . $name : $this->getDestination($targetPath . $name);
try
{
move_uploaded_file($this->path, $destination);
}
catch (Exception $e)
{
$error = error_get_last();
throw HTTPException::forMoveFailed(basename($this->path), $targetPath, strip_tags($error['message']));
}
@chmod($targetPath, 0777 & ~umask());
// Success, so store our new information
$this->path = $targetPath;
$this->name = basename($destination);
$this->hasMoved = true;
return true;
}
/**
* create file target path if
* the set path does not exist
*
* @param string $path
*
* @return string The path set or created.
*/
protected function setPath(string $path): string
{
if (! is_dir($path))
{
mkdir($path, 0777, true);
//create the index.html file
if (! is_file($path . 'index.html'))
{
$file = fopen($path . 'index.html', 'x+');
fclose($file);
}
}
return $path;
}
//--------------------------------------------------------------------
/**
* Returns whether the file has been moved or not. If it has,
* the move() method will not work and certain properties, like
* the tempName, will no longer be available.
*
* @return boolean
*/
public function hasMoved(): bool
{
return $this->hasMoved;
}
//--------------------------------------------------------------------
/**
* Retrieve the error associated with the uploaded file.
*
* The return value MUST be one of PHP's UPLOAD_ERR_XXX constants.
*
* If the file was uploaded successfully, this method MUST return
* UPLOAD_ERR_OK.
*
* Implementations SHOULD return the value stored in the "error" key of
* the file in the $_FILES array.
*
* @see http://php.net/manual/en/features.file-upload.errors.php
* @return integer One of PHP's UPLOAD_ERR_XXX constants.
*/
public function getError(): int
{
if (is_null($this->error))
{
return UPLOAD_ERR_OK;
}
return $this->error;
}
//--------------------------------------------------------------------
/**
* Get error string
*
* @return string
*/
public function getErrorString(): string
{
$errors = [
UPLOAD_ERR_OK => lang('HTTP.uploadErrOk'),
UPLOAD_ERR_INI_SIZE => lang('HTTP.uploadErrIniSize'),
UPLOAD_ERR_FORM_SIZE => lang('HTTP.uploadErrFormSize'),
UPLOAD_ERR_PARTIAL => lang('HTTP.uploadErrPartial'),
UPLOAD_ERR_NO_FILE => lang('HTTP.uploadErrNoFile'),
UPLOAD_ERR_CANT_WRITE => lang('HTTP.uploadErrCantWrite'),
UPLOAD_ERR_NO_TMP_DIR => lang('HTTP.uploadErrNoTmpDir'),
UPLOAD_ERR_EXTENSION => lang('HTTP.uploadErrExtension'),
];
$error = is_null($this->error) ? UPLOAD_ERR_OK : $this->error;
return sprintf($errors[$error] ?? lang('HTTP.uploadErrUnknown'), $this->getName());
}
//--------------------------------------------------------------------
/**
* Returns the mime type as provided by the client.
* This is NOT a trusted value.
* For a trusted version, use getMimeType() instead.
*
* @return string The media type sent by the client or null if none was provided.
*/
public function getClientMimeType(): string
{
return $this->originalMimeType;
}
//--------------------------------------------------------------------
/**
* Retrieve the filename. This will typically be the filename sent
* by the client, and should not be trusted. If the file has been
* moved, this will return the final name of the moved file.
*
* @return string The filename sent by the client or null if none was provided.
*/
public function getName(): string
{
return $this->name;
}
//--------------------------------------------------------------------
/**
* Returns the name of the file as provided by the client during upload.
*
* @return string
*/
public function getClientName(): string
{
return $this->originalName;
}
//--------------------------------------------------------------------
/**
* Gets the temporary filename where the file was uploaded to.
*
* @return string
*/
public function getTempName(): string
{
return $this->path;
}
//--------------------------------------------------------------------
/**
* Overrides SPLFileInfo's to work with uploaded files, since
* the temp file that's been uploaded doesn't have an extension.
*
* Is simply an alias for guessExtension for a safer method
* than simply relying on the provided extension.
* Additionally it will return clientExtension in case if there are
* other extensions with the same mime type.
*/
public function getExtension(): string
{
return $this->guessExtension();
}
/**
* Attempts to determine the best file extension.
*
* @return string|null
*/
public function guessExtension(): string
{
return Mimes::guessExtensionFromType($this->getClientMimeType(), $this->getClientExtension()) ?? $this->getClientExtension();
}
//--------------------------------------------------------------------
/**
* Returns the original file extension, based on the file name that
* was uploaded. This is NOT a trusted source.
* For a trusted version, use guessExtension() instead.
*
* @return string
*/
public function getClientExtension(): string
{
return pathinfo($this->originalName, PATHINFO_EXTENSION) ?? '';
}
//--------------------------------------------------------------------
/**
* Returns whether the file was uploaded successfully, based on whether
* it was uploaded via HTTP and has no errors.
*
* @return boolean
*/
public function isValid(): bool
{
return is_uploaded_file($this->path) && $this->error === UPLOAD_ERR_OK;
}
/**
* Save the uploaded file to a new location.
*
* By default, upload files are saved in writable/uploads directory. The YYYYMMDD folder
* and random file name will be created.
*
* @param string $folderName the folder name to writable/uploads directory.
* @param string $fileName the name to rename the file to.
* @return string file full path
*/
public function store(string $folderName = null, string $fileName = null): string
{
$folderName = rtrim($folderName ?? date('Ymd'), '/') . '/' ;
$fileName = $fileName ?? $this->getRandomName();
// Move the uploaded file to a new location.
return ($this->move(WRITEPATH . 'uploads/' . $folderName, $fileName)) ?
$folderName . $this->name : null;
}
//--------------------------------------------------------------------
}

View File

@@ -0,0 +1,203 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP\Files;
/**
* Value object representing a single file uploaded through an
* HTTP request. Used by the IncomingRequest class to
* provide files.
*
* Typically, implementors will extend the SplFileInfo class.
*
* @package CodeIgniter\HTTP
*/
interface UploadedFileInterface
{
/**
* Accepts the file information as would be filled in from the $_FILES array.
*
* @param string $path The temporary location of the uploaded file.
* @param string $originalName The client-provided filename.
* @param string $mimeType The type of file as provided by PHP
* @param integer $size The size of the file, in bytes
* @param integer $error The error constant of the upload (one of PHP's UPLOADERRXXX constants)
*/
public function __construct(string $path, string $originalName, string $mimeType = null, int $size = null, int $error = null);
//--------------------------------------------------------------------
/**
* Move the uploaded file to a new location.
*
* $targetPath may be an absolute path, or a relative path. If it is a
* relative path, resolution should be the same as used by PHP's rename()
* function.
*
* The original file MUST be removed on completion.
*
* If this method is called more than once, any subsequent calls MUST raise
* an exception.
*
* When used in an SAPI environment where $_FILES is populated, when writing
* files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be
* used to ensure permissions and upload status are verified correctly.
*
* If you wish to move to a stream, use getStream(), as SAPI operations
* cannot guarantee writing to stream destinations.
*
* @see http://php.net/is_uploaded_file
* @see http://php.net/move_uploaded_file
*
* @param string $targetPath Path to which to move the uploaded file.
* @param string $name the name to rename the file to.
*
* @throws \InvalidArgumentException if the $path specified is invalid.
* @throws \RuntimeException on any error during the move operation.
* @throws \RuntimeException on the second or subsequent call to the method.
*/
public function move(string $targetPath, string $name = null);
//--------------------------------------------------------------------
/**
* Returns whether the file has been moved or not. If it has,
* the move() method will not work and certain properties, like
* the tempName, will no longer be available.
*
* @return boolean
*/
public function hasMoved(): bool;
//--------------------------------------------------------------------
/**
* Retrieve the error associated with the uploaded file.
*
* The return value MUST be one of PHP's UPLOAD_ERR_XXX constants.
*
* If the file was uploaded successfully, this method MUST return
* UPLOAD_ERR_OK.
*
* Implementations SHOULD return the value stored in the "error" key of
* the file in the $_FILES array.
*
* @see http://php.net/manual/en/features.file-upload.errors.php
* @return integer One of PHP's UPLOAD_ERR_XXX constants.
*/
public function getError(): int;
//--------------------------------------------------------------------
/**
* Retrieve the filename sent by the client.
*
* Do not trust the value returned by this method. A client could send
* a malicious filename with the intention to corrupt or hack your
* application.
*
* Implementations SHOULD return the value stored in the "name" key of
* the file in the $_FILES array.
*
* @return string|null The filename sent by the client or null if none
* was provided.
*/
public function getName(): string;
//--------------------------------------------------------------------
/**
* Gets the temporary filename where the file was uploaded to.
*
* @return string
*/
public function getTempName(): string;
//--------------------------------------------------------------------
/**
* Returns the original file extension, based on the file name that
* was uploaded. This is NOT a trusted source.
* For a trusted version, use guessExtension() instead.
*
* @return string|null
*/
public function getClientExtension(): string;
//--------------------------------------------------------------------
/**
* Returns the mime type as provided by the client.
* This is NOT a trusted value.
* For a trusted version, use getMimeType() instead.
*
* @return string|null
*/
public function getClientMimeType(): string;
//--------------------------------------------------------------------
/**
* Returns whether the file was uploaded successfully, based on whether
* it was uploaded via HTTP and has no errors.
*
* @return boolean
*/
public function isValid(): bool;
//--------------------------------------------------------------------
/**
* Returns the destination path for the move operation where overwriting is not expected.
*
* First, it checks whether the delimiter is present in the filename, if it is, then it checks whether the
* last element is an integer as there may be cases that the delimiter may be present in the filename.
* For the all other cases, it appends an integer starting from zero before the file's extension.
*
* @param string $destination
* @param string $delimiter
* @param integer $i
*
* @return string
*/
public function getDestination(string $destination, string $delimiter = '_', int $i = 0): string;
//--------------------------------------------------------------------
}

239
system/HTTP/Header.php Normal file
View File

@@ -0,0 +1,239 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP;
/**
* Class Header
*
* Represents a single HTTP header.
*
* @package CodeIgniter\HTTP
*/
class Header
{
/**
* The name of the header.
*
* @var string
*/
protected $name;
/**
* The value of the header. May have more than one
* value. If so, will be an array of strings.
*
* @var string|array
*/
protected $value;
//--------------------------------------------------------------------
/**
* Header constructor. name is mandatory, if a value is provided, it will be set.
*
* @param string $name
* @param string|array|null $value
*/
public function __construct(string $name, $value = null)
{
$this->name = $name;
$this->value = $value;
}
//--------------------------------------------------------------------
/**
* Returns the name of the header, in the same case it was set.
*
* @return string
*/
public function getName(): string
{
return $this->name;
}
//--------------------------------------------------------------------
/**
* Gets the raw value of the header. This may return either a string
* of an array, depending on whether the header has multiple values or not.
*
* @return array|null|string
*/
public function getValue()
{
return $this->value;
}
//--------------------------------------------------------------------
/**
* Sets the name of the header, overwriting any previous value.
*
* @param string $name
*
* @return $this
*/
public function setName(string $name)
{
$this->name = $name;
return $this;
}
//--------------------------------------------------------------------
/**
* Sets the value of the header, overwriting any previous value(s).
*
* @param null $value
*
* @return $this
*/
public function setValue($value = null)
{
$this->value = $value;
return $this;
}
//--------------------------------------------------------------------
/**
* Appends a value to the list of values for this header. If the
* header is a single value string, it will be converted to an array.
*
* @param null $value
*
* @return $this
*/
public function appendValue($value = null)
{
if (! is_array($this->value))
{
$this->value = [$this->value];
}
$this->value[] = $value;
return $this;
}
//--------------------------------------------------------------------
/**
* Prepends a value to the list of values for this header. If the
* header is a single value string, it will be converted to an array.
*
* @param null $value
*
* @return $this
*/
public function prependValue($value = null)
{
if (! is_array($this->value))
{
$this->value = [$this->value];
}
array_unshift($this->value, $value);
return $this;
}
//--------------------------------------------------------------------
/**
* Retrieves a comma-separated string of the values for a single header.
*
* NOTE: Not all header values may be appropriately represented using
* comma concatenation. For such headers, use getHeader() instead
* and supply your own delimiter when concatenating.
*
* @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
*/
public function getValueLine(): string
{
if (is_string($this->value))
{
return $this->value;
}
else if (! is_array($this->value))
{
return '';
}
$options = [];
foreach ($this->value as $key => $value)
{
if (is_string($key) && ! is_array($value))
{
$options[] = $key . '=' . $value;
}
else if (is_array($value))
{
$key = key($value);
$options[] = $key . '=' . $value[$key];
}
else if (is_numeric($key))
{
$options[] = $value;
}
}
return implode(', ', $options);
}
//--------------------------------------------------------------------
/**
* Returns a representation of the entire header string, including
* the header name and all values converted to the proper format.
*
* @return string
*/
public function __toString(): string
{
return $this->name . ': ' . $this->getValueLine();
}
//--------------------------------------------------------------------
}

View File

@@ -0,0 +1,822 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP;
use CodeIgniter\HTTP\Exceptions\HTTPException;
use CodeIgniter\HTTP\Files\FileCollection;
use CodeIgniter\HTTP\Files\UploadedFile;
use CodeIgniter\Config\Services;
/**
* Class IncomingRequest
*
* Represents an incoming, getServer-side HTTP request.
*
* Per the HTTP specification, this interface includes properties for
* each of the following:
*
* - Protocol version
* - HTTP method
* - URI
* - Headers
* - Message body
*
* Additionally, it encapsulates all data as it has arrived to the
* application from the CGI and/or PHP environment, including:
*
* - The values represented in $_SERVER.
* - Any cookies provided (generally via $_COOKIE)
* - Query string arguments (generally via $_GET, or as parsed via parse_str())
* - Upload files, if any (as represented by $_FILES)
* - Deserialized body binds (generally from $_POST)
*
* @package CodeIgniter\HTTP
*/
class IncomingRequest extends Request
{
/**
* Enable CSRF flag
*
* Enables a CSRF cookie token to be set.
* Set automatically based on Config setting.
*
* @var boolean
*/
protected $enableCSRF = false;
/**
* A \CodeIgniter\HTTP\URI instance.
*
* @var URI
*/
public $uri;
/**
* File collection
*
* @var Files\FileCollection
*/
protected $files;
/**
* Negotiator
*
* @var \CodeIgniter\HTTP\Negotiate
*/
protected $negotiator;
/**
* The default Locale this request
* should operate under.
*
* @var string
*/
protected $defaultLocale;
/**
* The current locale of the application.
* Default value is set in Config\App.php
*
* @var string
*/
protected $locale;
/**
* Stores the valid locale codes.
*
* @var array
*/
protected $validLocales = [];
/**
* Configuration settings.
*
* @var \Config\App
*/
public $config;
/**
* Holds the old data from a redirect.
*
* @var array
*/
protected $oldInput = [];
/**
* The user agent this request is from.
*
* @var \CodeIgniter\HTTP\UserAgent
*/
protected $userAgent;
//--------------------------------------------------------------------
/**
* Constructor
*
* @param object $config
* @param \CodeIgniter\HTTP\URI $uri
* @param string|null $body
* @param \CodeIgniter\HTTP\UserAgent $userAgent
*/
public function __construct($config, URI $uri = null, $body = 'php://input', UserAgent $userAgent)
{
// Get our body from php://input
if ($body === 'php://input')
{
$body = file_get_contents('php://input');
}
$this->body = $body;
$this->config = $config;
$this->userAgent = $userAgent;
parent::__construct($config);
$this->populateHeaders();
$this->uri = $uri;
$this->detectURI($config->uriProtocol, $config->baseURL);
$this->validLocales = $config->supportedLocales;
$this->detectLocale($config);
}
//--------------------------------------------------------------------
/**
* Handles setting up the locale, perhaps auto-detecting through
* content negotiation.
*
* @param $config
*/
public function detectLocale($config)
{
$this->locale = $this->defaultLocale = $config->defaultLocale;
if (! $config->negotiateLocale)
{
return;
}
$this->setLocale($this->negotiate('language', $config->supportedLocales));
}
//--------------------------------------------------------------------
/**
* Returns the default locale as set in Config\App.php
*
* @return string
*/
public function getDefaultLocale(): string
{
return $this->defaultLocale;
}
//--------------------------------------------------------------------
/**
* Gets the current locale, with a fallback to the default
* locale if none is set.
*
* @return string
*/
public function getLocale(): string
{
return $this->locale ?? $this->defaultLocale;
}
//--------------------------------------------------------------------
/**
* Sets the locale string for this request.
*
* @param string $locale
*
* @return IncomingRequest
*/
public function setLocale(string $locale)
{
// If it's not a valid locale, set it
// to the default locale for the site.
if (! in_array($locale, $this->validLocales))
{
$locale = $this->defaultLocale;
}
$this->locale = $locale;
// If the intl extension is loaded, make sure
// that we set the locale for it... if not, though,
// don't worry about it.
// this should not block code coverage thru unit testing
// @codeCoverageIgnoreStart
try
{
if (class_exists('\Locale', false))
{
\Locale::setDefault($locale);
}
}
catch (\Exception $e)
{
}
// @codeCoverageIgnoreEnd
return $this;
}
//--------------------------------------------------------------------
/**
* Determines if this request was made from the command line (CLI).
*
* @return boolean
*/
public function isCLI(): bool
{
return is_cli();
}
//--------------------------------------------------------------------
/**
* Test to see if a request contains the HTTP_X_REQUESTED_WITH header.
*
* @return boolean
*/
public function isAJAX(): bool
{
return ( ! empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
}
//--------------------------------------------------------------------
/**
* Attempts to detect if the current connection is secure through
* a few different methods.
*
* @return boolean
*/
public function isSecure(): bool
{
if (! empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off')
{
return true;
}
elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
{
return true;
}
elseif (! empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) !== 'off')
{
return true;
}
return false;
}
//--------------------------------------------------------------------
/**
* Fetch an item from the $_REQUEST object. This is the simplest way
* to grab data from the request object and can be used in lieu of the
* other get* methods in most cases.
*
* @param string|array|null $index
* @param integer|null $filter Filter constant
* @param mixed $flags
*
* @return mixed
*/
public function getVar($index = null, $filter = null, $flags = null)
{
return $this->fetchGlobal('request', $index, $filter, $flags);
}
//--------------------------------------------------------------------
/**
* A convenience method that grabs the raw input stream and decodes
* the JSON into an array.
*
* If $assoc == true, then all objects in the response will be converted
* to associative arrays.
*
* @param boolean $assoc Whether to return objects as associative arrays
* @param integer $depth How many levels deep to decode
* @param integer $options Bitmask of options
*
* @see http://php.net/manual/en/function.json-decode.php
*
* @return mixed
*/
public function getJSON(bool $assoc = false, int $depth = 512, int $options = 0)
{
return json_decode($this->body, $assoc, $depth, $options);
}
//--------------------------------------------------------------------
/**
* A convenience method that grabs the raw input stream(send method in PUT, PATCH, DELETE) and decodes
* the String into an array.
*
* @return mixed
*/
public function getRawInput()
{
parse_str($this->body, $output);
return $output;
}
//--------------------------------------------------------------------
/**
* Fetch an item from GET data.
*
* @param string|array|null $index Index for item to fetch from $_GET.
* @param integer|null $filter A filter name to apply.
* @param mixed|null $flags
*
* @return mixed
*/
public function getGet($index = null, $filter = null, $flags = null)
{
return $this->fetchGlobal('get', $index, $filter, $flags);
}
//--------------------------------------------------------------------
/**
* Fetch an item from POST.
*
* @param string|array|null $index Index for item to fetch from $_POST.
* @param integer|null $filter A filter name to apply
* @param mixed $flags
*
* @return mixed
*/
public function getPost($index = null, $filter = null, $flags = null)
{
return $this->fetchGlobal('post', $index, $filter, $flags);
}
//--------------------------------------------------------------------
/**
* Fetch an item from POST data with fallback to GET.
*
* @param string|array|null $index Index for item to fetch from $_POST or $_GET
* @param integer|null $filter A filter name to apply
* @param mixed $flags
*
* @return mixed
*/
public function getPostGet($index = null, $filter = null, $flags = null)
{
// Use $_POST directly here, since filter_has_var only
// checks the initial POST data, not anything that might
// have been added since.
return isset($_POST[$index]) ? $this->getPost($index, $filter, $flags) : $this->getGet($index, $filter, $flags);
}
//--------------------------------------------------------------------
/**
* Fetch an item from GET data with fallback to POST.
*
* @param string|array|null $index Index for item to be fetched from $_GET or $_POST
* @param integer|null $filter A filter name to apply
* @param mixed $flags
*
* @return mixed
*/
public function getGetPost($index = null, $filter = null, $flags = null)
{
// Use $_GET directly here, since filter_has_var only
// checks the initial GET data, not anything that might
// have been added since.
return isset($_GET[$index]) ? $this->getGet($index, $filter, $flags) : $this->getPost($index, $filter, $flags);
}
//--------------------------------------------------------------------
/**
* Fetch an item from the COOKIE array.
*
* @param string|array|null $index Index for item to be fetched from $_COOKIE
* @param integer|null $filter A filter name to be applied
* @param mixed $flags
*
* @return mixed
*/
public function getCookie($index = null, $filter = null, $flags = null)
{
return $this->fetchGlobal('cookie', $index, $filter, $flags);
}
//--------------------------------------------------------------------
/**
* Fetch the user agent string
*
* @return \CodeIgniter\HTTP\UserAgent
*/
public function getUserAgent()
{
return $this->userAgent;
}
//--------------------------------------------------------------------
/**
* Attempts to get old Input data that has been flashed to the session
* with redirect_with_input(). It first checks for the data in the old
* POST data, then the old GET data and finally check for dot arrays
*
* @param string $key
*
* @return mixed
*/
public function getOldInput(string $key)
{
// If the session hasn't been started, or no
// data was previously saved, we're done.
if (empty($_SESSION['_ci_old_input']))
{
return;
}
// Check for the value in the POST array first.
if (isset($_SESSION['_ci_old_input']['post'][$key]))
{
return $_SESSION['_ci_old_input']['post'][$key];
}
// Next check in the GET array.
if (isset($_SESSION['_ci_old_input']['get'][$key]))
{
return $_SESSION['_ci_old_input']['get'][$key];
}
helper('array');
// Check for an array value in POST.
if (isset($_SESSION['_ci_old_input']['post']))
{
$value = dot_array_search($key, $_SESSION['_ci_old_input']['post']);
if (! is_null($value))
{
return $value;
}
}
// Check for an array value in GET.
if (isset($_SESSION['_ci_old_input']['get']))
{
$value = dot_array_search($key, $_SESSION['_ci_old_input']['get']);
if (! is_null($value))
{
return $value;
}
}
// // return null if requested session key not found
// return null;
}
/**
* Returns an array of all files that have been uploaded with this
* request. Each file is represented by an UploadedFile instance.
*
* @return array
*/
public function getFiles(): array
{
if (is_null($this->files))
{
$this->files = new FileCollection();
}
return $this->files->all(); // return all files
}
//--------------------------------------------------------------------
/**
* Verify if a file exist, by the name of the input field used to upload it, in the collection
* of uploaded files and if is have been uploaded with multiple option.
*
* @param string $fileID
*
* @return array|null
*/
public function getFileMultiple(string $fileID)
{
if (is_null($this->files))
{
$this->files = new FileCollection();
}
return $this->files->getFileMultiple($fileID);
}
//--------------------------------------------------------------------
/**
* Retrieves a single file by the name of the input field used
* to upload it.
*
* @param string $fileID
*
* @return UploadedFile|null
*/
public function getFile(string $fileID)
{
if (is_null($this->files))
{
$this->files = new FileCollection();
}
return $this->files->getFile($fileID);
}
//--------------------------------------------------------------------
/**
* Sets up our URI object based on the information we have. This is
* either provided by the user in the baseURL Config setting, or
* determined from the environment as needed.
*
* @param string $protocol
* @param string $baseURL
*/
protected function detectURI(string $protocol, string $baseURL)
{
$this->uri->setPath($this->detectPath($protocol));
// It's possible the user forgot a trailing slash on their
// baseURL, so let's help them out.
$baseURL = ! empty($baseURL) ? rtrim($baseURL, '/ ') . '/' : $baseURL;
// Based on our baseURL provided by the developer (if set)
// set our current domain name, scheme
if (! empty($baseURL))
{
// We cannot add the path here, otherwise it's possible
// that the routing will not work correctly if we are
// within a sub-folder scheme. So it's modified in
// the
$this->uri->setScheme(parse_url($baseURL, PHP_URL_SCHEME));
$this->uri->setHost(parse_url($baseURL, PHP_URL_HOST));
$this->uri->setPort(parse_url($baseURL, PHP_URL_PORT));
$this->uri->resolveRelativeURI(parse_url($baseURL, PHP_URL_PATH));
// Ensure we have any query vars
$this->uri->setQuery($_SERVER['QUERY_STRING'] ?? '');
}
else
{
// @codeCoverageIgnoreStart
if (! is_cli())
{
die('You have an empty or invalid base URL. The baseURL value must be set in Config\App.php, or through the .env file.');
}
// @codeCoverageIgnoreEnd
}
}
//--------------------------------------------------------------------
/**
* Based on the URIProtocol Config setting, will attempt to
* detect the path portion of the current URI.
*
* @param string $protocol
*
* @return string
*/
public function detectPath(string $protocol = ''): string
{
if (empty($protocol))
{
$protocol = 'REQUEST_URI';
}
switch ($protocol)
{
case 'REQUEST_URI':
$path = $this->parseRequestURI();
break;
case 'QUERY_STRING':
$path = $this->parseQueryString();
break;
case 'PATH_INFO':
default:
$path = $this->fetchGlobal('server', $protocol) ?? $this->parseRequestURI();
break;
}
return $path;
}
//--------------------------------------------------------------------
/**
* Provides a convenient way to work with the Negotiate class
* for content negotiation.
*
* @param string $type
* @param array $supported
* @param boolean $strictMatch
*
* @return string
*/
public function negotiate(string $type, array $supported, bool $strictMatch = false): string
{
if (is_null($this->negotiator))
{
$this->negotiator = Services::negotiator($this, true);
}
switch (strtolower($type))
{
case 'media':
return $this->negotiator->media($supported, $strictMatch);
case 'charset':
return $this->negotiator->charset($supported);
case 'encoding':
return $this->negotiator->encoding($supported);
case 'language':
return $this->negotiator->language($supported);
}
throw HTTPException::forInvalidNegotiationType($type);
}
//--------------------------------------------------------------------
/**
* Will parse the REQUEST_URI and automatically detect the URI from it,
* fixing the query string if necessary.
*
* @return string The URI it found.
*/
protected function parseRequestURI(): string
{
if (! isset($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME']))
{
return '';
}
// parse_url() returns false if no host is present, but the path or query string
// contains a colon followed by a number
$parts = parse_url('http://dummy' . $_SERVER['REQUEST_URI']);
$query = $parts['query'] ?? '';
$uri = $parts['path'] ?? '';
if (isset($_SERVER['SCRIPT_NAME'][0]))
{
// strip the script name from the beginning of the URI
if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0)
{
$uri = (string) substr($uri, strlen($_SERVER['SCRIPT_NAME']));
}
// if the script is nested, strip the parent folder & script from the URI
elseif (strpos($uri, $_SERVER['SCRIPT_NAME']) > 0)
{
$uri = (string) substr($uri, strpos($uri, $_SERVER['SCRIPT_NAME']) + strlen($_SERVER['SCRIPT_NAME']));
}
// or if index.php is implied
elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0)
{
$uri = (string) substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME'])));
}
}
// This section ensures that even on servers that require the URI to contain the query string (Nginx) a correct
// URI is found, and also fixes the QUERY_STRING getServer var and $_GET array.
if (trim($uri, '/') === '' && strncmp($query, '/', 1) === 0)
{
$query = explode('?', $query, 2);
$uri = $query[0];
$_SERVER['QUERY_STRING'] = $query[1] ?? '';
}
else
{
$_SERVER['QUERY_STRING'] = $query;
}
parse_str($_SERVER['QUERY_STRING'], $_GET);
if ($uri === '/' || $uri === '')
{
return '/';
}
return $this->removeRelativeDirectory($uri);
}
//--------------------------------------------------------------------
/**
* Parse QUERY_STRING
*
* Will parse QUERY_STRING and automatically detect the URI from it.
*
* @return string
*/
protected function parseQueryString(): string
{
$uri = $_SERVER['QUERY_STRING'] ?? @getenv('QUERY_STRING');
if (trim($uri, '/') === '')
{
return '';
}
elseif (strncmp($uri, '/', 1) === 0)
{
$uri = explode('?', $uri, 2);
$_SERVER['QUERY_STRING'] = $uri[1] ?? '';
$uri = $uri[0];
}
parse_str($_SERVER['QUERY_STRING'], $_GET);
return $this->removeRelativeDirectory($uri);
}
//--------------------------------------------------------------------
/**
* Remove relative directory (../) and multi slashes (///)
*
* Do some final cleaning of the URI and return it, currently only used in static::_parse_request_uri()
*
* @param string $uri
*
* @return string
*/
protected function removeRelativeDirectory(string $uri): string
{
$uris = [];
$tok = strtok($uri, '/');
while ($tok !== false)
{
if (( ! empty($tok) || $tok === '0') && $tok !== '..')
{
$uris[] = $tok;
}
$tok = strtok('/');
}
return implode('/', $uris);
}
// --------------------------------------------------------------------
}

400
system/HTTP/Message.php Normal file
View File

@@ -0,0 +1,400 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP;
use CodeIgniter\HTTP\Exceptions\HTTPException;
/**
* An HTTP message
*/
class Message
{
/**
* List of all HTTP request headers.
*
* @var array
*/
protected $headers = [];
/**
* Holds a map of lower-case header names
* and their normal-case key as it is in $headers.
* Used for case-insensitive header access.
*
* @var array
*/
protected $headerMap = [];
/**
* Protocol version
*
* @var string
*/
protected $protocolVersion;
/**
* List of valid protocol versions
*
* @var array
*/
protected $validProtocolVersions = [
'1.0',
'1.1',
'2',
];
/**
* Message body
*
* @var string
*/
protected $body;
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// Body
//--------------------------------------------------------------------
/**
* Returns the Message's body.
*
* @return mixed
*/
public function getBody()
{
return $this->body;
}
//--------------------------------------------------------------------
/**
* Sets the body of the current message.
*
* @param $data
*
* @return Message|Response
*/
public function setBody($data)
{
$this->body = $data;
return $this;
}
//--------------------------------------------------------------------
/**
* Appends data to the body of the current message.
*
* @param $data
*
* @return Message|Response
*/
public function appendBody($data)
{
$this->body .= (string) $data;
return $this;
}
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// Headers
//--------------------------------------------------------------------
/**
* Populates the $headers array with any headers the getServer knows about.
*/
public function populateHeaders()
{
$contentType = $_SERVER['CONTENT_TYPE'] ?? getenv('CONTENT_TYPE');
if (! empty($contentType))
{
$this->setHeader('Content-Type', $contentType);
}
unset($contentType);
foreach ($_SERVER as $key => $val)
{
if (sscanf($key, 'HTTP_%s', $header) === 1)
{
// take SOME_HEADER and turn it into Some-Header
$header = str_replace('_', ' ', strtolower($header));
$header = str_replace(' ', '-', ucwords($header));
$this->setHeader($header, $_SERVER[$key]);
// Add us to the header map so we can find them case-insensitively
$this->headerMap[strtolower($header)] = $header;
}
}
}
//--------------------------------------------------------------------
/**
* Returns an array containing all headers.
*
* @return array An array of the request headers
*/
public function getHeaders(): array
{
// If no headers are defined, but the user is
// requesting it, then it's likely they want
// it to be populated so do that...
if (empty($this->headers))
{
$this->populateHeaders();
}
return $this->headers;
}
//--------------------------------------------------------------------
/**
* Returns a single header object. If multiple headers with the same
* name exist, then will return an array of header objects.
*
* @param string $name
*
* @return array|\CodeIgniter\HTTP\Header
*/
public function getHeader(string $name)
{
$orig_name = $this->getHeaderName($name);
if (! isset($this->headers[$orig_name]))
{
return null;
}
return $this->headers[$orig_name];
}
//--------------------------------------------------------------------
/**
* Determines whether a header exists.
*
* @param string $name
*
* @return boolean
*/
public function hasHeader(string $name): bool
{
$orig_name = $this->getHeaderName($name);
return isset($this->headers[$orig_name]);
}
//--------------------------------------------------------------------
/**
* Retrieves a comma-separated string of the values for a single header.
*
* This method returns all of the header values of the given
* case-insensitive header name as a string concatenated together using
* a comma.
*
* NOTE: Not all header values may be appropriately represented using
* comma concatenation. For such headers, use getHeader() instead
* and supply your own delimiter when concatenating.
*
* @param string $name
*
* @return string
*/
public function getHeaderLine(string $name): string
{
$orig_name = $this->getHeaderName($name);
if (! array_key_exists($orig_name, $this->headers))
{
return '';
}
return $this->headers[$orig_name]->getValueLine();
}
//--------------------------------------------------------------------
/**
* Sets a header and it's value.
*
* @param string $name
* @param array|null|string $value
*
* @return Message|Response
*/
public function setHeader(string $name, $value)
{
$origName = $this->getHeaderName($name);
if (isset($this->headers[$origName]) && is_array($this->headers[$origName]))
{
$this->appendHeader($origName, $value);
}
else
{
$this->headers[$origName] = new Header($origName, $value);
$this->headerMap[strtolower($origName)] = $origName;
}
return $this;
}
//--------------------------------------------------------------------
/**
* Removes a header from the list of headers we track.
*
* @param string $name
*
* @return Message
*/
public function removeHeader(string $name)
{
$orig_name = $this->getHeaderName($name);
unset($this->headers[$orig_name]);
unset($this->headerMap[strtolower($name)]);
return $this;
}
//--------------------------------------------------------------------
/**
* Adds an additional header value to any headers that accept
* multiple values (i.e. are an array or implement ArrayAccess)
*
* @param string $name
* @param string $value
*
* @return Message
*/
public function appendHeader(string $name, string $value)
{
$orig_name = $this->getHeaderName($name);
$this->headers[$orig_name]->appendValue($value);
return $this;
}
//--------------------------------------------------------------------
/**
* Adds an additional header value to any headers that accept
* multiple values (i.e. are an array or implement ArrayAccess)
*
* @param string $name
* @param string $value
*
* @return Message
*/
public function prependHeader(string $name, string $value)
{
$orig_name = $this->getHeaderName($name);
$this->headers[$orig_name]->prependValue($value);
return $this;
}
//--------------------------------------------------------------------
/**
* Returns the HTTP Protocol Version.
*
* @return string
*/
public function getProtocolVersion(): string
{
return $this->protocolVersion ?? '1.1';
}
//--------------------------------------------------------------------
/**
* Sets the HTTP protocol version.
*
* @param string $version
*
* @return Message
*/
public function setProtocolVersion(string $version)
{
if (! is_numeric($version))
{
$version = substr($version, strpos($version, '/') + 1);
}
if (! in_array($version, $this->validProtocolVersions))
{
throw HTTPException::forInvalidHTTPProtocol(implode(', ', $this->validProtocolVersions));
}
$this->protocolVersion = $version;
return $this;
}
//--------------------------------------------------------------------
/**
* Takes a header name in any case, and returns the
* normal-case version of the header.
*
* @param string $name
*
* @return string
*/
protected function getHeaderName(string $name): string
{
$lower_name = strtolower($name);
return $this->headerMap[$lower_name] ?? $name;
}
//--------------------------------------------------------------------
}

432
system/HTTP/Negotiate.php Normal file
View File

@@ -0,0 +1,432 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP;
use CodeIgniter\HTTP\Exceptions\HTTPException;
/**
* Class Negotiate
*
* Provides methods to negotiate with the HTTP headers to determine the best
* type match between what the application supports and what the requesting
* getServer wants.
*
* @see http://tools.ietf.org/html/rfc7231#section-5.3
* @package CodeIgniter\HTTP
*/
class Negotiate
{
/**
* Request
*
* @var \CodeIgniter\HTTP\RequestInterface|\CodeIgniter\HTTP\IncomingRequest
*/
protected $request;
//--------------------------------------------------------------------
/**
* Constructor
*
* @param \CodeIgniter\HTTP\RequestInterface $request
*/
public function __construct(RequestInterface $request = null)
{
if (! is_null($request))
{
$this->request = $request;
}
}
//--------------------------------------------------------------------
/**
* Stores the request instance to grab the headers from.
*
* @param RequestInterface $request
*
* @return $this
*/
public function setRequest(RequestInterface $request)
{
$this->request = $request;
return $this;
}
//--------------------------------------------------------------------
/**
* Determines the best content-type to use based on the $supported
* types the application says it supports, and the types requested
* by the client.
*
* If no match is found, the first, highest-ranking client requested
* type is returned.
*
* @param array $supported
* @param boolean $strictMatch If TRUE, will return an empty string when no match found.
* If FALSE, will return the first supported element.
*
* @return string
*/
public function media(array $supported, bool $strictMatch = false): string
{
return $this->getBestMatch($supported, $this->request->getHeaderLine('accept'), true, $strictMatch);
}
//--------------------------------------------------------------------
/**
* Determines the best charset to use based on the $supported
* types the application says it supports, and the types requested
* by the client.
*
* If no match is found, the first, highest-ranking client requested
* type is returned.
*
* @param array $supported
*
* @return string
*/
public function charset(array $supported): string
{
$match = $this->getBestMatch($supported, $this->request->getHeaderLine('accept-charset'), false, true);
// If no charset is shown as a match, ignore the directive
// as allowed by the RFC, and tell it a default value.
if (empty($match))
{
return 'utf-8';
}
return $match;
}
//--------------------------------------------------------------------
/**
* Determines the best encoding type to use based on the $supported
* types the application says it supports, and the types requested
* by the client.
*
* If no match is found, the first, highest-ranking client requested
* type is returned.
*
* @param array $supported
*
* @return string
*/
public function encoding(array $supported = []): string
{
array_push($supported, 'identity');
return $this->getBestMatch($supported, $this->request->getHeaderLine('accept-encoding'));
}
//--------------------------------------------------------------------
/**
* Determines the best language to use based on the $supported
* types the application says it supports, and the types requested
* by the client.
*
* If no match is found, the first, highest-ranking client requested
* type is returned.
*
* @param array $supported
*
* @return string
*/
public function language(array $supported): string
{
return $this->getBestMatch($supported, $this->request->getHeaderLine('accept-language'));
}
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// Utility Methods
//--------------------------------------------------------------------
/**
* Does the grunt work of comparing any of the app-supported values
* against a given Accept* header string.
*
* Portions of this code base on Aura.Accept library.
*
* @param array $supported App-supported values
* @param string $header header string
* @param boolean $enforceTypes If TRUE, will compare media types and sub-types.
* @param boolean $strictMatch If TRUE, will return empty string on no match.
* If FALSE, will return the first supported element.
*
* @return string Best match
*/
protected function getBestMatch(array $supported, string $header = null, bool $enforceTypes = false, bool $strictMatch = false): string
{
if (empty($supported))
{
throw HTTPException::forEmptySupportedNegotiations();
}
if (empty($header))
{
return $strictMatch ? '' : $supported[0];
}
$acceptable = $this->parseHeader($header);
foreach ($acceptable as $accept)
{
// if acceptable quality is zero, skip it.
if ($accept['q'] === 0.0)
{
continue;
}
// if acceptable value is "anything", return the first available
if ($accept['value'] === '*' || $accept['value'] === '*/*')
{
return $supported[0];
}
// If an acceptable value is supported, return it
foreach ($supported as $available)
{
if ($this->match($accept, $available, $enforceTypes))
{
return $available;
}
}
}
// No matches? Return the first supported element.
return $strictMatch ? '' : $supported[0];
}
//--------------------------------------------------------------------
/**
* Parses an Accept* header into it's multiple values.
*
* This is based on code from Aura.Accept library.
*
* @param string $header
*
* @return array
*/
public function parseHeader(string $header): array
{
$results = [];
$acceptable = explode(',', $header);
foreach ($acceptable as $value)
{
$pairs = explode(';', $value);
$value = $pairs[0];
unset($pairs[0]);
$parameters = [];
foreach ($pairs as $pair)
{
$param = [];
preg_match(
'/^(?P<name>.+?)=(?P<quoted>"|\')?(?P<value>.*?)(?:\k<quoted>)?$/', $pair, $param
);
$parameters[trim($param['name'])] = trim($param['value']);
}
$quality = 1.0;
if (array_key_exists('q', $parameters))
{
$quality = $parameters['q'];
unset($parameters['q']);
}
$results[] = [
'value' => trim($value),
'q' => (float) $quality,
'params' => $parameters,
];
}
// Sort to get the highest results first
usort($results, function ($a, $b) {
if ($a['q'] === $b['q'])
{
$a_ast = substr_count($a['value'], '*');
$b_ast = substr_count($b['value'], '*');
// '*/*' has lower precedence than 'text/*',
// and 'text/*' has lower priority than 'text/plain'
//
// This seems backwards, but needs to be that way
// due to the way PHP7 handles ordering or array
// elements created by reference.
if ($a_ast > $b_ast)
{
return 1;
}
// If the counts are the same, but one element
// has more params than another, it has higher precedence.
//
// This seems backwards, but needs to be that way
// due to the way PHP7 handles ordering or array
// elements created by reference.
if ($a_ast === $b_ast)
{
return count($b['params']) - count($a['params']);
}
return 0;
}
// Still here? Higher q values have precedence.
return ($a['q'] > $b['q']) ? -1 : 1;
});
return $results;
}
//--------------------------------------------------------------------
/**
* Match-maker
*
* @param array $acceptable
* @param string $supported
* @param boolean $enforceTypes
* @return boolean
*/
protected function match(array $acceptable, string $supported, bool $enforceTypes = false): bool
{
$supported = $this->parseHeader($supported);
if (is_array($supported) && count($supported) === 1)
{
$supported = $supported[0];
}
// Is it an exact match?
if ($acceptable['value'] === $supported['value'])
{
return $this->matchParameters($acceptable, $supported);
}
// Do we need to compare types/sub-types? Only used
// by negotiateMedia().
if ($enforceTypes)
{
return $this->matchTypes($acceptable, $supported);
}
return false;
}
//--------------------------------------------------------------------
/**
* Checks two Accept values with matching 'values' to see if their
* 'params' are the same.
*
* @param array $acceptable
* @param array $supported
*
* @return boolean
*/
protected function matchParameters(array $acceptable, array $supported): bool
{
if (count($acceptable['params']) !== count($supported['params']))
{
return false;
}
foreach ($supported['params'] as $label => $value)
{
if (! isset($acceptable['params'][$label]) ||
$acceptable['params'][$label] !== $value)
{
return false;
}
}
return true;
}
//--------------------------------------------------------------------
/**
* Compares the types/subtypes of an acceptable Media type and
* the supported string.
*
* @param array $acceptable
* @param array $supported
*
* @return boolean
*/
public function matchTypes(array $acceptable, array $supported): bool
{
list($aType, $aSubType) = explode('/', $acceptable['value']);
list($sType, $sSubType) = explode('/', $supported['value']);
// If the types don't match, we're done.
if ($aType !== $sType)
{
return false;
}
// If there's an asterisk, we're cool
if ($aSubType === '*')
{
return true;
}
// Otherwise, subtypes must match also.
return $aSubType === $sSubType;
}
//--------------------------------------------------------------------
}

View File

@@ -0,0 +1,172 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP;
use CodeIgniter\HTTP\Exceptions\HTTPException;
use Config\Services;
/**
* Handle a redirect response
*/
class RedirectResponse extends Response
{
/**
* Sets the URI to redirect to and, optionally, the HTTP status code to use.
* If no code is provided it will be automatically determined.
*
* @param string $uri The URI to redirect to
* @param integer|null $code HTTP status code
* @param string $method
*
* @return $this
*/
public function to(string $uri, int $code = null, string $method = 'auto')
{
// If it appears to be a relative URL, then convert to full URL
// for better security.
if (strpos($uri, 'http') !== 0)
{
$url = current_url(true)->resolveRelativeURI($uri);
$uri = (string)$url;
}
return $this->redirect($uri, $method, $code);
}
/**
* Sets the URI to redirect to but as a reverse-routed or named route
* instead of a raw URI.
*
* @param string $route
* @param array $params
* @param integer|null $code
* @param string $method
*
* @return $this
*/
public function route(string $route, array $params = [], int $code = 302, string $method = 'auto')
{
$routes = Services::routes(true);
$route = $routes->reverseRoute($route, ...$params);
if (! $route)
{
throw HTTPException::forInvalidRedirectRoute($route);
}
return $this->redirect(base_url($route), $method, $code);
}
/**
* Helper function to return to previous page.
*
* Example:
* return redirect()->back();
*
* @param integer|null $code
* @param string $method
*
* @return $this
*/
public function back(int $code = null, string $method = 'auto')
{
$this->ensureSession();
return $this->redirect(previous_url(), $method, $code);
}
/**
* Specifies that the current $_GET and $_POST arrays should be
* packaged up with the response. It will then be available via
* the 'old()' helper function.
*
* @return $this
*/
public function withInput()
{
$session = $this->ensureSession();
$input = [
'get' => $_GET ?? [],
'post' => $_POST ?? [],
];
$session->setFlashdata('_ci_old_input', $input);
// If the validator has any errors, transmit those back
// so they can be displayed when the validation is
// handled within a method different than displaying the form.
$validator = Services::validation();
if (! empty($validator->getErrors()))
{
$session->setFlashdata('_ci_validation_errors', serialize($validator->getErrors()));
}
return $this;
}
/**
* Adds a key and message to the session as Flashdata.
*
* @param string $key
* @param string|array $message
*
* @return $this
*/
public function with(string $key, $message)
{
$session = $this->ensureSession();
$session->setFlashdata($key, $message);
return $this;
}
/**
* Ensures the session is loaded and started.
*
* @return \CodeIgniter\Session\Session
*/
protected function ensureSession()
{
return Services::session();
}
}

468
system/HTTP/Request.php Normal file
View File

@@ -0,0 +1,468 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP;
/**
* Representation of an HTTP request.
*/
class Request extends Message implements RequestInterface
{
/**
* IP address of the current user.
*
* @var string
*/
protected $ipAddress = '';
/**
* Proxy IPs
*
* @var string|array
*/
protected $proxyIPs;
/**
* Request method.
*
* @var string
*/
protected $method;
/**
* Stores values we've retrieved from
* PHP globals.
*
* @var array
*/
protected $globals = [];
//--------------------------------------------------------------------
/**
* Constructor.
*
* @param object $config
*/
public function __construct($config)
{
$this->proxyIPs = $config->proxyIPs;
if (empty($this->method))
{
$this->method = $this->getServer('REQUEST_METHOD') ?? 'GET';
}
}
//--------------------------------------------------------------------
/**
* Gets the user's IP address.
*
* @return string IP address
*/
public function getIPAddress(): string
{
if (! empty($this->ipAddress))
{
return $this->ipAddress;
}
$proxy_ips = $this->proxyIPs;
if (! empty($this->proxyIPs) && ! is_array($this->proxyIPs))
{
$proxy_ips = explode(',', str_replace(' ', '', $this->proxyIPs));
}
$this->ipAddress = $this->getServer('REMOTE_ADDR');
if ($proxy_ips)
{
foreach (['HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP'] as $header)
{
if (($spoof = $this->getServer($header)) !== null)
{
// Some proxies typically list the whole chain of IP
// addresses through which the client has reached us.
// e.g. client_ip, proxy_ip1, proxy_ip2, etc.
sscanf($spoof, '%[^,]', $spoof);
if (! $this->isValidIP($spoof))
{
$spoof = null;
}
else
{
break;
}
}
}
if ($spoof)
{
for ($i = 0, $c = count($proxy_ips); $i < $c; $i ++)
{
// Check if we have an IP address or a subnet
if (strpos($proxy_ips[$i], '/') === false)
{
// An IP address (and not a subnet) is specified.
// We can compare right away.
if ($proxy_ips[$i] === $this->ipAddress)
{
$this->ipAddress = $spoof;
break;
}
continue;
}
// We have a subnet ... now the heavy lifting begins
isset($separator) || $separator = $this->isValidIP($this->ipAddress, 'ipv6') ? ':' : '.';
// If the proxy entry doesn't match the IP protocol - skip it
if (strpos($proxy_ips[$i], $separator) === false)
{
continue;
}
// Convert the REMOTE_ADDR IP address to binary, if needed
if (! isset($ip, $sprintf))
{
if ($separator === ':')
{
// Make sure we're have the "full" IPv6 format
$ip = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($this->ipAddress, ':')), $this->ipAddress
)
);
for ($j = 0; $j < 8; $j ++)
{
$ip[$j] = intval($ip[$j], 16);
}
$sprintf = '%016b%016b%016b%016b%016b%016b%016b%016b';
}
else
{
$ip = explode('.', $this->ipAddress);
$sprintf = '%08b%08b%08b%08b';
}
$ip = vsprintf($sprintf, $ip);
}
// Split the netmask length off the network address
sscanf($proxy_ips[$i], '%[^/]/%d', $netaddr, $masklen);
// Again, an IPv6 address is most likely in a compressed form
if ($separator === ':')
{
$netaddr = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr));
for ($i = 0; $i < 8; $i ++)
{
$netaddr[$i] = intval($netaddr[$i], 16);
}
}
else
{
$netaddr = explode('.', $netaddr);
}
// Convert to binary and finally compare
if (strncmp($ip, vsprintf($sprintf, $netaddr), $masklen) === 0)
{
$this->ipAddress = $spoof;
break;
}
}
}
}
if (! $this->isValidIP($this->ipAddress))
{
return $this->ipAddress = '0.0.0.0';
}
return empty($this->ipAddress) ? '' : $this->ipAddress;
}
//--------------------------------------------------------------------
/**
* Validate an IP address
*
* @param string $ip IP Address
* @param string $which IP protocol: 'ipv4' or 'ipv6'
*
* @return boolean
*/
public function isValidIP(string $ip = null, string $which = null): bool
{
switch (strtolower( (string) $which))
{
case 'ipv4':
$which = FILTER_FLAG_IPV4;
break;
case 'ipv6':
$which = FILTER_FLAG_IPV6;
break;
default:
$which = null;
break;
}
return (bool) filter_var($ip, FILTER_VALIDATE_IP, $which);
}
//--------------------------------------------------------------------
/**
* Get the request method.
*
* @param boolean $upper Whether to return in upper or lower case.
*
* @return string
*/
public function getMethod(bool $upper = false): string
{
return ($upper) ? strtoupper($this->method) : strtolower($this->method);
}
//--------------------------------------------------------------------
/**
* Sets the request method. Used when spoofing the request.
*
* @param string $method
*
* @return Request
*/
public function setMethod(string $method)
{
$this->method = $method;
return $this;
}
//--------------------------------------------------------------------
/**
* Fetch an item from the $_SERVER array.
*
* @param string|array|null $index Index for item to be fetched from $_SERVER
* @param integer|null $filter A filter name to be applied
* @param null $flags
*
* @return mixed
*/
public function getServer($index = null, $filter = null, $flags = null)
{
return $this->fetchGlobal('server', $index, $filter, $flags);
}
//--------------------------------------------------------------------
/**
* Fetch an item from the $_ENV array.
*
* @param null $index Index for item to be fetched from $_ENV
* @param null $filter A filter name to be applied
* @param null $flags
*
* @return mixed
*/
public function getEnv($index = null, $filter = null, $flags = null)
{
return $this->fetchGlobal('env', $index, $filter, $flags);
}
//--------------------------------------------------------------------
/**
* Allows manually setting the value of PHP global, like $_GET, $_POST, etc.
*
* @param string $method
* @param $value
*
* @return $this
*/
public function setGlobal(string $method, $value)
{
$this->globals[$method] = $value;
return $this;
}
//--------------------------------------------------------------------
/**
* Fetches one or more items from a global, like cookies, get, post, etc.
* Can optionally filter the input when you retrieve it by passing in
* a filter.
*
* If $type is an array, it must conform to the input allowed by the
* filter_input_array method.
*
* http://php.net/manual/en/filter.filters.sanitize.php
*
* @param string $method Input filter constant
* @param string|array|null $index
* @param integer|null $filter Filter constant
* @param mixed $flags
*
* @return mixed
*/
public function fetchGlobal($method, $index = null, $filter = null, $flags = null)
{
$method = strtolower($method);
if (! isset($this->globals[$method]))
{
$this->populateGlobals($method);
}
// Null filters cause null values to return.
if (is_null($filter))
{
$filter = FILTER_DEFAULT;
}
// Return all values when $index is null
if (is_null($index))
{
$values = [];
foreach ($this->globals[$method] as $key => $value)
{
$values[$key] = is_array($value)
? $this->fetchGlobal($method, $key, $filter, $flags)
: filter_var($value, $filter, $flags);
}
return $values;
}
// allow fetching multiple keys at once
if (is_array($index))
{
$output = [];
foreach ($index as $key)
{
$output[$key] = $this->fetchGlobal($method, $key, $filter, $flags);
}
return $output;
}
// Does the index contain array notation?
if (($count = preg_match_all('/(?:^[^\[]+)|\[[^]]*\]/', $index, $matches)) > 1)
{
$value = $this->globals[$method];
for ($i = 0; $i < $count; $i++)
{
$key = trim($matches[0][$i], '[]');
if ($key === '') // Empty notation will return the value as array
{
break;
}
if (isset($value[$key]))
{
$value = $value[$key];
}
else
{
return null;
}
}
}
if (! isset($value))
{
$value = $this->globals[$method][$index] ?? null;
}
// Cannot filter these types of data automatically...
if (is_array($value) || is_object($value) || is_null($value))
{
return $value;
}
return filter_var($value, $filter, $flags);
}
//--------------------------------------------------------------------
/**
* Saves a copy of the current state of one of several PHP globals
* so we can retrieve them later.
*
* @param string $method
*/
protected function populateGlobals(string $method)
{
if (! isset($this->globals[$method]))
{
$this->globals[$method] = [];
}
// Don't populate ENV as it might contain
// sensitive data that we don't want to get logged.
switch($method)
{
case 'get':
$this->globals['get'] = $_GET;
break;
case 'post':
$this->globals['post'] = $_POST;
break;
case 'request':
$this->globals['request'] = $_REQUEST;
break;
case 'cookie':
$this->globals['cookie'] = $_COOKIE;
break;
case 'server':
$this->globals['server'] = $_SERVER;
break;
}
}
}

View File

@@ -0,0 +1,94 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP;
/**
* Expected behavior of an HTTP request
*
* @mixin \CodeIgniter\HTTP\IncomingRequest
* @mixin \CodeIgniter\HTTP\CLIRequest
* @mixin \CodeIgniter\HTTP\CURLRequest
*/
interface RequestInterface
{
/**
* Gets the user's IP address.
*
* @return string IP address
*/
public function getIPAddress(): string;
//--------------------------------------------------------------------
/**
* Validate an IP address
*
* @param string $ip IP Address
* @param string $which IP protocol: 'ipv4' or 'ipv6'
*
* @return boolean
*/
public function isValidIP(string $ip, string $which = null): bool;
//--------------------------------------------------------------------
/**
* Get the request method.
*
* @param boolean $upper Whether to return in upper or lower case.
*
* @return string
*/
public function getMethod(bool $upper = false): string;
//--------------------------------------------------------------------
/**
* Fetch an item from the $_SERVER array.
*
* @param string $index Index for item to be fetched from $_SERVER
* @param null $filter A filter name to be applied
* @return mixed
*/
public function getServer($index = null, $filter = null);
//--------------------------------------------------------------------
}

1074
system/HTTP/Response.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,274 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP;
/**
* Representation of an outgoing, getServer-side response.
*
* Per the HTTP specification, this interface includes properties for
* each of the following:
*
* - Protocol version
* - Status code and reason phrase
* - Headers
* - Message body
*
* @package CodeIgniter\HTTP
* @mixin \CodeIgniter\HTTP\RedirectResponse
*/
interface ResponseInterface
{
/**
* Constants for status codes.
* From https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
*/
// Informational
const HTTP_CONTINUE = 100;
const HTTP_SWITCHING_PROTOCOLS = 101;
const HTTP_PROCESSING = 102;
const HTTP_EARLY_HINTS = 103;
// Success
const HTTP_OK = 200;
const HTTP_CREATED = 201;
const HTTP_ACCEPTED = 202;
const HTTP_NONAUTHORITATIVE_INFORMATION = 203;
const HTTP_NO_CONTENT = 204;
const HTTP_RESET_CONTENT = 205;
const HTTP_PARTIAL_CONTENT = 206;
const HTTP_MULTI_STATUS = 207;
const HTTP_ALREADY_REPORTED = 208;
const HTTP_IM_USED = 226;
// Redirection
const HTTP_MULTIPLE_CHOICES = 300;
const HTTP_MOVED_PERMANENTLY = 301;
const HTTP_FOUND = 302;
const HTTP_SEE_OTHER = 303;
const HTTP_NOT_MODIFIED = 304;
const HTTP_USE_PROXY = 305;
const HTTP_SWITCH_PROXY = 306;
const HTTP_TEMPORARY_REDIRECT = 307;
const HTTP_PERMANENT_REDIRECT = 308;
// Client Error
const HTTP_BAD_REQUEST = 400;
const HTTP_UNAUTHORIZED = 401;
const HTTP_PAYMENT_REQUIRED = 402;
const HTTP_FORBIDDEN = 403;
const HTTP_NOT_FOUND = 404;
const HTTP_METHOD_NOT_ALLOWED = 405;
const HTTP_NOT_ACCEPTABLE = 406;
const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
const HTTP_REQUEST_TIMEOUT = 408;
const HTTP_CONFLICT = 409;
const HTTP_GONE = 410;
const HTTP_LENGTH_REQUIRED = 411;
const HTTP_PRECONDITION_FAILED = 412;
const HTTP_PAYLOAD_TOO_LARGE = 413;
const HTTP_URI_TOO_LONG = 414;
const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
const HTTP_RANGE_NOT_SATISFIABLE = 416;
const HTTP_EXPECTATION_FAILED = 417;
const HTTP_IM_A_TEAPOT = 418;
const HTTP_MISDIRECTED_REQUEST = 421;
const HTTP_UNPROCESSABLE_ENTITY = 422;
const HTTP_LOCKED = 423;
const HTTP_FAILED_DEPENDENCY = 424;
const HTTP_TOO_EARLY = 425;
const HTTP_UPGRADE_REQUIRED = 426;
const HTTP_PRECONDITION_REQUIRED = 428;
const HTTP_TOO_MANY_REQUESTS = 429;
const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451;
const HTTP_CLIENT_CLOSED_REQUEST = 499;
// Server Error
const HTTP_INTERNAL_SERVER_ERROR = 500;
const HTTP_NOT_IMPLEMENTED = 501;
const HTTP_BAD_GATEWAY = 502;
const HTTP_SERVICE_UNAVAILABLE = 503;
const HTTP_GATEWAY_TIMEOUT = 504;
const HTTP_HTTP_VERSION_NOT_SUPPORTED = 505;
const HTTP_VARIANT_ALSO_NEGOTIATES = 506;
const HTTP_INSUFFICIENT_STORAGE = 507;
const HTTP_LOOP_DETECTED = 508;
const HTTP_NOT_EXTENDED = 510;
const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511;
const HTTP_NETWORK_CONNECT_TIMEOUT_ERROR = 599;
/**
* Gets the response status code.
*
* The status code is a 3-digit integer result code of the getServer's attempt
* to understand and satisfy the request.
*
* @return integer Status code.
*/
public function getStatusCode(): int;
//--------------------------------------------------------------------
/**
* Return an instance with the specified status code and, optionally, reason phrase.
*
* If no reason phrase is specified, will default recommended reason phrase for
* the response's status code.
*
* @see http://tools.ietf.org/html/rfc7231#section-6
* @see http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
*
* @param integer $code The 3-digit integer result code to set.
* @param string $reason The reason phrase to use with the
* provided status code; if none is provided, will
* default to the IANA name.
*
* @return self
* @throws \InvalidArgumentException For invalid status code arguments.
*/
public function setStatusCode(int $code, string $reason = '');
//--------------------------------------------------------------------
/**
* Gets the response response phrase associated with the status code.
*
* @see http://tools.ietf.org/html/rfc7231#section-6
* @see http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
*
* @return string
*/
public function getReason(): string;
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// Convenience Methods
//--------------------------------------------------------------------
/**
* Sets the date header
*
* @param \DateTime $date
*
* @return ResponseInterface
*/
public function setDate(\DateTime $date);
//--------------------------------------------------------------------
/**
* Sets the Content Type header for this response with the mime type
* and, optionally, the charset.
*
* @param string $mime
* @param string $charset
*
* @return ResponseInterface
*/
public function setContentType(string $mime, string $charset = 'UTF-8');
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// Cache Control Methods
//
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
//--------------------------------------------------------------------
/**
* Sets the appropriate headers to ensure this response
* is not cached by the browsers.
*/
public function noCache();
//--------------------------------------------------------------------
/**
* A shortcut method that allows the developer to set all of the
* cache-control headers in one method call.
*
* The options array is used to provide the cache-control directives
* for the header. It might look something like:
*
* $options = [
* 'max-age' => 300,
* 's-maxage' => 900
* 'etag' => 'abcde',
* ];
*
* Typical options are:
* - etag
* - last-modified
* - max-age
* - s-maxage
* - private
* - public
* - must-revalidate
* - proxy-revalidate
* - no-transform
*
* @param array $options
*
* @return ResponseInterface
*/
public function setCache(array $options = []);
//--------------------------------------------------------------------
/**
* Sets the Last-Modified date header.
*
* $date can be either a string representation of the date or,
* preferably, an instance of DateTime.
*
* @param string|\DateTime $date
*/
public function setLastModified($date);
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// Output Methods
//--------------------------------------------------------------------
/**
* Sends the output to the browser.
*
* @return ResponseInterface
*/
public function send();
//--------------------------------------------------------------------
}

1205
system/HTTP/URI.php Normal file

File diff suppressed because it is too large Load Diff

505
system/HTTP/UserAgent.php Normal file
View File

@@ -0,0 +1,505 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\HTTP;
use Config\UserAgents;
/**
* Abstraction for an HTTP user agent
*/
class UserAgent
{
/**
* Current user-agent
*
* @var string
*/
protected $agent = null;
/**
* Flag for if the user-agent belongs to a browser
*
* @var boolean
*/
protected $isBrowser = false;
/**
* Flag for if the user-agent is a robot
*
* @var boolean
*/
protected $isRobot = false;
/**
* Flag for if the user-agent is a mobile browser
*
* @var boolean
*/
protected $isMobile = false;
/**
* Holds the config file contents.
*
* @var \Config\UserAgents
*/
protected $config;
/**
* Current user-agent platform
*
* @var string
*/
protected $platform = '';
/**
* Current user-agent browser
*
* @var string
*/
protected $browser = '';
/**
* Current user-agent version
*
* @var string
*/
protected $version = '';
/**
* Current user-agent mobile name
*
* @var string
*/
protected $mobile = '';
/**
* Current user-agent robot name
*
* @var string
*/
protected $robot = '';
/**
* HTTP Referer
*
* @var mixed
*/
protected $referrer;
//--------------------------------------------------------------------
/**
* Constructor
*
* Sets the User Agent and runs the compilation routine
*
* @param null $config
*/
public function __construct($config = null)
{
if (is_null($config))
{
$this->config = new UserAgents();
}
if (isset($_SERVER['HTTP_USER_AGENT']))
{
$this->agent = trim($_SERVER['HTTP_USER_AGENT']);
$this->compileData();
}
}
//--------------------------------------------------------------------
/**
* Is Browser
*
* @param string $key
*
* @return boolean
*/
public function isBrowser(string $key = null): bool
{
if (! $this->isBrowser)
{
return false;
}
// No need to be specific, it's a browser
if ($key === null)
{
return true;
}
// Check for a specific browser
return (isset($this->config->browsers[$key]) && $this->browser === $this->config->browsers[$key]);
}
//--------------------------------------------------------------------
/**
* Is Robot
*
* @param string $key
*
* @return boolean
*/
public function isRobot(string $key = null): bool
{
if (! $this->isRobot)
{
return false;
}
// No need to be specific, it's a robot
if ($key === null)
{
return true;
}
// Check for a specific robot
return (isset($this->config->robots[$key]) && $this->robot === $this->config->robots[$key]);
}
//--------------------------------------------------------------------
/**
* Is Mobile
*
* @param string $key
*
* @return boolean
*/
public function isMobile(string $key = null): bool
{
if (! $this->isMobile)
{
return false;
}
// No need to be specific, it's a mobile
if ($key === null)
{
return true;
}
// Check for a specific robot
return (isset($this->config->mobiles[$key]) && $this->mobile === $this->config->mobiles[$key]);
}
//--------------------------------------------------------------------
/**
* Is this a referral from another site?
*
* @return boolean
*/
public function isReferral(): bool
{
if (! isset($this->referrer))
{
if (empty($_SERVER['HTTP_REFERER']))
{
$this->referrer = false;
}
else
{
$referer_host = @parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST);
$own_host = parse_url(\base_url(), PHP_URL_HOST);
$this->referrer = ($referer_host && $referer_host !== $own_host);
}
}
return $this->referrer;
}
//--------------------------------------------------------------------
/**
* Agent String
*
* @return string
*/
public function getAgentString(): string
{
return $this->agent;
}
//--------------------------------------------------------------------
/**
* Get Platform
*
* @return string
*/
public function getPlatform(): string
{
return $this->platform;
}
//--------------------------------------------------------------------
/**
* Get Browser Name
*
* @return string
*/
public function getBrowser(): string
{
return $this->browser;
}
//--------------------------------------------------------------------
/**
* Get the Browser Version
*
* @return string
*/
public function getVersion(): string
{
return $this->version;
}
//--------------------------------------------------------------------
/**
* Get The Robot Name
*
* @return string
*/
public function getRobot(): string
{
return $this->robot;
}
//--------------------------------------------------------------------
/**
* Get the Mobile Device
*
* @return string
*/
public function getMobile(): string
{
return $this->mobile;
}
//--------------------------------------------------------------------
/**
* Get the referrer
*
* @return string
*/
public function getReferrer(): string
{
return empty($_SERVER['HTTP_REFERER']) ? '' : trim($_SERVER['HTTP_REFERER']);
}
//--------------------------------------------------------------------
/**
* Parse a custom user-agent string
*
* @param string $string
*
* @return void
*/
public function parse(string $string)
{
// Reset values
$this->isBrowser = false;
$this->isRobot = false;
$this->isMobile = false;
$this->browser = '';
$this->version = '';
$this->mobile = '';
$this->robot = '';
// Set the new user-agent string and parse it, unless empty
$this->agent = $string;
if (! empty($string))
{
$this->compileData();
}
}
//--------------------------------------------------------------------
/**
* Compile the User Agent Data
*
* @return void
*/
protected function compileData()
{
$this->setPlatform();
foreach (['setRobot', 'setBrowser', 'setMobile'] as $function)
{
if ($this->$function() === true)
{
break;
}
}
}
//--------------------------------------------------------------------
/**
* Set the Platform
*
* @return boolean
*/
protected function setPlatform(): bool
{
if (is_array($this->config->platforms) && $this->config->platforms)
{
foreach ($this->config->platforms as $key => $val)
{
if (preg_match('|' . preg_quote($key) . '|i', $this->agent))
{
$this->platform = $val;
return true;
}
}
}
$this->platform = 'Unknown Platform';
return false;
}
//--------------------------------------------------------------------
/**
* Set the Browser
*
* @return boolean
*/
protected function setBrowser(): bool
{
if (is_array($this->config->browsers) && $this->config->browsers)
{
foreach ($this->config->browsers as $key => $val)
{
if (preg_match('|' . $key . '.*?([0-9\.]+)|i', $this->agent, $match))
{
$this->isBrowser = true;
$this->version = $match[1];
$this->browser = $val;
$this->setMobile();
return true;
}
}
}
return false;
}
//--------------------------------------------------------------------
/**
* Set the Robot
*
* @return boolean
*/
protected function setRobot(): bool
{
if (is_array($this->config->robots) && $this->config->robots)
{
foreach ($this->config->robots as $key => $val)
{
if (preg_match('|' . preg_quote($key) . '|i', $this->agent))
{
$this->isRobot = true;
$this->robot = $val;
$this->setMobile();
return true;
}
}
}
return false;
}
//--------------------------------------------------------------------
/**
* Set the Mobile Device
*
* @return boolean
*/
protected function setMobile(): bool
{
if (is_array($this->config->mobiles) && $this->config->mobiles)
{
foreach ($this->config->mobiles as $key => $val)
{
if (false !== (stripos($this->agent, $key)))
{
$this->isMobile = true;
$this->mobile = $val;
return true;
}
}
}
return false;
}
//--------------------------------------------------------------------
/**
* Outputs the original Agent String when cast as a string.
*
* @return string
*/
public function __toString(): string
{
return $this->getAgentString();
}
}