Completed
Pull Request — master (#2)
by
unknown
01:26
created

Http::getQueryParamsCollapsed()   C

Complexity

Conditions 8
Paths 11

Size

Total Lines 41
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 41
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 24
nc 11
nop 2
1
<?php
2
3
namespace DominionEnterprises\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(\DominionEnterprises\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 (!is_string($rawHeaders) || trim($rawHeaders) === '') {
52
            throw new InvalidArgumentException('$rawHeaders was not a string');
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::setRequest($match, $headers);
84
                continue;
85
            }
86
87
            if (preg_match('#HTTP/([\d.]+) +(\d{3}) +(.*)#', $field, $match)) {
88
                $headers = self::setResponse($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 setRequest($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 setResponse($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 = \DominionEnterprises\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 InvalidArgumentException if $url was not a string
171
     * @throws Exception if more than one value in a $collapsedParams param
172
     */
173
    public static function getQueryParams(string $url, array $collapsedParams = []) : array
174
    {
175
        if (!is_string($url)) {
176
            throw new InvalidArgumentException('$url was not a string');
177
        }
178
179
        $queryString = parse_url($url, PHP_URL_QUERY);
180
        if (!is_string($queryString)) {
181
            return [];
182
        }
183
184
        $collapsedParams = array_flip($collapsedParams);
185
186
        $result = [];
187
        foreach (explode('&', $queryString) as $arg) {
188
            $name = $arg;
189
            $value = '';
190
            $nameAndValue = explode('=', $arg);
191
            if (isset($nameAndValue[1])) {
192
                list($name, $value) = $nameAndValue;
193
            }
194
195
            $name = rawurldecode($name);
196
            $value = rawurldecode($value);
197
            $collapsed = isset($collapsedParams[$name]);
198
199
            if (!array_key_exists($name, $result)) {
200
                if ($collapsed) {
201
                    $result[$name] = $value;
202
                    continue;
203
                }
204
205
                $result[$name] = [];
206
            }
207
208
            if ($collapsed) {
209
                throw new Exception("Parameter '{$name}' had more than one value but in \$collapsedParams");
210
            }
211
212
            $result[$name][] = $value;
213
        }
214
215
        return $result;
216
    }
217
218
    /**
219
     * Get an array of all url parameters.
220
     *
221
     * @param string $url The url to parse such as http://foo.com/bar/?single=boo&multi=wee&multi=boo
222
     * @param array $expectedArrayParams List of parameter names which are not collapsed.
223
     *
224
     * @return array such as ['single' => 'boo', 'multi' => ['wee', 'boo']] if 'multi' is given in $expectedArrayParams
225
     *
226
     * @throws InvalidArgumentException if $url was not a string
227
     * @throws Exception if a parameter is given as array but not included in the expected array argument
228
     */
229
    public static function getQueryParamsCollapsed(string $url, array $expectedArrayParams = []) : array
230
    {
231
        if (!is_string($url)) {
232
            throw new InvalidArgumentException('$url was not a string');
233
        }
234
235
        $queryString = parse_url($url, PHP_URL_QUERY);
236
        if (!is_string($queryString)) {
237
            return [];
238
        }
239
240
        $result = [];
241
        foreach (explode('&', $queryString) as $arg) {
242
            $name = $arg;
243
            $value = '';
244
            $nameAndValue = explode('=', $arg);
245
            if (isset($nameAndValue[1])) {
246
                list($name, $value) = $nameAndValue;
247
            }
248
249
            $name = rawurldecode($name);
250
            $value = rawurldecode($value);
251
252
            if (!array_key_exists($name, $result)) {
253
                $result[$name] = $value;
254
                continue;
255
            }
256
257
            if (!in_array($name, $expectedArrayParams)) {
258
                throw new Exception("Parameter '{$name}' is not expected to be an array, but array given");
259
            }
260
261
            if (!is_array($result[$name])) {
262
                $result[$name] = [$result[$name]];
263
            }
264
265
            $result[$name][] = $value;
266
        }
267
268
        return $result;
269
    }
270
}
271