From: Kevin Day Date: Thu, 27 Apr 2017 05:07:06 +0000 (-0500) Subject: Progress: work on http request processing, path processing, and other fixes X-Git-Url: https://git.kevux.org/?a=commitdiff_plain;h=24e221c2f5aa3b8ddf177ce7799f29eb660ff997;p=koopa Progress: work on http request processing, path processing, and other fixes Process the HTTP request methods, such as GET, POST, HEAD, OPTIONS, etc... Add validation against these request methods on a per path basis. Improve path processing logic (there is still more work to do). Other fixes and improvements. I specifically added an nginx example configuration file (it still requires separate PHP-fcgi configuration). --- diff --git a/common/base/classes/base_http.php b/common/base/classes/base_http.php index f868a74..366e1ae 100644 --- a/common/base/classes/base_http.php +++ b/common/base/classes/base_http.php @@ -53,19 +53,20 @@ class c_base_http extends c_base_rfc_string { const REQUEST_IF_RANGE = 22; const REQUEST_IF_UNMODIFIED_SINCE = 23; const REQUEST_MAX_FORWARDS = 24; - const REQUEST_ORIGIN = 25; - const REQUEST_POST = 26; - const REQUEST_PRAGMA = 27; - const REQUEST_PROXY_AUTHORIZATION = 28; - const REQUEST_RANGE = 29; - const REQUEST_REFERER = 30; - const REQUEST_SCRIPT_NAME = 31; - const REQUEST_TE = 32; - const REQUEST_UPGRADE = 33; - const REQUEST_URI = 34; - const REQUEST_USER_AGENT = 35; - const REQUEST_VIA = 36; - const REQUEST_WARNING = 37; + const REQUEST_METHOD = 25; + const REQUEST_ORIGIN = 26; + const REQUEST_POST = 27; + const REQUEST_PRAGMA = 28; + const REQUEST_PROXY_AUTHORIZATION = 29; + const REQUEST_RANGE = 30; + const REQUEST_REFERER = 31; + const REQUEST_SCRIPT_NAME = 32; + const REQUEST_TE = 33; + const REQUEST_UPGRADE = 34; + const REQUEST_URI = 35; + const REQUEST_USER_AGENT = 36; + const REQUEST_VIA = 37; + const REQUEST_WARNING = 38; const REQUEST_UNKNOWN = 999; // non-standard, but supported, request headers @@ -553,6 +554,7 @@ class c_base_http extends c_base_rfc_string { self::REQUEST_IF_RANGE, self::REQUEST_IF_UNMODIFIED_SINCE, self::REQUEST_MAX_FORWARDS, + self::REQUEST_METHOD, self::REQUEST_ORIGIN, self::REQUEST_PRAGMA, self::REQUEST_PROXY_AUTHORIZATION, @@ -727,6 +729,11 @@ class c_base_http extends c_base_rfc_string { unset($headers['max_forwards']); } + // request method is stored in $_SERVER['REQUEST_METHOD'] and should always be defined (by PHP) for valid HTTP requests. + if (isset($_SERVER['REQUEST_METHOD'])) { + $this->p_load_request_method(); + } + if (array_key_exists('origin', $this->headers)) { $this->p_load_request_origin(); unset($headers['origin']); @@ -2534,7 +2541,7 @@ class c_base_http extends c_base_rfc_string { if (is_array($uri)) { $uri_string = $this->pr_rfc_string_combine_uri_array($uri); - if ($combined === FALSE) { + if ($uri_string === FALSE) { unset($parts); unset($combined); @@ -6221,6 +6228,57 @@ class c_base_http extends c_base_rfc_string { } /** + * Load and process the HTTP request parameter: request method. + * + * @see: https://tools.ietf.org/html/rfc2616#section-5.1.1 + */ + private function p_load_request_method() { + $this->request[self::REQUEST_METHOD]['defined'] = TRUE; + + $method_string = c_base_utf8::s_lowercase($_SERVER['REQUEST_METHOD'])->get_value_exact(); + if ($method_string == 'get') { + $this->request[self::REQUEST_METHOD]['data'] = self::HTTP_METHOD_GET; + } + elseif ($method_string == 'head') { + $this->request[self::REQUEST_METHOD]['data'] = self::HTTP_METHOD_HEAD; + } + elseif ($method_string == 'post') { + $this->request[self::REQUEST_METHOD]['data'] = self::HTTP_METHOD_POST; + } + elseif ($method_string == 'put') { + $this->request[self::REQUEST_METHOD]['data'] = self::HTTP_METHOD_PUT; + } + elseif ($method_string == 'delete') { + $this->request[self::REQUEST_METHOD]['data'] = self::HTTP_METHOD_DELETE; + } + elseif ($method_string == 'trace') { + $this->request[self::REQUEST_METHOD]['data'] = self::HTTP_METHOD_TRACE; + } + elseif ($method_string == 'options') { + $this->request[self::REQUEST_METHOD]['data'] = self::HTTP_METHOD_OPTIONS; + } + elseif ($method_string == 'connect') { + $this->request[self::REQUEST_METHOD]['data'] = self::HTTP_METHOD_CONNECT; + } + elseif ($method_string == 'patch') { + $this->request[self::REQUEST_METHOD]['data'] = self::HTTP_METHOD_PATCH; + } + elseif ($method_string == 'track') { + $this->request[self::REQUEST_METHOD]['data'] = self::HTTP_METHOD_TRACK; + } + elseif ($method_string == 'debug') { + $this->request[self::REQUEST_METHOD]['data'] = self::HTTP_METHOD_DEBUG; + } + else { + // use 'none' to represent all unknown methods. + $this->request[self::REQUEST_METHOD]['data'] = self::HTTP_METHOD_NONE; + } + unset($method_string); + + $this->request[self::REQUEST_METHOD]['invalid'] = FALSE; + } + + /** * Load and process the HTTP request parameter: origin. * * Errata: I cannot find Origin specified in the RFC's that I looked at. @@ -7303,8 +7361,8 @@ class c_base_http extends c_base_rfc_string { } if (isset($pieces[1])) { - $lower_piece_1 = preg_replace('/(^\s+)|(\s+$)/us', '', c_base_utf8::s_lowercase($pieces[0]))->get_value_exact(); - $lower_piece_2 = preg_replace('/(^\s+)|(\s+$)/us', '', c_base_utf8::s_lowercase($pieces[1]))->get_value_exact(); + $lower_piece_1 = preg_replace('/(^\s+)|(\s+$)/us', '', c_base_utf8::s_lowercase($pieces[0])->get_value_exact()); + $lower_piece_2 = preg_replace('/(^\s+)|(\s+$)/us', '', c_base_utf8::s_lowercase($pieces[1])->get_value_exact()); if ($lower_piece_1 == 'trident') { $result['engine_name_machine'] = 'trident'; @@ -7638,8 +7696,8 @@ class c_base_http extends c_base_rfc_string { $pieces = mb_split('/', $agent_matches[0]); $total_pieces = count($pieces); if ($total_pieces == 2) { - $lower_piece_1 = preg_replace('/(^\s+)|(\s+$)/us', '', c_base_utf8::s_lowercase($pieces[0]))->get_value_exact(); - $lower_piece_2 = preg_replace('/(^\s+)|(\s+$)/us', '', c_base_utf8::s_lowercase($pieces[1]))->get_value_exact(); + $lower_piece_1 = preg_replace('/(^\s+)|(\s+$)/us', '', c_base_utf8::s_lowercase($pieces[0])->get_value_exact()); + $lower_piece_2 = preg_replace('/(^\s+)|(\s+$)/us', '', c_base_utf8::s_lowercase($pieces[1])->get_value_exact()); if ($lower_piece_1 == 'curl') { $result['engine_name_machine'] = 'curl'; diff --git a/common/base/classes/base_path.php b/common/base/classes/base_path.php index 87380ec..4e8e8f6 100644 --- a/common/base/classes/base_path.php +++ b/common/base/classes/base_path.php @@ -68,6 +68,13 @@ require_once('common/base/classes/base_cookie.php'); class c_base_path extends c_base_rfc_string { use t_base_return_value_exact; + private const DEFAULT_ALLOWED_METHODS = array( + c_base_http::HTTP_METHOD_GET => c_base_http::HTTP_METHOD_GET, + c_base_http::HTTP_METHOD_POST => c_base_http::HTTP_METHOD_POST, + c_base_http::HTTP_METHOD_HEAD => c_base_http::HTTP_METHOD_HEAD, + c_base_http::HTTP_METHOD_OPTIONS => c_base_http::HTTP_METHOD_OPTIONS, + ); + protected $id_group = NULL; protected $is_content = NULL; @@ -87,6 +94,8 @@ class c_base_path extends c_base_rfc_string { protected $include_directory = NULL; protected $include_name = NULL; + protected $allowed_methods = NULL; + /** * Class constructor. */ @@ -112,6 +121,8 @@ class c_base_path extends c_base_rfc_string { $this->include_directory = NULL; $this->include_name = NULL; + + $this->allowed_methods = self::DEFAULT_ALLOWED_METHODS; } /** @@ -137,6 +148,8 @@ class c_base_path extends c_base_rfc_string { unset($this->include_directory); unset($this->include_name); + unset($this->allowed_methods); + parent::__destruct(); } @@ -727,6 +740,63 @@ class c_base_path extends c_base_rfc_string { } /** + * Assign an allowed http method. + * + * @param int $method + * The id of the method to allow. + * @param bool $append + * (optional) When TRUE, the method id is appended. + * When FALSE, the array is re-created with $method as the only array value. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_allowed_method($method, $append = TRUE) { + if (!is_int($method)) { + $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 (!is_bool($append)) { + $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'append', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT); + return c_base_return_error::s_false($error); + } + + if (!$append) { + $this->allowed_methods = array(); + } + + $this->allowed_methods[$method] = $method; + return new c_base_return_true(); + } + + /** + * Assign all allowed http methods. + * + * @param array $method + * An array of method ids of the method to allow. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_allowed_methods($methods) { + if (!is_array($methods)) { + $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'methods', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT); + return c_base_return_error::s_false($error); + } + + $this->allowed_methods = array(); + foreach ($methods as $method) { + if (is_int($method)) { + $this->allowed_methods[$method] = $method; + } + } + unset($method); + + return new c_base_return_true(); + } + + /** * Gets the ID sort setting. * * @return c_base_return_int @@ -954,6 +1024,23 @@ class c_base_path extends c_base_rfc_string { } /** + * Get the assigned include path name. + * + * This is the suffix part of the path. + * + * @return c_base_return_array + * An array of allowed methods is returned. + * An empty array with the error bit set is returned on error. + */ + public function get_allowed_methods() { + if (!is_array($this->allowed_methods)) { + $this->allowed_methods = self::DEFAULT_ALLOWED_METHODS; + } + + return c_base_return_array::s_new($this->allowed_methods); + } + + /** * Execute using the specified path, rendering the page. * * @param c_base_http $http @@ -970,27 +1057,30 @@ class c_base_path extends c_base_rfc_string { * An executed array object with error bit set is returned on error. */ public function do_execute(&$http, &$database, &$session, $settings = array()) { + $executed = new c_base_path_executed(); + if (!($http instanceof c_base_http)) { $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'http', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT); - return c_base_return_error::s_value(array(), 'c_base_path_executed', $error); + $executed->set_error($error); + unset($error); } - - if (!($database instanceof c_base_database)) { + elseif (!($database instanceof c_base_database)) { $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'database', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT); - return c_base_return_error::s_value(array(), 'c_base_path_executed', $error); + $executed->set_error($error); + unset($error); } - - if (!($session instanceof c_base_session)) { + elseif (!($session instanceof c_base_session)) { $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'session', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT); - return c_base_return_error::s_value(array(), 'c_base_path_executed', $error); + $executed->set_error($error); + unset($error); } - - if (!is_array($settings)) { + elseif (!is_array($settings)) { $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'settings', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT); - return c_base_return_error::s_value(array(), 'c_base_path_executed', $error); + $executed->set_error($error); + unset($error); } - return new c_base_path_executed(); + return $executed; } /** @@ -1067,6 +1157,27 @@ class c_base_path extends c_base_rfc_string { // @fixme: $value is returned unsanitized until this code is implemented. return c_base_return_value::s_new($_POST[$id]); } + + /** + * Obtains the current HTTP request method. + * + * @param c_base_http &$http + * The HTTP information object. + * + * @return int + * An HTTP request method is always returned. + */ + protected function pr_get_method(&$http) { + $method = $http->get_request(c_base_http::REQUEST_METHOD)->get_value_exact(); + if (isset($method['data']) && is_int($method['data'])) { + $method = $method['data']; + } + else { + $method = c_base_http::HTTP_METHOD_NONE; + } + + return $method; + } } /** @@ -1162,15 +1273,16 @@ class c_base_path_executed extends c_base_return_array { /** * Assign output. * - * @param c_base_return + * @param c_base_return|null * The output to assign. + * NULL may be specified to remove any output. * * @return c_base_return_status * TRUE is returned on success. * FALSE with error bit set is returned on error. */ public function set_output($output) { - if (!($output instanceof c_base_return)) { + if (!is_null($output) && !($output instanceof c_base_return)) { $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'output', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT); return c_base_return_error::s_false($error); } @@ -1268,12 +1380,15 @@ class c_base_paths extends c_base_return { * @param string|null $name * (optional) The suffix path (relative to the PHP includes) to include that contains the requested path. * When not NULL, both $directory and $name must not be NULL. + * @param array|null $allowed_methods + * (optional) An array of ids of allowed methods. + * When NULL, this value is ignored. * * @return c_base_return_status * TRUE is returned on success. * FALSE with error bit set is returned on error. */ - public function set_path($path, $handler, $include_directory = NULL, $include_name = NULL) { + public function set_path($path, $handler, $include_directory = NULL, $include_name = NULL, $allowed_methods = NULL) { if (!is_string($path)) { $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'path', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT); return c_base_return_error::s_false($error); @@ -1294,14 +1409,33 @@ class c_base_paths extends c_base_return { return c_base_return_error::s_false($error); } + + // get allowed methods + $path_object = new c_base_path(); + if (is_null($allowed_methods)) { + $methods = $path_object->get_allowed_methods()->get_value_exact(); + if (!is_array($methods)) { + $methods = array(); + } + } + else { + $methods = $allowed_methods; + } + + if (mb_strlen($path) == 0) { - $this->root = array('handler' => $handler, 'include_directory' => $include_directory, 'include_name' => $include_name, 'is_root' => TRUE); + unset($path_object); + $this->root = array('handler' => $handler, 'include_directory' => $include_directory, 'include_name' => $include_name, 'is_root' => TRUE, 'methods' => $methods); return new c_base_return_true(); } - $path_object = new c_base_path(); - $valid_path = $path_object->set_value($path); + if (!is_null($allowed_methods) && !is_array($allowed_methods)) { + unset($path_object); + $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'allowed_methods', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT); + return c_base_return_error::s_false($error); + } + $valid_path = $path_object->set_value($path); if (!$valid_path) { unset($path_object); unset($valid_path); @@ -1311,7 +1445,6 @@ class c_base_paths extends c_base_return { } unset($valid_path); - $path_string = $path_object->get_value_exact(); unset($path_object); @@ -1343,25 +1476,49 @@ class c_base_paths extends c_base_return { $depth_current = 1; $depth_total = count($path_parts); - foreach ($path_parts as $path_part) { - if ($depth_current == $depth_total) { - $path_tree['include_directory'] = $include_directory; - $path_tree['include_name'] = $include_name; - $path_tree['handler'] = $handler; - break; - } - if (!isset($path_tree['paths'][$path_part])) { - $path_tree['paths'][$path_part] = array( - 'paths' => array(), - 'include_directory' => NULL, - 'include_name' => NULL, - 'handler' => NULL, - ); - } + // make sure the first path exists. + $path_part = array_shift($path_parts); + if (!array_key_exists($path_part, $path_tree)) { + $path_tree[$path_part] = array( + 'paths' => array(), + 'include_directory' => NULL, + 'include_name' => NULL, + 'handler' => NULL, + 'methods' => array(), + ); + } + + $path_tree = &$path_tree[$path_part]; + if ($depth_current == $depth_total) { + $path_tree['include_directory'] = $include_directory; + $path_tree['include_name'] = $include_name; + $path_tree['handler'] = $handler; + $path_tree['methods'] = $methods; + } + else { + foreach ($path_parts as $path_part) { + if (!isset($path_tree['paths'][$path_part])) { + $path_tree['paths'][$path_part] = array( + 'paths' => array(), + 'include_directory' => NULL, + 'include_name' => NULL, + 'handler' => NULL, + 'methods' => array(), + ); + } - $path_tree = &$path_tree['paths'][$path_part]; - $depth_current++; + $path_tree = &$path_tree['paths'][$path_part]; + $depth_current++; + + if ($depth_current == $depth_total) { + $path_tree['include_directory'] = $include_directory; + $path_tree['include_name'] = $include_name; + $path_tree['handler'] = $handler; + $path_tree['methods'] = $methods; + break; + } + } } unset($path_part); unset($path_parts); @@ -1445,33 +1602,50 @@ class c_base_paths extends c_base_return { $depth_total = count($path_parts); $found = NULL; $path_tree = &$this->paths[$id_group]; - foreach ($path_parts as $path_part) { - if ($depth_current == $depth_total) { - if (isset($path_tree['handler'])) { - $found = array('include_directory' => $path_tree['include_directory'], 'include_name' => $path_tree['include_name'], 'handler' => $path_tree['handler']); - break; - } + + // @fixme: the current design needs to handle multiple possible wildcard paths when searching (such as '/a/b/c/%', '/a/%/c', where '/a/b/c/%' would prevent '/a/%/c' from ever matching). + $path_part = array_shift($path_parts); + if (array_key_exists($path_part, $path_tree) || array_key_exists('%', $path_tree)) { + if (array_key_exists($path_part, $path_tree)) { + $path_tree = &$path_tree[$path_part]; + } + else { + $path_tree = &$path_tree['%']; } - if (!isset($path_tree['paths'][$path_part])) { - if ($depth_current == $depth_total) { - if (isset($path_tree['handler'])) { - $found = array('include_directory' => $path_tree['include_directory'], 'include_name' => $path_tree['include_name'], 'handler' => $path_tree['handler']); + if ($depth_current == $depth_total) { + $found = array( + 'include_directory' => $path_tree['include_directory'], + 'include_name' => $path_tree['include_name'], + 'handler' => $path_tree['handler'], + 'methods' => $path_tree['methods'], + ); + } + else { + foreach ($path_parts as $path_part) { + if (array_key_exists($path_part, $path_tree['paths'])) { + $path_tree = &$path_tree['paths'][$path_part]; + $depth_current++; + } + elseif (array_key_exists('%', $path_tree['paths'])) { + $path_tree = &$path_tree['paths']['%']; + $depth_current++; + } + else { break; } - } - if (isset($path_tree['paths']['%'])) { - $path_tree = &$path_tree['paths']['%']; - $depth_current++; - continue; + if ($depth_current == $depth_total) { + $found = array( + 'include_directory' => $path_tree['include_directory'], + 'include_name' => $path_tree['include_name'], + 'handler' => $path_tree['handler'], + 'methods' => $path_tree['methods'], + ); + break; + } } - - break; } - - $path_tree = &$path_tree['paths'][$path_part]; - $depth_current++; } unset($path_part); unset($path_parts); @@ -1479,7 +1653,7 @@ class c_base_paths extends c_base_return { unset($depth_total); unset($path_tree); - if (is_array($found)) { + if (is_array($found) && !is_null($found['handler'])) { return c_base_return_array::s_new($found); } unset($found); diff --git a/common/base/classes/base_session.php b/common/base/classes/base_session.php index 1bfb688..f8f8344 100644 --- a/common/base/classes/base_session.php +++ b/common/base/classes/base_session.php @@ -161,6 +161,12 @@ class c_base_session extends c_base_return { // require a single closing '/' at the end of the path. $this->socket_directory = preg_replace('@/*$@i', '', $socket_directory) . '/'; + + // if the directory is changed after the socket path is defined, then update the socket path. + if (!is_null($this->socket_path)) { + $this->socket_path = $this->socket_directory . $this->system_name . self::SOCKET_PATH_SUFFIX; + } + return new c_base_return_true(); } @@ -233,6 +239,11 @@ class c_base_session extends c_base_return { return c_base_return_error::s_false($error); } + // make sure the socket directory is defined before assigning the socket path based on the system name. + if (is_null($this->socket_directory)) { + $this->socket_directory = self::SOCKET_PATH_PREFIX; + } + $this->system_name = basename($system_name); $this->socket_path = $this->socket_directory . $this->system_name . self::SOCKET_PATH_SUFFIX; diff --git a/examples/nginx-configuration.example b/examples/nginx-configuration.example new file mode 100644 index 0000000..daa1288 --- /dev/null +++ b/examples/nginx-configuration.example @@ -0,0 +1,30 @@ +server { + listen 80 default_server; + listen 443 default_server ssl; + + ssl_certificate server.crt; + ssl_certificate_key server.key; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers HIGH:!aNULL:!MD5; + + # Disable nginx server http header + server_tokens off; + more_clear_headers 'Server'; + + # set the root website files (do not store the PHP source code here, do not even provide an index.php) + root /var/www; + + # Make site accessible from http://localhost/ + server_name localhost; + + # send all paths to the PHP fastcgi service. + location ~* { + include fastcgi_params; + + #fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME /var/git/koopa/program/reservation/index.php; + #fastcgi_intercept_errors on; + fastcgi_pass 127.0.0.1:9000; + } +} diff --git a/program/reservation/index.php b/program/reservation/index.php index e24708d..528cd0f 100644 --- a/program/reservation/index.php +++ b/program/reservation/index.php @@ -61,9 +61,9 @@ function reservation_load_settings() { $settings['session_max'] = 1800; // 30 minutes // ldap information - $settings['ldap_server'] = 'ldaps://127.0.0.1:1636/'; - $settings['ldap_base_dn'] = 'ou=users,ou=People'; - $settings['ldap_fields'] = array('mail', 'gecos', 'givenname', 'cn', 'sn', 'employeenumber'); + $settings['ldap_server'] = NULL; // 'ldaps://127.0.0.1:1636/'; + $settings['ldap_base_dn'] = ''; + $settings['ldap_fields'] = array(); // base settings $settings['base_scheme'] = 'https'; @@ -127,6 +127,15 @@ function reservation_receive_request() { * Http object. */ function reservation_send_response($http) { + // get current http header method, which may determine which headers to set or not set. + $method = $http->get_request(c_base_http::REQUEST_METHOD)->get_value_exact(); + if (isset($method['data']) && is_int($method['data'])) { + $method = $method['data']; + } + else { + $method = c_base_http::HTTP_METHOD_NONE; + } + // add headers $http->set_response_date(); $http->set_response_content_type('text/html'); @@ -140,9 +149,15 @@ function reservation_send_response($http) { $http->set_response_vary('Accept-Language'); #$http->set_response_warning('1234 This site is under active development.'); + // finalize the content prior to sending headers to ensure header accuracy. $http->encode_response_content(); + // http head method responses do not sent content. + if ($method === c_base_http::HTTP_METHOD_HEAD) { + $http->set_response_content('', FALSE); + } + // manually disable output buffering (if enabled) when transfer headers and content. $old_output_buffering = ini_get('output_buffering'); @@ -249,12 +264,13 @@ function reservation_render_theme($http, $output) { * Http object. * @param c_base_session &$session * Session information. - * @param string $markup - * The HTML markup. + * @param string $output + * The output string. + * Can be any string, such as HTML markup. */ -function reservation_build_response(&$http, &$session, $markup) { +function reservation_build_response(&$http, &$session, $output) { $http->set_response_checksum_header(c_base_http::CHECKSUM_ACTION_AUTO); - $http->set_response_content($markup); + $http->set_response_content($output); // send the session cookie if a session id is specified. @@ -314,14 +330,18 @@ function reservation_main() { // 5: build or finalize theme. - $markup = reservation_render_theme($http, $output)->get_value_exact(); - unset($output); + if (($output instanceof c_base_return_null)) { + $output = ''; + } + else { + $output = reservation_render_theme($http, $output)->get_value_exact(); + } gc_collect_cycles(); // 6: build response information. - reservation_build_response($http, $session, $markup); - unset($markup); + reservation_build_response($http, $session, $output); + unset($output); gc_collect_cycles(); diff --git a/program/reservation/paths/u/login.php b/program/reservation/paths/u/login.php index 60af345..d160a44 100644 --- a/program/reservation/paths/u/login.php +++ b/program/reservation/paths/u/login.php @@ -66,7 +66,7 @@ class c_reservation_path_user_login extends c_base_path { require_once(self::PATH_REDIRECTS); $destination = $settings['uri']; - $destination['path'] = $settings['base_path'] . '/u/dashboard'; + $destination['path'] = $settings['base_path'] . 'u/dashboard'; // note: by using a SEE OTHER redirect, the client knows to make a GET request and that the redirect is temporary. $redirect = c_reservation_path_redirect::s_create_redirect($destination, c_base_http_status::SEE_OTHER, FALSE); @@ -241,16 +241,15 @@ class c_reservation_path_user_login extends c_base_path { if (empty($_POST['login_form-username'])) { $problems[] = c_base_form_problem::s_create_error('login_form-username', 'No valid username has been supplied.'); } + elseif ($_POST['login_form-username'] == 'u_reservation_public') { + // explicitly deny access to internal user accounts + $problems[] = c_base_form_problem::s_create_error('login_form-username', 'Unable to login, an incorrect user name or password has been specified.'); + } if (empty($_POST['login_form-password'])) { $problems[] = c_base_form_problem::s_create_error('login_form-password', 'No valid password has been supplied.'); } - // explicitly deny access to internal user accounts - if ($_POST['login_form-username'] == 'u_reservation_public') { - $problems[] = c_base_form_problem::s_create_error('login_form-username', 'Unable to login, an incorrect user name or password has been specified.'); - } - // return current list of problems before continuing to login attempt with credentials. if (!empty($problems)) { return c_base_return_array::s_new($problems); @@ -422,8 +421,6 @@ class c_reservation_path_user_login extends c_base_path { if (isset($ldap['status']) && $ldap['status'] instanceof c_base_return_false) { $problems[] = c_base_form_problem::s_create_error('login_form-username', 'Failed to retrieve ldap information for specified user.'); - - // @todo: handle error situation. } $user_data = reservation_database_get_user_data($database, $_POST['login_form-username'], $ldap['data'])->get_value(); diff --git a/program/reservation/reservation_database.php b/program/reservation/reservation_database.php index 3d325cd..d2e5ada 100644 --- a/program/reservation/reservation_database.php +++ b/program/reservation/reservation_database.php @@ -321,6 +321,13 @@ function reservation_database_load_ldap_data($settings, $user_name) { 'data' => NULL, ); + + // ldap support is disabled if ldap_server is set to NULL. + if (is_null($settings['ldap_server'])) { + return c_base_return_array::s_new($return_data); + } + + $ldap = new c_base_ldap(); $ldap->set_name($settings['ldap_server']); #$ldap->set_bind_name(''); diff --git a/program/reservation/reservation_paths.php b/program/reservation/reservation_paths.php index 4ea62b4..fe0edce 100644 --- a/program/reservation/reservation_paths.php +++ b/program/reservation/reservation_paths.php @@ -7,6 +7,7 @@ require_once('common/base/classes/base_error.php'); require_once('common/base/classes/base_return.php'); require_once('common/base/classes/base_markup.php'); require_once('common/base/classes/base_html.php'); +require_once('common/base/classes/base_http.php'); require_once('common/base/classes/base_charset.php'); require_once('common/base/classes/base_ascii.php'); require_once('common/base/classes/base_form.php'); @@ -170,15 +171,65 @@ class c_reservation_paths { $this->p_paths_create(); + // load the http method. + $method = $this->http->get_request(c_base_http::REQUEST_METHOD)->get_value_exact(); + if (isset($method['data']) && is_int($method['data'])) { + $method = $method['data']; + } + else { + $method = c_base_http::HTTP_METHOD_NONE; + } + + // find the path $handler_settings = $this->paths->find_path($this->settings['uri']['path'])->get_value(); - if (!is_array($handler_settings)) { + // for all invalid pages, report bad method if not HTTP GET or HTTP POST. + if ($method !== c_base_http::HTTP_METHOD_GET && $method !== c_base_http::HTTP_METHOD_POST) { + unset($method); + + $failure_path = $this->p_get_path_bad_method(); + + return $failure_path->do_execute($this->http, $this->database, $this->session, $this->settings); + } + unset($method); + $not_found = $this->p_get_path_not_found(); return $not_found->do_execute($this->http, $this->database, $this->session, $this->settings); } + // validate allowed methods. + if (isset($handler_settings['methods']) && is_array($handler_settings['methods'])) { + if (!array_key_exists($method, $handler_settings['methods'])) { + unset($method); + + $failure_path = $this->p_get_path_bad_method(); + + return $failure_path->do_execute($this->http, $this->database, $this->session, $this->settings); + } + } + + + // HTTP OPTIONS method does not process the page, only returns available methods. + if ($method === c_base_http::HTTP_METHOD_OPTIONS) { + unset($method); + + $options_method_path = $this->p_get_path_options_method(); + + if (isset($handler_settings['methods']) && is_array($handler_settings['methods'])) { + $options_method_path->set_allowed_methods($handler_settings['methods']); + } + else { + $options_method_path->set_allowed_methods(array()); + } + + return $options_method_path->do_execute($this->http, $this->database, $this->session, $this->settings); + } + + if (array_key_exists('redirect', $handler_settings)) { + unset($method); + // successfully logged in. require_once(self::PATH_REDIRECTS . self::NAME_REDIRECTS . '.php'); @@ -241,28 +292,7 @@ class c_reservation_paths { } unset($handler_settings); - // handle request method validation. - if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST') { - // @todo: considering limiting _POST to different path groups here. - } - elseif (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'GET') { - $id_group = $this->path->get_id_group()->get_value_exact(); - - // all paths except /s/ and /x/ may use GET. - if ($id_group === c_base_ascii::LOWER_S || $id_group === c_base_ascii::LOWER_X) { - $failure_path = $this->p_get_path_bad_method(); - - return $failure_path->do_execute($this->http, $this->database, $this->session, $this->settings); - } - unset($id_group); - } - else { - $failure_path = $this->p_get_path_bad_method(); - - return $failure_path->do_execute($this->http, $this->database, $this->session, $this->settings); - } - - return $this->p_paths_normal(); + return $this->p_paths_normal($method); } /** @@ -280,8 +310,8 @@ class c_reservation_paths { $this->paths->set_path('', 'c_reservation_path_user_dashboard', 'program/reservation/paths/u/', 'dashboard.php'); // create login/logout paths - $this->paths->set_path('/u/login', 'c_reservation_path_user_login', 'program/reservation/', 'login.php'); - $this->paths->set_path('/u/logout', 'c_reservation_path_user_logout', 'program/reservation/', 'logout.php'); + $this->paths->set_path('/u/login', 'c_reservation_path_user_login', 'program/reservation/paths/u/', 'login.php'); + $this->paths->set_path('/u/logout', 'c_reservation_path_user_logout', 'program/reservation/paths/u/', 'logout.php'); // user dashboard $this->paths->set_path('/u/dashboard', 'c_reservation_path_user_dashboard', 'program/reservation/paths/u/', 'dashboard.php'); @@ -290,11 +320,14 @@ class c_reservation_paths { /** * Process request paths and determine what to do. * + * @param int $method + * The id of the HTTP request method. + * * @return c_base_path_executed * The execution results. * The execution results with the error bit set on error. */ - private function p_paths_normal() { + private function p_paths_normal($method) { $id_group = $this->path->get_id_group()->get_value_exact(); // regardless of path-specific settings, the following paths always require login credentials to access. @@ -432,6 +465,13 @@ class c_reservation_paths { } /** + * Load and return the internal server error path. + */ + private function p_get_path_options_method() { + return new c_reservation_path_options_method(); + } + + /** * Load and save the current preferred language alias. * * This will be stored in $this->alias. @@ -511,3 +551,69 @@ class c_reservation_paths { return new $class(); } } + +/** + * Provide the HTTP options response. + * + * This does not provide any content body. + */ +final class c_reservation_path_options_method extends c_base_path { + + /** + * Implements do_execute(). + */ + public function do_execute(&$http, &$database, &$session, $settings = array()) { + // the parent function performs validation on the parameters. + $executed = parent::do_execute($http, $database, $session, $settings); + if (c_base_return::s_has_error($executed)) { + return $executed; + } + + + // assign HTTP response status. + $allowed_methods = $this->allowed_methods; + $allowed_method = array_shift($allowed_methods); + $http->set_response_allow($allowed_method, TRUE); + + if (!empty($allowed_methods)) { + foreach ($allowed_methods as $allowed_method) { + $http->set_response_allow($allowed_method); + } + } + unset($allowed_method); + unset($allowed_methods); + + return $executed; + } + + /** + * Load the title text associated with this page. + * + * This is provided here as a means for a language class to override with a custom language for the title. + * + * @return string|null + * A string is returned as the custom title. + * NULL is returned to enforce default title. + */ + protected function pr_get_title() { + return NULL; + } + + /** + * Load text for a supported language. + * + * @param int $index + * A number representing which block of text to return. + */ + protected function pr_get_text($code) { + switch ($code) { + case 0: + return 'Server Error'; + case 1: + return 'Something went wrong while processing your request, please try again later.'; + } + + return ''; + } +} + diff --git a/program/sessionize_accounts/readme.txt b/program/sessionize_accounts/readme.txt index 2cb885b..cdba144 100644 --- a/program/sessionize_accounts/readme.txt +++ b/program/sessionize_accounts/readme.txt @@ -6,9 +6,9 @@ Compile the source code: gcc -g source/c/sessionize_ldap_accounts_in_postgresql.c -o /programs/bin/sessionize_ldap_accounts_in_postgresql Add and enable the init script: - cp -v source/bash/sessionize_ldap_accounts_in_postgresql.sh /etc/init.d/sessionize_ldap_accounts_in_postgresql - chkconfig --add sessionize_ldap_accounts_in_postgresql - chkconfig sessionize_ldap_accounts_in_postgresql on + cp -v source/bash/sessionize_accounts.sh /etc/init.d/sessionize_accounts + chkconfig --add sessionize_accounts + chkconfig sessionize_accounts on Configure the settings (assuming system called "example"): mkdir -vp /programs/settings/sessionize_ldap_accounts_in_postgresql/ diff --git a/program/sessionize_accounts/source/bash/sessionize_accounts.sh b/program/sessionize_accounts/source/bash/sessionize_accounts.sh index e230244..a92c756 100644 --- a/program/sessionize_accounts/source/bash/sessionize_accounts.sh +++ b/program/sessionize_accounts/source/bash/sessionize_accounts.sh @@ -16,7 +16,9 @@ ### END INIT INFO # Source function library. -. /etc/rc.d/init.d/functions +if [[ -f /etc/rc.d/init.d/functions ]] ; then + . /etc/rc.d/init.d/functions +fi main() { local process_owner= @@ -25,8 +27,9 @@ main() { local path_service="/usr/local/bin/php ${path_programs}bin/sessionize_accounts-server.php" local path_settings="${path_programs}settings/sessionize_accounts/" local path_systems="${path_settings}systems.settings" - local path_pids="/var/run/sessionize_accounts/" - local path_socket_directory="/var/www/sockets/sessionize_accounts/" + local path_pids="/programs/run/sessionize_accounts/" + local path_socket_directory="/programs/sockets/sessionize_accounts/" + local path_socket_name="sessions.socket" local parameter_system=$2 local sa_systems= local i= @@ -271,8 +274,8 @@ start_command() { # make sure no session socket already exists before starting. # this assumes that the pid file has already been checked and therefore no existing process is using the socket file (aka: assume this is a stale socket file). - if [[ -e $path_socket_directory/$sa_system/sessions.socket ]] ; then - rm -f $path_socket_directory/$sa_system/sessions.socket + if [[ -e $path_socket_directory/$sa_system/$path_socket_name ]] ; then + rm -f $path_socket_directory/$sa_system/$path_socket_name fi if [[ $process_owner == "" ]] ; then @@ -283,6 +286,11 @@ start_command() { result=$? fi + # make sure the socket can be written to. + if [[ -e $path_socket_directory/$sa_system/$path_socket_name ]] ; then + chmod ugo+w $path_socket_directory/$sa_system/$path_socket_name + fi + if [[ $result -ne 0 ]] ; then echo "Failed to start process, command: $path_service \"$sa_system\"." any_failure=1 @@ -306,7 +314,7 @@ stop_command() { sleep 0.1 # cleanup the session socket ad pid file. - rm -f $path_socket_directory/$sa_system/sessions.socket + rm -f $path_socket_directory/$sa_system/$path_socket_name rm -f $pid_file fi } diff --git a/program/sessionize_accounts/source/php/sessionize_accounts-server.php b/program/sessionize_accounts/source/php/sessionize_accounts-server.php index 57649ba..4674420 100644 --- a/program/sessionize_accounts/source/php/sessionize_accounts-server.php +++ b/program/sessionize_accounts/source/php/sessionize_accounts-server.php @@ -99,9 +99,9 @@ function main($argc, $argv) { // long running processes should have PHP's garbage collection enabled, otherwise no cleanup ever happens. gc_enable(); - $pid_path_directory = '/var/run/sessionize_accounts/'; + $pid_path_directory = '/programs/run/sessionize_accounts/'; $pid_path = $pid_path_directory . $argv[1] . '.pid'; - $socket_path = '/var/www/sockets/sessionize_accounts/' . $argv[1] . '/sessions.socket'; + $socket_path = '/programs/sockets/sessionize_accounts/' . $argv[1] . '/sessions.socket'; $socket_family = AF_UNIX; $socket_port = 0; $socket_protocol = 0;