1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @file Response.php |
5
|
|
|
* @brief This file contains the Response class. |
6
|
|
|
* @details |
7
|
|
|
* @author Filippo F. Fadda |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
|
11
|
|
|
namespace Surfer\Message; |
12
|
|
|
|
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* @brief After receiving and interpreting a request message, a server responds with an HTTP response message. This class |
16
|
|
|
* represents a server response. |
17
|
|
|
* @nosubgrouping |
18
|
|
|
*/ |
19
|
|
|
final class Response extends Message { |
20
|
|
|
|
21
|
|
|
/** @name Response Header Fields */ |
22
|
|
|
//!@{ |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @brief The Accept-Ranges response-header field allows the server to indicate its acceptance of range requests for |
26
|
|
|
* a resource. |
27
|
|
|
* @see http://www.w3.org/cols/rfc2616/rfc2616-sec14.html#sec14.5 |
28
|
|
|
*/ |
29
|
|
|
const ACCEPT_RANGES_HF = "Accept-Ranges"; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @brief The Age response-header field conveys the sender's estimate of the amount of time since the response (or its |
33
|
|
|
* revalidation) was generated at the origin server. A cached response is "fresh" if its age does not exceed its |
34
|
|
|
* freshness lifetime. |
35
|
|
|
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.6 |
36
|
|
|
*/ |
37
|
|
|
const AGE_HF = "Age"; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @brief In order to force the browser to show SaveAs dialog when clicking a hyperlink you have to include this |
41
|
|
|
* header field. |
42
|
|
|
*/ |
43
|
|
|
const CONTENT_DISPOSITION_HF = "Content-Disposition"; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @brief An identifier for a specific version of a resource, often a message digest. |
47
|
|
|
* @attention The constant should be ETag, not Etag, see the below bug. |
48
|
|
|
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19 |
49
|
|
|
* @bug https://issues.apache.org/jira/browse/COUCHDB-3105 |
50
|
|
|
*/ |
51
|
|
|
const ETAG_HF = "Etag"; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @brief Used to express a typed relationship with another resource, where the relation type is defined by RFC 5988. |
55
|
|
|
* @see http://tools.ietf.org/html/rfc5988 |
56
|
|
|
*/ |
57
|
|
|
const LINK_HF = "Link"; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @brief Used in redirection for completion of the request or identification of a new resource, or when a new resource |
61
|
|
|
* has been created. |
62
|
|
|
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30 |
63
|
|
|
*/ |
64
|
|
|
const LOCATION_HF = "Location"; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* @brief This header is supposed to set P3P policy, in the form of P3P:CP="your_compact_policy". However, P3P did not |
68
|
|
|
* take off, most browsers have never fully implemented it, a lot of websites set this header with fake policy text, |
69
|
|
|
* that was enough to fool browsers the existence of P3P policy and grant permissions for third party cookies. |
70
|
|
|
* @see http://en.wikipedia.org/wiki/P3P |
71
|
|
|
*/ |
72
|
|
|
const P3P_HF = "P3P"; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* @brief Request authentication to access the proxy. |
76
|
|
|
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.33 |
77
|
|
|
*/ |
78
|
|
|
const PROXY_AUTHENTICATE_HF = "Proxy-Authenticate"; |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* @brief Used in redirection, or when a new resource has been created. This refresh redirects after 5 seconds. This is |
82
|
|
|
* a proprietary, non-standard header extension introduced by Netscape and supported by most web browsers. |
83
|
|
|
* @see http://en.wikipedia.org/wiki/HTTP_refresh |
84
|
|
|
*/ |
85
|
|
|
const REFRESH_HF = "Refresh"; |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* @brief If an entity is temporarily unavailable, this instructs the client to try again after a specified period of time (seconds). |
89
|
|
|
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.37 |
90
|
|
|
*/ |
91
|
|
|
const RETRY_AFTER_HF = "Retry-After"; |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* @brief This response-header specifies the server name. |
95
|
|
|
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.38 |
96
|
|
|
*/ |
97
|
|
|
const SERVER_HF = "Server"; |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* @brief Sets an HTTP Cookie. |
101
|
|
|
* @see http://en.wikipedia.org/wiki/HTTP_cookie |
102
|
|
|
*/ |
103
|
|
|
const SET_COOKIE_HF = "Set-Cookie"; |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* @brief A HSTS Policy informing the HTTP client how long to cache the HTTPS only policy and whether this applies to |
107
|
|
|
* subdomains. |
108
|
|
|
* @see http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security |
109
|
|
|
*/ |
110
|
|
|
const STRICT_TRANSPORT_SECURITY_HF = "Strict-Transport-Security"; |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* @brief Tells downstream proxies how to match future request headers to decide whether the cached response can be |
114
|
|
|
* used rather than requesting a fresh one from the origin server. |
115
|
|
|
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44 |
116
|
|
|
*/ |
117
|
|
|
const VARY_HF = "Vary"; |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* @brief Indicates the authentication scheme that should be used to access the requested entity. |
121
|
|
|
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.47 |
122
|
|
|
*/ |
123
|
|
|
const WWW_AUTHENTICATE_HF = "WWW-Authenticate"; |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* @brief Clickjacking protection: "deny" - no rendering within a frame, "sameorigin" - no rendering if origin mismatch. |
127
|
|
|
* @see http://en.wikipedia.org/wiki/Clickjacking |
128
|
|
|
*/ |
129
|
|
|
const X_FRAME_OPTIONS_HF = "X-Frame-Options"; |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* @brief Cross-site scripting (XSS) filter. |
133
|
|
|
* @see http://en.wikipedia.org/wiki/Cross-site_scripting |
134
|
|
|
*/ |
135
|
|
|
const X_XSS_PROTECTION_HF = "X-XSS-Protection"; |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* @brief The only defined value, "nosniff", prevents Internet Explorer from MIME-sniffing a response away from the |
139
|
|
|
* declared content-type. |
140
|
|
|
*/ |
141
|
|
|
const X_CONTENT_TYPE_OPTIONS_HF = "X-Content-Type-Options"; |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* @brief A de facto standard for identifying the originating protocol of an HTTP request, since a reverse proxy (load |
145
|
|
|
* balancer) may communicate with a web server using HTTP even if the request to the reverse proxy is HTTPS. |
146
|
|
|
*/ |
147
|
|
|
const X_FORWARDED_PROTO_HF = "X-Forwarded-Proto"; |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* @brief Specifies the technology (e.g. ASP.NET, PHP, JBoss) supporting the web application (version details are often |
151
|
|
|
* in X-Runtime, X-Version, or X-AspNet-Version) |
152
|
|
|
*/ |
153
|
|
|
const X_POWERED_BY_HF = "X-Powered-By"; |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* @brief Recommends the preferred rendering engine (often a backward-compatibility mode) to use to display the content. |
157
|
|
|
* Also used to activate Chrome Frame in Internet Explorer. |
158
|
|
|
* @see http://en.wikipedia.org/wiki/Chrome_Frame |
159
|
|
|
*/ |
160
|
|
|
const X_UA_COMPATIBLE_HF = "X-UA-Compatible"; |
161
|
|
|
|
162
|
|
|
//!@} |
163
|
|
|
|
164
|
|
|
// Stores the header fields supported by a Response. |
165
|
|
|
protected static $supportedHeaderFields = array( // Cannot use [] syntax otherwise Doxygen generates a warning. |
166
|
|
|
self::ACCEPT_RANGES_HF => NULL, |
167
|
|
|
self::AGE_HF => NULL, |
168
|
|
|
self::CONTENT_DISPOSITION_HF => NULL, |
169
|
|
|
self::ETAG_HF => NULL, |
170
|
|
|
self::LINK_HF => NULL, |
171
|
|
|
self::LOCATION_HF => NULL, |
172
|
|
|
self::P3P_HF => NULL, |
173
|
|
|
self::PROXY_AUTHENTICATE_HF => NULL, |
174
|
|
|
self::REFRESH_HF => NULL, |
175
|
|
|
self::RETRY_AFTER_HF => NULL, |
176
|
|
|
self::SERVER_HF => NULL, |
177
|
|
|
self::SET_COOKIE_HF => NULL, |
178
|
|
|
self::STRICT_TRANSPORT_SECURITY_HF => NULL, |
179
|
|
|
self::VARY_HF => NULL, |
180
|
|
|
self::WWW_AUTHENTICATE_HF => NULL, |
181
|
|
|
self::X_FRAME_OPTIONS_HF => NULL, |
182
|
|
|
self::X_XSS_PROTECTION_HF => NULL, |
183
|
|
|
self::X_CONTENT_TYPE_OPTIONS_HF => NULL, |
184
|
|
|
self::X_FORWARDED_PROTO_HF => NULL, |
185
|
|
|
self::X_POWERED_BY_HF => NULL, |
186
|
|
|
self::X_UA_COMPATIBLE_HF => NULL |
187
|
|
|
); |
188
|
|
|
|
189
|
|
|
/** @name Informational Status Codes */ |
190
|
|
|
//!@{ |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* @brief Indicates that an initial part of the request was received and the client should continue. |
194
|
|
|
* @details After sending this, the server must respond after receiving the body request. |
195
|
|
|
*/ |
196
|
|
|
const CONTINUE_SC = 100; |
197
|
|
|
|
198
|
|
|
//!@} |
199
|
|
|
|
200
|
|
|
/** @name Success Status Codes |
201
|
|
|
//!@{ |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* @brief Request is OK, entity body contains requested resource. |
205
|
|
|
*/ |
206
|
|
|
const OK_SC = 200; |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* @brief For requests that create server objects (e.g., PUT). |
210
|
|
|
* @details The entity body of the response should contain the various URLs for referencing the created resource, |
211
|
|
|
* with the Location header containing the most specific reference. |
212
|
|
|
*/ |
213
|
|
|
const CREATED_SC = 201; |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* @brief The request was accepted, but the server has not yet performed any action with it. |
217
|
|
|
* @details There are no guarantees that the server will complete the request; this just means that the request |
218
|
|
|
* looked valid when accepted. |
219
|
|
|
*/ |
220
|
|
|
const ACCEPTED_SC = 202; |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* @brief The response message contains headers and a status line, but no entity body. |
224
|
|
|
* @details Primarily used to update browsers without having them move to a new document. |
225
|
|
|
*/ |
226
|
|
|
const NO_CONTENT_SC = 204; |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* @brief A partial or range request was successful. |
230
|
|
|
* @details A 206 response must include a Content-Range, Date, and either ETag or Content-Location header. |
231
|
|
|
*/ |
232
|
|
|
const PARTIAL_CONTENT_SC = 206; |
233
|
|
|
|
234
|
|
|
//!@} |
235
|
|
|
|
236
|
|
|
/** @name Redirection Status Codes */ |
237
|
|
|
//!@{ |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* @brief Used when the requested URL has been moved. |
241
|
|
|
* @details The response should contain in the Location header the URL where the resource now resides. |
242
|
|
|
*/ |
243
|
|
|
const MOVED_PERMANENTLY_SC = 301; |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* @brief Like the 301 status code. |
247
|
|
|
* @details However, the client should use the URL given in the Location header to locate the resource temporarily. |
248
|
|
|
* Future requests should use the old URL. |
249
|
|
|
*/ |
250
|
|
|
const FOUND_SC = 302; |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* @brief Clients can make their requests conditional by the request headers they include. |
254
|
|
|
* @details If a client makes a conditional request, such as a GET if the resource has not been changed recently, |
255
|
|
|
* this code is used to indicate that the resource has not changed. Responses with this status code should not |
256
|
|
|
* contain an entity body. |
257
|
|
|
*/ |
258
|
|
|
const NOT_MODIFIED_SC = 304; |
259
|
|
|
|
260
|
|
|
//!@} |
261
|
|
|
|
262
|
|
|
/** @name Client Error Status Codes */ |
263
|
|
|
//!@{ |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* @brief Used to tell the client that it has sent a malformed request. |
267
|
|
|
*/ |
268
|
|
|
const BAD_REQUEST_SC = 400; |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* @brief Returned along with appropriate headers that ask the client to authenticate itself before it can gain access |
272
|
|
|
* to the resource. |
273
|
|
|
*/ |
274
|
|
|
const UNAUTHORIZED_SC = 401; |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* @brief Used to indicate that the request was refused by the server. |
278
|
|
|
* @details If the server wants to indicate why the request was denied, it can include an entity body describing the |
279
|
|
|
* reason. However, this code usually is used when the server does not want to reveal the reason for the refusal. |
280
|
|
|
*/ |
281
|
|
|
const FORBIDDEN_SC = 403; |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* @brief Used to indicate that the server cannot find the requested URL. |
285
|
|
|
* @details Often, an entity is included for the client application to display to the user. |
286
|
|
|
*/ |
287
|
|
|
const NOT_FOUND_SC = 404; |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* @brief Used when a request is made with a method that is not supported for the requested URL. |
291
|
|
|
* @details The Allow header should be included in the response to tell the client what methods are allowed on the |
292
|
|
|
* requested resource. |
293
|
|
|
*/ |
294
|
|
|
const METHOD_NOT_ALLOWED_SC = 405; |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* @brief Clients can specify parameters about what types of entities they are willing to accept. |
298
|
|
|
* @details This code is used when the server has no resource matching the URL that is acceptable for the client. |
299
|
|
|
* Often, servers include headers that allow the client to figure out why the request could not be satisfied. |
300
|
|
|
*/ |
301
|
|
|
const NOT_ACCEPTABLE_SC = 406; |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* @brief Used to indicate some conflict that the request may be causing on a resource. |
305
|
|
|
* @details Servers might send this code when they fear that a request could cause a conflict. The response should |
306
|
|
|
* contain a body describing the conflict. |
307
|
|
|
*/ |
308
|
|
|
const CONFLICT_SC = 409; |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* @brief Used if a client makes a conditional request and one of the conditions fails. |
312
|
|
|
* @details Conditional requests occur when a client includes an unexpected header. |
313
|
|
|
*/ |
314
|
|
|
const PRECONDITION_FAILED_SC = 412; |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* @brief Used when a client sends an entity body that is larger than the server can or wants to process. |
318
|
|
|
*/ |
319
|
|
|
const REQUEST_ENTITY_TOO_LARGE_SC = 413; |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* @brief Used when a client sends an entity of a content type that the server does not understand or support. |
323
|
|
|
*/ |
324
|
|
|
const UNSUPPORTED_MEDIA_TYPE_SC = 415; |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* @brief Used when the request message requested a range of a given resource and that range either was invalid or |
328
|
|
|
* could not be met. |
329
|
|
|
*/ |
330
|
|
|
const REQUESTED_RANGE_NOT_SATISFIABLE_SC = 416; |
331
|
|
|
|
332
|
|
|
/** |
333
|
|
|
* @brief Used when the request contained an expectation in the request header that the server could not satisfy. |
334
|
|
|
*/ |
335
|
|
|
const EXPECTATION_FAILED_SC = 417; |
336
|
|
|
|
337
|
|
|
//!@} |
338
|
|
|
|
339
|
|
|
/** @name Server Error Status Codes */ |
340
|
|
|
//!@{ |
341
|
|
|
|
342
|
|
|
/** |
343
|
|
|
* @brief Used when the server encounters an error that prevents it from servicing the request. |
344
|
|
|
*/ |
345
|
|
|
const INTERNAL_SERVER_ERROR_SC = 500; |
346
|
|
|
|
347
|
|
|
//!@} |
348
|
|
|
|
349
|
|
|
// Array of HTTP Status Codes |
350
|
|
|
private static $supportedStatusCodes = array( // Cannot use [] syntax otherwise Doxygen generates a warning. |
351
|
|
|
// Informational Status Codes |
352
|
|
|
self::CONTINUE_SC => "Continue", |
353
|
|
|
// Success Status Codes |
354
|
|
|
self::OK_SC => "OK", |
355
|
|
|
self::CREATED_SC => "Created", |
356
|
|
|
self::ACCEPTED_SC => "Accepted", |
357
|
|
|
self::NO_CONTENT_SC => "No Content", |
358
|
|
|
self::PARTIAL_CONTENT_SC => "Partial Content", |
359
|
|
|
// Redirection Status Codes |
360
|
|
|
self::MOVED_PERMANENTLY_SC => "Moved Permanently", |
361
|
|
|
self::FOUND_SC => "Found", |
362
|
|
|
self::NOT_MODIFIED_SC => "Not Modified", |
363
|
|
|
// Client Error Status Codes |
364
|
|
|
self::BAD_REQUEST_SC => "Bad Request", |
365
|
|
|
self::UNAUTHORIZED_SC => "Unauthorized", |
366
|
|
|
self::FORBIDDEN_SC => "Forbidden", |
367
|
|
|
self::NOT_FOUND_SC => "Not Found", |
368
|
|
|
self::METHOD_NOT_ALLOWED_SC => "Method Not Allowed", |
369
|
|
|
self::NOT_ACCEPTABLE_SC => "Not Acceptable", |
370
|
|
|
self::CONFLICT_SC => "Conflict", |
371
|
|
|
self::PRECONDITION_FAILED_SC => "Precondition Failed", |
372
|
|
|
self::REQUEST_ENTITY_TOO_LARGE_SC => "Request Entity Too Large", |
373
|
|
|
self::UNSUPPORTED_MEDIA_TYPE_SC => "Unsupported Media Type", |
374
|
|
|
self::REQUESTED_RANGE_NOT_SATISFIABLE_SC => "Requested Range Not Satisfiable", |
375
|
|
|
self::EXPECTATION_FAILED_SC => "Expectation Failed", |
376
|
|
|
// Server Error Status Codes |
377
|
|
|
self::INTERNAL_SERVER_ERROR_SC => "Internal Server Error", |
378
|
|
|
); |
379
|
|
|
|
380
|
|
|
private $statusCode; |
381
|
|
|
|
382
|
|
|
// Used to know if the constructor has been already called. |
383
|
|
|
private static $initialized = FALSE; |
384
|
|
|
|
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* @brief Creates a new Response object. |
388
|
|
|
* @param string $message The complete Response string. |
389
|
|
|
*/ |
390
|
|
|
public function __construct($message) { |
391
|
|
|
parent::__construct(); |
392
|
|
|
|
393
|
|
|
// We can avoid to call the following code every time a Response instance is created, testing a static property. |
394
|
|
|
// Because the static nature of self::$initialized, this code will be executed only one time, even multiple Response |
395
|
|
|
// instances are created. |
396
|
|
|
if (!self::$initialized) { |
397
|
|
|
self::$initialized = TRUE; |
398
|
|
|
self::$supportedHeaderFields += parent::$supportedHeaderFields; |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
$this->parseStatusCodeAndHeader($message); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
|
405
|
|
|
/** |
406
|
|
|
* @brief Returns a comprehensible representation of the HTTP Response to be used for debugging purpose. |
407
|
|
|
* @retval string |
408
|
|
|
*/ |
409
|
|
|
public function __toString() { |
410
|
|
|
$response = [ |
411
|
|
|
$this->getStatusCode()." ".$this->getSupportedStatusCodes()[$this->getStatusCode()], |
412
|
|
|
$this->getHeaderAsString(), |
413
|
|
|
$this->getBody() |
414
|
|
|
]; |
415
|
|
|
|
416
|
|
|
return implode(PHP_EOL.PHP_EOL, $response); |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* @brief Parses the response's status code. |
422
|
|
|
* @param string $rawMessage The raw message. |
423
|
|
|
*/ |
424
|
|
|
protected function parseStatusCode($rawMessage) { |
425
|
|
|
$matches = []; |
426
|
|
|
if (preg_match('%HTTP/1\.[0-1] (\d\d\d) %', $rawMessage, $matches)) |
427
|
|
|
$this->statusCode = $matches[1]; |
428
|
|
|
else |
429
|
|
|
throw new \UnexpectedValueException("HTTP Status Code undefined."); |
430
|
|
|
|
431
|
|
|
if (!array_key_exists($this->statusCode, self::$supportedStatusCodes)) |
432
|
|
|
throw new \UnexpectedValueException("HTTP Status Code unknown."); |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
|
436
|
|
|
/** |
437
|
|
|
* @brief Parses the response's header fields. |
438
|
|
|
* @param string $rawHeader The raw header. |
439
|
|
|
*/ |
440
|
|
|
protected function parseHeaderFields($rawHeader) { |
441
|
|
|
$fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $rawHeader)); |
442
|
|
|
|
443
|
|
|
foreach ($fields as $field) { |
444
|
|
|
if (preg_match('/([^:]+): (.+)/m', $field, $matches)) { |
445
|
|
|
// With the advent of PHP 5.5, the /e modifier is deprecated, so we use preg_replace_callback(). |
446
|
|
|
$matches[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', |
447
|
|
|
function($matches) { |
448
|
|
|
return strtoupper($matches[0]); |
449
|
|
|
}, |
450
|
|
|
strtolower(trim($matches[1]))); |
451
|
|
|
|
452
|
|
|
if (isset($this->header[$matches[1]])) |
453
|
|
|
$this->header[$matches[1]] = array($this->header[$matches[1]], $matches[2]); |
454
|
|
|
else |
455
|
|
|
$this->header[$matches[1]] = trim($matches[2]); |
456
|
|
|
} |
457
|
|
|
} |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
|
461
|
|
|
/** |
462
|
|
|
* @brief Parses the response's status code and header. |
463
|
|
|
* @param string $rawMessage The raw message. |
464
|
|
|
*/ |
465
|
|
|
protected function parseStatusCodeAndHeader($rawMessage) { |
466
|
|
|
if (!is_string($rawMessage)) |
|
|
|
|
467
|
|
|
throw new \InvalidArgumentException("\$rawMessage must be a string."); |
468
|
|
|
|
469
|
|
|
if (empty($rawMessage)) |
470
|
|
|
throw new \UnexpectedValueException("\$rawMessage is null."); |
471
|
|
|
|
472
|
|
|
$this->parseStatusCode($rawMessage); |
473
|
|
|
|
474
|
|
|
// In case server sends a "100 Continue" response, we must parse the message twice. This happens when a client uses |
475
|
|
|
// the "Expect: 100-continue" header field. |
476
|
|
|
// @see http://www.jmarshall.com/easy/http/#http1.1s5 |
477
|
|
|
if ($this->statusCode == self::CONTINUE_SC) { |
478
|
|
|
$rawMessage = preg_split('/\r\n\r\n/', $rawMessage, 2)[1]; |
479
|
|
|
$this->parseStatusCodeAndHeader($rawMessage); |
480
|
|
|
} |
481
|
|
|
|
482
|
|
|
$rawMessage = preg_split('/\r\n\r\n/', $rawMessage, 2); |
483
|
|
|
|
484
|
|
|
if (empty($rawMessage)) |
485
|
|
|
throw new \RuntimeException("The server didn't return a valid Response for the Request."); |
486
|
|
|
|
487
|
|
|
// $rawMessage[0] contains header fields. |
488
|
|
|
$this->parseHeaderFields($rawMessage[0]); |
489
|
|
|
|
490
|
|
|
// $rawMessage[1] contains the entity-body. |
491
|
|
|
$this->body = $rawMessage[1]; |
492
|
|
|
} |
493
|
|
|
|
494
|
|
|
|
495
|
|
|
/** |
496
|
|
|
* @brief Returns the HTTP Status Code for the current response. |
497
|
|
|
* @retval string |
498
|
|
|
*/ |
499
|
|
|
public function getStatusCode() { |
500
|
|
|
return $this->statusCode; |
501
|
|
|
} |
502
|
|
|
|
503
|
|
|
|
504
|
|
|
/** |
505
|
|
|
* @brief Sets the Response status code. |
506
|
|
|
* @param int $value The status code. |
507
|
|
|
*/ |
508
|
|
|
public function setStatusCode($value) { |
509
|
|
|
if (array_key_exists($value, self::$supportedStatusCodes)) { |
510
|
|
|
$this->statusCode = $value; |
511
|
|
|
} |
512
|
|
|
else |
513
|
|
|
throw new \UnexpectedValueException("Status Code $value is not supported."); |
514
|
|
|
} |
515
|
|
|
|
516
|
|
|
|
517
|
|
|
/** |
518
|
|
|
* @brief Returns a list of all supported status codes. |
519
|
|
|
* @retval array An associative array |
520
|
|
|
*/ |
521
|
|
|
public function getSupportedStatusCodes() { |
522
|
|
|
return self::$supportedStatusCodes; |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
|
526
|
|
|
/** |
527
|
|
|
* @brief Adds a non standard HTTP Status Code. |
528
|
|
|
* @param string $code The Status Code. |
529
|
|
|
* @param string $description A description for the Status Code. |
530
|
|
|
*/ |
531
|
|
|
public static function addCustomStatusCode($code, $description) { |
532
|
|
|
if (in_array($code, self::$supportedStatusCodes)) |
533
|
|
|
throw new \UnexpectedValueException("Status Code $code is supported and already exists."); |
534
|
|
|
elseif (is_int($code) and $code > 0) |
|
|
|
|
535
|
|
|
self::$supportedStatusCodes[$code] = $description; |
536
|
|
|
else |
537
|
|
|
throw new \InvalidArgumentException("\$code must be a positive integer."); |
538
|
|
|
} |
539
|
|
|
|
540
|
|
|
} |