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 $headers = 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: