1: <?php
2:
3: /**
4: * Request handling
5: * @package framework
6: * @subpackage request
7: */
8:
9: /**
10: * Data request details
11: *
12: * This is an interface to HTTP request details. All request data
13: * must be white-listed and sanitized by module set filters.
14: */
15: class Hm_Request {
16:
17: /* sanitized $_POST variables */
18: public $post = [];
19:
20: /* sanitized $_GET variables */
21: public $get = [];
22:
23: /* sanitized $_COOKIE variables */
24: public $cookie = [];
25:
26: /* sanitized $_SERVER variables */
27: public $server = [];
28:
29: /* $_ENV variables (fallback for missing $_SERVER vals) */
30: private $env = [];
31:
32: /* request type. either AJAX or HTTP */
33: public $type = '';
34:
35: /* PHP sapi method used for the request */
36: public $sapi = '';
37:
38: /* Output format, either Hm_Format_JSON or Hm_Format_HTML5 */
39: public $format = '';
40:
41: /* bool indicating if the request was over SSL/TLS */
42: public $tls = false;
43:
44: /* bool indicating if this looks like a mobile OS request */
45: public $mobile = false;
46:
47: /* URL path */
48: public $path = '';
49:
50: /* allowed AJAX output field names defined by module sets */
51: public $allowed_output = [];
52:
53: /* bool indicating unknown input data */
54: public $invalid_input_detected = false;
55:
56: /* invalid input fields */
57: public $invalid_input_fields = [];
58:
59: /* uploaded file details */
60: public $files = [];
61:
62: /* module filters */
63: public $filters = [];
64:
65: /* HTTP request method */
66: public $method = false;
67:
68: /* force mobile ui */
69: private $always_mobile = false;
70:
71: /**
72: * Process request details
73: * @param array $filters list of input filters from module sets
74: * @param object $config site config object
75: */
76: public function __construct($filters, $config) {
77: if ($config->get('always_mobile_ui', false)) {
78: $this->always_mobile = true;
79: }
80: $this->filters = $filters;
81: $this->filter_request_input();
82: $this->get_other_request_details();
83: $this->files = $_FILES;
84: if (!$config->get('disable_empty_superglobals', false)) {
85: $this->empty_super_globals();
86: }
87: Hm_Debug::add('Using sapi: '.$this->sapi, 'info');
88: Hm_Debug::add('Request type: '.$this->type, 'info');
89: Hm_Debug::add('Request path: '.$this->path, 'info');
90: Hm_Debug::add('TLS request: '.intval($this->tls), 'info');
91: Hm_Debug::add('Mobile request: '.intval($this->mobile), 'info');
92: }
93:
94: /**
95: * Sanitize and filter user and server input
96: * @return void
97: */
98: private function filter_request_input() {
99: if (array_key_exists('allowed_server', $this->filters)) {
100: $this->server = $this->filter_input(INPUT_SERVER, $this->filters['allowed_server']);
101: $this->env = $this->filter_input(INPUT_ENV, $this->filters['allowed_server']);
102: }
103: if (array_key_exists('allowed_post', $this->filters)) {
104: $this->post = $this->filter_input(INPUT_POST, $this->filters['allowed_post']);
105: }
106: if (array_key_exists('allowed_get', $this->filters)) {
107: $this->get = $this->filter_input(INPUT_GET, $this->filters['allowed_get']);
108: }
109: if (array_key_exists('allowed_cookie', $this->filters)) {
110: $this->cookie = $this->filter_input(INPUT_COOKIE, $this->filters['allowed_cookie']);
111: }
112: }
113:
114: /**
115: * Collect other useful details about a request
116: * @return void
117: */
118: private function get_other_request_details() {
119: $this->sapi = php_sapi_name();
120: if (array_key_exists('allowed_output', $this->filters)) {
121: $this->allowed_output = $this->filters['allowed_output'];
122: }
123: if (array_key_exists('REQUEST_URI', $this->server)) {
124: $this->path = $this->get_clean_url_path($this->server['REQUEST_URI']);
125: }
126: if (array_key_exists('REQUEST_METHOD', $this->server)) {
127: $this->method = $this->server['REQUEST_METHOD'];
128: } elseif (array_key_exists('REQUEST_METHOD', $this->env)) {
129: $this->method = $this->env['REQUEST_METHOD'];
130: $this->server = $this->env;
131: }
132: $this->get_request_type();
133: $this->is_tls();
134: $this->is_mobile();
135: }
136:
137: /**
138: * Empty out super globals.
139: * @return void
140: */
141: private function empty_super_globals() {
142: $_POST = [];
143: $_SERVER = [];
144: $_GET = [];
145: $_COOKIE = [];
146: $_FILES = [];
147: $_REQUEST = [];
148: $_ENV = [];
149:
150: foreach (array_keys($GLOBALS) as $key) {
151: unset($GLOBALS[$key]);
152: }
153: }
154:
155: /**
156: * Filter specified input against module defined filters
157: * @param type string the type of input (POST, GET, COOKIE, etc)
158: * @param filters array list of input filters from module sets
159: * @return array filtered input data
160: */
161: public function filter_input($type, $filters) {
162: $data = Hm_Functions::filter_input_array($type, $filters);
163: if ($data === false || $data === NULL) {
164: return [];
165: }
166: return $data;
167: }
168:
169: /**
170: * Look at the HTTP_USER_AGENT value and set a mobile OS flag
171: * @return void
172: */
173: private function is_mobile() {
174: if ($this->always_mobile) {
175: $this->mobile = true;
176: return;
177: }
178:
179: if (!empty($_COOKIE['is_mobile_screen']) && $_COOKIE['is_mobile_screen'] == '1') {
180: $this->mobile = true;
181: return;
182: }
183:
184: if (!empty($this->server['HTTP_USER_AGENT'])) {
185: if (preg_match("/(iphone|ipod|ipad|android|blackberry|webos|opera mini)/i", $this->server['HTTP_USER_AGENT'])) {
186: $this->mobile = true;
187: }
188: }
189: }
190:
191: /**
192: * Determine if a request was done over TLS
193: * @return void
194: */
195: private function is_tls() {
196: if (!empty($this->server['HTTPS']) && mb_strtolower($this->server['HTTPS']) == 'on') {
197: $this->tls = true;
198: } elseif (!empty($this->server['REQUEST_SCHEME']) && mb_strtolower($this->server['REQUEST_SCHEME']) == 'https') {
199: $this->tls = true;
200: }
201: }
202:
203: /**
204: * Determine the request type, either AJAX or HTTP
205: * @return void
206: */
207: private function get_request_type() {
208: if ($this->is_ajax()) {
209: $this->type = 'AJAX';
210: $this->format = 'Hm_Format_JSON';
211: } else {
212: $this->type = 'HTTP';
213: $this->format = 'Hm_Format_HTML5';
214: }
215: }
216:
217: /**
218: * Determine if a request is an AJAX call
219: * @return bool true if the request is from an AJAX call
220: */
221: public function is_ajax() {
222: return !empty($this->server['HTTP_X_REQUESTED_WITH']) && mb_strtolower($this->server['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
223: }
224:
225: /**
226: * Make sure a url path is sane
227: * @param string $uri path to check
228: * @return string clean url path
229: */
230: private function get_clean_url_path($uri) {
231: if (mb_strpos($uri, '?') !== false) {
232: $parts = explode('?', $uri, 2);
233: $path = $parts[0];
234: } else {
235: $path = $uri;
236: }
237: $path = str_replace('index.php', '', $path);
238: if (mb_substr($path, -1) != '/') {
239: $path .= '/';
240: }
241: return $path;
242: }
243: }
244: