Overview

Packages

  • None
  • SimplePie

Classes

  • SimplePie
  • SimplePie_Author
  • SimplePie_Autoloader
  • SimplePie_Cache
  • SimplePie_Cache_DB
  • SimplePie_Cache_File
  • SimplePie_Cache_Memcache
  • SimplePie_Cache_MySQL
  • SimplePie_Caption
  • SimplePie_Category
  • SimplePie_Content_Type_Sniffer
  • SimplePie_Copyright
  • SimplePie_Core
  • SimplePie_Credit
  • SimplePie_Decode_HTML_Entities
  • SimplePie_Enclosure
  • SimplePie_File
  • SimplePie_gzdecode
  • SimplePie_HTTP_Parser
  • SimplePie_IRI
  • SimplePie_Item
  • SimplePie_Locator
  • SimplePie_Misc
  • SimplePie_Net_IPv6
  • SimplePie_Parse_Date
  • SimplePie_Parser
  • SimplePie_Rating
  • SimplePie_Registry
  • SimplePie_Restriction
  • SimplePie_Sanitize
  • SimplePie_Source
  • SimplePie_XML_Declaration_Parser

Interfaces

  • SimplePie_Cache_Base
  • Overview
  • Package
  • Class
  • Tree
  • Deprecated
  • Todo
  1: <?php
  2: /**
  3:  * SimplePie
  4:  *
  5:  * A PHP-Based RSS and Atom Feed Framework.
  6:  * Takes the hard work out of managing a complete RSS/Atom solution.
  7:  *
  8:  * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
  9:  * All rights reserved.
 10:  *
 11:  * Redistribution and use in source and binary forms, with or without modification, are
 12:  * permitted provided that the following conditions are met:
 13:  *
 14:  *  * Redistributions of source code must retain the above copyright notice, this list of
 15:  *    conditions and the following disclaimer.
 16:  *
 17:  *  * Redistributions in binary form must reproduce the above copyright notice, this list
 18:  *    of conditions and the following disclaimer in the documentation and/or other materials
 19:  *    provided with the distribution.
 20:  *
 21:  *  * Neither the name of the SimplePie Team nor the names of its contributors may be used
 22:  *    to endorse or promote products derived from this software without specific prior
 23:  *    written permission.
 24:  *
 25:  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 26:  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 27:  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 28:  * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 29:  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 30:  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 31:  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 32:  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 33:  * POSSIBILITY OF SUCH DAMAGE.
 34:  *
 35:  * @package SimplePie
 36:  * @version 1.3-dev
 37:  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 38:  * @author Ryan Parman
 39:  * @author Geoffrey Sneddon
 40:  * @author Ryan McCue
 41:  * @link http://simplepie.org/ SimplePie
 42:  * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 43:  */
 44: 
 45: 
 46: /**
 47:  * HTTP Response Parser
 48:  *
 49:  * @package SimplePie
 50:  */
 51: class SimplePie_HTTP_Parser
 52: {
 53:     /**
 54:      * HTTP Version
 55:      *
 56:      * @var float
 57:      */
 58:     public $http_version = 0.0;
 59: 
 60:     /**
 61:      * Status code
 62:      *
 63:      * @var int
 64:      */
 65:     public $status_code = 0;
 66: 
 67:     /**
 68:      * Reason phrase
 69:      *
 70:      * @var string
 71:      */
 72:     public $reason = '';
 73: 
 74:     /**
 75:      * Key/value pairs of the headers
 76:      *
 77:      * @var array
 78:      */
 79:     public $headers = array();
 80: 
 81:     /**
 82:      * Body of the response
 83:      *
 84:      * @var string
 85:      */
 86:     public $body = '';
 87: 
 88:     /**
 89:      * Current state of the state machine
 90:      *
 91:      * @var string
 92:      */
 93:     protected $state = 'http_version';
 94: 
 95:     /**
 96:      * Input data
 97:      *
 98:      * @var string
 99:      */
100:     protected $data = '';
101: 
102:     /**
103:      * Input data length (to avoid calling strlen() everytime this is needed)
104:      *
105:      * @var int
106:      */
107:     protected $data_length = 0;
108: 
109:     /**
110:      * Current position of the pointer
111:      *
112:      * @var int
113:      */
114:     protected $position = 0;
115: 
116:     /**
117:      * Name of the hedaer currently being parsed
118:      *
119:      * @var string
120:      */
121:     protected $name = '';
122: 
123:     /**
124:      * Value of the hedaer currently being parsed
125:      *
126:      * @var string
127:      */
128:     protected $value = '';
129: 
130:     /**
131:      * Create an instance of the class with the input data
132:      *
133:      * @param string $data Input data
134:      */
135:     public function __construct($data)
136:     {
137:         $this->data = $data;
138:         $this->data_length = strlen($this->data);
139:     }
140: 
141:     /**
142:      * Parse the input data
143:      *
144:      * @return bool true on success, false on failure
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:      * Check whether there is data beyond the pointer
171:      *
172:      * @return bool true if there is further data, false if not
173:      */
174:     protected function has_data()
175:     {
176:         return (bool) ($this->position < $this->data_length);
177:     }
178: 
179:     /**
180:      * See if the next character is LWS
181:      *
182:      * @return bool true if the next character is LWS, false if not
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:      * Parse the HTTP version
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:      * Parse the status code
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:      * Parse the reason phrase
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:      * Deal with a new line, shifting data around as needed
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:             // We should only use the last Content-Type header. c.f. issue #1
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:      * Parse a header name
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:      * Parse LWS, replacing consecutive LWS characters with a single space
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:      * See what state to move to while within non-quoted header values
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:                     // Workaround for ETags: we have to include the quotes as
346:                     // part of the tag.
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:      * Parse a header value while outside quotes
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:      * See what state to move to while within quoted header values
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:      * Parse a header value while within quotes
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:      * Parse an escaped character within quotes
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:      * Parse the body
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:      * Parsed a "Transfer-Encoding: chunked" body
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:                 // Looks like it's not chunked after all
474:                 $this->state = 'emit';
475:                 return;
476:             }
477: 
478:             $length = hexdec(trim($matches[1]));
479:             if ($length === 0)
480:             {
481:                 // Ignore trailer headers
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: 
SimplePie Documentation API documentation generated by ApiGen 2.4.0