|
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
|
|
|
} |