Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
11.70% covered (danger)
11.70%
11 / 94
13.33% covered (danger)
13.33%
2 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
common_http_Request
11.70% covered (danger)
11.70%
11 / 94
13.33% covered (danger)
13.33%
2 / 15
1634.11
0.00% covered (danger)
0.00%
0 / 1
 currentRequest
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
110
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 isHttps
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
56
 getUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMethod
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getParams
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setParam
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setHeader
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHeaders
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHeaderValue
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 setBody
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBody
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 send
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
210
 postEncode
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 headerEncode
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2013-2016 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
19 *
20 */
21
22/**
23 * Represents an http request
24 *
25 * @access public
26 * @author Joel Bout, <joel@taotesting.com>
27 * @package generis
28
29 */
30class common_http_Request
31{
32    public const METHOD_POST = 'POST';
33
34    public const METHOD_GET = 'GET';
35
36    private const REDIRECT_CODES = [
37        301,
38        302,
39        303,
40        307,
41        308
42    ];
43
44    /**
45     * Creates an request from the current call
46     *
47     * The scheme in used (http|https) will be derived from
48     *
49     * * $_SERVER['HTTPS'] in case of a standard deployment
50     * * $_SERVER['HTTP_X_FORWARDED_PROTO'] or $_SERVER['HTTP_X_FORWARDED_SSL'] in case of being deployed behing a load
51     *   balancer/proxy.
52     *
53     * If no clues about whether HTTPS is in use are found, HTTP will be the scheme of the current request.
54     *
55     * @return common_http_Request A request corresponding to the current HTTP(S) context.
56     * @throws common_exception_Error In case of a CLI execution context.
57     */
58    public static function currentRequest()
59    {
60        if (php_sapi_name() == 'cli') {
61            throw new common_exception_Error('Cannot call ' . __FUNCTION__ . ' from command line');
62        }
63
64        $https = self::isHttps();
65
66        $scheme = $https ? 'https' : 'http';
67        $port = empty($_SERVER['HTTP_X_FORWARDED_PORT']) ? $_SERVER['SERVER_PORT'] : $_SERVER['HTTP_X_FORWARDED_PORT'];
68        $url = $scheme . '://' . $_SERVER['SERVER_NAME'] . ':' . $port . $_SERVER['REQUEST_URI'];
69
70        $method = $_SERVER['REQUEST_METHOD'];
71
72        if ($_SERVER['REQUEST_METHOD'] == self::METHOD_GET) {
73            $params = $_GET;
74        } else {
75            $params = $_POST;
76        }
77        if (function_exists('apache_request_headers')) {
78            $headers = apache_request_headers();
79        } else {
80            $headers = [];
81            if (isset($_SERVER['CONTENT_TYPE'])) {
82                $headers['Content-Type'] = $_SERVER['CONTENT_TYPE'];
83            }
84            if (isset($_ENV['CONTENT_TYPE'])) {
85                $headers['Content-Type'] = $_ENV['CONTENT_TYPE'];
86            }
87            foreach ($_SERVER as $key => $value) {
88                if (substr($key, 0, 5) == "HTTP_") {
89                    // this is chaos, basically it is just there to capitalize the first
90                    // letter of every word that is not an initial HTTP and strip HTTP
91                    // code from przemek
92                    $key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5)))));
93                    $headers[$key] = $value;
94                }
95            }
96        }
97        return new self($url, $method, $params, $headers);
98    }
99
100    private $url;
101
102    private $method;
103
104    private $params;
105
106    private $headers;
107
108    private $body;
109
110    public function __construct($url, $method = self::METHOD_POST, $params = [], $headers = [], $body = "")
111    {
112        $this->url = $url;
113        $this->method = $method;
114        $this->params = $params;
115        $this->headers = $headers;
116        $this->body = $body;
117    }
118
119    /**
120     * Detect whether we use https or http
121     * @return bool
122     */
123    public static function isHttps()
124    {
125        // Default is http scheme.
126        $https = false;
127
128        if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == "on") {
129            // $_SERVER['HTTPS'] is NOT set behind a proxy / load balancer
130            $https = true;
131        } elseif (
132            // $_SERVER['HTTPS'] is set behind a proxy / load balancer
133            !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' ||
134            !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on'
135        ) {
136            $https = true;
137        }
138        return $https;
139    }
140
141    public function getUrl()
142    {
143        return $this->url;
144    }
145
146    public function getMethod()
147    {
148        return $this->method;
149    }
150
151    public function getParams()
152    {
153        return $this->params;
154    }
155
156    public function setParam($key, $value)
157    {
158        return $this->params[$key] = $value;
159    }
160    public function setHeader($key, $value)
161    {
162        return $this->headers[$key] = $value;
163    }
164
165    public function getHeaders()
166    {
167        return $this->headers;
168    }
169
170    /**
171     * Get the value of an HTTP header
172     * The lookup is case insensitive.
173     * @param string $headerName the HTTP header name, 'Content-Type' for example
174     * @return boolean|string the value or false if not found
175     */
176    public function getHeaderValue($headerName)
177    {
178        if (is_string($headerName) && count($this->headers) > 0) {
179            $lowCaseHeaders = array_change_key_case($this->headers, CASE_LOWER);
180            $lowCaseHeaderName = strtolower($headerName);
181            if (isset($lowCaseHeaders[$lowCaseHeaderName])) {
182                return $lowCaseHeaders[$lowCaseHeaderName];
183            }
184        }
185        return false;
186    }
187
188    /**
189     * set request body to send
190     */
191    public function setBody($requestBodyData)
192    {
193        $this->body = $requestBodyData;
194    }
195    public function getBody()
196    {
197        return $this->body;
198    }
199
200    public function send(bool $followRedirects = false): common_http_Response
201    {
202        $curlHandler = curl_init($this->getUrl());
203
204        //set the headers
205        if ((is_array($this->headers)) and (count($this->headers) > 0)) {
206            curl_setopt($curlHandler, CURLOPT_HTTPHEADER, self::headerEncode($this->headers));
207        }
208        switch ($this->getMethod()) {
209            case "HEAD":
210                curl_setopt($curlHandler, CURLOPT_NOBODY, true);
211                curl_setopt($curlHandler, CURLOPT_HEADER, true);
212                break;
213
214            case "POST":
215                curl_setopt($curlHandler, CURLOPT_POST, 1);
216
217                if (is_array($this->params) and (count($this->params) > 0)) {
218                    $params =  $this->postEncode($this->params);
219                    //application/x-www-form-urlencoded
220                    curl_setopt($curlHandler, CURLOPT_POSTFIELDS, $params);
221                } else {
222                    //common_Logger::i(serialize($this->getBody()));
223                    if (!is_null(($this->getBody()))) {
224                        curl_setopt($curlHandler, CURLOPT_POSTFIELDS, ($this->getBody()));
225                    }
226                }
227
228
229                //analyse if there is a body or structured postfields
230
231                break;
232
233            case "PUT":
234                break;
235
236            case "GET":
237                //curl_setopt($curlHandler,CURLOPT_HTTPGET, true);
238                break;
239        }
240
241        curl_setopt($curlHandler, CURLOPT_RETURNTRANSFER, 1);
242        //curl_setopt($curlHandler, CURLINFO_HEADER_OUT, 1);
243        //curl_setopt($curlHandler, CURLOPT_HEADER, true);
244
245        //directly setting `FOLLOWLOCATION` to false to make sure next lines would be working as expected
246        //and we can get the redirect url from curl
247        curl_setopt($curlHandler, CURLOPT_FOLLOWLOCATION, 0);
248
249        $responseData = curl_exec($curlHandler);
250        $httpResponse = new common_http_Response();
251
252        $httpResponse->httpCode = curl_getinfo($curlHandler, CURLINFO_HTTP_CODE);
253        $httpResponse->headerOut = curl_getinfo($curlHandler, CURLINFO_HEADER_OUT);
254        $httpResponse->effectiveUrl = curl_getinfo($curlHandler, CURLINFO_EFFECTIVE_URL);
255        $httpResponse->responseData = $responseData;
256
257        $redirectUrl = curl_getinfo($curlHandler, CURLINFO_REDIRECT_URL);
258        $sameDomain = null;
259        if ($redirectUrl) {
260            $initialDomain = parse_url($this->getUrl(), PHP_URL_HOST);
261            $redirectDomain = parse_url($redirectUrl, PHP_URL_HOST);
262
263            $sameDomain = ($initialDomain === $redirectDomain);
264        }
265
266        //curl_setopt($curlHandler, );
267        curl_close($curlHandler);
268
269        if (
270            $followRedirects
271            && $sameDomain
272            && in_array($httpResponse->httpCode, self::REDIRECT_CODES, true)
273        ) {
274            $this->url = $redirectUrl;
275            $httpResponse = $this->send();
276        }
277
278        return $httpResponse;
279    }
280
281    /**
282     * @param array
283     * @return string
284     */
285
286    public static function postEncode($parameters)
287    {
288
289        //todo
290        //$content_type = isset($this->headers['Content-Type']) ? $this->headers['Content-Type'] : 'text/plain';
291        //should detect suitable encoding
292        $format = 'text/plain';
293        switch ($format) {
294            default:
295                return http_build_query($parameters, null, '&');
296                break;
297        }
298    }
299
300    public static function headerEncode($headers)
301    {
302        $encodedHeaders = [];
303        //todo using aray_walk
304        foreach ($headers as $key => $value) {
305            $encodedHeaders[] = $key . ": " . $value . "";
306        }
307        //print_r($encodedHeaders);
308        return $encodedHeaders;
309    }
310}