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