Passed
Push — master ( 6ebac0...a0be99 )
by Michael
19:32
created

http_parse_headers()   B

Complexity

Conditions 7
Paths 12

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 18
c 1
b 0
f 0
nc 12
nop 1
dl 0
loc 32
rs 8.8333
1
<?php
2
/**
3
 * Gentics Aloha Editor AJAX Gateway
4
 * Copyright (c) 2010 Gentics Software GmbH
5
 * Licensed unter the terms of http://www.aloha-editor.com/license.html
6
 * [email protected]
7
 * Author Haymo Meran [email protected]
8
 * Author Johannes Schüth [email protected]
9
 * Author Tobias Steiner [email protected]
10
 *
11
 * Testing from the command line:
12
 * function getallheaders(){return array('X-Gentics' => 'X');};
13
 * https url example: https://google.com/adsense
14
 *
15
 */
16
17
// for debugging
18
//$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.0';
19
//$_SERVER['REQUEST_METHOD'] = 'HEAD';
20
//error_reporting(E_ALL);
21
22
$request = [
23
    'method'   => $_SERVER['REQUEST_METHOD'],
24
    'protocol' => $_SERVER['SERVER_PROTOCOL'],
25
    'headers'  => getallheaders(),
26
    //TODO: multipart/form-data is not handled by php://input. there
27
    //doesn't seem to be a generic way to get at the raw post data for
28
    //that content-type.
29
    'payload'  => file_get_contents('php://input'),
30
];
31
32
// read url parameter
33
if (array_key_exists('url', $_GET)) {
34
    $request['url'] = urldecode($_GET['url']);
35
} else {
36
    header('HTTP/1.0 400 Bad Request');
37
    echo 'Aloha Editor AJAX Gateway failed because parameter url is missing.';
38
    exit();
39
}
40
41
// check if link exists
42
$response = http_request($request);
43
44
// Note HEAD does not always work even if specified...
45
// We use HEAD for Linkchecking so we do a 2nd request.
46
if (!array_key_exists('method', $response)) {
0 ignored issues
show
Bug introduced by
$response of type string is incompatible with the type ArrayObject|array expected by parameter $array of array_key_exists(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

46
if (!array_key_exists('method', /** @scrutinizer ignore-type */ $response)) {
Loading history...
47
    $response['method'] = false;
48
}
49
50
if ('HEAD' == strtoupper($response['method']) && (int)$response['status'] >= 400) {
51
    $request['method'] = 'GET';
52
    $response          = http_request($request);
53
54
    //since we handle a HEAD, we don't need to proxy any contents
55
    fclose($response['socket']);
56
    $response['socket'] = null;
57
}
58
59
// forward each returned header...
60
foreach ($response['headers'] as $key => $value) {
61
    if ('content-length' == strtolower($key)) {
62
        //there is no need to specify a content length since we don't do keep
63
        //alive, and this can cause problems for integration (e.g. gzip output,
64
        //which would change the content length)
65
        //Note: overriding with header('Content-length:') will set
66
        //the content-length to zero for some reason
67
        continue;
68
    }
69
    header("$key: $value");
70
}
71
72
header('Connection: close');
73
74
// output the contents if any
75
if (null !== $response['socket']) {
76
    fpassthru($response['socket']);
77
    fclose($response['socket']);
78
}
79
80
exit;
81
82
/**
83
 * Query an HTTP(S) URL with the given request parameters and return the
84
 * response headers and status code. The socket is returned as well and
85
 * will point to the begining of the response payload (after all headers
86
 * have been read), and must be closed with fclose().
87
 * @param $url     the request URL
88
 * @param $request the request method may optionally be overridden.
89
 * @param $timeout connection and read timeout in seconds
0 ignored issues
show
Bug introduced by
The type connection was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
90
 */
91
function http_request($request, $timeout = 5)
92
{
93
    $url = $request['url'];
94
    // Extract the hostname from url
95
    $parts = parse_url($url);
96
    if (array_key_exists('host', $parts)) {
97
        $remote = $parts['host'];
98
    } else {
99
        return myErrorHandler("url ($url) has no host. Is it relative?");
0 ignored issues
show
Bug introduced by
Are you sure the usage of myErrorHandler('url ('.$...host. Is it relative?') is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
100
    }
101
    if (array_key_exists('port', $parts)) {
102
        $port = $parts['port'];
103
    } else {
104
        $port = 0;
105
    }
106
107
    // Beware that RFC2616 (HTTP/1.1) defines header fields as case-insensitive entities.
108
    $request_headers = '';
109
    foreach ($request['headers'] as $name => $value) {
110
        switch (strtolower($name)) {
111
            //omit some headers
112
            case 'keep-alive':
113
            case 'connection':
114
            case 'cookie':
115
                //TODO: we don't handle any compression encodings. compression
116
                //can cause a problem if client communication is already being
117
                //compressed by the server/app that integrates this script
118
                //(which would double compress the content, once from the remote
119
                //server to us, and once from us to the client, but the client
120
                //would de-compress only once).
121
            case 'accept-encoding':
122
                break;
123
            // correct the host parameter
124
            case 'host':
125
                $host_info = $remote;
126
                if ($port) {
127
                    $host_info .= ':' . $port;
128
                }
129
                $request_headers .= "$name: $host_info\r\n";
130
                break;
131
            // forward all other headers
132
            default:
133
                $request_headers .= "$name: $value\r\n";
134
                break;
135
        }
136
    }
137
138
    //set fsockopen transport scheme, and the default port
139
    switch (strtolower($parts['scheme'])) {
140
        case 'https':
141
            $scheme = 'ssl://';
142
            if (!$port) {
143
                $port = 443;
144
            }
145
            break;
146
        case 'http':
147
            $scheme = '';
148
            if (!$port) {
149
                $port = 80;
150
            }
151
            break;
152
        default:
153
            //some other transports are available but not really supported
154
            //by this script: http://php.net/manual/en/transports.inet.php
155
            $scheme = $parts['scheme'] . '://';
156
            if (!$port) {
157
                return myErrorHandler("Unknown scheme ($scheme) and no port.");
0 ignored issues
show
Bug introduced by
Are you sure the usage of myErrorHandler('Unknown ...cheme.') and no port.') is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
158
            }
159
            break;
160
    }
161
162
    //we make the request with socket operations since we don't want to
163
    //depend on the curl extension, and the higher level wrappers don't
164
    //give us usable error information.
165
    $sock = @fsockopen("$scheme$remote", $port, $errno, $errstr, $timeout);
166
    if (!$sock) {
0 ignored issues
show
introduced by
$sock is of type false|resource, thus it always evaluated to false.
Loading history...
167
        return myErrorHandler("Unable to open URL ($url): $errstr");
0 ignored issues
show
Bug introduced by
Are you sure the usage of myErrorHandler('Unable t... ('.$url.'): '.$errstr) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
168
    }
169
170
    //the timeout in fsockopen is only for the connection, the following
171
    //is for reading the content
172
    stream_set_timeout($sock, $timeout);
173
174
    //an absolute url should only be specified for proxy requests
175
    if (array_key_exists('path', $parts)) {
176
        $path_info = $parts['path'];
177
    } else {
178
        $path_info = '/';
179
    }
180
181
    if (array_key_exists('query', $parts)) {
182
        $path_info .= '?' . $parts['query'];
183
    }
184
    if (array_key_exists('fragment', $parts)) {
185
        $path_info .= '#' . $parts['fragment'];
186
    }
187
188
    $out = $request['method'] . ' ' . $path_info . ' ' . $request['protocol'] . "\r\n" . $request_headers . "Connection: close\r\n\r\n";
189
    fwrite($sock, $out);
190
    fwrite($sock, $request['payload']);
191
192
    $header_str  = stream_get_line($sock, 1024 * 16, "\r\n\r\n");
193
    $headers     = http_parse_headers($header_str);
194
    $status_line = array_shift($headers);
195
196
    // get http status
197
    preg_match('|HTTP/\d+\.\d+\s+(\d+)\s+.*|i', $status_line, $match);
198
    $status = $match[1];
199
200
    return ['headers' => $headers, 'socket' => $sock, 'status' => $status];
201
}
202
203
/**
204
 * Parses a string containing multiple HTTP header lines into an array
205
 * of key => values.
206
 * Inspired by HTTP::Daemon (CPAN).
207
 */
208
function http_parse_headers($header_str)
209
{
210
    $headers = [];
211
212
    //ignore leading blank lines
213
    $header_str = preg_replace("/^(?:\x0D?\x0A)+/", '', $header_str);
214
215
    while (preg_match("/^([^\x0A]*?)\x0D?(?:\x0A|\$)/", $header_str, $matches)) {
216
        $header_str  = substr($header_str, strlen($matches[0]));
217
        $status_line = $matches[1];
218
219
        if (empty($headers)) {
220
            // the status line
221
            $headers[] = $status_line;
222
        } elseif (preg_match('/^([^:\s]+)\s*:\s*(.*)/', $status_line, $matches)) {
223
            if (isset($key)) {
224
                //previous header is finished (was potentially multi-line)
225
                $headers[$key] = $val;
226
            }
227
            list(, $key, $val) = $matches;
228
        } elseif (preg_match('/^\s+(.*)/', $status_line, $matches)) {
229
            //continue a multi-line header
230
            $val .= ' ' . $matches[1];
231
        } else {
232
            //empty (possibly malformed) header signals the end of all headers
233
            break;
234
        }
235
    }
236
    if (isset($key)) {
237
        $headers[$key] = $val;
238
    }
239
    return $headers;
240
}
241
242
function myErrorHandler($msg)
243
{
244
    // 500 could be misleading...
245
    // Should we return a special Error when a proxy error occurs?
246
    header('HTTP/1.0 500 Internal Error');
247
    echo "Gentics Aloha Editor AJAX Gateway Error: $msg";
248
    exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
249
}
250
251
//EOF
252