1 <?php
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
44
45
46 47 48 49 50 51
52 class SimplePie_HTTP_Parser
53 {
54 55 56 57 58
59 public $http_version = 0.0;
60
61 62 63 64 65
66 public $status_code = 0;
67
68 69 70 71 72
73 public $reason = '';
74
75 76 77 78 79
80 public = array();
81
82 83 84 85 86
87 public $body = '';
88
89 90 91 92 93
94 protected $state = 'http_version';
95
96 97 98 99 100
101 protected $data = '';
102
103 104 105 106 107
108 protected $data_length = 0;
109
110 111 112 113 114
115 protected $position = 0;
116
117 118 119 120 121
122 protected $name = '';
123
124 125 126 127 128
129 protected $value = '';
130
131 132 133 134 135
136 public function __construct($data)
137 {
138 $this->data = $data;
139 $this->data_length = strlen($this->data);
140 }
141
142 143 144 145 146
147 public function parse()
148 {
149 while ($this->state && $this->state !== 'emit' && $this->has_data())
150 {
151 $state = $this->state;
152 $this->$state();
153 }
154 $this->data = '';
155 if ($this->state === 'emit' || $this->state === 'body')
156 {
157 return true;
158 }
159 else
160 {
161 $this->http_version = '';
162 $this->status_code = '';
163 $this->reason = '';
164 $this->headers = array();
165 $this->body = '';
166 return false;
167 }
168 }
169
170 171 172 173 174
175 protected function has_data()
176 {
177 return (bool) ($this->position < $this->data_length);
178 }
179
180 181 182 183 184
185 protected function is_linear_whitespace()
186 {
187 return (bool) ($this->data[$this->position] === "\x09"
188 || $this->data[$this->position] === "\x20"
189 || ($this->data[$this->position] === "\x0A"
190 && isset($this->data[$this->position + 1])
191 && ($this->data[$this->position + 1] === "\x09" || $this->data[$this->position + 1] === "\x20")));
192 }
193
194 195 196
197 protected function http_version()
198 {
199 if (strpos($this->data, "\x0A") !== false && strtoupper(substr($this->data, 0, 5)) === 'HTTP/')
200 {
201 $len = strspn($this->data, '0123456789.', 5);
202 $this->http_version = substr($this->data, 5, $len);
203 $this->position += 5 + $len;
204 if (substr_count($this->http_version, '.') <= 1)
205 {
206 $this->http_version = (float) $this->http_version;
207 $this->position += strspn($this->data, "\x09\x20", $this->position);
208 $this->state = 'status';
209 }
210 else
211 {
212 $this->state = false;
213 }
214 }
215 else
216 {
217 $this->state = false;
218 }
219 }
220
221 222 223
224 protected function status()
225 {
226 if ($len = strspn($this->data, '0123456789', $this->position))
227 {
228 $this->status_code = (int) substr($this->data, $this->position, $len);
229 $this->position += $len;
230 $this->state = 'reason';
231 }
232 else
233 {
234 $this->state = false;
235 }
236 }
237
238 239 240
241 protected function reason()
242 {
243 $len = strcspn($this->data, "\x0A", $this->position);
244 $this->reason = trim(substr($this->data, $this->position, $len), "\x09\x0D\x20");
245 $this->position += $len + 1;
246 $this->state = 'new_line';
247 }
248
249 250 251
252 protected function new_line()
253 {
254 $this->value = trim($this->value, "\x0D\x20");
255 if ($this->name !== '' && $this->value !== '')
256 {
257 $this->name = strtolower($this->name);
258
259 if (isset($this->headers[$this->name]) && $this->name !== 'content-type')
260 {
261 $this->headers[$this->name] .= ', ' . $this->value;
262 }
263 else
264 {
265 $this->headers[$this->name] = $this->value;
266 }
267 }
268 $this->name = '';
269 $this->value = '';
270 if (substr($this->data[$this->position], 0, 2) === "\x0D\x0A")
271 {
272 $this->position += 2;
273 $this->state = 'body';
274 }
275 elseif ($this->data[$this->position] === "\x0A")
276 {
277 $this->position++;
278 $this->state = 'body';
279 }
280 else
281 {
282 $this->state = 'name';
283 }
284 }
285
286 287 288
289 protected function name()
290 {
291 $len = strcspn($this->data, "\x0A:", $this->position);
292 if (isset($this->data[$this->position + $len]))
293 {
294 if ($this->data[$this->position + $len] === "\x0A")
295 {
296 $this->position += $len;
297 $this->state = 'new_line';
298 }
299 else
300 {
301 $this->name = substr($this->data, $this->position, $len);
302 $this->position += $len + 1;
303 $this->state = 'value';
304 }
305 }
306 else
307 {
308 $this->state = false;
309 }
310 }
311
312 313 314
315 protected function linear_whitespace()
316 {
317 do
318 {
319 if (substr($this->data, $this->position, 2) === "\x0D\x0A")
320 {
321 $this->position += 2;
322 }
323 elseif ($this->data[$this->position] === "\x0A")
324 {
325 $this->position++;
326 }
327 $this->position += strspn($this->data, "\x09\x20", $this->position);
328 } while ($this->has_data() && $this->is_linear_whitespace());
329 $this->value .= "\x20";
330 }
331
332 333 334
335 protected function value()
336 {
337 if ($this->is_linear_whitespace())
338 {
339 $this->linear_whitespace();
340 }
341 else
342 {
343 switch ($this->data[$this->position])
344 {
345 case '"':
346
347
348 if (strtolower($this->name) === 'etag')
349 {
350 $this->value .= '"';
351 $this->position++;
352 $this->state = 'value_char';
353 break;
354 }
355 $this->position++;
356 $this->state = 'quote';
357 break;
358
359 case "\x0A":
360 $this->position++;
361 $this->state = 'new_line';
362 break;
363
364 default:
365 $this->state = 'value_char';
366 break;
367 }
368 }
369 }
370
371 372 373
374 protected function value_char()
375 {
376 $len = strcspn($this->data, "\x09\x20\x0A\"", $this->position);
377 $this->value .= substr($this->data, $this->position, $len);
378 $this->position += $len;
379 $this->state = 'value';
380 }
381
382 383 384
385 protected function quote()
386 {
387 if ($this->is_linear_whitespace())
388 {
389 $this->linear_whitespace();
390 }
391 else
392 {
393 switch ($this->data[$this->position])
394 {
395 case '"':
396 $this->position++;
397 $this->state = 'value';
398 break;
399
400 case "\x0A":
401 $this->position++;
402 $this->state = 'new_line';
403 break;
404
405 case '\\':
406 $this->position++;
407 $this->state = 'quote_escaped';
408 break;
409
410 default:
411 $this->state = 'quote_char';
412 break;
413 }
414 }
415 }
416
417 418 419
420 protected function quote_char()
421 {
422 $len = strcspn($this->data, "\x09\x20\x0A\"\\", $this->position);
423 $this->value .= substr($this->data, $this->position, $len);
424 $this->position += $len;
425 $this->state = 'value';
426 }
427
428 429 430
431 protected function quote_escaped()
432 {
433 $this->value .= $this->data[$this->position];
434 $this->position++;
435 $this->state = 'quote';
436 }
437
438 439 440
441 protected function body()
442 {
443 $this->body = substr($this->data, $this->position);
444 if (!empty($this->headers['transfer-encoding']))
445 {
446 unset($this->headers['transfer-encoding']);
447 $this->state = 'chunked';
448 }
449 else
450 {
451 $this->state = 'emit';
452 }
453 }
454
455 456 457
458 protected function chunked()
459 {
460 if (!preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', trim($this->body)))
461 {
462 $this->state = 'emit';
463 return;
464 }
465
466 $decoded = '';
467 $encoded = $this->body;
468
469 while (true)
470 {
471 $is_chunked = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $encoded, $matches );
472 if (!$is_chunked)
473 {
474
475 $this->state = 'emit';
476 return;
477 }
478
479 $length = hexdec(trim($matches[1]));
480 if ($length === 0)
481 {
482
483 $this->state = 'emit';
484 $this->body = $decoded;
485 return;
486 }
487
488 $chunk_length = strlen($matches[0]);
489 $decoded .= $part = substr($encoded, $chunk_length, $length);
490 $encoded = substr($encoded, $chunk_length + $length + 2);
491
492 if (trim($encoded) === '0' || empty($encoded))
493 {
494 $this->state = 'emit';
495 $this->body = $decoded;
496 return;
497 }
498 }
499 }
500 }
501