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 */
15class Hm_Request {
16
17    /* sanitized $_POST variables */
18    public $post = array();
19
20    /* sanitized $_GET variables */
21    public $get = array();
22
23    /* sanitized $_COOKIE variables */
24    public $cookie = array();
25
26    /* sanitized $_SERVER variables */
27    public $server = array();
28
29    /* $_ENV variables (fallback for missing $_SERVER vals) */
30    private $env = array();
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 = array();
52
53    /* bool indicating unknown input data */
54    public $invalid_input_detected = false;
55
56    /* invalid input fields */
57    public $invalid_input_fields = array();
58
59    /* uploaded file details */
60    public $files = array();
61
62    /* module filters */
63    public $filters = array();
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);
88        Hm_Debug::add('Request type: '.$this->type);
89        Hm_Debug::add('Request path: '.$this->path);
90        Hm_Debug::add('TLS request: '.intval($this->tls));
91        Hm_Debug::add('Mobile request: '.intval($this->mobile));
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        }
129        elseif (array_key_exists('REQUEST_METHOD', $this->env)) {
130            $this->method = $this->env['REQUEST_METHOD'];
131            $this->server = $this->env;
132        }
133        $this->get_request_type();
134        $this->is_tls();
135        $this->is_mobile();
136    }
137
138    /**
139     * Empty out super globals.
140     * @return void
141     */
142    private function empty_super_globals() {
143        $_POST = array();
144        $_SERVER = array();
145        $_GET = array();
146        $_COOKIE = array();
147        $_FILES = array();
148        $_REQUEST = array();
149        $_ENV = array();
150        $GLOBALS = array();
151    }
152
153    /**
154     * Filter specified input against module defined filters
155     * @param type string the type of input (POST, GET, COOKIE, etc)
156     * @param filters array list of input filters from module sets
157     * @return array filtered input data
158     */
159    public function filter_input($type, $filters) {
160        $data = Hm_Functions::filter_input_array($type, $filters);
161        if ($data === false || $data === NULL) {
162            return array();
163        }
164        return $data;
165    }
166
167    /**
168     * Look at the HTTP_USER_AGENT value and set a mobile OS flag
169     * @return void
170     */
171    private function is_mobile() {
172        if ($this->always_mobile) {
173            $this->mobile = true;
174            return;
175        }
176        if (array_key_exists('HTTP_USER_AGENT', $this->server)) {
177            if (preg_match("/(iphone|ipod|ipad|android|blackberry|webos)/i", $this->server['HTTP_USER_AGENT'])) {
178                $this->mobile = true;
179            }
180        }
181    }
182
183    /**
184     * Determine if a request was done over TLS
185     * @return void
186     */
187    private function is_tls() {
188        if (array_key_exists('HTTPS', $this->server) && strtolower($this->server['HTTPS']) == 'on') {
189            $this->tls = true;
190        }
191        elseif (array_key_exists('REQUEST_SCHEME', $this->server) && strtolower($this->server['REQUEST_SCHEME']) == 'https') {
192            $this->tls = true;
193        }
194    }
195
196    /**
197     * Determine the request type, either AJAX or HTTP
198     * @return void
199     */
200    private function get_request_type() {
201        if ($this->is_ajax()) {
202            $this->type = 'AJAX';
203            $this->format = 'Hm_Format_JSON';
204        }
205        else {
206            $this->type = 'HTTP';
207            $this->format = 'Hm_Format_HTML5';
208        }
209    }
210
211    /**
212     * Determine if a request is an AJAX call
213     * @return bool true if the request is from an AJAX call
214     */
215    public function is_ajax() {
216        return array_key_exists('HTTP_X_REQUESTED_WITH', $this->server) && strtolower($this->server['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
217    }
218
219    /**
220     * Make sure a url path is sane
221     * @param string $uri path to check
222     * @return string clean url path
223     */
224    private function get_clean_url_path($uri) {
225        if (strpos($uri, '?') !== false) {
226            $parts = explode('?', $uri, 2);
227            $path = $parts[0];
228        }
229        else {
230            $path = $uri;
231        }
232        $path = str_replace('index.php', '', $path);
233        if (substr($path, -1) != '/') {
234            $path .= '/';
235        }
236        return $path;
237    }
238}
239