Issues (2)

src/Util/Http.php (2 issues)

Severity
1
<?php
2
3
namespace TraderInteractive\Util;
4
5
use Exception;
6
use InvalidArgumentException;
7
8
/**
9
 * Static class with various HTTP related functions.
10
 */
11
final class Http
12
{
13
    /**
14
     * Parses HTTP headers into an associative array.
15
     *
16
     * Example:
17
     * <code>
18
     * $headers = "HTTP/1.1 200 OK\r\n".
19
     *            "content-type: text/html; charset=UTF-8\r\n".
20
     *            "Server: Funky/1.0\r\n".
21
     *            "Set-Cookie: foo=bar\r\n".
22
     *            "Set-Cookie: baz=quux\r\n".
23
     *            "Folds: are\r\n\treformatted\r\n";
24
     * print_r(\TraderInteractive\HttpUtil::parseHeaders($headers));
25
     * </code>
26
     * The above example will output:
27
     * <pre>
28
     * Array
29
     * (
30
     *     [Response Code] => 200
31
     *     [Response Status] => OK
32
     *     [Content-Type] => text/html; charset=UTF-8
33
     *     [Server] => Funky/1.0
34
     *     [Set-Cookie] => Array
35
     *     (
36
     *       [0] => foo=bar
37
     *       [1] => baz=quux
38
     *     )
39
     *     [Folds] => are reformatted
40
     * )
41
     * </pre>
42
     *
43
     * @param string $rawHeaders string containing HTTP headers
44
     *
45
     * @return array the parsed headers
46
     *
47
     * @throws Exception Thrown if unable to parse the headers
48
     */
49
    public static function parseHeaders(string $rawHeaders) : array
50
    {
51
        if (empty(trim($rawHeaders))) {
52
            throw new InvalidArgumentException('$rawHeaders cannot be whitespace');
53
        }
54
55
        $headers = [];
56
        $rawHeaders = preg_replace("/\r\n[\t ]+/", ' ', trim($rawHeaders));
57
        $fields = explode("\r\n", $rawHeaders);
58
        foreach ($fields as $field) {
59
            $match = null;
60
            if (preg_match('/([^:]+): (.+)/m', $field, $match)) {
61
                $key = $match[1];
62
                // convert 'some-header' to 'Some-Header'
63
                $key = strtolower(trim($key));
64
                $key = ucwords(preg_replace('/[\s-]/', ' ', $key));
65
                $key = strtr($key, ' ', '-');
66
67
                $value = trim($match[2]);
68
69
                if (!array_key_exists($key, $headers)) {
70
                    $headers[$key] = $value;
71
                    continue;
72
                }
73
74
                if (!is_array($headers[$key])) {
75
                    $headers[$key] = [$headers[$key]];
76
                }
77
78
                $headers[$key][] = $value;
79
                continue;
80
            }
81
82
            if (preg_match('#([A-Za-z]+) +([^ ]+) +HTTP/([\d.]+)#', $field, $match)) {
83
                $headers = self::addRequestDataToHeaders($match, $headers);
84
                continue;
85
            }
86
87
            if (preg_match('#HTTP/([\d.]+) +(\d{3}) +(.*)#', $field, $match)) {
88
                $headers = self::addResponseDataToHeaders($match, $headers);
89
                continue;
90
            }
91
92
            throw new Exception("Unsupported header format: {$field}");
93
        }
94
95
        return $headers;
96
    }
97
98
    private static function addRequestDataToHeaders(array $match, array $headers) : array
99
    {
100
        $headers['Request Method'] = trim($match[1]);
101
        $headers['Request Url'] = trim($match[2]);
102
        return $headers;
103
    }
104
105
    private static function addResponseDataToHeaders(array $match, array $headers) : array
106
    {
107
        $headers['Response Code'] = (int)$match[2];
108
        $headers['Response Status'] = trim($match[3]);
109
        return $headers;
110
    }
111
112
    /**
113
     * Generate URL-encoded query string
114
     *
115
     * Example:
116
     * <code>
117
     * $parameters = [
118
     *   'param1' => ['value', 'another value'],
119
     *   'param2' => 'a value',
120
     *   'param3' => false,
121
     * ];
122
     *
123
     * $queryString = \TraderInteractive\HttpUtil::buildQueryString($parameters);
124
     *
125
     * echo $queryString
126
     * </code>
127
     *
128
     * Output:
129
     * <pre>
130
     * param1=value&param1=another+value&param2=a+value&param3=false
131
     * </pre>
132
     *
133
     * @param array $parameters An associative array containing parameter key/value(s)
134
     *
135
     * @return string the built query string
136
     */
137
    public static function buildQueryString(array $parameters) : string
138
    {
139
        $queryStrings = [];
140
        foreach ($parameters as $parameterName => $parameterValue) {
141
            $parameterName = rawurlencode($parameterName);
142
143
            if (is_array($parameterValue)) {
144
                foreach ($parameterValue as $eachValue) {
145
                    $eachValue = rawurlencode($eachValue);
146
                    $queryStrings[] = "{$parameterName}={$eachValue}";
147
                }
148
            } elseif ($parameterValue === false) {
149
                $queryStrings[] = "{$parameterName}=false";
150
            } elseif ($parameterValue === true) {
151
                $queryStrings[] = "{$parameterName}=true";
152
            } else {
153
                $parameterValue = rawurlencode($parameterValue);
154
                $queryStrings[] = "{$parameterName}={$parameterValue}";
155
            }
156
        }
157
158
        return implode('&', $queryStrings);
159
    }
160
161
    /**
162
     * Get an array of all url parameters.
163
     *
164
     * @param string $url The url to parse such as http://foo.com/bar/?id=boo&another=wee&another=boo
165
     * @param array $collapsedParams Parameters to collapse. ex. 'id' => ['boo'] to just 'id' => 'boo'. Exception thrown
166
     *                               if more than 1 value
167
     *
168
     * @return array such as ['id' => ['boo'], 'another' => ['wee', 'boo']]
169
     *
170
     * @throws Exception if more than one value in a $collapsedParams param
171
     */
172
    public static function getQueryParams(string $url, array $collapsedParams = []) : array
173
    {
174
        $queryString = parse_url($url, PHP_URL_QUERY);
175
        if (!is_string($queryString)) {
0 ignored issues
show
The condition is_string($queryString) is always true.
Loading history...
176
            return [];
177
        }
178
179
        $collapsedParams = array_flip($collapsedParams);
180
181
        $result = [];
182
        foreach (explode('&', $queryString) as $arg) {
183
            $name = $arg;
184
            $value = '';
185
            $nameAndValue = explode('=', $arg);
186
            if (isset($nameAndValue[1])) {
187
                list($name, $value) = $nameAndValue;
188
            }
189
190
            $name = rawurldecode($name);
191
            $value = rawurldecode($value);
192
            $collapsed = isset($collapsedParams[$name]);
193
194
            if (!array_key_exists($name, $result)) {
195
                if ($collapsed) {
196
                    $result[$name] = $value;
197
                    continue;
198
                }
199
200
                $result[$name] = [];
201
            }
202
203
            if ($collapsed) {
204
                throw new Exception("Parameter '{$name}' had more than one value but in \$collapsedParams");
205
            }
206
207
            $result[$name][] = $value;
208
        }
209
210
        return $result;
211
    }
212
213
    /**
214
     * Get an array of all url parameters.
215
     *
216
     * @param string $url The url to parse such as http://foo.com/bar/?single=boo&multi=wee&multi=boo
217
     * @param array $expectedArrayParams List of parameter names which are not collapsed.
218
     *
219
     * @return array such as ['single' => 'boo', 'multi' => ['wee', 'boo']] if 'multi' is given in $expectedArrayParams
220
     *
221
     * @throws Exception if a parameter is given as array but not included in the expected array argument
222
     */
223
    public static function getQueryParamsCollapsed(string $url, array $expectedArrayParams = []) : array
224
    {
225
        $queryString = parse_url($url, PHP_URL_QUERY);
226
        if (!is_string($queryString)) {
0 ignored issues
show
The condition is_string($queryString) is always true.
Loading history...
227
            return [];
228
        }
229
230
        $result = [];
231
        foreach (explode('&', $queryString) as $arg) {
232
            $name = $arg;
233
            $value = '';
234
            $nameAndValue = explode('=', $arg);
235
            if (isset($nameAndValue[1])) {
236
                list($name, $value) = $nameAndValue;
237
            }
238
239
            $name = rawurldecode($name);
240
            $value = rawurldecode($value);
241
242
            if (!array_key_exists($name, $result)) {
243
                $result[$name] = $value;
244
                continue;
245
            }
246
247
            if (!in_array($name, $expectedArrayParams)) {
248
                throw new Exception("Parameter '{$name}' is not expected to be an array, but array given");
249
            }
250
251
            if (!is_array($result[$name])) {
252
                $result[$name] = [$result[$name]];
253
            }
254
255
            $result[$name][] = $value;
256
        }
257
258
        return $result;
259
    }
260
}
261