/**
* A generic class for managing the HTTP protocol.
*
- * @todo: many of the HTTP response fields can become HTML meta header fields.
- * add a function to return an array of HTTP attributes that can be translated into HTTP tags.
- * no processing is to be done here, let a class that is explicitly designed for HTML process it.
- * example, content-security-policy (https://en.wikipedia.org/wiki/Content_Security_Policy).
- *
* @see: https://www.iana.org/assignments/message-headers/message-headers.xhtml
* @see: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
*
const ENCODING_GZIP = 4; // Compression Options: -1 -> 9.
const ENCODING_BZIP = 5; // Compression Options: 1 -> 9.
const ENCODING_LZO = 6; // Compression Options: LZO1_99, LZO1A_99, LZO1B_999, LZO1C_999, LZO1F_999, LZO1X_999, LZO1Y_999, LZO1Z_999, LZO2A_999 (and many more).
- const ENCODING_XZ = 7;
+ const ENCODING_XZ = 7; // currently unsupported due to available libraries being defunct.
const ENCODING_EXI = 8;
const ENCODING_IDENTITY = 9;
const ENCODING_SDCH = 10;
}
/**
+ * Returns a list of HTTP headers that can be used as an HTML meta tag.
+ *
+ * The relationship between HTTP headers and HTML headers is not always one to one.
+ * These should be used
+ * @todo: this list will need to be reviewed once I work on the HTML meta handling code.
+ *
+ * The HTML language supports HTTP headers as HTML tags.
+ *
+ * @see: https://html.spec.whatwg.org/multipage/semantics.html#standard-metadata-names
+ * @see: https://www.w3.org/TR/html5/document-metadata.html#the-meta-element
+ */
+ public function get_response_headers_for_meta() {
+ return array(
+ self::RESPONSE_CACHE_CONTROL => self::RESPONSE_CACHE_CONTROL,
+ self::RESPONSE_CONTENT_ENCODING => self::RESPONSE_CONTENT_ENCODING,
+ self::RESPONSE_CONTENT_LANGUAGE => self::RESPONSE_CONTENT_LANGUAGE,
+ self::RESPONSE_CONTENT_SECURITY_POLICY => self::RESPONSE_CONTENT_SECURITY_POLICY,
+ self::RESPONSE_CONTENT_TYPE => self::RESPONSE_CONTENT_TYPE,
+ self::RESPONSE_EXPIRES => self::RESPONSE_EXPIRES,
+ self::RESPONSE_LINK => self::RESPONSE_LINK,
+ self::RESPONSE_PRAGMA => self::RESPONSE_PRAGMA,
+ self::RESPONSE_REFRESH => self::RESPONSE_REFRESH,
+ );
+ }
+
+ /**
* Get the HTTP request array.
*
* Load the entire HTTP request array or a specific request field.
unset($headers['accept_datetime']);
}
- // @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
if (array_key_exists('access_control_request_method', $this->headers)) {
- $this->p_load_request_rawish('access_control_request_method', $this->REQUEST_ACCESS_CONTROL_REQUEST_METHOD, 256);
+ $this->p_load_request_access_control_request_method();
unset($headers['access_control_request_method']);
}
- // @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
if (array_key_exists('access_control_request_headers', $this->headers)) {
- $this->p_load_request_rawish('access_request_allow_headers', $this->REQUEST_ACCESS_CONTROL_REQUEST_HEADERS, 256);
+ $this->p_load_request_access_control_request_headers();
unset($headers['access_control_request_headers']);
}
- // @see: https://tools.ietf.org/html/rfc7235#section-4.2
if (array_key_exists('authorization', $this->headers)) {
- $this->p_load_request_rawish('authorization', $this->REQUEST_AUTHORIZATION, 4096);
+ $this->p_load_request_authorization();
unset($headers['authorization']);
}
unset($headers['origin']);
}
- // @see: https://tools.ietf.org/html/rfc7235#section-4.4
if (array_key_exists('proxy_authorization', $this->headers)) {
- $this->p_load_request_rawish('proxy_authorization', $this->REQUEST_PROXY_AUTHORIZATION, 4096);
+ $this->p_load_request_proxy_authorization();
unset($headers['proxy_authorization']);
}
}
if (array_key_exists('x_requested_with', $this->headers)) {
- $this->p_load_request_rawish('x_requested_with', $this->REQUEST_X_REQUESTED_WITH, 64);
+ $this->p_load_request_x_requested_with();
unset($headers['x_requested_with']);
}
if (array_key_exists('x_forwarded_for', $this->headers)) {
- $this->p_load_request_rawish('x_forwarded_for', $this->REQUEST_X_FORWARDED_for, 512);
+ $this->p_load_request_x_requested_for();
unset($headers['x_forwarded_for']);
}
if (array_key_exists('x_forwarded_host', $this->headers)) {
- $this->p_load_request_rawish('x_forwarded_host', $this->REQUEST_X_FORWARDED_HOST, 512);
+ $this->p_load_request_x_requested_host();
unset($headers['x_forwarded_host']);
}
if (array_key_exists('x_forwarded_proto', $this->headers)) {
- $this->p_load_request_rawish('x_forwarded_proto', $this->REQUEST_X_FORWARDED_PROTO, 64);
+ $this->p_load_request_x_requested_proto();
unset($headers['x_forwarded_proto']);
}
/**
* Assign HTTP response header: access-control-allow-origin.
*
- * @todo: I only glanced at the documentation (which was incomplete on wikipedia).
- * More work is likely needed to be done with this and its structure is subject to change.
+ * Note on multiple urls: The standard appears to only support one url.
+ * Therefore, to have multiple urls, the clients ORIGIN should be checked against a known list.
+ * Then, from that list either the default or the clients ORIGIN is sent.
*
* @param string $uri
* The uri to assign to the specified header.
* FALSE with error bit set is returned on error.
*
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Origin
*/
public function set_response_access_control_allow_origin($uri) {
if (!is_string($uri)) {
$this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN] = array('wildcard' => TRUE);
}
else {
- $parsed = $this->p_parse_uri($uri);
+ $text = $this->pr_rfc_string_prepare($uri);
+ if ($text['invalid']) {
+ unset($text);
+
+ $error = c_base_error::s_log(NULL, array('arguments' => array(':operation_name' => 'this->pr_rfc_string_prepare', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::OPERATION_FAILURE);
+ return c_base_return_error::s_false($error);
+ }
+
+ $parsed = $this->pr_rfc_string_is_uri($text['ordinals'], $text['characters']);
+ unset($text);
+
if ($parsed['invalid']) {
unset($parsed);
$error = c_base_error::s_log(NULL, array('arguments' => array(':format_name' => 'uri', ':expected_format' => NULL, ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_FORMAT);
return c_base_return_error::s_false($error);
}
unset($parsed['invalid']);
+ unset($parsed['current']);
$parsed['wildcard'] = FALSE;
$this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN] = $parsed;
/**
* Assign HTTP response header: access-control-allow-credentials.
*
- * @todo: I only glanced at the documentation (which was incomplete on wikipedia).
- * More work is likely needed to be done with this and its structure is subject to change.
- *
* @param bool $allow_credentials
* The value to assign to the specified header.
*
* FALSE with error bit set is returned on error.
*
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Credentials
*/
public function set_response_access_control_allow_credentials($allow_credentials) {
if (!is_bool($allow_credentials)) {
/**
* Assign HTTP response header: access-control-expose-headers.
*
- * @todo: I only glanced at the documentation (which was incomplete on wikipedia).
- * More work is likely needed to be done with this and its structure is subject to change.
- * @todo: should this be written in the same way as set_response_allow()?
- *
* @param string $header_name
* The header name to assign to the specified header.
* @param bool $append
* FALSE with error bit set is returned on error.
*
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Expose-Headers
*/
public function set_response_access_control_expose_headers($header_name, $append = TRUE) {
if (!is_string($header_name)) {
/**
* Assign HTTP response header: access-control-max-age.
*
- * @todo: I only glanced at the documentation (which was incomplete on wikipedia).
- * More work is likely needed to be done with this and its structure is subject to change.
- *
- * @param int|float $timestamp
- * The timestamp to assign to the specified header.
+ * @param int|float $seconds
+ * The seconds to assign to the specified header.
*
* @return c_base_return_status
* TRUE on success, FALSE otherwise.
* FALSE with error bit set is returned on error.
*
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Max-Age
*/
- public function set_response_access_control_max_age($timestamp) {
- if (!is_int($timestamp) && !is_float($timestamp)) {
- $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'timestamp', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+ public function set_response_access_control_max_age($seconds) {
+ if (!is_int($seconds) && !is_float($seconds)) {
+ $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'seconds', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
return c_base_return_error::s_false($error);
}
- $this->response[self::RESPONSE_ACCESS_CONTROL_MAX_AGE] = $timestamp;
+ $this->response[self::RESPONSE_ACCESS_CONTROL_MAX_AGE] = $seconds;
return new c_base_return_true();
}
/**
* Assign HTTP response header: access-control-allow-methods.
*
- * @todo: I only glanced at the documentation (which was incomplete on wikipedia).
- * More work is likely needed to be done with this and its structure is subject to change.
- *
* @param int $method
* The code representing the method to assign to the specified header.
* @param bool $append
* FALSE with error bit set is returned on error.
*
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Methods
*/
public function set_response_access_control_allow_methods($method, $append = TRUE) {
if (!is_int($method)) {
}
+ // this method does nothing.
+ if ($method === self::HTTP_METHOD_NONE) {
+ return new c_base_return_true();
+ }
+
+
+ // require only valid/known methods.
+ switch ($method) {
+ case self::HTTP_METHOD_NONE:
+ case self::HTTP_METHOD_GET:
+ case self::HTTP_METHOD_HEAD:
+ case self::HTTP_METHOD_POST:
+ case self::HTTP_METHOD_PUT:
+ case self::HTTP_METHOD_DELETE:
+ case self::HTTP_METHOD_TRACE:
+ case self::HTTP_METHOD_OPTIONS:
+ case self::HTTP_METHOD_CONNECT:
+ case self::HTTP_METHOD_PATCH:
+ case self::HTTP_METHOD_TRACK:
+ case self::HTTP_METHOD_DEBUG:
+ break;
+
+ default:
+ $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'method', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+ return c_base_return_error::s_false($error);
+ }
+
+
if ($append) {
if (!isset($this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS]) || !is_array($this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS])) {
$this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] = array();
/**
* Assign HTTP response header: access-control-allow-headers.
*
- * @todo: I only glanced at the documentation (which was incomplete on wikipedia).
- * More work is likely needed to be done with this and its structure is subject to change.
- *
* @param string $header_name
* The header name to assign to the specified header.
* @param bool $append
* If FALSE, then assign the header name.
*
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Headers
*/
public function set_response_access_control_allow_headers($header_name, $append = TRUE) {
if (!is_string($header_name)) {
* The standard defines this as:
* - (uri) *(";" 1*(tchar) "=" 1*(1*(tchar) / 1*(quoted-string)))
*
- * @param string|null $uri
+ * This standard likely supports multiple link headers.
+ *
+ * @param string $uri
* The URI to assign to the specified header.
- * May be NULL when append is TRUE.
- * Both $null and $parameter_name may not be NULL.
* @param string|null $parameter_name
* (optional) A single link parameter to be added.
* If NULL, then this is ignored.
* (optional) A single link value to be added.
* If NULL, then this is ignored.
* @param bool $append
- * (optional) If TRUE, then append the header name.
- * If FALSE, then assign the header name.
+ * (optional) If TRUE, then append the parameter name.
+ * If FALSE, then assign the parameter name.
+ * When $parameter_name is NULL and this is FALSE, then assign an empty array for parameters associated with $uri.
*
* @return c_base_return_status
* TRUE on success, FALSE otherwise.
}
- $parsed_uri = $this->p_parse_uri($uri);
+ $text = $this->pr_rfc_string_prepare($uri);
+ if ($text['invalid']) {
+ unset($text);
+
+ $error = c_base_error::s_log(NULL, array('arguments' => array(':operation_name' => 'this->pr_rfc_string_prepare', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::OPERATION_FAILURE);
+ return c_base_return_error::s_false($error);
+ }
+
+ $parsed = $this->pr_rfc_string_is_uri($text['ordinals'], $text['characters']);
+ unset($text);
+
if ($parsed_uri['invalid']) {
unset($parsed_uri);
unset($parsed_uri['invalid']);
+ // when append is FALSE and there is no parameter name, then assign url instead of appending url.
+ if (!$append && is_null($parameter_name)) {
+ if (!isset($this->response[self::RESPONSE_LINK])) {
+ $this->response[self::RESPONSE_LINK] = array();
+ }
+
+ $this->response[self::RESPONSE_LINK][$uri] = array(
+ 'uri' => $parsed_uri,
+ 'parameters' => array(),
+ );
+ unset($parsed_uri);
+
+ return new c_base_return_true();
+ }
+
+
$prepared_parameter_name = NULL;
if (is_string($parameter_name)) {
$prepared_parameter_name = $this->p_prepare_token($parameter_name);
if (!isset($this->response[self::RESPONSE_LINK])) {
- // uri cannot be NULL if there is no uri currently assigned.
- if (is_null($uri)) {
- unset($prepared_token);
-
- $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'uri', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
- return c_base_return_error::s_false($error);
- }
-
- $this->response[self::RESPONSE_LINK] = array(
- 'uri' => NULL,
- 'parameters' => array(),
- );
+ $this->response[self::RESPONSE_LINK] = array();
}
- if (is_string($uri)) {
- $this->response[self::RESPONSE_LINK]['uri'] = $parsed_uri;
+ if (!array_key_exists($uri, $this->response[self::RESPONSE_LINK])) {
+ $this->response[self::RESPONSE_LINK][$uri] = array(
+ 'uri' => $parsed_uri,
+ 'parameters' => array()
+ );
}
unset($parsed_uri);
if (is_string($parameter_name)) {
if ($append) {
- $this->response[self::RESPONSE_LINK]['parameters'][$prepared_parameter_name] = $parsed_parameter_value['text'];
+ $this->response[self::RESPONSE_LINK][$uri]['parameters'][$prepared_parameter_name] = $parsed_parameter_value['text'];
}
else {
- $this->response[self::RESPONSE_LINK]['parameters'] = array($prepared_parameter_name => $parsed_parameter_value['text']);
+ $this->response[self::RESPONSE_LINK][$uri]['parameters'] = array($prepared_parameter_name => $parsed_parameter_value['text']);
}
}
unset($prepared_parameter_name);
return c_base_return_error::s_false($error);
}
- $parsed = $this->p_parse_uri($uri);
+
+ $text = $this->pr_rfc_string_prepare($uri);
+ if ($text['invalid']) {
+ unset($text);
+
+ $error = c_base_error::s_log(NULL, array('arguments' => array(':operation_name' => 'this->pr_rfc_string_prepare', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::OPERATION_FAILURE);
+ return c_base_return_error::s_false($error);
+ }
+
+ $parsed = $this->pr_rfc_string_is_uri($text['ordinals'], $text['characters']);
+ unset($text);
+
if ($parsed['invalid']) {
unset($parsed);
$error = c_base_error::s_log(NULL, array('arguments' => array(':format_name' => 'uri', ':expected_format' => NULL, ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_FORMAT);
unset($this->response[self::RESPONSE_CONTENT_LENGTH]);
}
- // RESPONSE_TRANSFER_ENCODING
-
- // @todo
+ // @todo: self::RESPONSE_TRANSFER_ENCODING
$error = c_base_error::s_log(NULL, array('arguments' => array(':functionality_name' => 'http response transfer encoding', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::NO_SUPPORT);
return c_base_return_error::s_false($error);
}
- $prepared_token = $this->p_prepare_token($header_name);
+ $prepared_token = $this->p_prepare_token($header_name, FALSE);
if ($prepared_token === FALSE) {
unset($prepared_token);
$error = c_base_error::s_log(NULL, array('arguments' => array(':operation_name' => 'this->p_prepare_token', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::OPERATION_FAILURE);
/**
* Obtain HTTP response header: access-control-allow-origin.
*
- * @todo: I only glanced at the documentation (which was incomplete on wikipedia).
- * More work is likely needed to be done with this and its structure is subject to change.
- *
* @return c_base_return_array|c_base_return_status
* A decoded uri split into its different parts inside an array.
* This array also contains a key called 'wildcard' which may be either TRUE or FALSE.
* FALSE with error bit set is returned on error, including when the key is not defined.
*
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Origin
*/
public function get_response_access_control_allow_origin() {
if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN, $this->response)) {
/**
* Obtain HTTP response header: access-control-allow-credentials.
*
- * @todo: I only glanced at the documentation (which was incomplete on wikipedia).
- * More work is likely needed to be done with this and its structure is subject to change.
- *
* @return c_base_return_bool|c_base_return_status
* A boolean representing whether or not to allow credentials.
* FALSE with error bit set is returned on error, including when the key is not defined.
*
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Credentials
+ *
*/
public function get_response_access_control_allow_credentials() {
if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_ALLOW_CREDENTIALS, $this->response)) {
/**
* Obtain HTTP response header: access-control-expose-headers.
*
- * @todo: I only glanced at the documentation (which was incomplete on wikipedia).
- * More work is likely needed to be done with this and its structure is subject to change.
- *
* @return c_base_return_array|c_base_return_status
* An array of headers to expose.
* FALSE with error bit set is returned on error, including when the key is not defined.
*
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Expose-Headers
*/
public function get_response_access_control_expose_headers() {
if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS, $this->response)) {
/**
* Obtain HTTP response header: access-control-max-age.
*
- * @todo: I only glanced at the documentation (which was incomplete on wikipedia).
- * More work is likely needed to be done with this and its structure is subject to change.
- *
* @return c_base_return_int|c_base_return_status
* An Unix timestamp representing the specified header.
* FALSE with error bit set is returned on error, including when the key is not defined.
*
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Max-Age
*/
public function get_response_access_control_max_age() {
if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_MAX_AGE, $this->response)) {
/**
* Obtain HTTP response header: access-control-allow-methods.
*
- * @todo: I only glanced at the documentation (which was incomplete on wikipedia).
- * More work is likely needed to be done with this and its structure is subject to change.
- *
* @return c_base_return_array|c_base_return_status
* FALSE with error bit set is returned on error, including when the key is not defined.
*
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Methods
*/
public function get_response_access_control_allow_methods() {
if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS, $this->response)) {
/**
* Obtain HTTP response header: access-control-allow-headers.
*
- * @todo: I only glanced at the documentation (which was incomplete on wikipedia).
- * More work is likely needed to be done with this and its structure is subject to change.
- *
* @return c_base_return_arrayl|c_base_return_status
* An array of allowed headers is returned.
* FALSE with error bit set is returned on error, including when the key is not defined.
+ *
+ * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Headers
*/
public function get_response_access_control_allow_headers() {
if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS, $this->response)) {
}
$this->p_prepare_header_response_access_control_allow_origin($header_id_to_names[self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN], $header_output);
- $this->p_prepare_header_response_simple_value($header_id_to_names[self::RESPONSE_ACCESS_CONTROL_ALLOW_CREDENTIALS], $header_output, self::RESPONSE_ACCESS_CONTROL_ALLOW_CREDENTIALS);
+ $this->p_prepare_header_response_boolean_value($header_id_to_names[self::RESPONSE_ACCESS_CONTROL_ALLOW_CREDENTIALS], $header_output, self::RESPONSE_ACCESS_CONTROL_ALLOW_CREDENTIALS);
$this->p_prepare_header_response_access_control_expose_headers($header_id_to_names[self::RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS], $header_output);
$this->p_prepare_header_response_simple_value($header_id_to_names[self::RESPONSE_ACCESS_CONTROL_MAX_AGE], $header_output, self::RESPONSE_ACCESS_CONTROL_MAX_AGE);
$this->p_prepare_header_response_access_control_allow_methods($header_id_to_names[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS], $header_output);
krsort($this->request[self::REQUEST_ACCEPT]['data']['weight']);
// The NULL key should be the first key in the weight.
- $this->p_prepend_array_value(NULL, $this->request[self::REQUEST_ACCEPT]['data']['weight']);
+ $this->pr_prepend_array_value(NULL, $this->request[self::REQUEST_ACCEPT]['data']['weight']);
// rename 'choices' array key to 'accept'.
$this->request[self::REQUEST_ACCEPT]['data']['accept'] = $this->request[self::REQUEST_ACCEPT]['data']['choices'];
krsort($this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['weight']);
// The NULL key should be the first key in the weight.
- $this->p_prepend_array_value(NULL, $this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['weight']);
+ $this->pr_prepend_array_value(NULL, $this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['weight']);
// rename 'choices' array key to 'language'.
$this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['language'] = $this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['choices'];
krsort($this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight']);
// The NULL key should be the first key in the weight.
- $this->p_prepend_array_value(NULL, $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight']);
+ $this->pr_prepend_array_value(NULL, $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight']);
// rename 'choices' array key to 'encoding'.
$this->request[self::REQUEST_ACCEPT_ENCODING]['data']['encoding'] = $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['choices'];
krsort($this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight']);
// The NULL key should be the first key in the weight.
- $this->p_prepend_array_value(NULL, $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight']);
+ $this->pr_prepend_array_value(NULL, $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight']);
}
unset($this->request[self::REQUEST_ACCEPT_CHARSET]['data']['invalid']);
}
/**
+ * Load and process the HTTP request parameter: accept-datetime.
+ *
+ * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Request-Method
+ */
+ private function p_load_request_access_control_request_method() {
+ $method_string = mb_strtolower($this->headers['access_control_request_method']);
+ $method_string = str_replace(' ', '', $method_string);
+
+ $methods = explode(',', $method_string);
+ unset($method_string);
+
+ if (empty($methods)) {
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['invalid'] = TRUE;
+ return;
+ }
+
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['data'] = array();
+
+ foreach ($methods as $method) {
+ switch ($method) {
+ case 'get':
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['data'][self::HTTP_METHOD_GET] = self::HTTP_METHOD_GET;
+ break;
+
+ case 'head':
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['data'][self::HTTP_METHOD_HEAD] = self::HTTP_METHOD_HEAD;
+ break;
+
+ case 'post':
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['data'][self::HTTP_METHOD_POST] = self::HTTP_METHOD_POST;
+ break;
+
+ case 'put':
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['data'][self::HTTP_METHOD_PUT] = self::HTTP_METHOD_PUT;
+ break;
+
+ case 'delete':
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['data'][self::HTTP_METHOD_DELETE] = self::HTTP_METHOD_DELETE;
+ break;
+
+ case 'trace':
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['data'][self::HTTP_METHOD_TRACE] = self::HTTP_METHOD_TRACE;
+ break;
+
+ case 'options':
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['data'][self::HTTP_METHOD_OPTIONS] = self::HTTP_METHOD_OPTIONS;
+ break;
+
+ case 'connect':
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['data'][self::HTTP_METHOD_CONNECT] = self::HTTP_METHOD_CONNECT;
+ break;
+
+ case 'patch':
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['data'][self::HTTP_METHOD_PATCH] = self::HTTP_METHOD_PATCH;
+ break;
+
+ case 'track':
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['data'][self::HTTP_METHOD_TRACK] = self::HTTP_METHOD_TRACK;
+ break;
+
+ case 'debug':
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['data'][self::HTTP_METHOD_DEBUG] = self::HTTP_METHOD_DEBUG;
+ break;
+
+ default:
+ // skip unknown methods instead of failing so that any discovered known methods are still loaded.
+ break;
+ }
+ }
+ unset($method);
+
+ if (!empty($methods) && empty($this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['data'])) {
+ unset($methods);
+
+ // no valid methods were found, now the error can be reported.
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['invalid'] = TRUE;
+ return;
+ }
+ unset($methods);
+
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['defined'] = TRUE;
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD]['invalid'] = FALSE;
+ }
+
+ /**
+ * Load and process the HTTP request parameter: accept-datetime.
+ *
+ * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Request-Headers
+ */
+ private function p_load_request_access_control_request_headers() {
+ if (empty($this->headers['access_control_request_headers'])) {
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_HEADERS]['invalid'] = TRUE;
+ return;
+ }
+
+ $text = $this->pr_rfc_string_prepare($this->headers['access_control_request_headers']);
+ if ($text['invalid']) {
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_HEADERS]['invalid'] = TRUE;
+ unset($text);
+ return;
+ }
+
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_HEADERS]['data'] = $this->pr_rfc_string_is_token($text['ordinals'], $text['characters']);
+ unset($text);
+
+ if ($this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_HEADERS]['data']['invalid']) {
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_HEADERS]['invalid'] = TRUE;
+ }
+ else {
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_HEADERS]['defined'] = TRUE;
+
+ // rename 'tokens' array key to 'headers'.
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_HEADERS]['data']['headers'] = $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_HEADERS]['data']['tokens'];
+ unset($this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_HEADERS]['data']['tokens']);
+ }
+ unset($this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_HEADERS]['data']['invalid']);
+ unset($this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_HEADERS]['data']['current']);
+
+ $this->request[self::REQUEST_ACCESS_CONTROL_REQUEST_HEADERS]['invalid'] = FALSE;
+ }
+
+ /**
+ * Load and process the HTTP request parameter: authorization.
+ *
+ * 1*(tchar) 1*(wsp) 1*(tchar) *(wsp) "=" *(wsp) ( 1*(tchar) / quoted-string ) *( *(wsp) "," *(wsp) 1*(tchar) *(wsp) "=" *(wsp) ( 1*(tchar) / quoted-string ) ).
+ *
+ * @see: https://tools.ietf.org/html/rfc7235#section-4.2
+ */
+ private function p_load_request_authorization() {
+ if (empty($this->headers['authorization'])) {
+ $this->request[self::REQUEST_AUTHORIZATION]['invalid'] = TRUE;
+ return;
+ }
+
+ $text = $this->pr_rfc_string_prepare($this->headers['authorization']);
+ if ($text['invalid']) {
+ $this->request[self::REQUEST_AUTHORIZATION]['invalid'] = TRUE;
+ unset($text);
+ return;
+ }
+
+ $this->request[self::REQUEST_AUTHORIZATION]['data'] = $this->pr_rfc_string_is_credentials($text['ordinals'], $text['characters']);
+ unset($text);
+
+ if ($this->request[self::REQUEST_AUTHORIZATION]['data']['invalid']) {
+ $this->request[self::REQUEST_AUTHORIZATION]['invalid'] = TRUE;
+ }
+ else {
+ $this->request[self::REQUEST_AUTHORIZATION]['defined'] = TRUE;
+ }
+ unset($this->request[self::REQUEST_AUTHORIZATION]['data']['invalid']);
+ unset($this->request[self::REQUEST_AUTHORIZATION]['data']['current']);
+
+ $this->request[self::REQUEST_AUTHORIZATION]['invalid'] = FALSE;
+ }
+
+ /**
* Load and process the HTTP request parameter: cache-control.
*
* Only 'no-cache' is supported at this time for requests.
// rename 'tokens' array key to 'connection'.
$this->request[self::REQUEST_CONNECTION]['data']['connection'] = $this->request[self::REQUEST_CONNECTION]['data']['tokens'];
- unset($this->request[self::REQUEST_CONNECTION]['data']['tokens']);
+ unset($this->request[self::REQUEST_CONNECTION]['data']['text']);
}
unset($this->request[self::REQUEST_CONNECTION]['data']['invalid']);
unset($this->request[self::REQUEST_CONNECTION]['data']['current']);
return;
}
- $this->request[self::REQUEST_HOST]['data'] = $this->p_parse_uri($this->headers['host']);
+ $text = $this->pr_rfc_string_prepare($this->headers['host']);
+ if ($text['invalid']) {
+ unset($text);
+
+ $this->request[self::REQUEST_HOST]['invalid'] = TRUE;
+ return;
+ }
+
+ $this->request[self::REQUEST_HOST]['data'] = $this->pr_rfc_string_is_uri($text['ordinals'], $text['characters']);
+ unset($text);
if ($this->request[self::REQUEST_HOST]['data']['invalid']) {
$this->request[self::REQUEST_HOST]['invalid'] = TRUE;
*
* @see: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
*/
private function p_load_request_origin() {
if (empty($this->headers['origin'])) {
return;
}
- $this->request[self::REQUEST_ORIGIN]['data'] = $this->p_parse_uri($this->headers['origin']);
+ $text = $this->pr_rfc_string_prepare($this->headers['origin']);
+ if ($text['invalid']) {
+ unset($text);
+
+ $this->request[self::REQUEST_ORIGIN]['invalid'] = TRUE;
+ return;
+ }
+
+ $this->request[self::REQUEST_ORIGIN]['data'] = $this->pr_rfc_string_is_uri($text['ordinals'], $text['characters']);
+ unset($text);
if ($this->request[self::REQUEST_ORIGIN]['data']['invalid']) {
$this->request[self::REQUEST_ORIGIN]['invalid'] = TRUE;
}
/**
+ * Load and process the HTTP request parameter: proxy-authorization.
+ *
+ * 1*(tchar) 1*(wsp) 1*(tchar) *(wsp) "=" *(wsp) ( 1*(tchar) / quoted-string ) *( *(wsp) "," *(wsp) 1*(tchar) *(wsp) "=" *(wsp) ( 1*(tchar) / quoted-string ) ).
+ *
+ * @see: https://tools.ietf.org/html/rfc7235#section-4.4
+ */
+ private function p_load_request_proxy_authorization() {
+ if (empty($this->headers['proxy_authorization'])) {
+ $this->request[self::REQUEST_PROXY_AUTHORIZATION]['invalid'] = TRUE;
+ return;
+ }
+
+ $text = $this->pr_rfc_string_prepare($this->headers['proxy_authorization']);
+ if ($text['invalid']) {
+ $this->request[self::REQUEST_PROXY_AUTHORIZATION]['invalid'] = TRUE;
+ unset($text);
+ return;
+ }
+
+ $this->request[self::REQUEST_PROXY_AUTHORIZATION]['data'] = $this->pr_rfc_string_is_credentials($text['ordinals'], $text['characters']);
+ unset($text);
+
+ if ($this->request[self::REQUEST_PROXY_AUTHORIZATION]['data']['invalid']) {
+ $this->request[self::REQUEST_PROXY_AUTHORIZATION]['invalid'] = TRUE;
+ }
+ else {
+ $this->request[self::REQUEST_PROXY_AUTHORIZATION]['defined'] = TRUE;
+ }
+ unset($this->request[self::REQUEST_PROXY_AUTHORIZATION]['data']['invalid']);
+ unset($this->request[self::REQUEST_PROXY_AUTHORIZATION]['data']['current']);
+
+ $this->request[self::REQUEST_PROXY_AUTHORIZATION]['invalid'] = FALSE;
+ }
+
+ /**
* Load and process the HTTP request parameter: referer.
*
* @see: https://tools.ietf.org/html/rfc7231#section-5.5.2
return;
}
- $this->request[self::REQUEST_REFERER]['data'] = $this->p_parse_uri($this->headers['referer']);
+ $text = $this->pr_rfc_string_prepare($this->headers['referer']);
+ if ($text['invalid']) {
+ unset($text);
+
+ $this->request[self::REQUEST_REFERER]['invalid'] = TRUE;
+ return;
+ }
+
+ $this->request[self::REQUEST_REFERER]['data'] = $this->pr_rfc_string_is_uri($text['ordinals'], $text['characters']);
+ unset($text);
if ($this->request[self::REQUEST_REFERER]['data']['invalid']) {
$this->request[self::REQUEST_REFERER]['invalid'] = TRUE;
}
/**
- * Load and process the HTTP request parameter: checksum_header.
- *
- * The following format is expected:
- * - 1*(atext):1*(atext):1*(atext)
+ * Load and process the HTTP request parameter: x-requested-with.
*
- * The header should have either 'partial:checksum_type:checksum_value' or 'complete:checksum_type:checksum_value'.
- * The checksum_value is stored in base64.
+ * I am just assuming the syntax to be 1*(tchar).
+ *
+ * @see: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
+ */
+ private function p_load_request_x_requested_with() {
+ if (empty($this->headers['x-requested-with'])) {
+ $this->request[self::REQUEST_X_REQUESTED_WITH]['invalid'] = TRUE;
+ return;
+ }
+
+ $text = $this->pr_rfc_string_prepare($this->headers['x-requested-with']);
+ if ($text['invalid']) {
+ $this->request[self::REQUEST_X_REQUESTED_WITH]['invalid'] = TRUE;
+ unset($text);
+ return;
+ }
+
+ $this->request[self::REQUEST_X_REQUESTED_WITH]['data'] = $this->pr_rfc_string_is_token($text['ordinals'], $text['characters']);
+ unset($text);
+
+ if ($this->request[self::REQUEST_X_REQUESTED_WITH]['data']['invalid']) {
+ $this->request[self::REQUEST_X_REQUESTED_WITH]['invalid'] = TRUE;
+ }
+ else {
+ $this->request[self::REQUEST_X_REQUESTED_WITH]['defined'] = TRUE;
+ }
+ unset($this->request[self::REQUEST_X_REQUESTED_WITH]['data']['invalid']);
+
+ $this->request[self::REQUEST_X_REQUESTED_WITH]['invalid'] = FALSE;
+ }
+
+ /**
+ * Load and process the HTTP request parameter: x-requested-for.
+ *
+ * This could be a name or an ip address.
+ * I am just using 1*(tchar) because that also allows ip addresses.
+ *
+ * @see: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
+ */
+ private function p_load_request_x_requested_for() {
+ if (empty($this->headers['x-requested-for'])) {
+ $this->request[self::REQUEST_X_REQUESTED_FOR]['invalid'] = TRUE;
+ return;
+ }
+
+ $text = $this->pr_rfc_string_prepare($this->headers['x-requested-for']);
+ if ($text['invalid']) {
+ $this->request[self::REQUEST_X_REQUESTED_FOR]['invalid'] = TRUE;
+ unset($text);
+ return;
+ }
+
+ $this->request[self::REQUEST_X_REQUESTED_HOST]['data'] = $this->pr_rfc_string_is_token($text['ordinals'], $text['characters']);
+ unset($text);
+
+ if ($this->request[self::REQUEST_X_REQUESTED_HOST]['data']['invalid']) {
+ $this->request[self::REQUEST_X_REQUESTED_HOST]['invalid'] = TRUE;
+ }
+ else {
+ $this->request[self::REQUEST_X_REQUESTED_HOST]['defined'] = TRUE;
+ }
+ unset($this->request[self::REQUEST_X_REQUESTED_HOST]['data']['invalid']);
+ unset($this->request[self::REQUEST_X_REQUESTED_HOST]['data']['uri']);
+
+ $this->request[self::REQUEST_X_REQUESTED_HOST]['invalid'] = FALSE;
+ }
+
+ /**
+ * Load and process the HTTP request parameter: x-requested-host.
+ *
+ * I am just assuming the syntax to be (url).
+ *
+ * @see: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
+ */
+ private function p_load_request_x_requested_host() {
+ if (empty($this->headers['x-requested-host'])) {
+ $this->request[self::REQUEST_X_REQUESTED_HOST]['invalid'] = TRUE;
+ return;
+ }
+
+ $text = $this->pr_rfc_string_prepare($this->headers['x-requested-host']);
+ if ($text['invalid']) {
+ $this->request[self::REQUEST_X_REQUESTED_HOST]['invalid'] = TRUE;
+ unset($text);
+ return;
+ }
+
+ $this->request[self::REQUEST_X_REQUESTED_HOST]['data'] = $this->pr_rfc_string_is_uri($text['ordinals'], $text['characters']);
+ unset($text);
+
+ if ($this->request[self::REQUEST_X_REQUESTED_HOST]['data']['invalid'] || $this->request[self::REQUEST_X_REQUESTED_HOST]['data']['uri'] === FALSE) {
+ $this->request[self::REQUEST_X_REQUESTED_HOST]['invalid'] = TRUE;
+ }
+ else {
+ $this->request[self::REQUEST_X_REQUESTED_HOST]['defined'] = TRUE;
+ }
+ unset($this->request[self::REQUEST_X_REQUESTED_HOST]['data']['invalid']);
+ unset($this->request[self::REQUEST_X_REQUESTED_HOST]['data']['uri']);
+
+ $this->request[self::REQUEST_X_REQUESTED_HOST]['invalid'] = FALSE;
+ }
+
+ /**
+ * Load and process the HTTP request parameter: x-requested-proto.
+ *
+ * I am just assuming the syntax to be 1*(tchar).
+ *
+ * @see: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
+ */
+ private function p_load_request_x_requested_proto() {
+ if (empty($this->headers['x-requested-proto'])) {
+ $this->request[self::REQUEST_X_REQUESTED_PROTO]['invalid'] = TRUE;
+ return;
+ }
+
+ $text = $this->pr_rfc_string_prepare($this->headers['x-requested-proto']);
+ if ($text['invalid']) {
+ $this->request[self::REQUEST_X_REQUESTED_PROTO]['invalid'] = TRUE;
+ unset($text);
+ return;
+ }
+
+ $this->request[self::REQUEST_X_REQUESTED_PROTO]['data'] = $this->pr_rfc_string_is_token($text['ordinals'], $text['characters']);
+ unset($text);
+
+ if ($this->request[self::REQUEST_X_REQUESTED_PROTO]['data']['invalid']) {
+ $this->request[self::REQUEST_X_REQUESTED_PROTO]['invalid'] = TRUE;
+ }
+ else {
+ $this->request[self::REQUEST_X_REQUESTED_PROTO]['defined'] = TRUE;
+ }
+ unset($this->request[self::REQUEST_X_REQUESTED_PROTO]['data']['invalid']);
+
+ $this->request[self::REQUEST_X_REQUESTED_PROTO]['invalid'] = FALSE;
+ }
+
+ /**
+ * Load and process the HTTP request parameter: checksum_header.
+ *
+ * The following format is expected:
+ * - 1*(atext):1*(atext):1*(atext)
+ *
+ * The header should have either 'partial:checksum_type:checksum_value' or 'complete:checksum_type:checksum_value'.
+ * The checksum_value is stored in base64.
*
* The content checksum represents the checksum of the HTTP content (HTTP packet body).
* This should already be defined if and when a checksum_header is used.
}
/**
- * Decode and check that the given uri is valid.
- *
- * This does not decode the uri, it separates it into its individual parts.
- *
- * Validation is done according to rfc3986.
- *
- * foo://example.com:8042/over/there?name=ferret#nose
- * \_/ \______________/\_________/ \_________/ \__/
- * | | | | |
- * scheme authority path query fragment
- * | _____________________|__
- * / \ / \
- * urn:example:animal:ferret:nose
- *
- *
- * @param string $uri
- * The url to validate and decode.
- *
- * @return array
- * A decoded uri split into its different parts inside an array.
- * An array key called 'invalid' exists to designate that the uri is invalid.
- *
- * @see: https://tools.ietf.org/html/rfc3986
- */
- private function p_parse_uri($uri) {
- $result = array(
- 'scheme' => NULL,
- 'authority' => NULL,
- 'path' => NULL,
- 'query' => NULL,
- 'fragment' => NULL,
- 'url' => TRUE, // @todo: set to FALSE when uri is a urn instead of a url.
- 'invalid' => FALSE,
- );
-
- // @todo: completely rewrite below.
- $result['invalid'] = TRUE;
-/*
- $matches = array();
- $matched = preg_match('!^((\w[=|\w|\d|\s|\.|-|_|~|%]*)*:)*([^#|?]*)(\?([^#]+))*(#(.+))*$!iu', $uri, $matches);
-
- if ($matched == FALSE || !array_key_exists(3, $matches)) {
- unset($address);
- unset($matches);
- unset($matched);
- $result['invalid'] = TRUE;
- return $result;
- }
- unset($matched);
-
-
- // process scheme.
- if (array_key_exists(2, $matches) && self::p_length_string($matches[2]) > 0) {
- $combined = $matches[3];
- if (array_key_exists(4, $matches)) {
- $combined .= $matches[4];
- }
- if (array_key_exists(6, $matches)) {
- $combined .= $matches[6];
- }
-
- $scheme_string = preg_replace('!:' . $combined . '$!iu', '', $matches[0]);
- $result['scheme'] = mb_split(':', $scheme_string);
- unset($scheme_string);
- unset($combined);
-
- foreach ($result['scheme'] as &$s) {
- $s = urldecode($s);
- }
- unset($s);
- }
-
-
- // process authority.
- if (self::p_length_string($matches[3]) > 0) {
- // rfc6854 designates multiple uris, separated by commas.
- $authority = mb_split(',', $matches[3]);
- foreach ($authority as $a) {
- $sub_matches = array();
- $sub_matched = preg_match('!^(//|/|)(([^@]+)@)*(.*)$!iu', $a, $sub_matches);
-
- if ($sub_matched === FALSE || !isset($sub_matches[4])) {
- $result['invalid'] = TRUE;
-
- unset($sub_matches);
- unset($sub_matched);
- continue;
- }
-
-
- // process user information.
- $information_matches = array();
- if (preg_match('@^([=|!|$|&|\'|(|)|\*|\+|,|;|\w|\d|-|\.|_|~|%|\s]*)(:|)$@iu', $sub_matches[3], $information_matches) === FALSE || !isset($information_matches[1])) {
- $result['invalid'] = TRUE;
-
- unset($information_matches);
- unset($sub_matches);
- unset($sub_matched);
- continue;
- }
-
- $authority_setting = array(
- 'type_path' => self::URI_PATH_THIS,
- 'type_host' => 0,
- 'user' => urldecode($information_matches[1]),
- 'host' => NULL,
- 'port' => NULL,
- );
- unset($information_matches);
-
-
- // process host information.
- if ($sub_matches[1] == '//') {
- $authority_setting['type_path'] = self::URI_PATH_SITE;
- }
- elseif ($sub_matches[1] == '/') {
- $authority_setting['type_path'] = self::URI_PATH_BASE;
- }
-
- $ipv6_matches = array();
- if (preg_match('@^\[([^\]]+)\](:\d+$|$)@iu', $sub_matches[4], $ipv6_matches) !== FALSE && isset($ipv6_matches[1])) {
- $authority_setting['type_host'] = self::URI_HOST_IPV6;
-
- $ip = inet_pton($ipv6_matches[1]);
- if ($ip === FALSE) {
- $result['invalid'] = TRUE;
-
- unset($ip);
- unset($ipv6_matches);
- continue;
- }
-
- $authority_setting['host'] = inet_ntop($ip);
- unset($ip);
-
- if (isset($ipv6_matches[2]) && self::p_length_string($ipv6_matches[2]) > 0) {
- $authority_setting['port'] = (int) $ipv6_matches[2];
- }
-
- // @todo: ipvfuture is actually embedded inside of the the double brackets used by ipv6.
- // to support this, the ipv6 regex must be modified to check for the ipvfuture parameters.
- // $authority_setting['type_host'] = self::URI_HOST_IPVX;
- // '@v[\d|a|b|c|d|e|f]\.([=|!|$|&|\'|(|)|\*|\+|,|;|\w|\d|-|\.|_|~|%|:]*)@i'
- }
- unset($ipv6_matches);
-
- $ipv4_matches = array();
- if (is_null($authority_setting['host']) && preg_match('@(\d+\.\d+\.d+\.d+)(:(\d+)|)$@iu', $sub_matches[4], $ipv4_matches) !== FALSE && isset($ipv4_matches[1])) {
- $authority_setting['type_host'] = self::URI_HOST_IPV4;
-
- $ip = inet_pton($ipv4_matches[1]);
- if ($ip === FALSE) {
- $result['invalid'] = TRUE;
-
- unset($ip);
- unset($ipv4_matches);
- continue;
- }
-
- $authority_setting['host'] = inet_ntop($ip);
- unset($ip);
-
- if (isset($ipv4_matches[3]) && self::p_length_string($ipv4_matches[3]) > 0) {
- $authority_setting['port'] = (int) $ipv4_matches[3];
- }
- }
- unset($ipv4_matches);
-
- $ipv4_matches = array();
- if (is_null($authority_setting['host']) && preg_match('@(\d+\.\d+\.d+\.d+)(:(\d+)|)$@iu', $sub_matches[4], $ipv4_matches) !== FALSE && isset($ipv4_matches[1])) {
- $authority_setting['type_host'] = self::URI_HOST_IPV4;
-
- $ip = inet_pton($ipv4_matches[1]);
- if ($ip === FALSE) {
- $result['invalid'] = TRUE;
-
- unset($ip);
- unset($ipv4_matches);
- continue;
- }
-
- $authority_setting['host'] = inet_ntop($ip);
- unset($ip);
-
- if (isset($ipv4_matches[3]) && self::p_length_string($ipv4_matches[3]) > 0) {
- $authority_setting['port'] = (int) $ipv4_matches[3];
- }
- }
- unset($ipv4_matches);
-
- $name_matches = array();
- if (is_null($authority_setting['host']) && preg_match('@((=|\w|\d|-|\.|_|~|\!|$|&|\'|(|)|\*|\+|,|;)+)(:(\d+)|)$@iu', $sub_matches[4], $name_matches) !== FALSE && isset($name_matches[1])) {
- $authority_setting['type_host'] = self::URI_HOST_NAME;
- $authority_setting['host'] = $name_matches[2];
-
- if (isset($name_matches[4]) && self::p_length_string($name_matches[4]) > 0) {
- $authority_setting['port'] = (int) $name_matches[4];
- }
- }
- unset($name_matches);
-
- $result['authority'][] = $authority_setting;
-
- unset($authority_setting);
- unset($sub_matches);
- unset($sub_matched);
- }
-
- unset($a);
- unset($authority);
- }
-
-
- // process query.
- if (array_key_exists(5, $matches) && self::p_length_string($matches[5]) > 0) {
- $query_parts = mb_split(',', $matches[5]);
-
- foreach ($query_parts as $qp) {
- $qp_parts = mb_split('=', $qp, 2);
-
- if (is_array($qp_parts) && isset($qp_parts[0])) {
- $decoded = urldecode($qp_parts[0]);
- if (isset($qp_parts[1])) {
- $result['query'][$decoded] = urldecode($qp_parts[1]);
- }
- else {
- $result['query'][$decoded] = NULL;
- }
- unset($decoded);
- }
- }
- unset($qp);
- unset($query_parts);
- }
-
-
- // process fragment.
- if (array_key_exists(7, $matches) && self::p_length_string($matches[7]) > 0) {
- $result['fragment'][] = urldecode($matches[7]);
- }
-
- unset($matches);
-*/
- return $result;
- }
-
- /**
* Decode and check that the given string is a valid entity tag such as with if-match (but do not test for weakness).
*
* Validation is done according to rfc7232.
*
* @param string $token_name
* The string to sanitize as a token name.
+ * @param bool $lower_case
+ * (optional) Force token to be lower-case.
+ * There are some cases where the token case may need to remain untouched.
+ * In such cases, set this to FALSE.
*
* @return string|bool
* A sanitized string is return on success.
* FALSE is returned on error or if the header name is invalid.
*/
- private function p_prepare_token($token_name) {
- $trimmed = preg_replace('/(^\s+)|(\s+$)/us', '', mb_strtolower($token_name));
- if ($trimmed === FALSE) {
- unset($trimmed);
- return FALSE;
+ private function p_prepare_token($token_name, $lower_case = TRUE) {
+ if ($lower_case) {
+ $trimmed = preg_replace('/(^\s+)|(\s+$)/us', '', mb_strtolower($token_name));
+ if ($trimmed === FALSE) {
+ unset($trimmed);
+ return FALSE;
+ }
+ }
+ else {
+ $trimmed = preg_replace('/(^\s+)|(\s+$)/us', '', $token_name);
+ if ($trimmed === FALSE) {
+ unset($trimmed);
+ return FALSE;
+ }
}
$text = $this->pr_rfc_string_prepare($trimmed);
* The header output array to make changes to.
*
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Origin
*/
private function p_prepare_header_response_access_control_allow_origin($header_name, &$header_output) {
if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN, $this->response)) {
return;
}
- // @todo
+ if ($this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN]['wildcard']) {
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN] = $header_name . self::SEPARATOR_HEADER_NAME . '*';
+ return;
+ }
+
+ $combined = pr_rfc_string_combine_uri_array($this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN]);
+ if (is_string($combined)) {
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN] = $header_name . self::SEPARATOR_HEADER_NAME . $combined;
+ }
+ unset($combined);
}
/**
* The header output array to make changes to.
*
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Expose-Headers
*/
private function p_prepare_header_response_access_control_expose_headers($header_name, &$header_output) {
if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS, $this->response)) {
return;
}
- // @todo
+ $header_output[self::RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS] = $header_name . self::SEPARATOR_HEADER_NAME;
+
+ if (!empty($this->response[self::RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS])) {
+ $exposed_headers_array = $this->response[self::RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS];
+
+ reset($exposed_headers_array);
+ $exposed_header_name = array_shift($exposed_headers_array);
+ $header_output[self::RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS] .= $exposed_header_name;
+
+ foreach ($exposed_headers_array as $exposed_header_name) {
+ $header_output[self::RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS] .= ', ' . $exposed_header_name;
+ }
+ unset($exposed_headers_array);
+ unset($exposed_header_name);
+ }
}
/**
* The header output array to make changes to.
*
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Methods
*/
private function p_prepare_header_response_access_control_allow_methods($header_name, &$header_output) {
if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS, $this->response)) {
return;
}
- // @todo
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] = $header_name . self::SEPARATOR_HEADER_NAME;
+
+ $allow_methods_array = $this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS];
+
+ reset($allow_methods_array);
+ $methods = array_shift($allow_methods_array);
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] .= $methods;
+
+ foreach ($allow_methods_array as $methods) {
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] .= ', ' ;
+
+ switch ($methods) {
+ case self::HTTP_METHOD_HEAD:
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] .= 'HEAD';
+ break;
+
+ case self::HTTP_METHOD_POST:
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] .= 'POST';
+ break;
+
+ case self::HTTP_METHOD_PUT:
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] .= 'PUT';
+ break;
+
+ case self::HTTP_METHOD_DELETE:
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] .= 'DELETE';
+ break;
+
+ case self::HTTP_METHOD_TRACE:
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] .= 'TRACE';
+ break;
+
+ case self::HTTP_METHOD_OPTIONS:
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] .= 'OPTIONS';
+ break;
+
+ case self::HTTP_METHOD_CONNECT:
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] .= 'CONNECT';
+ break;
+
+ case self::HTTP_METHOD_PATCH:
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] .= 'PATCH';
+ break;
+
+ case self::HTTP_METHOD_TRACK:
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] .= 'TRACK';
+ break;
+
+ case self::HTTP_METHOD_DEBUG:
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] .= 'DEBUG';
+ break;
+
+ }
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] .= ', ' . $methods;
+ }
+ unset($allow_methods_array);
+ unset($methods);
+
}
/**
* The header output array to make changes to.
*
* @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ * @see: https://www.w3.org/TR/CSP2/
+ * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Headers
*/
private function p_prepare_header_response_access_control_allow_headers($header_name, &$header_output) {
if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS, $this->response)) {
return;
}
- // @todo
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS] = $header_name . self::SEPARATOR_HEADER_NAME;
+
+ if (empty($this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS])) {
+ return;
+ }
+
+ $allowed_headers_array = $this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS];
+
+ reset($allowed_headers_array);
+ $allowed_header_name = array_shift($allowed_headers_array);
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS] .= $allowed_header_name;
+
+ foreach ($allowed_headers_array as $allowed_header_name) {
+ $header_output[self::RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS] .= ', ' . $allowed_header_name;
+ }
+ unset($allowed_headers_array);
+ unset($allowed_header_name);
}
/**
return;
}
- $header_output[self::RESPONSE_LINK] = $header_name . self::SEPARATOR_HEADER_NAME;
-
- $uri = NULL;
- if ($this->response[self::RESPONSE_LINK]['uri']['url']) {
- if (!is_null($this->response[self::RESPONSE_LINK]['uri']['scheme'])) {
- $uri .= $this->response[self::RESPONSE_LINK]['uri']['scheme'] . '://';
- }
-
- if (!is_null($this->response[self::RESPONSE_LINK]['uri']['authority'])) {
- $uri .= $this->response[self::RESPONSE_LINK]['uri']['authority'] . '/';
- }
-
- if (!is_null($this->response[self::RESPONSE_LINK]['uri']['path'])) {
- $uri .= $this->response[self::RESPONSE_LINK]['uri']['path'];
- }
-
- if (!is_null($this->response[self::RESPONSE_LINK]['uri']['query'])) {
- $uri .= '?' . $this->response[self::RESPONSE_LINK]['uri']['query'];
- }
-
- if (!is_null($this->response[self::RESPONSE_LINK]['uri']['fragment'])) {
- $uri .= '#' . $this->response[self::RESPONSE_LINK]['uri']['fragment'];
- }
- }
- else {
- if (!is_null($this->response[self::RESPONSE_LINK]['uri']['scheme'])) {
- $uri .= $this->response[self::RESPONSE_LINK]['uri']['scheme'] . ':';
+ foreach ($this->response[self::RESPONSE_LINK] as $uris) {
+ $uri = $this->pr_rfc_string_combine_uri_array($uris['uri']);
+ if ($uri === FALSE) {
+ unset($uri);
+ unset($uris);
+ return;
}
- if (!is_null($this->response[self::RESPONSE_LINK]['uri']['path'])) {
- $uri .= $this->response[self::RESPONSE_LINK]['uri']['path'];
+ if (!isset($header_output[self::RESPONSE_LINK])) {
+ $header_output[self::RESPONSE_LINK] = '';
}
- }
-
- $header_output[self::RESPONSE_LINK] .= '<' . $uri . '>';
- unset($uri);
- if (!empty($this->response[self::RESPONSE_LINK]['parameters'])) {
- $parameter_value = reset($this->response[self::RESPONSE_LINK]['parameters']);
- $parameter_name = key($this->response[self::RESPONSE_LINK]['parameters']);
- unset($this->response[self::RESPONSE_LINK]['parameters'][$parameter_name]);
-
- if (is_null($parameter_value)) {
- $parameters_string = $parameter_name;
- }
- else {
- $parameters_string = $parameter_name . '=' . $parameter_value;
- }
+ $header_output[self::RESPONSE_LINK] .= $header_name . self::SEPARATOR_HEADER_NAME;
+ $header_output[self::RESPONSE_LINK] .= '<' . $uri . '>';
+ unset($uri);
- foreach($this->response[self::RESPONSE_LINK]['parameters'] as $parameter_name => $parameter_value) {
- $parameters_string .= '; ';
+ if (!empty($uris['parameters'])) {
+ $parameter_value = reset($uris['parameters']);
+ $parameter_name = key($uris['parameters']);
+ unset($uris['parameters'][$parameter_name]);
+ $parameters_string = '; ';
if (is_null($parameter_value)) {
$parameters_string .= $parameter_name;
}
else {
$parameters_string .= $parameter_name . '=' . $parameter_value;
}
- }
- unset($parameter_name);
- unset($parameter_value);
- $header_output[self::RESPONSE_LINK] .= $parameters_string;
- unset($parameters_string);
+ foreach($uris['parameters'] as $parameter_name => $parameter_value) {
+ $parameters_string .= '; ';
+
+ if (is_null($parameter_value)) {
+ $parameters_string .= $parameter_name;
+ }
+ else {
+ $parameters_string .= $parameter_name . '=' . $parameter_value;
+ }
+ }
+ unset($parameter_name);
+ unset($parameter_value);
+
+ $header_output[self::RESPONSE_LINK] .= $parameters_string;
+ unset($parameters_string);
+ }
}
+ unset($uris);
}
/**
return;
}
- // in this case, a new header is created for every single entry..
+ // in this case, a new header is created for every single entry.
$header_output[self::RESPONSE_X_UA_COMPATIBLE] = array();
- foreach($header_output[self::RESPONSE_X_UA_COMPATIBLE] as $browser_name => $compatible_version) {
+ foreach($this->response[self::RESPONSE_X_UA_COMPATIBLE] as $browser_name => $compatible_version) {
$header_output[self::RESPONSE_X_UA_COMPATIBLE][] = $browser_name . '=' . $compatible_version;
}
unset($browser_name);
}
/**
+ * Prepare HTTP response headers that are boolean values represented by the words true/false.
+ *
+ * @param string $header_name
+ * The HTTP header name, such as: 'Age'.
+ * @param array $header_output
+ * The header output array to make changes to.
+ * @param int $code
+ * The HTTP header code, such as: self::RESPONSE_AGE.
+ */
+ private function p_prepare_header_response_boolean_value($header_name, &$header_output, $code) {
+ if (!array_key_exists($code, $this->response)) {
+ return;
+ }
+
+ $header_output[$code] = $header_name . self::SEPARATOR_HEADER_NAME;
+
+ if ($this->response[$code]) {
+ $header_output[$code] .= 'true';
+ }
+ else {
+ $header_output[$code] .= 'false';
+ }
+ }
+
+ /**
* Prepare HTTP response header: date
*
* @param string $header_name
* - 'text': A string containing the processed entity tag.
* - 'current': an integer representing the position the counter stopped at.
* - 'invalid': a boolean representing whether or not this string is valid or if an error occurred.
- *
- * @see: base_rfc_char::pr_rfc_char_qtext()
*/
protected function pr_rfc_string_is_entity_tag($ordinals, $characters, $start = 0, $stop = NULL) {
$result = array(
/**
* Processes a string based on the rfc syntax: credentials.
*
- * A string that is a digit has the following syntax:
- * - token [ 1*(ws) ( token68 / [ ( "," / token *(ws) "=" *(ws) ( token / quoted-string ) ) *( *(ws) "," [ *(ws) token *(ws) "=" *(ws) ( token / quoted-string ) ] ) ] ) ]
+ * A string that has the following syntax:
+ * - 1*(tchar) 1*(wsp) 1*(tchar) *(wsp) "=" *(wsp) ( 1*(tchar) / quoted-string ) *( *(wsp) "," *(wsp) 1*(tchar) *(wsp) "=" *(wsp) ( 1*(tchar) / quoted-string ) ).
+ *
+ * @todo: this entire function is incomplete, finish writing it.
*
* @param array $ordinals
* An array of integers representing each character of the string.
*/
protected function pr_rfc_string_is_credentials($ordinals, $characters, $start = 0, $stop = NULL) {
$result = array(
- 'text' => NULL,
+ 'scheme' => NULL,
+ 'parameters' => array(),
'current' => $start,
'invalid' => FALSE,
);
- // @todo: this looks like a lot of work, so deal with this at some point in the future because this is a very low priority function.
- $result['invalid'] = TRUE;
-/*
if (is_null($stop)) {
$stop = count($ordinals);
}
return $result;
}
+
+ // load the scheme.
for (; $result['current'] < $stop; $result['current']++) {
if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
// @fixme: should error be reported? do some debugging with this.
break;
}
- $code = $ordinals[$result['current']];
+ if ($this->pr_rfc_char_is_wsp($ordinals[$result['current']])) {
+ // reached end of the scheme.
+ $result['current']++;
- if (!$this->pr_rfc_char_is_digit($code)) {
+ $this->p_rfc_string_skip_past_whitespace($ordinals, $characters, $stop, $result);
+ if ($result['invalid']) {
+ return $result;
+ }
+
+ break;
+ }
+ elseif (!$this->pr_rfc_char_is_tchar($ordinals[$result['current']])) {
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ $result['scheme'] .= $characters[$result['current']];
+ }
+
+ if ($result['current'] >= $stop) {
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+
+ // load the parameters.
+ for (; $result['current'] < $stop; $result['current']++) {
+ if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
+ // @fixme: should error be reported? do some debugging with this.
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ $this->p_rfc_string_skip_past_whitespace($ordinals, $characters, $stop, $result);
+ if ($result['invalid']) {
+ return $result;
+ }
+
+ // load the parameter name.
+ $parameter_name = NULL;
+ for (; $result['current'] < $stop; $result['current']++) {
+ if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
+ // @fixme: should error be reported? do some debugging with this.
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ elseif (!$this->pr_rfc_char_is_tchar($ordinals[$result['current']])) {
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ $parameter_name .= $characters[$result['current']];
+ }
+
+ $this->p_rfc_string_skip_past_whitespace($ordinals, $characters, $stop, $result);
+ if ($result['invalid']) {
+ return $result;
+ }
+
+ if ($result['current'] >= $stop) {
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ // a parameter name must be followed by an equal sign and then the parameter value..
+ if ($ordinals[$result['current']] != c_base_ascii::EQUAL) {
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ $this->p_rfc_string_skip_past_whitespace($ordinals, $characters, $stop, $result);
+ if ($result['invalid']) {
+ return $result;
+ }
+
+ if ($result['current'] >= $stop) {
$result['invalid'] = TRUE;
+ return $result;
+ }
+
+ // load the parameter value.
+ if ($ordinals[$result['current']] == c_base_ascii::QUOTE_DOUBLE) {
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ $parsed = $this->pr_rfc_string_is_quoted_string($ordinals, $characters, $result['current'], self::STOP_AT_CLOSING_CHARACTER);
+ $result['current'] = $parsed['current'];
+
+ if ($parsed['invalid']) {
+ unset($parsed);
+
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ $result['parameters'][$parameter_name] = $parsed['text'];
+ unset($parsed);
+ }
+ else {
+ for (; $result['current'] < $stop; $result['current']++) {
+ if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
+ // @fixme: should error be reported? do some debugging with this.
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ if (!$this->pr_rfc_char_is_tchar($ordinals[$result['current']])) {
+ break;
+ }
+
+ $result['parameters'][$parameter_name] .= $characters[$result['current']];
+ }
+ }
+
+ $this->p_rfc_string_skip_past_whitespace($ordinals, $characters, $stop, $result);
+ if ($result['invalid']) {
+ return $result;
+ }
+
+ if ($result['current'] >= $stop) {
break;
}
- $result['text'] .= $characters[$result['current']];
+
+ // A comma designates a new entry
+ if ($ordinals[$result['current']] == c_base_ascii::COMMA) {
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+ }
+
+ $parameter_name = NULL;
}
- unset($code);
-*/
+
return $result;
}
krsort($result['choices']);
// The NULL key should be the first key in the weight.
- $this->p_prepend_array_value(NULL, $result['choices']);
+ $this->pr_prepend_array_value(NULL, $result['choices']);
return $result;
}
* - 'current': an integer representing the position the counter stopped at.
* - 'invalid': a boolean representing whether or not this string is valid or if an error occurred.
*
- * @see: base_rfc_char::pr_rfc_char_tchar()
* @see: https://tools.ietf.org/html/rfc2616#section-3.7
*/
protected function pr_rfc_string_is_media_type($ordinals, $characters, $start = 0, $stop = NULL) {
* - 'invalid': a boolean representing whether or not this string is valid or if an error occurred.
*
* @see: self::pr_rfc_string_is_valued_token_comma()
- * @see: base_rfc_char::pr_rfc_char_tchar()
* @see: base_rfc_char::pr_rfc_string_is_media_type()
* @see: https://tools.ietf.org/html/rfc2616#section-3.7
*/
$code = $ordinals[$result['current']];
if ($this->pr_rfc_char_is_wsp($code)) {
- // @todo: handle whitespace between '='.
if (is_null($token_name)) {
continue;
}
* - 'invalid': a boolean representing whether or not this string is valid or if an error occurred.
*
* @see: self::pr_rfc_string_is_valued_token()
- * @see: base_rfc_char::pr_rfc_char_tchar()
* @see: base_rfc_char::pr_rfc_string_is_media_type()
* @see: https://tools.ietf.org/html/rfc2616#section-3.7
*/
$code = $ordinals[$result['current']];
if ($this->pr_rfc_char_is_wsp($code)) {
- // @todo: handle whitespace between '='.
if (is_null($token_name)) {
continue;
}
* - A simpler syntax will therefore be used.
*
* A valued_token has the following syntax:
- * - 1*(*(ws) "," *(ws) token)
+ * - 1*(*(wsp) "," *(wsp) token)
*
* Original valued_token standard syntax:
- * - *("," *(ws)) token *(*(ws) "," *(ws) token)
+ * - *("," *(wsp)) token *(*(wsp) "," *(wsp) token)
*
* @param array $ordinals
* An array of integers representing each character of the string.
* - 'current': an integer representing the position the counter stopped at.
* - 'invalid': a boolean representing whether or not this string is valid or if an error occurred.
*
- * @see: base_rfc_char::pr_rfc_char_tchar()
* @see: https://tools.ietf.org/html/rfc7230#appendix-B
*/
protected function pr_rfc_string_is_commad_token($ordinals, $characters, $start = 0, $stop = NULL) {
}
/**
+ * Processes a string based on the rfc syntax: path.
+ *
+ * A path has the following syntax:
+ * - *(ALPHA / DIGIT / "-" / "." / "_" / "~" / "%" HEXDIG HEXDIG / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" / ":" / "@" / "/" / "?")
+ *
+ * @param array $ordinals
+ * An array of integers representing each character of the string.
+ * @param array $characters
+ * An array of characters representing the string.
+ * @param int $start
+ * (optional) The position in the arrays to start checking.
+ * @param int|null $stop
+ * (optional) The position in the arrays to stop checking.
+ * If NULL, then the entire string is processed or the closing character is found.
+ *
+ * @return array
+ * The processed information:
+ * - 'text': A string containing the validated path.
+ * - 'current': an integer representing the position the counter stopped at.
+ * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred.
+ *
+ * @see: https://tools.ietf.org/html/rfc3986#section-3.4
+ */
+ protected function pr_rfc_string_is_path($ordinals, $characters, $start = 0, $stop = NULL) {
+ $result = array(
+ 'text' => NULL,
+ 'current' => $start,
+ 'invalid' => FALSE,
+ );
+
+ if (is_null($stop)) {
+ $stop = count($ordinals);
+ }
+
+ if ($start >= $stop) {
+ return $result;
+ }
+
+ for (; $result['current'] < $stop; $result['current']++) {
+ $code = $ordinals[$result['current']];
+
+ if ($code == c_base_ascii::PERCENT) {
+ // valid only if two hex digits immediately follow.
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ // this is invalid because it is cut off before 2 hex digits are required and none is found.
+ $result['invalid'] = TRUE;
+ break;
+ }
+
+ $code = $ordinals[$result['current']];
+ if (!$this->pr_rfc_char_is_hexdigit($ordinals[$result['current']])) {
+ $result['invalid'] = TRUE;
+ break;
+ }
+
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ // this is invalid because it is cut off before 2 hex digits are required and only one is found.
+ $result['invalid'] = TRUE;
+ break;
+ }
+
+ $code = $ordinals[$result['current']];
+ if (!$this->pr_rfc_char_is_hexdigit($ordinals[$result['current']])) {
+ $result['invalid'] = TRUE;
+ break;
+ }
+ }
+ elseif (self::pr_rfc_char_is_pchar($code)) {
+ // do nothing, valid.
+ }
+ elseif ($code == c_base_asccii::SLASH_FORWARD) {
+ // do nothing, valid.
+ }
+ else {
+ $result['invalid'] = TRUE;
+ break;
+ }
+
+ $result['text'] .= $characters[$result['current']];
+ }
+ unset($code);
+
+ return $result;
+ }
+
+ /**
* Processes a string based on the rfc syntax: query.
*
* This is also used by the rfc syntax: fragment.
*
* @return array
* The processed information:
- * - 'address': A string containing the processed ip address.
- * When is_future is TRUE, this is an array containing:
- * - 'version': The ipfuture version.
- * - 'ip': The ip address.
- * - 'is_future': A boolean that when TRUE represents an ipvfuture address and when FALSE represents an ipv6 address.
+ * - 'text': A string containing the validated query.
* - 'current': an integer representing the position the counter stopped at.
* - 'invalid': a boolean representing whether or not this string is valid or if an error occurred.
*
- * @see: base_rfc_char::pr_rfc_char_tchar()
- * @see: https://tools.ietf.org/html/rfc2616#section-3.7
+ * @see: https://tools.ietf.org/html/rfc3986#section-3.4
*/
protected function pr_rfc_string_is_query($ordinals, $characters, $start = 0, $stop = NULL) {
$result = array(
- 'address' => NULL,
- 'is_future' => FALSE,
+ 'text' => NULL,
'current' => $start,
'invalid' => FALSE,
);
return $result;
}
- // @todo: finish writing this!
+ for (; $result['current'] < $stop; $result['current']++) {
+ $code = $ordinals[$result['current']];
+
+ if ($code == c_base_ascii::PERCENT) {
+ // valid only if two hex digits immediately follow.
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ // this is invalid because it is cut off before 2 hex digits are required and none is found.
+ $result['invalid'] = TRUE;
+ break;
+ }
+
+ $code = $ordinals[$result['current']];
+ if (!$this->pr_rfc_char_is_hexdigit($ordinals[$result['current']])) {
+ $result['invalid'] = TRUE;
+ break;
+ }
+
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ // this is invalid because it is cut off before 2 hex digits are required and only one is found.
+ $result['invalid'] = TRUE;
+ break;
+ }
+
+ $code = $ordinals[$result['current']];
+ if (!$this->pr_rfc_char_is_hexdigit($ordinals[$result['current']])) {
+ $result['invalid'] = TRUE;
+ break;
+ }
+ }
+ elseif (self::pr_rfc_char_is_pchar($code)) {
+ // do nothing, valid.
+ }
+ elseif ($code == c_base_asccii::SLASH_FORWARD || $code == c_base_asccii::QUESTION_MARK) {
+ // do nothing, valid.
+ }
+ else {
+ $result['invalid'] = TRUE;
+ break;
+ }
+
+ $result['text'] .= $characters[$result['current']];
+ }
+ unset($code);
+
+ return $result;
}
/**
* - 'current': an integer representing the position the counter stopped at.
* - 'invalid': a boolean representing whether or not this string is valid or if an error occurred.
*
- * @see: base_rfc_char::pr_rfc_char_tchar()
* @see: https://tools.ietf.org/html/rfc2616#section-3.7
*/
protected function pr_rfc_string_is_ip_literal($ordinals, $characters, $start = 0, $stop = NULL) {
for (; $result['current'] < $stop; $result['current']++) {
$code = $ordinals[$result['current']];
- if (self::pr_rfc_char_is_digit($code)) {
- // do nothing, valid
- }
- elseif (self::pr_rfc_char_is_alpha($code)) {
- // do nothing, valid
- }
- elseif (self::pr_rfc_char_is_unreserved($code)) {
- // do nothing, valid
+ if (self::pr_rfc_char_is_unreserved($code)) {
+ // do nothing, valid.
}
elseif (self::pr_rfc_char_is_sub_delims($code)) {
- // do nothing, valid
+ // do nothing, valid.
}
elseif ($code == c_base_ascii::COLON) {
- // do nothing, valid
+ // do nothing, valid.
}
elseif ($code == c_base_ascii::BRACKET_CLOSE) {
break;
}
/**
- * Effectively unshift a value onto a given array with a specified index.
+ * Decode and check that the given uri is valid.
+ *
+ * This does not decode the uri, it separates it into its individual parts.
+ *
+ * Validation is done according to rfc3986.
+ *
+ * foo://example.com:8042/over/there?name=ferret#nose
+ * \_/ \______________/\_________/ \_________/ \__/
+ * | | | | |
+ * scheme authority path query fragment
+ * | _____________________|__
+ * / \ / \
+ * foo:example:animal:ferret:nose
+ *
+ * The standard is misleading in its definition of path.
+ * First it says path can be path-abempty, path-absolute, path-noscheme, path-rootless, or path-empty.
+ * it then defines those as follows:
+ * path-abempty: begins with "/" or is empty
+ * path-absolute: begins with "/" but not "//"
+ * path-noscheme: begins with a non-colon segment
+ * path-rootless: begins with a segment
+ * path-empty: zero characters
+ *
+ * path-abempty's definition includes a '//', making path-absolute irrelevant/redundant.
+ * path-rootless's definition includes a colon, making path-noscheme irrelevant/redundant.
+ * path-empty's definition is inconsistent, why not say 'is empty' as with path-abempty?
+ *
+ * I am going to assume that path-abempty is meant to be defined as 'begins with "/" but not "//"' or 'is empty'.
+ *
+ * The standard also provides a regex example that violates their own rules:
+ * - Regex: ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ * - Their URI: http://www.ics.uci.edu/pub/ietf/uri/#Related
+ * - Example URI: example:relative:url/that:is:not/a:urn
+ *
+ * That example is syntatically valid for 'path-absolute', but has part of it turned into a scheme.
+ * While it is currently not popular to use ':' in a url path, it is valid according to the standard.
+ * Therefore, their example regex is non-conforming.
+ *
+ * In fact, as-written, the standard provides no means to distinguish between a scheme and a path.
+ * - Example Scheme: my_scheme:
+ * - Example Path: my_path:
+ * Both of those are valid for their appropriate syntax.
+ *
+ * I believe that the way they wrote the standard is very mis-leading.
+ * path = path-abempty / path-absolute / path-noscheme / path-rootless / path-empty
+ * means that any one of those could be valid, thats an OR.
+ * but they are likely interpretting the '/' to be a separation betweem if/then conditionals that are not clearly defined.
+ *
+ * I am going to assume that what they mean is ':' is supported only in path IF a scheme is supplied.
+ * In this case the first colon separates the schema from everything else.
+ * Even with this interpretation, there are still problems because the following would still be syntatically valid:
+ * - Example URI: http://www.example.com/a:syntatically:valid:path
+ * If that example is what is necessary, then how does one make a valid relative uri for paths on that site!?
+ * - schemes are not allowed in relative paths, but that path still exists!
+ * - The standard supports these paths as absolute but does not support them as relative.
+ *
+ * For simplicity purposes, this function will violate the literal standard to follow what I am guessing to be the intended standard.
+ * If a colon is to appear in the path, it must be a URN and if so then it must have a scheme.
*
- * The NULL key should be the first key in the weight.
- * PHP does not provide a way to preserve keys when merging arrays nor does PHP provide a way to unshift a value onto an array with a specific key.
+ * @param array $ordinals
+ * An array of integers representing each character of the string.
+ * @param array $characters
+ * An array of characters representing the string.
+ * @param int $start
+ * (optional) The position in the arrays to start checking.
+ * @param int|null $stop
+ * (optional) The position in the arrays to stop checking.
+ * If NULL, then the entire string is processed.
*
- * @param $key
- * The index name to unshift the value onto the array as.
- * @param array $array
- * The array to unshift onto.
+ * @return array
+ * The processed information:
+ * - 'scheme': The protocol string.
+ * - 'authority': The domain string.
+ * - 'path': The path string.
+ * - 'query': An array of url arguments.
+ * - 'fragment': The id string.
+ * - 'url': A boolean that when TRUE means the string is a url and when FALSE the string is a urn.
+ * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred.
+ *
+ * @see: self::p_combine_uri_array()
+ * @see: self::pr_rfc_string_is_scheme()
+ * @see: self::pr_rfc_string_is_authority()
+ * @see: self::pr_rfc_string_is_path()
+ * @see: self::pr_rfc_string_is_query()
+ * @see: https://tools.ietf.org/html/rfc3986
*/
- protected function p_prepend_array_value($key, &$array) {
- if (!array_key_exists($key, $array)) {
- return;
- }
-
- $value = $array[$key];
- unset($array[$key]);
+ protected function pr_rfc_string_is_uri($ordinals, $characters, $start = 0, $stop = NULL) {
+ $result = array(
+ 'scheme' => NULL,
+ 'authority' => NULL,
+ 'path' => NULL,
+ 'query' => NULL,
+ 'fragment' => NULL,
+ 'url' => TRUE,
+ 'current' => $start,
+ 'invalid' => FALSE,
+ );
+
+ if (is_null($stop)) {
+ $stop = count($ordinals);
+ }
+
+ if ($start >= $stop) {
+ return $result;
+ }
+
+
+ // handle path cases that begin with a forward slash because they are easy to identify.
+ if ($ordinals[$result['current']] == c_base_ascii::SLASH_FORWARD) {
+ $this->p_rfc_string_is_uri_path($ordinals, $characters, $stop, $result);
+ if ($result['invalid'] || $result['current'] >= $stop) {
+ return $result;
+ }
+
+
+ // check for query.
+ if ($ordinals[$result['current']] == c_base_ascii::QUESTION_MARK) {
+ // the first question mark is not recorded so skip past it before validating the fragment.
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ return $result;
+ }
+
+ $this->p_rfc_string_is_uri_query($ordinals, $characters, $stop, $result);
+ if ($result['invalid'] || $result['current'] >= $stop) {
+ return $result;
+ }
+ }
+
+
+ // check for fragment.
+ if ($ordinals[$result['current']] == c_base_ascii::HASH) {
+ // only the first hash is supported in the fragment (and it is not recorded) so skip past it before validating the fragment.
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ return $result;
+ }
+
+ $this->p_rfc_string_is_uri_fragment($ordinals, $characters, $stop, $result);
+ if ($result['invalid'] || $result['current'] >= $stop) {
+ return $result;
+ }
+ }
+
+ return $result;
+ }
+
+
+ // handle fragment cases first because they are easy to identify.
+ if ($ordinals[$result['current']] == c_base_ascii::HASH) {
+ for (; $result['current'] < $stop; $result['current']++) {
+ if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
+ // @fixme: should error be reported? do some debugging with this.
+ $result['invalid'] = TRUE;
+ break;
+ }
+
+ $code = $ordinals[$result['current']];
+
+ // the syntax for query is identical to fragment.
+ if (!$this->pr_rfc_char_is_query($code)) {
+ $result['invalid'] = TRUE;
+ break;
+ }
+
+ $result['fragment'] .= $characters[$result['current']];
+ }
+ unset($code);
+
+ return $result;
+ }
+
+
+ $not_scheme = FALSE;
+ $not_authority = FALSE;
+ $not_path = FALSE;
+ $processed_string = '';
+ for (; $result['current'] < $stop; $result['current']++) {
+ if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
+ // @fixme: should error be reported? do some debugging with this.
+ $result['invalid'] = TRUE;
+ break;
+ }
+
+ $code = $ordinals[$result['current']];
+
+ if ($this->pr_rfc_char_is_alpha($code)) {
+ // allowed in: scheme, authority, path
+ }
+ elseif ($this->pr_rfc_char_is_digit($code)) {
+ // allowed in: scheme, authority, path
+ }
+ elseif ($code == c_base_ascii::COLON) {
+ $not_path = TRUE;
+
+ if ($not_scheme) {
+ // must be an authority as per notes in the comments section of the function.
+ if ($not_authority) {
+ unset($not_scheme);
+ unset($not_authority);
+ unset($not_path);
+ unset($processed_string);
+
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+ }
+ else {
+ // must be an scheme as per notes in the comments section of the function.
+ $not_authority = TRUE;
+ }
+ }
+ elseif ($code == c_base_ascii::PLUS || $code == c_base_ascii::MINUS || $code == c_base_ascii::PERIOD) {
+ // allowed in: scheme, authority, path
+ }
+ elseif ($code == c_base_ascii::AT || $code == c_base_ascii::SLASH_FORWARD) {
+ // allowed in: authority, path
+
+ $not_scheme = TRUE;
+ }
+ elseif ($this->pr_rfc_char_is_unreserved($code)) {
+ // allowed in: authority, path
+
+ $not_scheme = TRUE;
+ }
+ elseif ($code == c_base_ascii::BRACKET_OPEN) {
+ // allowed in: authority
+
+ $not_scheme = TRUE;
+ $not_path = TRUE;
+ }
+ else {
+ unset($not_scheme);
+ unset($not_authority);
+ unset($not_path);
+ unset($processed_string);
+
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ if (($not_scheme && $not_path) || ($not_scheme && $not_authority) || ($not_authority && $not_path)) {
+ break;
+ }
+
+ $processed_string .= $characters[$result['current']];
+ }
+ unset($code);
+
+ if ($result['current'] >= $stop) {
+ unset($not_scheme);
+ unset($not_authority);
+ unset($not_path);
+ unset($processed_string);
+
+ return $result;
+ }
+
+ if ($not_authority && $not_path) {
+ unset($not_scheme);
+ unset($not_authority);
+ unset($not_path);
+
+ $result['scheme'] = $processed_string;
+ unset($processed_string);
+
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ return $result;
+ }
+
+ // check to see if '/' immediately follows, if not then this is a urn.
+ $code = $ordinals[$result['current']];
+ if ($code == c_base_ascii::SLASH_FORWARD) {
+ unset($code);
+
+ // at this point it is known that this is a url instead of a urn.
+ $this->p_rfc_string_is_uri_path($ordinals, $characters, $stop, $result);
+ if ($result['invalid'] || $result['current'] >= $stop) {
+ return $result;
+ }
+
+ // check for query.
+ if ($ordinals[$result['current']] == c_base_ascii::QUESTION_MARK) {
+ // the first question mark is not recorded so skip past it before validating the fragment.
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ return $result;
+ }
+
+ $this->p_rfc_string_is_uri_query($ordinals, $characters, $stop, $result);
+ if ($result['invalid'] || $result['current'] >= $stop) {
+ return $result;
+ }
+ }
+
+ // check for fragment.
+ if ($ordinals[$result['current']] == c_base_ascii::HASH) {
+ // only the first hash is supported in the fragment (and it is not recorded) so skip past it before validating the fragment.
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ return $result;
+ }
+
+ $this->p_rfc_string_is_uri_fragment($ordinals, $characters, $stop, $result);
+ if ($result['invalid'] || $result['current'] >= $stop) {
+ return $result;
+ }
+ }
+
+ return $result;
+ }
+ unset($code);
+
+ // process path argument and if a single ':' is found, then this is a urn.
+ for (; $result['current'] < $stop; $result['current']++) {
+ if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
+ unset($code);
+
+ // @fixme: should error be reported? do some debugging with this.
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ $code = $ordinals[$result['current']];
+
+ if ($code == c_base_ascii::HASH || $code == c_base_ascii::QUESTION_MARK) {
+ // found possible query or fragment.
+ $result['url'] = TRUE;
+ break;
+ }
+ elseif ($code == c_base_ascii::COLON) {
+ $result['url'] = FALSE;
+ }
+ elseif (!$this->pr_rfc_char_is_pchar($code)) {
+ unset($code);
+
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ $result['path'] .= $characters[$result['current']];
+ }
+ unset($code);
+
+ if ($result['current'] >= $stop) {
+ return $result;
+ }
+
+ // check for query.
+ if ($ordinals[$result['current']] == c_base_ascii::QUESTION_MARK) {
+ // the first question mark is not recorded so skip past it before validating the fragment.
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ return $result;
+ }
+
+ $this->p_rfc_string_is_uri_query($ordinals, $characters, $stop, $result);
+ if ($result['invalid'] || $result['current'] >= $stop) {
+ return $result;
+ }
+ }
+
+ // check for fragment.
+ if ($ordinals[$result['current']] == c_base_ascii::HASH) {
+ // only the first hash is supported in the fragment (and it is not recorded) so skip past it before validating the fragment.
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ return $result;
+ }
+
+ $this->p_rfc_string_is_uri_fragment($ordinals, $characters, $stop, $result);
+ if ($result['invalid'] || $result['current'] >= $stop) {
+ return $result;
+ }
+ }
+
+ return $result;
+ }
+ elseif ($not_scheme && $not_path) {
+ unset($not_scheme);
+ unset($not_authority);
+ unset($not_path);
+
+ $result['authority'] = $processed_string;
+ unset($processed_string);
+
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ return $result;
+ }
+
+ // check for authority.
+ $this->p_rfc_string_is_uri_authority($ordinals, $characters, $stop, $result);
+ if ($result['invalid'] || $result['current'] >= $stop) {
+ return $result;
+ }
+
+ // check for path.
+ $this->p_rfc_string_is_uri_path($ordinals, $characters, $stop, $result);
+ if ($result['invalid'] || $result['current'] >= $stop) {
+ return $result;
+ }
+
+ // check for query.
+ if ($ordinals[$result['current']] == c_base_ascii::QUESTION_MARK) {
+ // the first question mark is not recorded so skip past it before validating the fragment.
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ return $result;
+ }
+
+ $this->p_rfc_string_is_uri_query($ordinals, $characters, $stop, $result);
+ if ($result['invalid'] || $result['current'] >= $stop) {
+ return $result;
+ }
+ }
+
+ // check for fragment.
+ if ($ordinals[$result['current']] == c_base_ascii::HASH) {
+ // only the first hash is supported in the fragment (and it is not recorded) so skip past it before validating the fragment.
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ return $result;
+ }
+
+ $this->p_rfc_string_is_uri_fragment($ordinals, $characters, $stop, $result);
+ if ($result['invalid'] || $result['current'] >= $stop) {
+ return $result;
+ }
+ }
+ }
+ elseif ($not_scheme && $not_authority) {
+ unset($not_scheme);
+ unset($not_authority);
+ unset($not_path);
+
+ $result['path'] = $processed_string;
+ unset($processed_string);
+
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ return $result;
+ }
+
+ // check for path.
+ $this->p_rfc_string_is_uri_path($ordinals, $characters, $stop, $result);
+ if ($result['invalid'] || $result['current'] >= $stop) {
+ return $result;
+ }
+
+ // check for query.
+ if ($ordinals[$result['current']] == c_base_ascii::QUESTION_MARK) {
+ // the first question mark is not recorded so skip past it before validating the fragment.
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ return $result;
+ }
+
+ $this->p_rfc_string_is_uri_query($ordinals, $characters, $stop, $result);
+ if ($result['invalid'] || $result['current'] >= $stop) {
+ return $result;
+ }
+ }
+
+ // check for fragment.
+ if ($ordinals[$result['current']] == c_base_ascii::HASH) {
+ // only the first hash is supported in the fragment (and it is not recorded) so skip past it before validating the fragment.
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ return $result;
+ }
+
+ $this->p_rfc_string_is_uri_fragment($ordinals, $characters, $stop, $result);
+ if ($result['invalid'] || $result['current'] >= $stop) {
+ return $result;
+ }
+ }
+ }
+ unset($not_scheme);
+ unset($not_authority);
+ unset($not_path);
+ unset($processed_string);
+
+ $result['invalid'] = TRUE;
+
+ return $result;
+ }
+
+ /**
+ * Combine a uri array into a single uri.
+ *
+ * This does not validate the uri, it simply merges an array.
+ *
+ * foo://example.com:8042/over/there?name=ferret#nose
+ * \_/ \______________/\_________/ \_________/ \__/
+ * | | | | |
+ * scheme authority path query fragment
+ * | _____________________|__
+ * / \ / \
+ * urn:example:animal:ferret:nose
+ *
+ *
+ * @param array $uri_array
+ * A url array with the following structure:
+ * - 'scheme': The protocol string.
+ * - 'authority': The domain string.
+ * - 'path': The path string.
+ * - 'query': An array of url arguments.
+ * - 'fragment': The id string.
+ *
+ * @return string|bool
+ * A combined url array on success.
+ * FALSE is returned on error.
+ *
+ * @see: self::pr_rfc_string_is_uri()
+ * @see: https://tools.ietf.org/html/rfc3986
+ */
+ protected function pr_rfc_string_combine_uri_array($uri_array) {
+ if (!$uri_array['url']) {
+ // both scheme and path are required for urn.
+ if (!isset($uri_array['scheme']) || !isset($uri_array['path'])) {
+ return FALSE;
+ }
+
+ $combined .= $uri_array['scheme'] . ':' . $uri_array['path'];
+
+ return $combined;
+ }
+
+ $combined = NULL;
+ if (isset($uri_array['scheme'])) {
+ $combined .= $uri_array['scheme'] . ':';
+ }
+
+ if (isset($uri_array['authority'])) {
+ $combined .= $uri_array['authority'];
+ }
+
+ if (isset($uri_array['path'])) {
+ $combined .= $uri_array['path'];
+ }
+
+ if (!empty($uri_array['query'])) {
+ if (is_string($uri_array['query'])) {
+ $combined .= '?' . $uri_array['query'];
+ }
+ elseif (is_array($uri_array['query'])) {
+ $combined .= '?';
+
+ reset($uri_array['query']);
+ $query_name = key($uri_array['query']);
+ $query_value = $uri_array['query'][$query_name];
+ unset($uri_array['query'][$query_name]);
+
+ if (is_null($query_value)) {
+ $combined .= $query_name;
+ }
+ else {
+ $combined .= $query_name . '=' . $query_value;
+ }
+
+ foreach ($uri_array['query'] as $query_name => $query_value) {
+ if (is_null($query_value)) {
+ $combined .= $query_name;
+ continue;
+ }
+
+ $combined .= '&' . $query_name . '=' . $query_value;
+ }
+ unset($query_name);
+ unset($query_value);
+ }
+ }
+
+ if (isset($uri_array['fragment'])) {
+ $combined .= '#' . $uri_array['fragment'];
+ }
+
+ return $combined;
+ }
+
+ /**
+ * Effectively unshift a value onto a given array with a specified index.
+ *
+ * The NULL key should be the first key in the weight.
+ * PHP does not provide a way to preserve keys when merging arrays nor does PHP provide a way to unshift a value onto an array with a specific key.
+ *
+ * @param $key
+ * The index name to unshift the value onto the array as.
+ * @param array $array
+ * The array to unshift onto.
+ */
+ protected function pr_prepend_array_value($key, &$array) {
+ if (!array_key_exists($key, $array)) {
+ return;
+ }
+
+ $value = $array[$key];
+ unset($array[$key]);
$new_array = array(
$key => $value,
$array = $new_array;
unset($new_array);
}
+
+ /**
+ * Helper function for pr_rfc_string_is_uri() to process: authority.
+ *
+ * @param array $ordinals
+ * An array of integers representing each character of the string.
+ * @param array $characters
+ * An array of characters representing the string.
+ * @param int $stop
+ * The position in the arrays to stop checking.
+ * @param array $result
+ * An array of return results used by the pr_rfc_string_is_uri().
+ *
+ * @see: self::pr_rfc_string_is_uri()
+ */
+ private function p_rfc_string_is_uri_authority(&$ordinals, &$characters, &$stop, &$result) {
+ for (; $result['current'] < $stop; $result['current']++) {
+ if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
+ unset($code);
+
+ // @fixme: should error be reported? do some debugging with this.
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ $code = $ordinals[$result['current']];
+
+ if ($code == c_base_ascii::PERCENT) {
+ // valid only if two hex digits immediately follow.
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ // this is invalid because it is cut off before 2 hex digits are required and none is found.
+ $result['invalid'] = TRUE;
+ break;
+ }
+
+ $code = $ordinals[$result['current']];
+ if (!$this->pr_rfc_char_is_hexdigit($ordinals[$result['current']])) {
+ $result['invalid'] = TRUE;
+ break;
+ }
+
+ $result['current']++;
+ if ($result['current'] >= $stop) {
+ // this is invalid because it is cut off before 2 hex digits are required and only one is found.
+ $result['invalid'] = TRUE;
+ break;
+ }
+
+ $code = $ordinals[$result['current']];
+ if (!$this->pr_rfc_char_is_hexdigit($ordinals[$result['current']])) {
+ $result['invalid'] = TRUE;
+ break;
+ }
+ }
+ elseif ($code == c_base_ascii::AT || $code == c_base_ascii::COLON) {
+ // this is valid.
+ }
+ elseif ($code == c_base_ascii::BRACKET_OPEN || $code == c_base_ascii::BRACKET_CLOSE) {
+ // this is valid.
+ }
+ elseif ($this->pr_rfc_char_is_unreserved($code)) {
+ // this is valid.
+ }
+ elseif ($this->pr_rfc_char_is_sub_delims($code)) {
+ // this is valid.
+ }
+ else {
+ unset($code);
+
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ $result['authority'] .= $characters[$result['current']];
+ }
+ unset($code);
+ }
+
+ /**
+ * Helper function for pr_rfc_string_is_uri() to process: path.
+ *
+ * @param array $ordinals
+ * An array of integers representing each character of the string.
+ * @param array $characters
+ * An array of characters representing the string.
+ * @param int $stop
+ * The position in the arrays to stop checking.
+ * @param array $result
+ * An array of return results used by the pr_rfc_string_is_uri().
+ *
+ * @see: self::pr_rfc_string_is_uri()
+ */
+ private function p_rfc_string_is_uri_path(&$ordinals, &$characters, &$stop, &$result) {
+ for (; $result['current'] < $stop; $result['current']++) {
+ if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
+ unset($code);
+
+ // @fixme: should error be reported? do some debugging with this.
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ $code = $ordinals[$result['current']];
+
+ if ($code == c_base_ascii::HASH || $code == c_base_ascii::QUESTION_MARK) {
+ // found possible query or fragment.
+ break;
+ }
+ elseif ($code == c_base_ascii::SLASH_FORWARD) {
+ // this is valid.
+ }
+ elseif (!$this->pr_rfc_char_is_pchar($code)) {
+ unset($code);
+
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ $result['path'] .= $characters[$result['current']];
+ }
+ unset($code);
+ }
+
+ /**
+ * Helper function for pr_rfc_string_is_uri() to process: query.
+ *
+ * @param array $ordinals
+ * An array of integers representing each character of the string.
+ * @param array $characters
+ * An array of characters representing the string.
+ * @param int $stop
+ * The position in the arrays to stop checking.
+ * @param array $result
+ * An array of return results used by the pr_rfc_string_is_uri().
+ *
+ * @see: self::pr_rfc_string_is_uri()
+ */
+ private function p_rfc_string_is_uri_query(&$ordinals, &$characters, &$stop, &$result) {
+ $query_name = NULL;
+ $query_value = NULL;
+ $no_value = FALSE;
+
+ $result['query'] = array();
+ for (; $result['current'] < $stop; $result['current']++) {
+ if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
+ unset($code);
+ unset($query_name);
+ unset($query_value);
+ unset($no_value);
+
+ // @fixme: should error be reported? do some debugging with this.
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ $code = $ordinals[$result['current']];
+
+ if ($code == c_base_ascii::HASH) {
+ // hash is not part of the query but does mark the end of the query as it is the start of the fragment.
+ break;
+ }
+ elseif ($code == c_base_ascii::AMPERSAND) {
+ // The '&' designates a new name and value, separate each individual value inside the array.
+ $result['query'][$query_name] = $query_value;
+
+ $query_name = NULL;
+ $query_value = NULL;
+ $no_value = FALSE;
+
+ continue;
+ }
+ elseif ($code == c_base_ascii::EQUAL) {
+ // The '=' designates a value for the current name.
+ if ($no_value || is_null($query_name)) {
+ $query_name .= $characters[$result['current']];
+ $no_value = TRUE;
+ continue;
+ }
+
+ $query_value = '';
+ continue;
+ }
+ elseif (!$this->pr_rfc_char_is_query($code)) {
+ unset($code);
+ unset($query_name);
+ unset($query_value);
+ unset($no_value);
+
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ if (is_null($query_value)) {
+ $query_name .= $characters[$result['current']];
+ }
+ else {
+ $query_value .= $characters[$result['current']];
+ }
+ }
+ unset($code);
+ unset($no_value);
+
+ $result['query'][$query_name] = $query_value;
+
+ unset($query_name);
+ unset($query_value);
+ }
+
+ /**
+ * Helper function for pr_rfc_string_is_uri() to process: fragment.
+ *
+ * @param array $ordinals
+ * An array of integers representing each character of the string.
+ * @param array $characters
+ * An array of characters representing the string.
+ * @param int $stop
+ * The position in the arrays to stop checking.
+ * @param array $result
+ * An array of return results used by the pr_rfc_string_is_uri().
+ *
+ * @see: self::pr_rfc_string_is_uri()
+ */
+ private function p_rfc_string_is_uri_fragment(&$ordinals, &$characters, &$stop, &$result) {
+ for (; $result['current'] < $stop; $result['current']++) {
+ if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
+ unset($code);
+
+ // @fixme: should error be reported? do some debugging with this.
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ $code = $ordinals[$result['current']];
+
+ // the syntax for query is identical to fragment.
+ if (!$this->pr_rfc_char_is_query($code)) {
+ unset($code);
+
+ $result['invalid'] = TRUE;
+ return $result;
+ }
+
+ $result['fragment'] .= $characters[$result['current']];
+ }
+ unset($code);
+ }
+
+ /**
+ * Helper function for bypassing whitespaces.
+ *
+ * @param array $ordinals
+ * An array of integers representing each character of the string.
+ * @param array $characters
+ * An array of characters representing the string.
+ * @param int $stop
+ * The position in the arrays to stop checking.
+ * @param array $result
+ * An array of return results used by the pr_rfc_string_is_uri().
+ *
+ * @see: self::pr_rfc_string_is_uri()
+ */
+ private function p_rfc_string_skip_past_whitespace(&$ordinals, &$characters, &$stop, &$result) {
+ for (; $result['current'] < $stop; $result['current']++) {
+ if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
+ // @fixme: should error be reported? do some debugging with this.
+ $result['invalid'] = TRUE;
+ break;
+ }
+
+ if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) {
+ break;
+ }
+ }
+ }
}