Completed
Pull Request — master (#2)
by
unknown
02:23 queued 54s
created

Http::setResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
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
        $headers = [];
52
        $rawHeaders = preg_replace("/\r\n[\t ]+/", ' ', trim($rawHeaders));
53
        $fields = explode("\r\n", $rawHeaders);
54
        foreach ($fields as $field) {
55
            $match = null;
56
            if (preg_match('/([^:]+): (.+)/m', $field, $match)) {
57
                $key = $match[1];
58
                // convert 'some-header' to 'Some-Header'
59
                $key = strtolower(trim($key));
60
                $key = ucwords(preg_replace('/[\s-]/', ' ', $key));
61
                $key = strtr($key, ' ', '-');
62
63
                $value = trim($match[2]);
64
65
                if (!array_key_exists($key, $headers)) {
66
                    $headers[$key] = $value;
67
                    continue;
68
                }
69
70
                if (!is_array($headers[$key])) {
71
                    $headers[$key] = [$headers[$key]];
72
                }
73
74
                $headers[$key][] = $value;
75
                continue;
76
            }
77
78
            if (preg_match('#([A-Za-z]+) +([^ ]+) +HTTP/([\d.]+)#', $field, $match)) {
79
                $headers = self::setRequest($match, $headers);
80
                continue;
81
            }
82
83
            if (preg_match('#HTTP/([\d.]+) +(\d{3}) +(.*)#', $field, $match)) {
84
                $headers = self::setResponse($match, $headers);
85
                continue;
86
            }
87
88
            throw new Exception("Unsupported header format: {$field}");
89
        }
90
91
        return $headers;
92
    }
93
94
    private static function setRequest(array $match, array $headers) : array
95
    {
96
        $headers['Request Method'] = trim($match[1]);
97
        $headers['Request Url'] = trim($match[2]);
98
        return $headers;
99
    }
100
101
    private static function setResponse(array $match, array $headers) : array
102
    {
103
        $headers['Response Code'] = (int)$match[2];
104
        $headers['Response Status'] = trim($match[3]);
105
        return $headers;
106
    }
107
108
    /**
109
     * Generate URL-encoded query string
110
     *
111
     * Example:
112
     * <code>
113
     * $parameters = [
114
     *   'param1' => ['value', 'another value'],
115
     *   'param2' => 'a value',
116
     *   'param3' => false,
117
     * ];
118
     *
119
     * $queryString = \DominionEnterprises\HttpUtil::buildQueryString($parameters);
120
     *
121
     * echo $queryString
122
     * </code>
123
     *
124
     * Output:
125
     * <pre>
126
     * param1=value&param1=another+value&param2=a+value&param3=false
127
     * </pre>
128
     *
129
     * @param array $parameters An associative array containing parameter key/value(s)
130
     *
131
     * @return string the built query string
132
     */
133
    public static function buildQueryString(array $parameters) : string
134
    {
135
        $queryStrings = [];
136
        foreach ($parameters as $parameterName => $parameterValue) {
137
            $parameterName = rawurlencode($parameterName);
138
139
            if (is_array($parameterValue)) {
140
                foreach ($parameterValue as $eachValue) {
141
                    $eachValue = rawurlencode($eachValue);
142
                    $queryStrings[] = "{$parameterName}={$eachValue}";
143
                }
144
            } elseif ($parameterValue === false) {
145
                $queryStrings[] = "{$parameterName}=false";
146
            } elseif ($parameterValue === true) {
147
                $queryStrings[] = "{$parameterName}=true";
148
            } else {
149
                $parameterValue = rawurlencode($parameterValue);
150
                $queryStrings[] = "{$parameterName}={$parameterValue}";
151
            }
152
        }
153
154
        return implode('&', $queryStrings);
155
    }
156
157
    /**
158
     * Get an array of all url parameters.
159
     *
160
     * @param string $url The url to parse such as http://foo.com/bar/?id=boo&another=wee&another=boo
161
     * @param array $collapsedParams Parameters to collapse. ex. 'id' => ['boo'] to just 'id' => 'boo'. Exception thrown
162
     *                               if more than 1 value
163
     *
164
     * @return array such as ['id' => ['boo'], 'another' => ['wee', 'boo']]
165
     *
166
     * @throws InvalidArgumentException if $url was not a string
167
     * @throws Exception if more than one value in a $collapsedParams param
168
     */
169
    public static function getQueryParams(string $url, array $collapsedParams = []) : array
170
    {
171
        if (!is_string($url)) {
172
            throw new InvalidArgumentException('$url was not a string');
173
        }
174
175
        $queryString = parse_url($url, PHP_URL_QUERY);
176
        if (!is_string($queryString)) {
177
            return [];
178
        }
179
180
        $collapsedParams = array_flip($collapsedParams);
181
182
        $result = [];
183
        foreach (explode('&', $queryString) as $arg) {
184
            $name = $arg;
185
            $value = '';
186
            $nameAndValue = explode('=', $arg);
187
            if (isset($nameAndValue[1])) {
188
                list($name, $value) = $nameAndValue;
189
            }
190
191
            $name = rawurldecode($name);
192
            $value = rawurldecode($value);
193
            $collapsed = isset($collapsedParams[$name]);
194
195
            if (!array_key_exists($name, $result)) {
196
                if ($collapsed) {
197
                    $result[$name] = $value;
198
                    continue;
199
                }
200
201
                $result[$name] = [];
202
            }
203
204
            if ($collapsed) {
205
                throw new Exception("Parameter '{$name}' had more than one value but in \$collapsedParams");
206
            }
207
208
            $result[$name][] = $value;
209
        }
210
211
        return $result;
212
    }
213
214
    /**
215
     * Get an array of all url parameters.
216
     *
217
     * @param string $url The url to parse such as http://foo.com/bar/?single=boo&multi=wee&multi=boo
218
     * @param array $expectedArrayParams List of parameter names which are not collapsed.
219
     *
220
     * @return array such as ['single' => 'boo', 'multi' => ['wee', 'boo']] if 'multi' is given in $expectedArrayParams
221
     *
222
     * @throws InvalidArgumentException if $url was not a string
223
     * @throws Exception if a parameter is given as array but not included in the expected array argument
224
     */
225
    public static function getQueryParamsCollapsed(string $url, array $expectedArrayParams = []) : array
226
    {
227
        if (!is_string($url)) {
228
            throw new InvalidArgumentException('$url was not a string');
229
        }
230
231
        $queryString = parse_url($url, PHP_URL_QUERY);
232
        if (!is_string($queryString)) {
233
            return [];
234
        }
235
236
        $result = [];
237
        foreach (explode('&', $queryString) as $arg) {
238
            $name = $arg;
239
            $value = '';
240
            $nameAndValue = explode('=', $arg);
241
            if (isset($nameAndValue[1])) {
242
                list($name, $value) = $nameAndValue;
243
            }
244
245
            $name = rawurldecode($name);
246
            $value = rawurldecode($value);
247
248
            if (!array_key_exists($name, $result)) {
249
                $result[$name] = $value;
250
                continue;
251
            }
252
253
            if (!in_array($name, $expectedArrayParams)) {
254
                throw new Exception("Parameter '{$name}' is not expected to be an array, but array given");
255
            }
256
257
            if (!is_array($result[$name])) {
258
                $result[$name] = [$result[$name]];
259
            }
260
261
            $result[$name][] = $value;
262
        }
263
264
        return $result;
265
    }
266
}
267