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