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