'application/xml', 'rawxml' => 'application/xml', 'json' => 'application/json', 'serialize' => 'application/vnd.php.serialized', 'php' => 'text/plain', 'html' => 'text/html', 'csv' => 'application/csv' ); // Constructor function function __construct() { parent::Controller(); // How is this request being made? POST, DELETE, GET, PUT? $this->_method = $this->_detect_method(); // Lets grab the config and get ready to party $this->load->config('rest'); if($this->config->item('rest_auth') == 'basic') { $this->_prepare_basic_auth(); } elseif($this->config->item('rest_auth') == 'digest') { $this->_prepare_digest_auth(); } switch($this->_method) { case 'put': // Set up out PUT variables parse_str(file_get_contents('php://input'), $this->_put_args); break; case 'delete': // Set up out PUT variables parse_str(file_get_contents('php://input'), $this->_delete_args); break; } // Set up our GET variables $this->_get_args = $this->uri->ruri_to_assoc(); // Merge both for one mega-args variable $this->_args = array_merge($this->_get_args, $this->_put_args, $this->_delete_args); // Which format should the data be returned in? $this->_format = $this->_detect_format(); } /* * Remap * * Requests are not made to methods directly The request will be for an "object". * this simply maps the object and method to the correct Controller method. */ function _remap($object_called) { $controller_method = $object_called.'_'.$this->_method; if(method_exists($this, $controller_method)) { $this->$controller_method(); } else { show_404(); } } /* * response * * Takes pure data and optionally a status code, then creates the response */ function response($data = array(), $http_code = 200) { if(empty($data)) { $this->output->set_status_header(404); return; } $this->output->set_status_header($http_code); // If the format method exists, call and return the output in that format if(method_exists($this, '_format_'.$this->_format)) { // Set the correct format header $this->output->set_header('Content-type: '.$this->_supported_formats[$this->_format]); $formatted_data = $this->{'_format_'.$this->_format}($data); $this->output->set_output( $formatted_data ); } // Format not supported, output directly else { $this->output->set_output( $data ); } } /* * Detect format * * Detect which format should be used to output the data */ private function _detect_format() { // A format has been passed in the URL and it is supported if(array_key_exists('format', $this->_args) && array_key_exists($this->_args['format'], $this->_supported_formats)) { return $this->_args['format']; } // Otherwise, check the HTTP_ACCEPT (if it exists and we are allowed) if($this->config->item('rest_ignore_http_accept') === FALSE && $this->input->server('HTTP_ACCEPT')) { // Check all formats against the HTTP_ACCEPT header foreach(array_keys($this->_supported_formats) as $format) { // Has this format been requested? if(strpos($this->input->server('HTTP_ACCEPT'), $format) !== FALSE) { // If not HTML or XML assume its right and send it on its way if($format != 'html' && $format != 'xml') { return $format; } // HTML or XML have shown up as a match else { // If it is truely HTML, it wont want any XML if($format == 'html' && strpos($this->input->server('HTTP_ACCEPT'), 'xml') === FALSE) { return $format; } // If it is truely XML, it wont want any HTML elseif($format == 'xml' && strpos($this->input->server('HTTP_ACCEPT'), 'html') === FALSE) { return $format; } } } } } // End HTTP_ACCEPT checking // Well, none of that has worked! Let's see if the controller has a default if($this->rest_format != NULL) { return $this->rest_format; } // Just use whatever the first supported type is, nothing else is working! list($default)=array_keys($this->_supported_formats); return $default; } /* * Detect method * * Detect which method (POST, PUT, GET, DELETE) is being used */ private function _detect_method() { $method = strtolower($this->input->server('REQUEST_METHOD')); if(in_array($method, array('get', 'delete', 'post', 'put'))) { return $method; } return 'get'; } // INPUT FUNCTION -------------------------------------------------------------- public function get($key, $xss_clean = TRUE) { return array_key_exists($key, $this->_get_args) ? $this->_xss_clean( $this->_get_args[$key], $xss_clean ) : $this->input->get($key, $xss_clean) ; } public function post($key, $xss_clean = TRUE) { return $this->input->post($key, $xss_clean); } public function put($key, $xss_clean = TRUE) { return array_key_exists($key, $this->_put_args) ? $this->_xss_clean( $this->_put_args[$key], $xss_clean ) : FALSE ; } public function delete($key, $xss_clean = TRUE) { return array_key_exists($key, $this->_delete_args) ? $this->_xss_clean( $this->_delete_args[$key], $xss_clean ) : FALSE ; } private function _xss_clean($val, $bool) { return $bool ? $this->input->xss_clean($val) : $val; } // SECURITY FUNCTIONS --------------------------------------------------------- private function _check_login($username = '', $password = NULL) { if(empty($username)) { return FALSE; } $valid_logins =& $this->config->item('rest_valid_logins'); if(!array_key_exists($username, $valid_logins)) { return FALSE; } // If actually NULL (not empty string) then do not check it if($password !== NULL) { if($valid_logins[$username] != $password) { return FALSE; } } return TRUE; } private function _prepare_basic_auth() { $username = NULL; $password = NULL; // mod_php if ($this->input->server('PHP_AUTH_USER')) { $username = $this->input->server('PHP_AUTH_USER'); $password = $this->input->server('PHP_AUTH_PW'); } // most other servers elseif ( $this->input->server('HTTP_AUTHENTICATION') ) { if (strpos(strtolower($this->input->server('HTTP_AUTHENTICATION')),'basic') === 0) { list($username,$password) = explode(':',base64_decode(substr($this->input->server('HTTP_AUTHORIZATION'), 6))); } } if ( !$this->_check_login($username, $password) ) { $this->_force_login(); } } private function _prepare_digest_auth() { $uniqid = uniqid(""); // Empty argument for backward compatibility // We need to test which server authentication variable to use // because the PHP ISAPI module in IIS acts different from CGI if($this->input->server('PHP_AUTH_DIGEST')) { $digest_string = $this->input->server('PHP_AUTH_DIGEST'); } elseif($this->input->server('HTTP_AUTHORIZATION')) { $digest_string = $this->input->server('HTTP_AUTHORIZATION'); } else { $digest_string = ""; } /* The $_SESSION['error_prompted'] variabile is used to ask the password again if none given or if the user enters a wrong auth. informations. */ if ( empty($digest_string) ) { $this->_force_login($uniqid); } // We need to retrieve authentication informations from the $auth_data variable preg_match_all('@(username|nonce|uri|nc|cnonce|qop|response)=[\'"]?([^\'",]+)@', $digest_string, $matches); $digest = array_combine($matches[1], $matches[2]); if ( !array_key_exists('username', $digest) || !$this->_check_login($digest['username']) ) { $this->_force_login($uniqid); } $valid_logins =& $this->config->item('rest_valid_logins'); $valid_pass = $valid_logins[$digest['username']]; // This is the valid response expected $A1 = md5($digest['username'] . ':' . $this->config->item('rest_realm') . ':' . $valid_pass); $A2 = md5(strtoupper($this->_method).':'.$digest['uri']); $valid_response = md5($A1.':'.$digest['nonce'].':'.$digest['nc'].':'.$digest['cnonce'].':'.$digest['qop'].':'.$A2); if ($digest['response'] != $valid_response) { header('HTTP/1.0 401 Unauthorized'); header('HTTP/1.1 401 Unauthorized'); exit; } } private function _force_login($nonce = '') { header('HTTP/1.0 401 Unauthorized'); header('HTTP/1.1 401 Unauthorized'); if($this->config->item('rest_auth') == 'basic') { header('WWW-Authenticate: Basic realm="'.$this->config->item('rest_realm').'"'); } elseif($this->config->item('rest_auth') == 'digest') { header('WWW-Authenticate: Digest realm="'.$this->config->item('rest_realm'). '" qop="auth" nonce="'.$nonce.'" opaque="'.md5($this->config->item('rest_realm')).'"'); } echo 'Text to send if user hits Cancel button'; die(); } // Force it into an array private function _force_loopable($data) { // Force it to be something useful if(!is_array($data) && !is_object($data)) { $data = (array) $data; } return $data; } // FORMATING FUNCTIONS --------------------------------------------------------- // Format XML for output private function _format_xml($data = array(), $structure = NULL, $basenode = 'xml') { // turn off compatibility mode as simple xml throws a wobbly if you don't. if (ini_get('zend.ze1_compatibility_mode') == 1) { ini_set ('zend.ze1_compatibility_mode', 0); } if ($structure == NULL) { $structure = simplexml_load_string("<$basenode />"); } // loop through the data passed in. $data = $this->_force_loopable($data); foreach($data as $key => $value) { // no numeric keys in our xml please! if (is_numeric($key)) { // make string key... //$key = "item_". (string) $key; $key = "item"; } // replace anything not alpha numeric $key = preg_replace('/[^a-z]/i', '', $key); // if there is another array found recrusively call this function if (is_array($value) || is_object($value)) { $node = $structure->addChild($key); // recrusive call. $this-> _format_xml($value, $node, $basenode); } else { // add single node. $value = htmlentities($value, ENT_NOQUOTES, "UTF-8"); $UsedKeys[] = $key; $structure->addChild($key, $value); } } // pass back as string. or simple xml object if you want! return $structure->asXML(); } // Format Raw XML for output private function _format_rawxml($data = array(), $structure = NULL, $basenode = 'xml') { // turn off compatibility mode as simple xml throws a wobbly if you don't. if (ini_get('zend.ze1_compatibility_mode') == 1) { ini_set ('zend.ze1_compatibility_mode', 0); } if ($structure == NULL) { $structure = simplexml_load_string("<$basenode />"); } // loop through the data passed in. $data = $this->_force_loopable($data); foreach( $data as $key => $value) { // no numeric keys in our xml please! if (is_numeric($key)) { // make string key... //$key = "item_". (string) $key; $key = "item"; } // replace anything not alpha numeric $key = preg_replace('/[^a-z0-9_-]/i', '', $key); // if there is another array found recrusively call this function if (is_array($value) || is_object($value)) { $node = $structure->addChild($key); // recrusive call. $this->_format_rawxml($value, $node, $basenode); } else { // add single node. $value = htmlentities($value, ENT_NOQUOTES, "UTF-8"); $UsedKeys[] = $key; $structure->addChild($key, $value); } } // pass back as string. or simple xml object if you want! return $structure->asXML(); } // Format HTML for output private function _format_html($data = array()) { // Multi-dimentional array if(isset($data[0])) { $headings = array_keys($data[0]); } // Single array else { $headings = array_keys($data); $data = array($data); } $this->load->library('table'); $this->table->set_heading($headings); foreach($data as &$row) { $this->table->add_row($row); } return $this->table->generate(); } // Format HTML for output private function _format_csv($data = array()) { // Multi-dimentional array if(isset($data[0])) { $headings = array_keys($data[0]); } // Single array else { $headings = array_keys($data); $data = array($data); } $output = implode(',', $headings)."\r\n"; foreach($data as &$row) { $output .= '"'.implode('","',$row)."\"\r\n"; } return $output; } // Encode as JSON private function _format_json($data = array()) { return json_encode($data); } // Encode as Serialized array private function _format_serialize($data = array()) { return serialize($data); } // Encode raw PHP private function _format_php($data = array()) { return var_export($data, TRUE); } } ?>