Overview

Packages

  • CodeIgniter
    • Libraries
    • Rest
  • None

Classes

  • Example
  • Format
  • Key
  • REST_Controller
  • Rest_server
  • Welcome
  • Overview
  • Package
  • Class
   1: <?php
   2: 
   3: defined('BASEPATH') OR exit('No direct script access allowed');
   4: 
   5: /**
   6:  * CodeIgniter Rest Controller
   7:  * A fully RESTful server implementation for CodeIgniter using one library, one config file and one controller.
   8:  *
   9:  * @package         CodeIgniter
  10:  * @subpackage      Libraries
  11:  * @category        Libraries
  12:  * @author          Phil Sturgeon, Chris Kacerguis
  13:  * @license         MIT
  14:  * @link            https://github.com/chriskacerguis/codeigniter-restserver
  15:  * @version         3.0.0
  16:  */
  17: abstract class REST_Controller extends CI_Controller {
  18: 
  19:     // Note: Only the widely used HTTP status codes are documented
  20: 
  21:     // Informational
  22: 
  23:     const HTTP_CONTINUE = 100;
  24:     const HTTP_SWITCHING_PROTOCOLS = 101;
  25:     const HTTP_PROCESSING = 102;            // RFC2518
  26: 
  27:     // Success
  28: 
  29:     /**
  30:      * The request has succeeded
  31:      */
  32:     const HTTP_OK = 200;
  33: 
  34:     /**
  35:      * The server successfully created a new resource
  36:      */
  37:     const HTTP_CREATED = 201;
  38:     const HTTP_ACCEPTED = 202;
  39:     const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;
  40: 
  41:     /**
  42:      * The server successfully processed the request, though no content is returned
  43:      */
  44:     const HTTP_NO_CONTENT = 204;
  45:     const HTTP_RESET_CONTENT = 205;
  46:     const HTTP_PARTIAL_CONTENT = 206;
  47:     const HTTP_MULTI_STATUS = 207;          // RFC4918
  48:     const HTTP_ALREADY_REPORTED = 208;      // RFC5842
  49:     const HTTP_IM_USED = 226;               // RFC3229
  50: 
  51:     // Redirection
  52: 
  53:     const HTTP_MULTIPLE_CHOICES = 300;
  54:     const HTTP_MOVED_PERMANENTLY = 301;
  55:     const HTTP_FOUND = 302;
  56:     const HTTP_SEE_OTHER = 303;
  57: 
  58:     /**
  59:      * The resource has not been modified since the last request
  60:      */
  61:     const HTTP_NOT_MODIFIED = 304;
  62:     const HTTP_USE_PROXY = 305;
  63:     const HTTP_RESERVED = 306;
  64:     const HTTP_TEMPORARY_REDIRECT = 307;
  65:     const HTTP_PERMANENTLY_REDIRECT = 308;  // RFC7238
  66: 
  67:     // Client Error
  68: 
  69:     /**
  70:      * The request cannot be fulfilled due to multiple errors
  71:      */
  72:     const HTTP_BAD_REQUEST = 400;
  73: 
  74:     /**
  75:      * The user is unauthorized to access the requested resource
  76:      */
  77:     const HTTP_UNAUTHORIZED = 401;
  78:     const HTTP_PAYMENT_REQUIRED = 402;
  79: 
  80:     /**
  81:      * The requested resource is unavailable at this present time
  82:      */
  83:     const HTTP_FORBIDDEN = 403;
  84: 
  85:     /**
  86:      * The requested resource could not be found
  87:      *
  88:      * Note: This is sometimes used to mask if there was an UNAUTHORIZED (401) or
  89:      * FORBIDDEN (403) error, for security reasons
  90:      */
  91:     const HTTP_NOT_FOUND = 404;
  92: 
  93:     /**
  94:      * The request method is not supported by the following resource
  95:      */
  96:     const HTTP_METHOD_NOT_ALLOWED = 405;
  97: 
  98:     /**
  99:      * The request was not acceptable
 100:      */
 101:     const HTTP_NOT_ACCEPTABLE = 406;
 102:     const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
 103:     const HTTP_REQUEST_TIMEOUT = 408;
 104: 
 105:     /**
 106:      * The request could not be completed due to a conflict with the current state
 107:      * of the resource
 108:      */
 109:     const HTTP_CONFLICT = 409;
 110:     const HTTP_GONE = 410;
 111:     const HTTP_LENGTH_REQUIRED = 411;
 112:     const HTTP_PRECONDITION_FAILED = 412;
 113:     const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
 114:     const HTTP_REQUEST_URI_TOO_LONG = 414;
 115:     const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
 116:     const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
 117:     const HTTP_EXPECTATION_FAILED = 417;
 118:     const HTTP_I_AM_A_TEAPOT = 418;                                               // RFC2324
 119:     const HTTP_UNPROCESSABLE_ENTITY = 422;                                        // RFC4918
 120:     const HTTP_LOCKED = 423;                                                      // RFC4918
 121:     const HTTP_FAILED_DEPENDENCY = 424;                                           // RFC4918
 122:     const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425;   // RFC2817
 123:     const HTTP_UPGRADE_REQUIRED = 426;                                            // RFC2817
 124:     const HTTP_PRECONDITION_REQUIRED = 428;                                       // RFC6585
 125:     const HTTP_TOO_MANY_REQUESTS = 429;                                           // RFC6585
 126:     const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431;                             // RFC6585
 127: 
 128:     // Server Error
 129: 
 130:     /**
 131:      * The server encountered an unexpected error
 132:      *
 133:      * Note: This is a generic error message when no specific message
 134:      * is suitable
 135:      */
 136:     const HTTP_INTERNAL_SERVER_ERROR = 500;
 137: 
 138:     /**
 139:      * The server does not recognise the request method
 140:      */
 141:     const HTTP_NOT_IMPLEMENTED = 501;
 142:     const HTTP_BAD_GATEWAY = 502;
 143:     const HTTP_SERVICE_UNAVAILABLE = 503;
 144:     const HTTP_GATEWAY_TIMEOUT = 504;
 145:     const HTTP_VERSION_NOT_SUPPORTED = 505;
 146:     const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506;                        // RFC2295
 147:     const HTTP_INSUFFICIENT_STORAGE = 507;                                        // RFC4918
 148:     const HTTP_LOOP_DETECTED = 508;                                               // RFC5842
 149:     const HTTP_NOT_EXTENDED = 510;                                                // RFC2774
 150:     const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511;
 151: 
 152:     /**
 153:      * This defines the rest format.
 154:      * Must be overridden it in a controller so that it is set.
 155:      *
 156:      * @var string|NULL
 157:      */
 158:     protected $rest_format = NULL;
 159: 
 160:     /**
 161:      * Defines the list of method properties such as limit, log and level
 162:      *
 163:      * @var array
 164:      */
 165:     protected $methods = [];
 166: 
 167:     /**
 168:      * List of allowed HTTP methods
 169:      *
 170:      * @var array
 171:      */
 172:     protected $allowed_http_methods = ['get', 'delete', 'post', 'put', 'options', 'patch', 'head'];
 173: 
 174:     /**
 175:      * Contains details about the request
 176:      * Fields: body, format, method, ssl
 177:      * Note: This is a dynamic object (stdClass)
 178:      *
 179:      * @var object
 180:      */
 181:     protected $request = NULL;
 182: 
 183:     /**
 184:      * Contains details about the response
 185:      * Fields: format, lang
 186:      * Note: This is a dynamic object (stdClass)
 187:      *
 188:      * @var object
 189:      */
 190:     protected $response = NULL;
 191: 
 192:     /**
 193:      * Contains details about the REST API
 194:      * Fields: db, ignore_limits, key, level, user_id
 195:      * Note: This is a dynamic object (stdClass)
 196:      *
 197:      * @var object
 198:      */
 199:     protected $rest = NULL;
 200: 
 201:     /**
 202:      * The arguments for the GET request method
 203:      *
 204:      * @var array
 205:      */
 206:     protected $_get_args = [];
 207: 
 208:     /**
 209:      * The arguments for the POST request method
 210:      *
 211:      * @var array
 212:      */
 213:     protected $_post_args = [];
 214: 
 215:     /**
 216:      * The insert_id of the log entry (if we have one)
 217:      *
 218:      * @var string
 219:      */
 220:     protected $_insert_id = '';
 221: 
 222:     /**
 223:      * The arguments for the PUT request method
 224:      *
 225:      * @var array
 226:      */
 227:     protected $_put_args = [];
 228: 
 229:     /**
 230:      * The arguments for the DELETE request method
 231:      *
 232:      * @var array
 233:      */
 234:     protected $_delete_args = [];
 235: 
 236:     /**
 237:      * The arguments for the PATCH request method
 238:      *
 239:      * @var array
 240:      */
 241:     protected $_patch_args = [];
 242: 
 243:     /**
 244:      * The arguments for the HEAD request method
 245:      *
 246:      * @var array
 247:      */
 248:     protected $_head_args = [];
 249: 
 250:     /**
 251:      * The arguments for the OPTIONS request method
 252:      *
 253:      * @var array
 254:      */
 255:     protected $_options_args = [];
 256: 
 257:     /**
 258:      * The arguments for the query parameters
 259:      *
 260:      * @var array
 261:      */
 262:     protected $_query_args = [];
 263: 
 264:     /**
 265:      * The arguments from GET, POST, PUT, DELETE, PATCH, HEAD and OPTIONS request methods combined
 266:      *
 267:      * @var array
 268:      */
 269:     protected $_args = [];
 270: 
 271:     /**
 272:      * If the request is allowed based on the API key provided.
 273:      *
 274:      * @var bool
 275:      */
 276:     protected $_allow = TRUE;
 277: 
 278:     /**
 279:      * The LDAP Distinguished Name of the User post authentication
 280:      *
 281:      * @var string
 282:      */
 283:     protected $_user_ldap_dn = '';
 284: 
 285:     /**
 286:      * The start of the response time from the server
 287:      *
 288:      * @var string
 289:      */
 290:     protected $_start_rtime = '';
 291: 
 292:     /**
 293:      * The end of the response time from the server
 294:      *
 295:      * @var string
 296:      */
 297:     protected $_end_rtime = '';
 298: 
 299:     /**
 300:      * List all supported methods, the first will be the default format
 301:      *
 302:      * @var array
 303:      */
 304:     protected $_supported_formats = [
 305:             'json' => 'application/json',
 306:             'array' => 'application/json',
 307:             'csv' => 'application/csv',
 308:             'html' => 'text/html',
 309:             'jsonp' => 'application/javascript',
 310:             'php' => 'text/plain',
 311:             'serialized' => 'application/vnd.php.serialized',
 312:             'xml' => 'application/xml'
 313:         ];
 314: 
 315:     /**
 316:      * Information about the current API user
 317:      *
 318:      * @var object
 319:      */
 320:     protected $_apiuser;
 321: 
 322:     /**
 323:      * Enable XSS flag
 324:      * Determines whether the XSS filter is always active when
 325:      * GET, OPTIONS, HEAD, POST, PUT, DELETE and PATCH data is encountered.
 326:      * Set automatically based on config setting.
 327:      *
 328:      * @var bool
 329:      */
 330:     protected $_enable_xss = FALSE;
 331: 
 332:     /**
 333:      * HTTP status codes and their respective description
 334:      * Note: Only the widely used HTTP status codes are used
 335:      *
 336:      * @var array
 337:      * @link http://www.restapitutorial.com/httpstatuscodes.html
 338:      */
 339:     protected $http_status_codes = [
 340:         self::HTTP_OK => 'OK',
 341:         self::HTTP_CREATED => 'CREATED',
 342:         self::HTTP_NO_CONTENT => 'NO CONTENT',
 343:         self::HTTP_NOT_MODIFIED => 'NOT MODIFIED',
 344:         self::HTTP_BAD_REQUEST => 'BAD REQUEST',
 345:         self::HTTP_UNAUTHORIZED => 'UNAUTHORIZED',
 346:         self::HTTP_FORBIDDEN => 'FORBIDDEN',
 347:         self::HTTP_NOT_FOUND => 'NOT FOUND',
 348:         self::HTTP_METHOD_NOT_ALLOWED => 'METHOD NOT ALLOWED',
 349:         self::HTTP_NOT_ACCEPTABLE => 'NOT ACCEPTABLE',
 350:         self::HTTP_CONFLICT => 'CONFLICT',
 351:         self::HTTP_INTERNAL_SERVER_ERROR => 'INTERNAL SERVER ERROR',
 352:         self::HTTP_NOT_IMPLEMENTED => 'NOT IMPLEMENTED'
 353:     ];
 354: 
 355:     /**
 356:      * Extend this function to apply additional checking early on in the process
 357:      *
 358:      * @access protected
 359:      * @return void
 360:      */
 361:     protected function early_checks()
 362:     {
 363:     }
 364: 
 365:     /**
 366:      * Constructor for the REST API
 367:      *
 368:      * @access public
 369:      * @param string $config Configuration filename minus the file extension
 370:      * e.g: my_rest.php is passed as 'my_rest'
 371:      * @return void
 372:      */
 373:     public function __construct($config = 'rest')
 374:     {
 375:         parent::__construct();
 376: 
 377:         // Disable XML Entity (security vulnerability)
 378:         libxml_disable_entity_loader(TRUE);
 379: 
 380:         // Check to see if PHP is equal to or greater than 5.4.x
 381:         if (is_php('5.4') === FALSE)
 382:         {
 383:             // CodeIgniter 3 is recommended for v5.4 or above
 384:             exit('Using PHP v' . PHP_VERSION . ', though PHP v5.4 or greater is required');
 385:         }
 386: 
 387:         // Check to see if this is CI 3.x
 388:         if (explode('.', CI_VERSION, 2)[0] < 3)
 389:         {
 390:             exit('REST Server requires CodeIgniter 3.x');
 391:         }
 392: 
 393:         // Set the default value of global xss filtering. Same approach as CodeIgniter 3
 394:         $this->_enable_xss = ($this->config->item('global_xss_filtering') === TRUE);
 395: 
 396:         // Don't try to parse template variables like {elapsed_time} and {memory_usage}
 397:         // when output is displayed for not damaging data accidentally
 398:         $this->output->parse_exec_vars = FALSE;
 399: 
 400:         // Start the timer for how long the request takes
 401:         $this->_start_rtime = microtime(TRUE);
 402: 
 403:         // Load the rest.php configuration file
 404:         $this->load->config($config);
 405: 
 406:         // At present the library is bundled with REST_Controller 2.5+, but will eventually be part of CodeIgniter (no citation)
 407:         $this->load->library('format');
 408: 
 409:         // Get the language
 410:         $language = $this->config->item('rest_language');
 411:         if ($language === NULL)
 412:         {
 413:             $language = 'english';
 414:         }
 415: 
 416:         // Load the language file
 417:         $this->lang->load('rest_controller', $language);
 418: 
 419:         // Initialise the response, request and rest objects
 420:         $this->request = new stdClass();
 421:         $this->response = new stdClass();
 422:         $this->rest = new stdClass();
 423: 
 424:         // Check to see if the current IP address is blacklisted
 425:         if ($this->config->item('rest_ip_blacklist_enabled') === TRUE)
 426:         {
 427:             $this->_check_blacklist_auth();
 428:         }
 429: 
 430:         // Determine whether the connection is HTTPS
 431:         $this->request->ssl = is_https();
 432: 
 433:         // How is this request being made? GET, POST, PATCH, DELETE, INSERT, PUT, HEAD or OPTIONS
 434:         $this->request->method = $this->_detect_method();
 435: 
 436:         // Create an argument container if it doesn't exist e.g. _get_args
 437:         if (isset($this->{'_' . $this->request->method . '_args'}) === FALSE)
 438:         {
 439:             $this->{'_' . $this->request->method . '_args'} = [];
 440:         }
 441: 
 442:         // Set up the query parameters
 443:         $this->_parse_query();
 444: 
 445:         // Set up the GET variables
 446:         $this->_get_args = array_merge($this->_get_args, $this->uri->ruri_to_assoc());
 447: 
 448:         // Try to find a format for the request (means we have a request body)
 449:         $this->request->format = $this->_detect_input_format();
 450: 
 451:         // Not all methods have a body attached with them
 452:         $this->request->body = NULL;
 453: 
 454:         $this->{'_parse_' . $this->request->method}();
 455: 
 456:         // Now we know all about our request, let's try and parse the body if it exists
 457:         if ($this->request->format && $this->request->body)
 458:         {
 459:             $this->request->body = $this->format->factory($this->request->body, $this->request->format)->to_array();
 460:             // Assign payload arguments to proper method container
 461:             $this->{'_' . $this->request->method . '_args'} = $this->request->body;
 462:         }
 463: 
 464:         // Merge both for one mega-args variable
 465:         $this->_args = array_merge(
 466:             $this->_get_args,
 467:             $this->_options_args,
 468:             $this->_patch_args,
 469:             $this->_head_args,
 470:             $this->_put_args,
 471:             $this->_post_args,
 472:             $this->_delete_args,
 473:             $this->{'_' . $this->request->method . '_args'}
 474:         );
 475: 
 476:         // Which format should the data be returned in?
 477:         $this->response->format = $this->_detect_output_format();
 478: 
 479:         // Which language should the data be returned in?
 480:         $this->response->lang = $this->_detect_lang();
 481: 
 482:         // Extend this function to apply additional checking early on in the process
 483:         $this->early_checks();
 484: 
 485:         // Load DB if its enabled
 486:         if ($this->config->item('rest_database_group') && ($this->config->item('rest_enable_keys') || $this->config->item('rest_enable_logging')))
 487:         {
 488:             $this->rest->db = $this->load->database($this->config->item('rest_database_group'), TRUE);
 489:         }
 490: 
 491:         // Use whatever database is in use (isset returns FALSE)
 492:         elseif (property_exists($this, 'db'))
 493:         {
 494:             $this->rest->db = $this->db;
 495:         }
 496: 
 497:         // Check if there is a specific auth type for the current class/method
 498:         // _auth_override_check could exit so we need $this->rest->db initialized before
 499:         $this->auth_override = $this->_auth_override_check();
 500: 
 501:         // Checking for keys? GET TO WorK!
 502:         // Skip keys test for $config['auth_override_class_method']['class'['method'] = 'none'
 503:         if ($this->config->item('rest_enable_keys') && $this->auth_override !== TRUE)
 504:         {
 505:             $this->_allow = $this->_detect_api_key();
 506:         }
 507: 
 508:         // Only allow ajax requests
 509:         if ($this->input->is_ajax_request() === FALSE && $this->config->item('rest_ajax_only'))
 510:         {
 511:             // Display an error response
 512:             $this->response([
 513:                     $this->config->item('rest_status_field_name') => FALSE,
 514:                     $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ajax_only')
 515:                 ], self::HTTP_NOT_ACCEPTABLE);
 516:         }
 517: 
 518:         // When there is no specific override for the current class/method, use the default auth value set in the config
 519:         if ($this->auth_override === FALSE && !($this->config->item('rest_enable_keys') && $this->_allow === TRUE))
 520:         {
 521:             $rest_auth = strtolower($this->config->item('rest_auth'));
 522:             switch ($rest_auth)
 523:             {
 524:                 case 'basic':
 525:                     $this->_prepare_basic_auth();
 526:                     break;
 527:                 case 'digest':
 528:                     $this->_prepare_digest_auth();
 529:                     break;
 530:                 case 'session':
 531:                     $this->_check_php_session();
 532:                     break;
 533:             }
 534:             if ($this->config->item('rest_ip_whitelist_enabled') === TRUE)
 535:             {
 536:                 $this->_check_whitelist_auth();
 537:             }
 538:         }
 539:     }
 540: 
 541:     /**
 542:      * Deconstructor
 543:      *
 544:      * @author Chris Kacerguis
 545:      * @access public
 546:      * @return void
 547:      */
 548:     public function __destruct()
 549:     {
 550:         // Get the current timestamp
 551:         $this->_end_rtime = microtime(TRUE);
 552: 
 553:         // Log the loading time to the log table
 554:         if ($this->config->item('rest_enable_logging') === TRUE)
 555:         {
 556:             $this->_log_access_time();
 557:         }
 558:     }
 559: 
 560:     /**
 561:      * Requests are not made to methods directly, the request will be for
 562:      * an "object". This simply maps the object and method to the correct
 563:      * Controller method.
 564:      *
 565:      * @access public
 566:      * @param  string $object_called
 567:      * @param  array $arguments The arguments passed to the controller method.
 568:      */
 569:     public function _remap($object_called, $arguments)
 570:     {
 571:         // Should we answer if not over SSL?
 572:         if ($this->config->item('force_https') && $this->request->ssl === FALSE)
 573:         {
 574:             $this->response([
 575:                     $this->config->item('rest_status_field_name') => FALSE,
 576:                     $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unsupported')
 577:                 ], self::HTTP_FORBIDDEN);
 578:         }
 579: 
 580:         // Remove the supported format from the function name e.g. index.json => index
 581:         $object_called = preg_replace('/^(.*)\.(?:' . implode('|', array_keys($this->_supported_formats)) . ')$/', '$1', $object_called);
 582: 
 583:         $controller_method = $object_called . '_' . $this->request->method;
 584: 
 585:         // Do we want to log this method (if allowed by config)?
 586:         $log_method = !(isset($this->methods[$controller_method]['log']) && $this->methods[$controller_method]['log'] === FALSE);
 587: 
 588:         // Use keys for this method?
 589:         $use_key = !(isset($this->methods[$controller_method]['key']) && $this->methods[$controller_method]['key'] === FALSE);
 590: 
 591:         // They provided a key, but it wasn't valid, so get them out of here.
 592:         if ($this->config->item('rest_enable_keys') && $use_key && $this->_allow === FALSE)
 593:         {
 594:             if ($this->config->item('rest_enable_logging') && $log_method)
 595:             {
 596:                 $this->_log_request();
 597:             }
 598: 
 599:             $this->response([
 600:                     $this->config->item('rest_status_field_name') => FALSE,
 601:                     $this->config->item('rest_message_field_name') => sprintf($this->lang->line('text_rest_invalid_api_key'), $this->rest->key)
 602:                 ], self::HTTP_FORBIDDEN);
 603:         }
 604: 
 605:         // Check to see if this key has access to the requested controller.
 606:         if ($this->config->item('rest_enable_keys') && $use_key && empty($this->rest->key) === FALSE && $this->_check_access() === FALSE)
 607:         {
 608:             if ($this->config->item('rest_enable_logging') && $log_method)
 609:             {
 610:                 $this->_log_request();
 611:             }
 612: 
 613:             $this->response([
 614:                     $this->config->item('rest_status_field_name') => FALSE,
 615:                     $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_unauthorized')
 616:                 ], self::HTTP_UNAUTHORIZED);
 617:         }
 618: 
 619:         // Sure it exists, but can they do anything with it?
 620:         if (method_exists($this, $controller_method) === FALSE)
 621:         {
 622:             $this->response([
 623:                     $this->config->item('rest_status_field_name') => FALSE,
 624:                     $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unknown_method')
 625:                 ], self::HTTP_NOT_FOUND);
 626:         }
 627: 
 628:         // Doing key related stuff? Can only do it if they have a key right?
 629:         if ($this->config->item('rest_enable_keys') && empty($this->rest->key) === FALSE)
 630:         {
 631:             // Check the limit
 632:             if ($this->config->item('rest_enable_limits') && $this->_check_limit($controller_method) === FALSE)
 633:             {
 634:                 $response = [$this->config->item('rest_status_field_name') => FALSE, $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_time_limit')];
 635:                 $this->response($response, self::HTTP_UNAUTHORIZED);
 636:             }
 637: 
 638:             // If no level is set use 0, they probably aren't using permissions
 639:             $level = isset($this->methods[$controller_method]['level']) ? $this->methods[$controller_method]['level'] : 0;
 640: 
 641:             // If no level is set, or it is lower than/equal to the key's level
 642:             $authorized = $level <= $this->rest->level;
 643: 
 644:             // IM TELLIN!
 645:             if ($this->config->item('rest_enable_logging') && $log_method)
 646:             {
 647:                 $this->_log_request($authorized);
 648:             }
 649: 
 650:             // They don't have good enough perms
 651:             $response = [$this->config->item('rest_status_field_name') => FALSE, $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_permissions')];
 652:             $authorized || $this->response($response, self::HTTP_UNAUTHORIZED);
 653:         }
 654: 
 655:         // No key stuff, but record that stuff is happening
 656:         elseif ($this->config->item('rest_enable_logging') && $log_method)
 657:         {
 658:             $this->_log_request($authorized = TRUE);
 659:         }
 660: 
 661:         // Call the controller method and passed arguments
 662:         try
 663:         {
 664:             call_user_func_array([$this, $controller_method], $arguments);
 665:         }
 666:         catch (Exception $ex)
 667:         {
 668:             // If the method doesn't exist, then the error will be caught and an error response shown
 669:             $this->response([
 670:                     $this->config->item('rest_status_field_name') => FALSE,
 671:                     $this->config->item('rest_message_field_name') => [
 672:                         'classname' => get_class($ex),
 673:                         'message' => $ex->getMessage()
 674:                     ]
 675:                 ], self::HTTP_INTERNAL_SERVER_ERROR);
 676:         }
 677:     }
 678: 
 679:     /**
 680:      * Takes mixed data and optionally a status code, then creates the response
 681:      *
 682:      * @access public
 683:      * @param array|NULL $data Data to output to the user
 684:      * @param int|NULL $http_code HTTP status code
 685:      * @param bool $continue TRUE to flush the response to the client and continue
 686:      * running the script; otherwise, exit
 687:      */
 688:     public function response($data = NULL, $http_code = NULL, $continue = FALSE)
 689:     {
 690:         // If the HTTP status is not NULL, then cast as an integer
 691:         if ($http_code !== NULL)
 692:         {
 693:             // So as to be safe later on in the process
 694:             $http_code = (int) $http_code;
 695:         }
 696: 
 697:         // Set the output as NULL by default
 698:         $output = NULL;
 699: 
 700:         // If data is NULL and no HTTP status code provided, then display, error and exit
 701:         if ($data === NULL && $http_code === NULL)
 702:         {
 703:             $http_code = self::HTTP_NOT_FOUND;
 704:         }
 705: 
 706:         // If data is not NULL and a HTTP status code provided, then continue
 707:         elseif ($data !== NULL)
 708:         {
 709:             // If the format method exists, call and return the output in that format
 710:             if (method_exists($this->format, 'to_' . $this->response->format))
 711:             {
 712:                 // Set the format header
 713:                 $this->output->set_content_type($this->_supported_formats[$this->response->format], strtolower($this->config->item('charset')));
 714:                 $output = $this->format->factory($data)->{'to_' . $this->response->format}();
 715: 
 716:                 // An array must be parsed as a string, so as not to cause an array to string error.
 717:                 // Json is the most appropriate form for such a datatype
 718:                 if ($this->response->format === 'array')
 719:                 {
 720:                     $output = $this->format->factory($output)->{'to_json'}();
 721:                 }
 722:             }
 723:             else
 724:             {
 725:                 // If an array or object, then parse as a json, so as to be a 'string'
 726:                 if (is_array($data) || is_object($data))
 727:                 {
 728:                     $data = $this->format->factory($data)->{'to_json'}();
 729:                 }
 730: 
 731:                 // Format is not supported, so output the raw data as a string
 732:                 $output = $data;
 733:             }
 734:         }
 735: 
 736:         // If not greater than zero, then set the HTTP status code as 200 by default
 737:         // Though perhaps 500 should be set instead, for the developer not passing a
 738:         // correct HTTP status code
 739:         $http_code > 0 || $http_code = self::HTTP_OK;
 740: 
 741:         $this->output->set_status_header($http_code);
 742: 
 743:         // JC: Log response code only if rest logging enabled
 744:         if ($this->config->item('rest_enable_logging') === TRUE)
 745:         {
 746:             $this->_log_response_code($http_code);
 747:         }
 748: 
 749:         // Output the data
 750:         $this->output->set_output($output);
 751: 
 752:         if ($continue === FALSE)
 753:         {
 754:             // Display the data and exit execution
 755:             $this->output->_display();
 756:             exit;
 757:         }
 758: 
 759:         // Otherwise dump the output automatically
 760:     }
 761: 
 762:     /**
 763:      * Takes mixed data and optionally a status code, then creates the response
 764:      * within the buffers of the Output class. The response is sent to the client
 765:      * lately by the framework, after the current controller's method termination.
 766:      * All the hooks after the controller's method termination are executable.
 767:      *
 768:      * @access public
 769:      * @param array|NULL $data Data to output to the user
 770:      * @param int|NULL $http_code HTTP status code
 771:      */
 772:     public function set_response($data = NULL, $http_code = NULL)
 773:     {
 774:         $this->response($data, $http_code, TRUE);
 775:     }
 776: 
 777:     /**
 778:      * Get the input format e.g. json or xml
 779:      *
 780:      * @access protected
 781:      * @return string|NULL Supported input format; otherwise, NULL
 782:      */
 783:     protected function _detect_input_format()
 784:     {
 785:         // Get the CONTENT-TYPE value from the SERVER variable
 786:         $content_type = $this->input->server('CONTENT_TYPE');
 787: 
 788:         if (empty($content_type) === FALSE)
 789:         {
 790:             // Check all formats against the HTTP_ACCEPT header
 791:             foreach ($this->_supported_formats as $key => $value)
 792:             {
 793:                 // $key = format e.g. csv
 794:                 // $value = mime type e.g. application/csv
 795: 
 796:                 // If a semi-colon exists in the string, then explode by ; and get the value of where
 797:                 // the current array pointer resides. This will generally be the first element of the array
 798:                 $content_type = (strpos($content_type, ';') !== FALSE ? current(explode(';', $content_type)) : $content_type);
 799: 
 800:                 // If both the mime types match, then return the format
 801:                 if ($content_type === $value)
 802:                 {
 803:                     return $key;
 804:                 }
 805:             }
 806:         }
 807: 
 808:         return NULL;
 809:     }
 810: 
 811:     /**
 812:      * Detect which format should be used to output the data
 813:      *
 814:      * @access protected
 815:      * @return mixed|NULL|string Output format
 816:      */
 817:     protected function _detect_output_format()
 818:     {
 819:         // Concatenate formats to a regex pattern e.g. \.(csv|json|xml)
 820:         $pattern = '/\.(' . implode('|', array_keys($this->_supported_formats)) . ')($|\/)/';
 821:         $matches = [];
 822: 
 823:         // Check if a file extension is used e.g. http://example.com/api/index.json?param1=param2
 824:         if (preg_match($pattern, $this->uri->uri_string(), $matches))
 825:         {
 826:             return $matches[1];
 827:         }
 828: 
 829:         // Get the format parameter named as 'format'
 830:         if (isset($this->_get_args['format']))
 831:         {
 832:             $format = strtolower($this->_get_args['format']);
 833: 
 834:             if (isset($this->_supported_formats[$format]) === TRUE)
 835:             {
 836:                 return $format;
 837:             }
 838:         }
 839: 
 840:         // Get the HTTP_ACCEPT server variable
 841:         $http_accept = $this->input->server('HTTP_ACCEPT');
 842: 
 843:         // Otherwise, check the HTTP_ACCEPT server variable
 844:         if ($this->config->item('rest_ignore_http_accept') === FALSE && $http_accept !== NULL)
 845:         {
 846:             // Check all formats against the HTTP_ACCEPT header
 847:             foreach (array_keys($this->_supported_formats) as $format)
 848:             {
 849:                 // Has this format been requested?
 850:                 if (strpos($http_accept, $format) !== FALSE)
 851:                 {
 852:                     if ($format !== 'html' && $format !== 'xml')
 853:                     {
 854:                         // If not HTML or XML assume it's correct
 855:                         return $format;
 856:                     }
 857:                     elseif ($format === 'html' && strpos($http_accept, 'xml') === FALSE)
 858:                     {
 859:                         // HTML or XML have shown up as a match
 860:                         // If it is truly HTML, it wont want any XML
 861:                         return $format;
 862:                     }
 863:                     else if ($format === 'xml' && strpos($http_accept, 'html') === FALSE)
 864:                     {
 865:                         // If it is truly XML, it wont want any HTML
 866:                         return $format;
 867:                     }
 868:                 }
 869:             }
 870:         }
 871: 
 872:         // Check if the controller has a default format
 873:         if (empty($this->rest_format) === FALSE)
 874:         {
 875:             return $this->rest_format;
 876:         }
 877: 
 878:         // Obtain the default format from the configuration
 879:         return $this->config->item('rest_default_format');
 880:     }
 881: 
 882:     /**
 883:      * Get the HTTP request string e.g. get or post
 884:      *
 885:      * @access protected
 886:      * @return string|NULL Supported request method as a lowercase string; otherwise, NULL if not supported
 887:      */
 888:     protected function _detect_method()
 889:     {
 890:         // Declare a variable to store the method
 891:         $method = NULL;
 892: 
 893:         // Determine whether the 'enable_emulate_request' setting is enabled
 894:         if ($this->config->item('enable_emulate_request') === TRUE)
 895:         {
 896:             $method = $this->input->post('_method');
 897:             if ($method === NULL)
 898:             {
 899:                 $method = $this->input->server('HTTP_X_HTTP_METHOD_OVERRIDE');
 900:             }
 901: 
 902:             $method = strtolower($method);
 903:         }
 904: 
 905:         if (empty($method))
 906:         {
 907:             // Get the request method as a lowercase string.
 908:             $method = $this->input->method();
 909:         }
 910: 
 911:         return in_array($method, $this->allowed_http_methods) && method_exists($this, '_parse_' . $method) ? $method : 'get';
 912:     }
 913: 
 914:     /**
 915:      * See if the user has provided an API key
 916:      *
 917:      * @access protected
 918:      * @return bool
 919:      */
 920:     protected function _detect_api_key()
 921:     {
 922:         // Get the api key name variable set in the rest config file
 923:         $api_key_variable = $this->config->item('rest_key_name');
 924: 
 925:         // Work out the name of the SERVER entry based on config
 926:         $key_name = 'HTTP_' . strtoupper(str_replace('-', '_', $api_key_variable));
 927: 
 928:         $this->rest->key = NULL;
 929:         $this->rest->level = NULL;
 930:         $this->rest->user_id = NULL;
 931:         $this->rest->ignore_limits = FALSE;
 932: 
 933:         // Find the key from server or arguments
 934:         if (($key = isset($this->_args[$api_key_variable]) ? $this->_args[$api_key_variable] : $this->input->server($key_name)))
 935:         {
 936:             if (!($row = $this->rest->db->where($this->config->item('rest_key_column'), $key)->get($this->config->item('rest_keys_table'))->row()))
 937:             {
 938:                 return FALSE;
 939:             }
 940: 
 941:             $this->rest->key = $row->{$this->config->item('rest_key_column')};
 942: 
 943:             isset($row->user_id) && $this->rest->user_id = $row->user_id;
 944:             isset($row->level) && $this->rest->level = $row->level;
 945:             isset($row->ignore_limits) && $this->rest->ignore_limits = $row->ignore_limits;
 946: 
 947:             $this->_apiuser = $row;
 948: 
 949:             /*
 950:              * If "is private key" is enabled, compare the ip address with the list
 951:              * of valid ip addresses stored in the database.
 952:              */
 953:             if (empty($row->is_private_key) === FALSE)
 954:             {
 955:                 // Check for a list of valid ip addresses
 956:                 if (isset($row->ip_addresses))
 957:                 {
 958:                     // multiple ip addresses must be separated using a comma, explode and loop
 959:                     $list_ip_addresses = explode(',', $row->ip_addresses);
 960:                     $found_address = FALSE;
 961: 
 962:                     foreach ($list_ip_addresses as $ip_address)
 963:                     {
 964:                         if ($this->input->ip_address() === trim($ip_address))
 965:                         {
 966:                             // there is a match, set the the value to TRUE and break out of the loop
 967:                             $found_address = TRUE;
 968:                             break;
 969:                         }
 970:                     }
 971: 
 972:                     return $found_address;
 973:                 }
 974:                 else
 975:                 {
 976:                     // There should be at least one IP address for this private key.
 977:                     return FALSE;
 978:                 }
 979:             }
 980: 
 981:             return TRUE;
 982:         }
 983: 
 984:         // No key has been sent
 985:         return FALSE;
 986:     }
 987: 
 988:     /**
 989:      * Preferred return language
 990:      *
 991:      * @access protected
 992:      * @return string|NULL The language code
 993:      */
 994:     protected function _detect_lang()
 995:     {
 996:         $lang = $this->input->server('HTTP_ACCEPT_LANGUAGE');
 997:         if ($lang === NULL)
 998:         {
 999:             return NULL;
1000:         }
1001: 
1002:         // It appears more than one language has been sent using a comma delimiter
1003:         if (strpos($lang, ',') !== FALSE)
1004:         {
1005:             $langs = explode(',', $lang);
1006: 
1007:             $return_langs = [];
1008:             foreach ($langs as $lang)
1009:             {
1010:                 // Remove weight and trim leading and trailing whitespace
1011:                 list($lang) = explode(';', $lang);
1012:                 $return_langs[] = trim($lang);
1013:             }
1014: 
1015:             return $return_langs;
1016:         }
1017: 
1018:         // Otherwise simply return as a string
1019:         return $lang;
1020:     }
1021: 
1022:     /**
1023:      * Add the request to the log table
1024:      *
1025:      * @access protected
1026:      * @param bool $authorized TRUE the user is authorized; otherwise, FALSE
1027:      * @return bool TRUE the data was inserted; otherwise, FALSE
1028:      */
1029:     protected function _log_request($authorized = FALSE)
1030:     {
1031:         // Insert the request into the log table
1032:         $is_inserted = $this->rest->db
1033:             ->insert(
1034:                 $this->config->item('rest_logs_table'), [
1035:                 'uri' => $this->uri->uri_string(),
1036:                 'method' => $this->request->method,
1037:                 'params' => $this->_args ? ($this->config->item('rest_logs_json_params') === TRUE ? json_encode($this->_args) : serialize($this->_args)) : NULL,
1038:                 'api_key' => isset($this->rest->key) ? $this->rest->key : '',
1039:                 'ip_address' => $this->input->ip_address(),
1040:                 'time' => now(), // Used to be: function_exists('now') ? now() : time()
1041:                 'authorized' => $authorized
1042:             ]);
1043: 
1044:         // Get the last insert id to update at a later stage of the request
1045:         $this->_insert_id = $this->rest->db->insert_id();
1046: 
1047:         return $is_inserted;
1048:     }
1049: 
1050:     /**
1051:      * Check if the requests to a controller method exceed a limit
1052:      *
1053:      * @access protected
1054:      * @param  string $controller_method The method being called
1055:      * @return bool TRUE the call limit is below the threshold; otherwise, FALSE
1056:      */
1057:     protected function _check_limit($controller_method)
1058:     {
1059:         // They are special, or it might not even have a limit
1060:         if (empty($this->rest->ignore_limits) === FALSE)
1061:         {
1062:             // Everything is fine
1063:             return TRUE;
1064:         }
1065: 
1066:         switch ($this->config->item('rest_limits_method'))
1067:         {
1068:           case 'API_KEY':
1069:             $limited_uri = 'api-key:' . (isset($this->rest->key) ? $this->rest->key : '');
1070:             $limited_method_name = isset($this->rest->key) ? $this->rest->key : '';
1071:             break;
1072: 
1073:           case 'METHOD_NAME':
1074:             $limited_uri = 'method-name:' . $controller_method;
1075:             $limited_method_name =  $controller_method;
1076:             break;
1077: 
1078:           case 'ROUTED_URL':
1079:           default:
1080:             $limited_uri = $this->uri->ruri_string();
1081:             if (strpos(strrev($limited_uri), strrev($this->response->format)) === 0)
1082:             {
1083:                 $limited_uri = substr($limited_uri,0, -strlen($this->response->format) - 1);
1084:             }
1085:             $limited_uri = 'uri:' . $limited_uri . ':' . $this->request->method; // It's good to differentiate GET from PUT
1086:             $limited_method_name = $controller_method;
1087:             break;
1088:         }
1089: 
1090:         if (isset($this->methods[$limited_method_name]['limit']) === FALSE )
1091:         {
1092:             // Everything is fine
1093:             return TRUE;
1094:         }
1095: 
1096:         // How many times can you get to this method in a defined time_limit (default: 1 hour)?
1097:         $limit = $this->methods[$limited_method_name]['limit'];
1098: 
1099:         $time_limit = (isset($this->methods[$limited_method_name]['time']) ? $this->methods[$limited_method_name]['time'] : 3600); // 3600 = 60 * 60
1100: 
1101:         // Get data about a keys' usage and limit to one row
1102:         $result = $this->rest->db
1103:             ->where('uri', $limited_uri)
1104:             ->where('api_key', $this->rest->key)
1105:             ->get($this->config->item('rest_limits_table'))
1106:             ->row();
1107: 
1108:         // No calls have been made for this key
1109:         if ($result === NULL)
1110:         {
1111:             // Create a new row for the following key
1112:             $this->rest->db->insert($this->config->item('rest_limits_table'), [
1113:                 'uri' => $limited_uri,
1114:                 'api_key' => isset($this->rest->key) ? $this->rest->key : '',
1115:                 'count' => 1,
1116:                 'hour_started' => time()
1117:             ]);
1118:         }
1119: 
1120:         // Been a time limit (or by default an hour) since they called
1121:         elseif ($result->hour_started < (time() - $time_limit))
1122:         {
1123:             // Reset the started period and count
1124:             $this->rest->db
1125:                 ->where('uri', $limited_uri)
1126:                 ->where('api_key', isset($this->rest->key) ? $this->rest->key : '')
1127:                 ->set('hour_started', time())
1128:                 ->set('count', 1)
1129:                 ->update($this->config->item('rest_limits_table'));
1130:         }
1131: 
1132:         // They have called within the hour, so lets update
1133:         else
1134:         {
1135:             // The limit has been exceeded
1136:             if ($result->count >= $limit)
1137:             {
1138:                 return FALSE;
1139:             }
1140: 
1141:             // Increase the count by one
1142:             $this->rest->db
1143:                 ->where('uri', $limited_uri)
1144:                 ->where('api_key', $this->rest->key)
1145:                 ->set('count', 'count + 1', FALSE)
1146:                 ->update($this->config->item('rest_limits_table'));
1147:         }
1148: 
1149:         return TRUE;
1150:     }
1151: 
1152:     /**
1153:      * Check if there is a specific auth type set for the current class/method/HTTP-method being called
1154:      *
1155:      * @access protected
1156:      * @return bool
1157:      */
1158:     protected function _auth_override_check()
1159:     {
1160:         // Assign the class/method auth type override array from the config
1161:         $auth_override_class_method = $this->config->item('auth_override_class_method');
1162: 
1163:         // Check to see if the override array is even populated
1164:         if (!empty($auth_override_class_method))
1165:         {
1166:             // check for wildcard flag for rules for classes
1167:             if (!empty($auth_override_class_method[$this->router->class]['*'])) // Check for class overrides
1168:             {
1169:                 // None auth override found, prepare nothing but send back a TRUE override flag
1170:                 if ($auth_override_class_method[$this->router->class]['*'] === 'none')
1171:                 {
1172:                     return TRUE;
1173:                 }
1174: 
1175:                 // Basic auth override found, prepare basic
1176:                 if ($auth_override_class_method[$this->router->class]['*'] === 'basic')
1177:                 {
1178:                     $this->_prepare_basic_auth();
1179: 
1180:                     return TRUE;
1181:                 }
1182: 
1183:                 // Digest auth override found, prepare digest
1184:                 if ($auth_override_class_method[$this->router->class]['*'] === 'digest')
1185:                 {
1186:                     $this->_prepare_digest_auth();
1187: 
1188:                     return TRUE;
1189:                 }
1190: 
1191:                 // Whitelist auth override found, check client's ip against config whitelist
1192:                 if ($auth_override_class_method[$this->router->class]['*'] === 'whitelist')
1193:                 {
1194:                     $this->_check_whitelist_auth();
1195: 
1196:                     return TRUE;
1197:                 }
1198:             }
1199: 
1200:             // Check to see if there's an override value set for the current class/method being called
1201:             if (!empty($auth_override_class_method[$this->router->class][$this->router->method]))
1202:             {
1203:                 // None auth override found, prepare nothing but send back a TRUE override flag
1204:                 if ($auth_override_class_method[$this->router->class][$this->router->method] === 'none')
1205:                 {
1206:                     return TRUE;
1207:                 }
1208: 
1209:                 // Basic auth override found, prepare basic
1210:                 if ($auth_override_class_method[$this->router->class][$this->router->method] === 'basic')
1211:                 {
1212:                     $this->_prepare_basic_auth();
1213: 
1214:                     return TRUE;
1215:                 }
1216: 
1217:                 // Digest auth override found, prepare digest
1218:                 if ($auth_override_class_method[$this->router->class][$this->router->method] === 'digest')
1219:                 {
1220:                     $this->_prepare_digest_auth();
1221: 
1222:                     return TRUE;
1223:                 }
1224: 
1225:                 // Whitelist auth override found, check client's ip against config whitelist
1226:                 if ($auth_override_class_method[$this->router->class][$this->router->method] === 'whitelist')
1227:                 {
1228:                     $this->_check_whitelist_auth();
1229: 
1230:                     return TRUE;
1231:                 }
1232:             }
1233:         }
1234: 
1235:         // Assign the class/method/HTTP-method auth type override array from the config
1236:         $auth_override_class_method_http = $this->config->item('auth_override_class_method_http');
1237: 
1238:         // Check to see if the override array is even populated
1239:         if (!empty($auth_override_class_method_http))
1240:         {
1241:             // check for wildcard flag for rules for classes
1242:             if(!empty($auth_override_class_method_http[$this->router->class]['*'][$this->request->method]))
1243:             {
1244:                 // None auth override found, prepare nothing but send back a TRUE override flag
1245:                 if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'none')
1246:                 {
1247:                     return TRUE;
1248:                 }
1249: 
1250:                 // Basic auth override found, prepare basic
1251:                 if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'basic')
1252:                 {
1253:                     $this->_prepare_basic_auth();
1254: 
1255:                     return TRUE;
1256:                 }
1257: 
1258:                 // Digest auth override found, prepare digest
1259:                 if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'digest')
1260:                 {
1261:                     $this->_prepare_digest_auth();
1262: 
1263:                     return TRUE;
1264:                 }
1265: 
1266:                 // Whitelist auth override found, check client's ip against config whitelist
1267:                 if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'whitelist')
1268:                 {
1269:                     $this->_check_whitelist_auth();
1270: 
1271:                     return TRUE;
1272:                 }
1273:             }
1274: 
1275:             // Check to see if there's an override value set for the current class/method/HTTP-method being called
1276:             if(!empty($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method]))
1277:             {
1278:                 // None auth override found, prepare nothing but send back a TRUE override flag
1279:                 if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'none')
1280:                 {
1281:                     return TRUE;
1282:                 }
1283: 
1284:                 // Basic auth override found, prepare basic
1285:                 if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'basic')
1286:                 {
1287:                     $this->_prepare_basic_auth();
1288: 
1289:                     return TRUE;
1290:                 }
1291: 
1292:                 // Digest auth override found, prepare digest
1293:                 if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'digest')
1294:                 {
1295:                     $this->_prepare_digest_auth();
1296: 
1297:                     return TRUE;
1298:                 }
1299: 
1300:                 // Whitelist auth override found, check client's ip against config whitelist
1301:                 if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'whitelist')
1302:                 {
1303:                     $this->_check_whitelist_auth();
1304: 
1305:                     return TRUE;
1306:                 }
1307:             }
1308:         }
1309:         return FALSE;
1310:     }
1311: 
1312:     /**
1313:      * Parse the GET request arguments
1314:      *
1315:      * @access protected
1316:      * @return void
1317:      */
1318:     protected function _parse_get()
1319:     {
1320:         // Merge both the URI segments and query parameters
1321:         $this->_get_args = array_merge($this->_get_args, $this->_query_args);
1322:     }
1323: 
1324:     /**
1325:      * Parse the POST request arguments
1326:      *
1327:      * @access protected
1328:      * @return void
1329:      */
1330:     protected function _parse_post()
1331:     {
1332:         $this->_post_args = $_POST;
1333: 
1334:         if ($this->request->format)
1335:         {
1336:             $this->request->body = $this->input->raw_input_stream;
1337:         }
1338:     }
1339: 
1340:     /**
1341:      * Parse the PUT request arguments
1342:      *
1343:      * @access protected
1344:      * @return void
1345:      */
1346:     protected function _parse_put()
1347:     {
1348:         if ($this->request->format)
1349:         {
1350:             $this->request->body = $this->input->raw_input_stream;
1351:         }
1352:         else
1353:         {
1354:             // If no filetype is provided, then there are probably just arguments
1355:             if ($this->input->method() === 'put')
1356:             {
1357:                 $this->_put_args = $this->input->input_stream();
1358:             }
1359:         }
1360:     }
1361: 
1362:     /**
1363:      * Parse the HEAD request arguments
1364:      *
1365:      * @access protected
1366:      * @return void
1367:      */
1368:     protected function _parse_head()
1369:     {
1370:         // Parse the HEAD variables
1371:         parse_str(parse_url($this->input->server('REQUEST_URI'), PHP_URL_QUERY), $head);
1372: 
1373:         // Merge both the URI segments and HEAD params
1374:         $this->_head_args = array_merge($this->_head_args, $head);
1375:     }
1376: 
1377:     /**
1378:      * Parse the OPTIONS request arguments
1379:      *
1380:      * @access protected
1381:      * @return void
1382:      */
1383:     protected function _parse_options()
1384:     {
1385:         // Parse the OPTIONS variables
1386:         parse_str(parse_url($this->input->server('REQUEST_URI'), PHP_URL_QUERY), $options);
1387: 
1388:         // Merge both the URI segments and OPTIONS params
1389:         $this->_options_args = array_merge($this->_options_args, $options);
1390:     }
1391: 
1392:     /**
1393:      * Parse the PATCH request arguments
1394:      *
1395:      * @access protected
1396:      * @return void
1397:      */
1398:     protected function _parse_patch()
1399:     {
1400:         // It might be a HTTP body
1401:         if ($this->request->format)
1402:         {
1403:             $this->request->body = $this->input->raw_input_stream;
1404:         }
1405:         else
1406:         {
1407:             // If no filetype is provided, then there are probably just arguments
1408:             if ($this->input->method() === 'patch')
1409:             {
1410:                 $this->_patch_args = $this->input->input_stream();
1411:             }
1412:         }
1413:     }
1414: 
1415:     /**
1416:      * Parse the DELETE request arguments
1417:      *
1418:      * @access protected
1419:      * @return void
1420:      */
1421:     protected function _parse_delete()
1422:     {
1423:         // These should exist if a DELETE request
1424:         if ($this->input->method() === 'delete')
1425:         {
1426:             $this->_delete_args = $this->input->input_stream();
1427:         }
1428:     }
1429: 
1430:     /**
1431:      * Parse the query parameters
1432:      *
1433:      * @access protected
1434:      * @return void
1435:      */
1436:     protected function _parse_query()
1437:     {
1438:         // Declare a variable that will hold the REQUEST_URI
1439:         $request_uri = NULL;
1440: 
1441:         // If using the commandline version
1442:         if (is_cli())
1443:         {
1444:             $args = $this->input->server('argv');
1445:             unset($args[0]);
1446: 
1447:             // Combine the arguments using '/' as the delimiter
1448:             $request_uri = '/' . implode('/', $args) . '/';
1449: 
1450:             // Set the following server variables (perhaps not required anymore?)
1451:             $_SERVER['REQUEST_URI'] = $request_uri;
1452:             $_SERVER['PATH_INFO'] = $request_uri;
1453:             $_SERVER['QUERY_STRING'] = $request_uri;
1454:         }
1455:         else
1456:         {
1457:             $request_uri = $this->input->server('REQUEST_URI');
1458:         }
1459: 
1460:         // Parse the query parameters from the query string
1461:         parse_str(parse_url($request_uri, PHP_URL_QUERY), $this->_query_args);
1462:     }
1463: 
1464:     // INPUT FUNCTION --------------------------------------------------------------
1465: 
1466:     /**
1467:      * Retrieve a value from a GET request
1468:      *
1469:      * @access public
1470:      * @param NULL $key Key to retrieve from the GET request
1471:      * If NULL an array of arguments is returned
1472:      * @param NULL $xss_clean Whether to apply XSS filtering
1473:      * @return array|string|NULL Value from the GET request; otherwise, NULL
1474:      */
1475:     public function get($key = NULL, $xss_clean = NULL)
1476:     {
1477:         if ($key === NULL)
1478:         {
1479:             return $this->_get_args;
1480:         }
1481: 
1482:         return isset($this->_get_args[$key]) ? $this->_xss_clean($this->_get_args[$key], $xss_clean) : NULL;
1483:     }
1484: 
1485:     /**
1486:      * Retrieve a value from a OPTIONS request
1487:      *
1488:      * @access public
1489:      * @param NULL $key Key to retrieve from the OPTIONS request.
1490:      * If NULL an array of arguments is returned
1491:      * @param NULL $xss_clean Whether to apply XSS filtering
1492:      * @return array|string|NULL Value from the OPTIONS request; otherwise, NULL
1493:      */
1494:     public function options($key = NULL, $xss_clean = NULL)
1495:     {
1496:         if ($key === NULL)
1497:         {
1498:             return $this->_options_args;
1499:         }
1500: 
1501:         return isset($this->_options_args[$key]) ? $this->_xss_clean($this->_options_args[$key], $xss_clean) : NULL;
1502:     }
1503: 
1504:     /**
1505:      * Retrieve a value from a HEAD request
1506:      *
1507:      * @access public
1508:      * @param NULL $key Key to retrieve from the HEAD request
1509:      * If NULL an array of arguments is returned
1510:      * @param NULL $xss_clean Whether to apply XSS filtering
1511:      * @return array|string|NULL Value from the HEAD request; otherwise, NULL
1512:      */
1513:     public function head($key = NULL, $xss_clean = NULL)
1514:     {
1515:         if ($key === NULL)
1516:         {
1517:             return $this->head_args;
1518:         }
1519: 
1520:         return isset($this->head_args[$key]) ? $this->_xss_clean($this->head_args[$key], $xss_clean) : NULL;
1521:     }
1522: 
1523:     /**
1524:      * Retrieve a value from a POST request
1525:      *
1526:      * @access public
1527:      * @param NULL $key Key to retrieve from the POST request
1528:      * If NULL an array of arguments is returned
1529:      * @param NULL $xss_clean Whether to apply XSS filtering
1530:      * @return array|string|NULL Value from the POST request; otherwise, NULL
1531:      */
1532:     public function post($key = NULL, $xss_clean = NULL)
1533:     {
1534:         if ($key === NULL)
1535:         {
1536:             return $this->_post_args;
1537:         }
1538: 
1539:         return isset($this->_post_args[$key]) ? $this->_xss_clean($this->_post_args[$key], $xss_clean) : NULL;
1540:     }
1541: 
1542:     /**
1543:      * Retrieve a value from a PUT request
1544:      *
1545:      * @access public
1546:      * @param NULL $key Key to retrieve from the PUT request
1547:      * If NULL an array of arguments is returned
1548:      * @param NULL $xss_clean Whether to apply XSS filtering
1549:      * @return array|string|NULL Value from the PUT request; otherwise, NULL
1550:      */
1551:     public function put($key = NULL, $xss_clean = NULL)
1552:     {
1553:         if ($key === NULL)
1554:         {
1555:             return $this->_put_args;
1556:         }
1557: 
1558:         return isset($this->_put_args[$key]) ? $this->_xss_clean($this->_put_args[$key], $xss_clean) : NULL;
1559:     }
1560: 
1561:     /**
1562:      * Retrieve a value from a DELETE request
1563:      *
1564:      * @access public
1565:      * @param NULL $key Key to retrieve from the DELETE request
1566:      * If NULL an array of arguments is returned
1567:      * @param NULL $xss_clean Whether to apply XSS filtering
1568:      * @return array|string|NULL Value from the DELETE request; otherwise, NULL
1569:      */
1570:     public function delete($key = NULL, $xss_clean = NULL)
1571:     {
1572:         if ($key === NULL)
1573:         {
1574:             return $this->_delete_args;
1575:         }
1576: 
1577:         return isset($this->_delete_args[$key]) ? $this->_xss_clean($this->_delete_args[$key], $xss_clean) : NULL;
1578:     }
1579: 
1580:     /**
1581:      * Retrieve a value from a PATCH request
1582:      *
1583:      * @access public
1584:      * @param NULL $key Key to retrieve from the PATCH request
1585:      * If NULL an array of arguments is returned
1586:      * @param NULL $xss_clean Whether to apply XSS filtering
1587:      * @return array|string|NULL Value from the PATCH request; otherwise, NULL
1588:      */
1589:     public function patch($key = NULL, $xss_clean = NULL)
1590:     {
1591:         if ($key === NULL)
1592:         {
1593:             return $this->_patch_args;
1594:         }
1595: 
1596:         return isset($this->_patch_args[$key]) ? $this->_xss_clean($this->_patch_args[$key], $xss_clean) : NULL;
1597:     }
1598: 
1599:     /**
1600:      * Retrieve a value from the query parameters
1601:      *
1602:      * @access public
1603:      * @param NULL $key Key to retrieve from the query parameters
1604:      * If NULL an array of arguments is returned
1605:      * @param NULL $xss_clean Whether to apply XSS filtering
1606:      * @return array|string|NULL Value from the query parameters; otherwise, NULL
1607:      */
1608:     public function query($key = NULL, $xss_clean = NULL)
1609:     {
1610:         if ($key === NULL)
1611:         {
1612:             return $this->_query_args;
1613:         }
1614: 
1615:         return isset($this->_query_args[$key]) ? $this->_xss_clean($this->_query_args[$key], $xss_clean) : NULL;
1616:     }
1617: 
1618:     /**
1619:      * Sanitizes data so that Cross Site Scripting Hacks can be
1620:      * prevented.
1621:      *
1622:      * @access protected
1623:      * @param  string $value Input data
1624:      * @param  bool $xss_clean Whether to apply XSS filtering
1625:      * @return string
1626:      */
1627:     protected function _xss_clean($value, $xss_clean)
1628:     {
1629:         is_bool($xss_clean) || $xss_clean = $this->_enable_xss;
1630: 
1631:         return $xss_clean === TRUE ? $this->security->xss_clean($value) : $value;
1632:     }
1633: 
1634:     /**
1635:      * Retrieve the validation errors
1636:      *
1637:      * @access public
1638:      * @return array
1639:      */
1640:     public function validation_errors()
1641:     {
1642:         $string = strip_tags($this->form_validation->error_string());
1643: 
1644:         return explode(PHP_EOL, trim($string, PHP_EOL));
1645:     }
1646: 
1647:     // SECURITY FUNCTIONS ---------------------------------------------------------
1648: 
1649:     /**
1650:      * Perform LDAP Authentication
1651:      *
1652:      * @access protected
1653:      * @param  string $username The username to validate
1654:      * @param  string $password The password to validate
1655:      * @return bool
1656:      */
1657:     protected function _perform_ldap_auth($username = '', $password = NULL)
1658:     {
1659:         if (empty($username))
1660:         {
1661:             log_message('debug', 'LDAP Auth: failure, empty username');
1662:             return FALSE;
1663:         }
1664: 
1665:         log_message('debug', 'LDAP Auth: Loading configuration');
1666: 
1667:         $this->config->load('ldap.php', TRUE);
1668: 
1669:         $ldap = [
1670:             'timeout' => $this->config->item('timeout', 'ldap'),
1671:             'host' => $this->config->item('server', 'ldap'),
1672:             'port' => $this->config->item('port', 'ldap'),
1673:             'rdn' => $this->config->item('binduser', 'ldap'),
1674:             'pass' => $this->config->item('bindpw', 'ldap'),
1675:             'basedn' => $this->config->item('basedn', 'ldap'),
1676:         ];
1677: 
1678:         log_message('debug', 'LDAP Auth: Connect to ' . (isset($ldaphost) ? $ldaphost : '[ldap not configured]'));
1679: 
1680:         // Connect to the ldap server
1681:         $ldapconn = ldap_connect($ldap['host'], $ldap['port']);
1682:         if ($ldapconn)
1683:         {
1684:             log_message('debug', 'Setting timeout to ' . $ldap['timeout'] . ' seconds');
1685: 
1686:             ldap_set_option($ldapconn, LDAP_OPT_NETWORK_TIMEOUT, $ldap['timeout']);
1687: 
1688:             log_message('debug', 'LDAP Auth: Binding to ' . $ldap['host'] . ' with dn ' . $ldap['rdn']);
1689: 
1690:             // Binding to the ldap server
1691:             $ldapbind = ldap_bind($ldapconn, $ldap['rdn'], $ldap['pass']);
1692: 
1693:             // Verify the binding
1694:             if ($ldapbind === FALSE)
1695:             {
1696:                 log_message('error', 'LDAP Auth: bind was unsuccessful');
1697:                 return FALSE;
1698:             }
1699: 
1700:             log_message('debug', 'LDAP Auth: bind successful');
1701:         }
1702: 
1703:         // Search for user
1704:         if (($res_id = ldap_search($ldapconn, $ldap['basedn'], "uid=$username")) === FALSE)
1705:         {
1706:             log_message('error', 'LDAP Auth: User ' . $username . ' not found in search');
1707:             return FALSE;
1708:         }
1709: 
1710:         if (ldap_count_entries($ldapconn, $res_id) !== 1)
1711:         {
1712:             log_message('error', 'LDAP Auth: Failure, username ' . $username . 'found more than once');
1713:             return FALSE;
1714:         }
1715: 
1716:         if (($entry_id = ldap_first_entry($ldapconn, $res_id)) === FALSE)
1717:         {
1718:             log_message('error', 'LDAP Auth: Failure, entry of search result could not be fetched');
1719:             return FALSE;
1720:         }
1721: 
1722:         if (($user_dn = ldap_get_dn($ldapconn, $entry_id)) === FALSE)
1723:         {
1724:             log_message('error', 'LDAP Auth: Failure, user-dn could not be fetched');
1725:             return FALSE;
1726:         }
1727: 
1728:         // User found, could not authenticate as user
1729:         if (($link_id = ldap_bind($ldapconn, $user_dn, $password)) === FALSE)
1730:         {
1731:             log_message('error', 'LDAP Auth: Failure, username/password did not match: ' . $user_dn);
1732:             return FALSE;
1733:         }
1734: 
1735:         log_message('debug', 'LDAP Auth: Success ' . $user_dn . ' authenticated successfully');
1736: 
1737:         $this->_user_ldap_dn = $user_dn;
1738: 
1739:         ldap_close($ldapconn);
1740: 
1741:         return TRUE;
1742:     }
1743: 
1744:     /**
1745:      * Perform Library Authentication - Override this function to change the way the library is called
1746:      *
1747:      * @access protected
1748:      * @param  string $username The username to validate
1749:      * @param  string $password The password to validate
1750:      * @return bool
1751:      */
1752:     protected function _perform_library_auth($username = '', $password = NULL)
1753:     {
1754:         if (empty($username))
1755:         {
1756:             log_message('error', 'Library Auth: Failure, empty username');
1757:             return FALSE;
1758:         }
1759: 
1760:         $auth_library_class = strtolower($this->config->item('auth_library_class'));
1761:         $auth_library_function = strtolower($this->config->item('auth_library_function'));
1762: 
1763:         if (empty($auth_library_class))
1764:         {
1765:             log_message('debug', 'Library Auth: Failure, empty auth_library_class');
1766:             return FALSE;
1767:         }
1768: 
1769:         if (empty($auth_library_function))
1770:         {
1771:             log_message('debug', 'Library Auth: Failure, empty auth_library_function');
1772:             return FALSE;
1773:         }
1774: 
1775:         if (is_callable([$auth_library_class, $auth_library_function]) === FALSE)
1776:         {
1777:             $this->load->library($auth_library_class);
1778:         }
1779: 
1780:         return $this->{$auth_library_class}->$auth_library_function($username, $password);
1781:     }
1782: 
1783:     /**
1784:      * Check if the user is logged in
1785:      *
1786:      * @access protected
1787:      * @param  string $username The user's name
1788:      * @param  bool|string $password The user's password
1789:      * @return bool
1790:      */
1791:     protected function _check_login($username = NULL, $password = FALSE)
1792:     {
1793:         if (empty($username))
1794:         {
1795:             return FALSE;
1796:         }
1797: 
1798:         $auth_source = strtolower($this->config->item('auth_source'));
1799:         $rest_auth = strtolower($this->config->item('rest_auth'));
1800:         $valid_logins = $this->config->item('rest_valid_logins');
1801: 
1802:         if (!$this->config->item('auth_source') && $rest_auth === 'digest')
1803:         {
1804:             // For digest we do not have a password passed as argument
1805:             return md5($username . ':' . $this->config->item('rest_realm') . ':' . (isset($valid_logins[$username]) ? $valid_logins[$username] : ''));
1806:         }
1807: 
1808:         if ($password === FALSE)
1809:         {
1810:             return FALSE;
1811:         }
1812: 
1813:         if ($auth_source === 'ldap')
1814:         {
1815:             log_message('debug', "Performing LDAP authentication for $username");
1816: 
1817:             return $this->_perform_ldap_auth($username, $password);
1818:         }
1819: 
1820:         if ($auth_source === 'library')
1821:         {
1822:             log_message('debug', "Performing Library authentication for $username");
1823: 
1824:             return $this->_perform_library_auth($username, $password);
1825:         }
1826: 
1827:         if (array_key_exists($username, $valid_logins) === FALSE)
1828:         {
1829:             return FALSE;
1830:         }
1831: 
1832:         if ($valid_logins[$username] !== $password)
1833:         {
1834:             return FALSE;
1835:         }
1836: 
1837:         return TRUE;
1838:     }
1839: 
1840:     /**
1841:      * Check to see if the user is logged in with a PHP session key
1842:      *
1843:      * @access protected
1844:      * @return void
1845:      */
1846:     protected function _check_php_session()
1847:     {
1848:         // Get the auth_source config item
1849:         $key = $this->config->item('auth_source');
1850: 
1851:         // If falsy, then the user isn't logged in
1852:         if (!$this->session->userdata($key))
1853:         {
1854:             // Display an error response
1855:             $this->response([
1856:                     $this->config->item('rest_status_field_name') => FALSE,
1857:                     $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unauthorized')
1858:                 ], self::HTTP_UNAUTHORIZED);
1859:         }
1860:     }
1861: 
1862:     /**
1863:      * Prepares for basic authentication
1864:      *
1865:      * @access protected
1866:      * @return void
1867:      */
1868:     protected function _prepare_basic_auth()
1869:     {
1870:         // If whitelist is enabled it has the first chance to kick them out
1871:         if ($this->config->item('rest_ip_whitelist_enabled'))
1872:         {
1873:             $this->_check_whitelist_auth();
1874:         }
1875: 
1876:         // Returns NULL if the SERVER variables PHP_AUTH_USER and HTTP_AUTHENTICATION don't exist
1877:         $username = $this->input->server('PHP_AUTH_USER');
1878:         $http_auth = $this->input->server('HTTP_AUTHENTICATION');
1879: 
1880:         $password = NULL;
1881:         if ($username !== NULL)
1882:         {
1883:             $password = $this->input->server('PHP_AUTH_PW');
1884:         }
1885:         elseif ($http_auth !== NULL)
1886:         {
1887:             // If the authentication header is set as basic, then extract the username and password from
1888:             // HTTP_AUTHORIZATION e.g. my_username:my_password. This is passed in the .htaccess file
1889:             if (strpos(strtolower($http_auth), 'basic') === 0)
1890:             {
1891:                 // Search online for HTTP_AUTHORIZATION workaround to explain what this is doing
1892:                 list($username, $password) = explode(':', base64_decode(substr($this->input->server('HTTP_AUTHORIZATION'), 6)));
1893:             }
1894:         }
1895: 
1896:         // Check if the user is logged into the system
1897:         if ($this->_check_login($username, $password) === FALSE)
1898:         {
1899:             $this->_force_login();
1900:         }
1901:     }
1902: 
1903:     /**
1904:      * Prepares for digest authentication
1905:      *
1906:      * @access protected
1907:      * @return void
1908:      */
1909:     protected function _prepare_digest_auth()
1910:     {
1911:         // If whitelist is enabled it has the first chance to kick them out
1912:         if ($this->config->item('rest_ip_whitelist_enabled'))
1913:         {
1914:             $this->_check_whitelist_auth();
1915:         }
1916: 
1917:         // We need to test which server authentication variable to use,
1918:         // because the PHP ISAPI module in IIS acts different from CGI
1919:         $digest_string = $this->input->server('PHP_AUTH_DIGEST');
1920:         if ($digest_string === NULL)
1921:         {
1922:             $digest_string = $this->input->server('HTTP_AUTHORIZATION');
1923:         }
1924: 
1925:         $unique_id = uniqid();
1926: 
1927:         // The $_SESSION['error_prompted'] variable is used to ask the password
1928:         // again if none given or if the user enters wrong auth information
1929:         if (empty($digest_string))
1930:         {
1931:             $this->_force_login($unique_id);
1932:         }
1933: 
1934:         // We need to retrieve authentication data from the $digest_string variable
1935:         $matches = [];
1936:         preg_match_all('@(username|nonce|uri|nc|cnonce|qop|response)=[\'"]?([^\'",]+)@', $digest_string, $matches);
1937:         $digest = (empty($matches[1]) || empty($matches[2])) ? [] : array_combine($matches[1], $matches[2]);
1938: 
1939:         // For digest authentication the library function should return already stored md5(username:restrealm:password) for that username @see rest.php::auth_library_function config
1940:         $username = $this->_check_login($digest['username'], TRUE);
1941:         if (array_key_exists('username', $digest) === FALSE || $username === FALSE)
1942:         {
1943:             $this->_force_login($unique_id);
1944:         }
1945: 
1946:         $md5 = md5(strtoupper($this->request->method) . ':' . $digest['uri']);
1947:         $valid_response = md5($username . ':' . $digest['nonce'] . ':' . $digest['nc'] . ':' . $digest['cnonce'] . ':' . $digest['qop'] . ':' . $md5);
1948: 
1949:         // Check if the string don't compare (case-insensitive)
1950:         if (strcasecmp($digest['response'], $valid_response) !== 0)
1951:         {
1952:             // Display an error response
1953:             $this->response([
1954:                     $this->config->item('rest_status_field_name') => FALSE,
1955:                     $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_invalid_credentials')
1956:                 ], self::HTTP_UNAUTHORIZED);
1957:         }
1958:     }
1959: 
1960:     /**
1961:      * Checks if the client's ip is in the 'rest_ip_blacklist' config and generates a 401 response
1962:      *
1963:      * @access protected
1964:      * @return void
1965:      */
1966:     protected function _check_blacklist_auth()
1967:     {
1968:         // Match an ip address in a blacklist e.g. 127.0.0.0, 0.0.0.0
1969:         $pattern = sprintf('/(?:,\s*|^)\Q%s\E(?=,\s*|$)/m', $this->input->ip_address());
1970: 
1971:         // Returns 1, 0 or FALSE (on error only). Therefore implicitly convert 1 to TRUE
1972:         if (preg_match($pattern, $this->config->item('rest_ip_blacklist')))
1973:         {
1974:             // Display an error response
1975:             $this->response([
1976:                     $this->config->item('rest_status_field_name') => FALSE,
1977:                     $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ip_denied')
1978:                 ], self::HTTP_UNAUTHORIZED);
1979:         }
1980:     }
1981: 
1982:     /**
1983:      * Check if the client's ip is in the 'rest_ip_whitelist' config and generates a 401 response
1984:      *
1985:      * @access protected
1986:      * @return void
1987:      */
1988:     protected function _check_whitelist_auth()
1989:     {
1990:         $whitelist = explode(',', $this->config->item('rest_ip_whitelist'));
1991: 
1992:         array_push($whitelist, '127.0.0.1', '0.0.0.0');
1993: 
1994:         foreach ($whitelist as &$ip)
1995:         {
1996:             // As $ip is a reference, trim leading and trailing whitespace, then store the new value
1997:             // using the reference
1998:             $ip = trim($ip);
1999:         }
2000: 
2001:         if (in_array($this->input->ip_address(), $whitelist) === FALSE)
2002:         {
2003:             $this->response([
2004:                     $this->config->item('rest_status_field_name') => FALSE,
2005:                     $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ip_unauthorized')
2006:                 ], self::HTTP_UNAUTHORIZED);
2007:         }
2008:     }
2009: 
2010:     /**
2011:      * Force logging in by setting the WWW-Authenticate header
2012:      *
2013:      * @access protected
2014:      * @param string $nonce A server-specified data string which should be uniquely generated
2015:      * each time
2016:      * @return void
2017:      */
2018:     protected function _force_login($nonce = '')
2019:     {
2020:         $rest_auth = $this->config->item('rest_auth');
2021:         $rest_realm = $this->config->item('rest_realm');
2022:         if (strtolower($rest_auth) === 'basic')
2023:         {
2024:             // See http://tools.ietf.org/html/rfc2617#page-5
2025:             header('WWW-Authenticate: Basic realm="' . $rest_realm . '"');
2026:         }
2027:         elseif (strtolower($rest_auth) === 'digest')
2028:         {
2029:             // See http://tools.ietf.org/html/rfc2617#page-18
2030:             header(
2031:                 'WWW-Authenticate: Digest realm="' . $rest_realm
2032:                 . '", qop="auth", nonce="' . $nonce
2033:                 . '", opaque="' . md5($rest_realm) . '"');
2034:         }
2035: 
2036:         // Display an error response
2037:         $this->response([
2038:                 $this->config->item('rest_status_field_name') => FALSE,
2039:                 $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unauthorized')
2040:             ], self::HTTP_UNAUTHORIZED);
2041:     }
2042: 
2043:     /**
2044:      * Updates the log table with the total access time
2045:      *
2046:      * @access protected
2047:      * @author Chris Kacerguis
2048:      * @return bool TRUE log table updated; otherwise, FALSE
2049:      */
2050:     protected function _log_access_time()
2051:     {
2052:         $payload['rtime'] = $this->_end_rtime - $this->_start_rtime;
2053: 
2054:         return $this->rest->db->update(
2055:                 $this->config->item('rest_logs_table'), $payload, [
2056:                 'id' => $this->_insert_id
2057:             ]);
2058:     }
2059: 
2060:     /**
2061:      * Updates the log table with HTTP response code
2062:      *
2063:      * @access protected
2064:      * @author Justin Chen
2065:      * @param $http_code int HTTP status code
2066:      * @return bool TRUE log table updated; otherwise, FALSE
2067:      */
2068:     protected function _log_response_code($http_code)
2069:     {
2070:         $payload['response_code'] = $http_code;
2071: 
2072:         return $this->rest->db->update(
2073:             $this->config->item('rest_logs_table'), $payload, [
2074:             'id' => $this->_insert_id
2075:         ]);
2076:     }
2077: 
2078:     /**
2079:      * Check to see if the API key has access to the controller and methods
2080:      *
2081:      * @access protected
2082:      * @return bool TRUE the API key has access; otherwise, FALSE
2083:      */
2084:     protected function _check_access()
2085:     {
2086:         // If we don't want to check access, just return TRUE
2087:         if ($this->config->item('rest_enable_access') === FALSE)
2088:         {
2089:             return TRUE;
2090:         }
2091: 
2092:         // Fetch controller based on path and controller name
2093:         $controller = implode(
2094:             '/', [
2095:             $this->router->directory,
2096:             $this->router->class
2097:         ]);
2098: 
2099:         // Remove any double slashes for safety
2100:         $controller = str_replace('//', '/', $controller);
2101: 
2102:         // Query the access table and get the number of results
2103:         return $this->rest->db
2104:             ->where('key', $this->rest->key)
2105:             ->where('controller', $controller)
2106:             ->get($this->config->item('rest_access_table'))
2107:             ->num_rows() > 0;
2108:     }
2109: 
2110: }
2111: 
API documentation generated by ApiGen