1: <?php
   2:    3:    4:    5:    6:    7:    8:    9:   10:   11:   12:   13:   14:   15:   16:   17: 
  18: 
  19: App::uses('CakeSocket', 'Network');
  20: App::uses('Router', 'Routing');
  21: App::uses('Hash', 'Utility');
  22: 
  23:   24:   25:   26:   27:   28:   29:   30: 
  31: class HttpSocket extends CakeSocket {
  32: 
  33:   34:   35:   36:   37:   38:   39: 
  40:     public $quirksMode = false;
  41: 
  42:   43:   44:   45:   46: 
  47:     public $request = array(
  48:         'method' => 'GET',
  49:         'uri' => array(
  50:             'scheme' => 'http',
  51:             'host' => null,
  52:             'port' => 80,
  53:             'user' => null,
  54:             'pass' => null,
  55:             'path' => null,
  56:             'query' => null,
  57:             'fragment' => null
  58:         ),
  59:         'version' => '1.1',
  60:         'body' => '',
  61:         'line' => null,
  62:         'header' => array(
  63:             'Connection' => 'close',
  64:             'User-Agent' => 'CakePHP'
  65:         ),
  66:         'raw' => null,
  67:         'redirect' => false,
  68:         'cookies' => array(),
  69:     );
  70: 
  71:   72:   73:   74:   75: 
  76:     public $response = null;
  77: 
  78:   79:   80:   81:   82: 
  83:     public $responseClass = 'HttpSocketResponse';
  84: 
  85:   86:   87:   88:   89: 
  90:     public $config = array(
  91:         'persistent' => false,
  92:         'host' => 'localhost',
  93:         'protocol' => 'tcp',
  94:         'port' => 80,
  95:         'timeout' => 30,
  96:         'ssl_verify_peer' => true,
  97:         'ssl_allow_self_signed' => false,
  98:         'ssl_verify_depth' => 5,
  99:         'ssl_verify_host' => true,
 100:         'request' => array(
 101:             'uri' => array(
 102:                 'scheme' => array('http', 'https'),
 103:                 'host' => 'localhost',
 104:                 'port' => array(80, 443)
 105:             ),
 106:             'redirect' => false,
 107:             'cookies' => array(),
 108:         )
 109:     );
 110: 
 111:  112:  113:  114:  115: 
 116:     protected $_auth = array();
 117: 
 118:  119:  120:  121:  122: 
 123:     protected $_proxy = array();
 124: 
 125:  126:  127:  128:  129: 
 130:     protected $_contentResource = null;
 131: 
 132:  133:  134:  135:  136:  137:  138:  139:  140:  141:  142:  143:  144:  145:  146:  147:  148:  149:  150:  151:  152: 
 153:     public function __construct($config = array()) {
 154:         if (is_string($config)) {
 155:             $this->_configUri($config);
 156:         } elseif (is_array($config)) {
 157:             if (isset($config['request']['uri']) && is_string($config['request']['uri'])) {
 158:                 $this->_configUri($config['request']['uri']);
 159:                 unset($config['request']['uri']);
 160:             }
 161:             $this->config = Hash::merge($this->config, $config);
 162:         }
 163:         parent::__construct($this->config);
 164:     }
 165: 
 166:  167:  168:  169:  170:  171:  172:  173:  174:  175:  176:  177:  178:  179:  180:  181:  182:  183:  184:  185:  186:  187:  188:  189:  190:  191:  192:  193:  194:  195:  196: 
 197:     public function configAuth($method, $user = null, $pass = null) {
 198:         if (empty($method)) {
 199:             $this->_auth = array();
 200:             return;
 201:         }
 202:         if (is_array($user)) {
 203:             $this->_auth = array($method => $user);
 204:             return;
 205:         }
 206:         $this->_auth = array($method => compact('user', 'pass'));
 207:     }
 208: 
 209:  210:  211:  212:  213:  214:  215:  216:  217:  218: 
 219:     public function configProxy($host, $port = 3128, $method = null, $user = null, $pass = null) {
 220:         if (empty($host)) {
 221:             $this->_proxy = array();
 222:             return;
 223:         }
 224:         if (is_array($host)) {
 225:             $this->_proxy = $host + array('host' => null);
 226:             return;
 227:         }
 228:         $this->_proxy = compact('host', 'port', 'method', 'user', 'pass');
 229:     }
 230: 
 231:  232:  233:  234:  235:  236:  237: 
 238:     public function setContentResource($resource) {
 239:         if ($resource === false) {
 240:             $this->_contentResource = null;
 241:             return;
 242:         }
 243:         if (!is_resource($resource)) {
 244:             throw new SocketException(__d('cake_dev', 'Invalid resource.'));
 245:         }
 246:         $this->_contentResource = $resource;
 247:     }
 248: 
 249:  250:  251:  252:  253:  254:  255:  256: 
 257:     public function request($request = array()) {
 258:         $this->reset(false);
 259: 
 260:         if (is_string($request)) {
 261:             $request = array('uri' => $request);
 262:         } elseif (!is_array($request)) {
 263:             return false;
 264:         }
 265: 
 266:         if (!isset($request['uri'])) {
 267:             $request['uri'] = null;
 268:         }
 269:         $uri = $this->_parseUri($request['uri']);
 270:         if (!isset($uri['host'])) {
 271:             $host = $this->config['host'];
 272:         }
 273:         if (isset($request['host'])) {
 274:             $host = $request['host'];
 275:             unset($request['host']);
 276:         }
 277:         $request['uri'] = $this->url($request['uri']);
 278:         $request['uri'] = $this->_parseUri($request['uri'], true);
 279:         $this->request = Hash::merge($this->request, array_diff_key($this->config['request'], array('cookies' => true)), $request);
 280: 
 281:         $this->_configUri($this->request['uri']);
 282: 
 283:         $Host = $this->request['uri']['host'];
 284:         if (!empty($this->config['request']['cookies'][$Host])) {
 285:             if (!isset($this->request['cookies'])) {
 286:                 $this->request['cookies'] = array();
 287:             }
 288:             if (!isset($request['cookies'])) {
 289:                 $request['cookies'] = array();
 290:             }
 291:             $this->request['cookies'] = array_merge($this->request['cookies'], $this->config['request']['cookies'][$Host], $request['cookies']);
 292:         }
 293: 
 294:         if (isset($host)) {
 295:             $this->config['host'] = $host;
 296:         }
 297: 
 298:         $this->_setProxy();
 299:         $this->request['proxy'] = $this->_proxy;
 300: 
 301:         $cookies = null;
 302: 
 303:         if (is_array($this->request['header'])) {
 304:             if (!empty($this->request['cookies'])) {
 305:                 $cookies = $this->buildCookies($this->request['cookies']);
 306:             }
 307:             $scheme = '';
 308:             $port = 0;
 309:             if (isset($this->request['uri']['scheme'])) {
 310:                 $scheme = $this->request['uri']['scheme'];
 311:             }
 312:             if (isset($this->request['uri']['port'])) {
 313:                 $port = $this->request['uri']['port'];
 314:             }
 315:             if (($scheme === 'http' && $port != 80) ||
 316:                 ($scheme === 'https' && $port != 443) ||
 317:                 ($port != 80 && $port != 443)
 318:             ) {
 319:                 $Host .= ':' . $port;
 320:             }
 321:             $this->request['header'] = array_merge(compact('Host'), $this->request['header']);
 322:         }
 323: 
 324:         if (isset($this->request['uri']['user'], $this->request['uri']['pass'])) {
 325:             $this->configAuth('Basic', $this->request['uri']['user'], $this->request['uri']['pass']);
 326:         } elseif (isset($this->request['auth'], $this->request['auth']['method'], $this->request['auth']['user'], $this->request['auth']['pass'])) {
 327:             $this->configAuth($this->request['auth']['method'], $this->request['auth']['user'], $this->request['auth']['pass']);
 328:         }
 329:         $authHeader = Hash::get($this->request, 'header.Authorization');
 330:         if (empty($authHeader)) {
 331:             $this->_setAuth();
 332:             $this->request['auth'] = $this->_auth;
 333:         }
 334: 
 335:         if (is_array($this->request['body'])) {
 336:             $this->request['body'] = http_build_query($this->request['body'], '', '&');
 337:         }
 338: 
 339:         if (!empty($this->request['body']) && !isset($this->request['header']['Content-Type'])) {
 340:             $this->request['header']['Content-Type'] = 'application/x-www-form-urlencoded';
 341:         }
 342: 
 343:         if (!empty($this->request['body']) && !isset($this->request['header']['Content-Length'])) {
 344:             $this->request['header']['Content-Length'] = strlen($this->request['body']);
 345:         }
 346:         if (isset($this->request['uri']['scheme']) && $this->request['uri']['scheme'] === 'https' && in_array($this->config['protocol'], array(false, 'tcp'))) {
 347:             $this->config['protocol'] = 'ssl';
 348:         }
 349: 
 350:         $connectionType = null;
 351:         if (isset($this->request['header']['Connection'])) {
 352:             $connectionType = $this->request['header']['Connection'];
 353:         }
 354:         $this->request['header'] = $this->_buildHeader($this->request['header']) . $cookies;
 355: 
 356:         if (empty($this->request['line'])) {
 357:             $this->request['line'] = $this->_buildRequestLine($this->request);
 358:         }
 359: 
 360:         if ($this->quirksMode === false && $this->request['line'] === false) {
 361:             return false;
 362:         }
 363: 
 364:         $this->request['raw'] = '';
 365:         if ($this->request['line'] !== false) {
 366:             $this->request['raw'] = $this->request['line'];
 367:         }
 368: 
 369:         if ($this->request['header'] !== false) {
 370:             $this->request['raw'] .= $this->request['header'];
 371:         }
 372: 
 373:         $this->request['raw'] .= "\r\n";
 374:         $this->request['raw'] .= $this->request['body'];
 375: 
 376:         
 377:         $this->write($this->request['raw']);
 378: 
 379:         $response = null;
 380:         $inHeader = true;
 381:         while (($data = $this->read()) !== false) {
 382:             if ($this->_contentResource) {
 383:                 if ($inHeader) {
 384:                     $response .= $data;
 385:                     $pos = strpos($response, "\r\n\r\n");
 386:                     if ($pos !== false) {
 387:                         $pos += 4;
 388:                         $data = substr($response, $pos);
 389:                         fwrite($this->_contentResource, $data);
 390: 
 391:                         $response = substr($response, 0, $pos);
 392:                         $inHeader = false;
 393:                     }
 394:                 } else {
 395:                     fwrite($this->_contentResource, $data);
 396:                     fflush($this->_contentResource);
 397:                 }
 398:             } else {
 399:                 $response .= $data;
 400:             }
 401:         }
 402: 
 403:         if ($connectionType === 'close') {
 404:             $this->disconnect();
 405:         }
 406: 
 407:         list($plugin, $responseClass) = pluginSplit($this->responseClass, true);
 408:         App::uses($responseClass, $plugin . 'Network/Http');
 409:         if (!class_exists($responseClass)) {
 410:             throw new SocketException(__d('cake_dev', 'Class %s not found.', $this->responseClass));
 411:         }
 412:         $this->response = new $responseClass($response);
 413: 
 414:         if (!empty($this->response->cookies)) {
 415:             if (!isset($this->config['request']['cookies'][$Host])) {
 416:                 $this->config['request']['cookies'][$Host] = array();
 417:             }
 418:             $this->config['request']['cookies'][$Host] = array_merge($this->config['request']['cookies'][$Host], $this->response->cookies);
 419:         }
 420: 
 421:         if ($this->request['redirect'] && $this->response->isRedirect()) {
 422:             $location = trim($this->response->getHeader('Location'), '=');
 423:             $request['uri'] = str_replace('%2F', '/', $location);
 424:             $request['redirect'] = is_int($this->request['redirect']) ? $this->request['redirect'] - 1 : $this->request['redirect'];
 425:             $this->response = $this->request($request);
 426:         }
 427: 
 428:         return $this->response;
 429:     }
 430: 
 431:  432:  433:  434:  435:  436:  437:  438:  439:  440:  441:  442:  443:  444:  445:  446:  447:  448:  449:  450:  451:  452:  453: 
 454:     public function get($uri = null, $query = array(), $request = array()) {
 455:         if (!empty($query)) {
 456:             $uri = $this->_parseUri($uri, $this->config['request']['uri']);
 457:             if (isset($uri['query'])) {
 458:                 $uri['query'] = array_merge($uri['query'], $query);
 459:             } else {
 460:                 $uri['query'] = $query;
 461:             }
 462:             $uri = $this->_buildUri($uri);
 463:         }
 464: 
 465:         $request = Hash::merge(array('method' => 'GET', 'uri' => $uri), $request);
 466:         return $this->request($request);
 467:     }
 468: 
 469:  470:  471:  472:  473:  474:  475:  476:  477:  478:  479: 
 480:     public function head($uri = null, $query = array(), $request = array()) {
 481:         if (!empty($query)) {
 482:             $uri = $this->_parseUri($uri, $this->config['request']['uri']);
 483:             if (isset($uri['query'])) {
 484:                 $uri['query'] = array_merge($uri['query'], $query);
 485:             } else {
 486:                 $uri['query'] = $query;
 487:             }
 488:             $uri = $this->_buildUri($uri);
 489:         }
 490: 
 491:         $request = Hash::merge(array('method' => 'HEAD', 'uri' => $uri), $request);
 492:         return $this->request($request);
 493:     }
 494: 
 495:  496:  497:  498:  499:  500:  501:  502:  503:  504:  505:  506:  507:  508:  509:  510:  511: 
 512:     public function post($uri = null, $data = array(), $request = array()) {
 513:         $request = Hash::merge(array('method' => 'POST', 'uri' => $uri, 'body' => $data), $request);
 514:         return $this->request($request);
 515:     }
 516: 
 517:  518:  519:  520:  521:  522:  523:  524: 
 525:     public function put($uri = null, $data = array(), $request = array()) {
 526:         $request = Hash::merge(array('method' => 'PUT', 'uri' => $uri, 'body' => $data), $request);
 527:         return $this->request($request);
 528:     }
 529: 
 530:  531:  532:  533:  534:  535:  536:  537: 
 538:     public function patch($uri = null, $data = array(), $request = array()) {
 539:         $request = Hash::merge(array('method' => 'PATCH', 'uri' => $uri, 'body' => $data), $request);
 540:         return $this->request($request);
 541:     }
 542: 
 543:  544:  545:  546:  547:  548:  549:  550: 
 551:     public function delete($uri = null, $data = array(), $request = array()) {
 552:         $request = Hash::merge(array('method' => 'DELETE', 'uri' => $uri, 'body' => $data), $request);
 553:         return $this->request($request);
 554:     }
 555: 
 556:  557:  558:  559:  560:  561:  562:  563:  564:  565:  566:  567:  568:  569:  570:  571:  572:  573:  574:  575:  576:  577:  578:  579:  580:  581:  582: 
 583:     public function url($url = null, $uriTemplate = null) {
 584:         if ($url === null) {
 585:             $url = '/';
 586:         }
 587:         if (is_string($url)) {
 588:             $scheme = $this->config['request']['uri']['scheme'];
 589:             if (is_array($scheme)) {
 590:                 $scheme = $scheme[0];
 591:             }
 592:             $port = $this->config['request']['uri']['port'];
 593:             if (is_array($port)) {
 594:                 $port = $port[0];
 595:             }
 596:             if ($url{0} === '/') {
 597:                 $url = $this->config['request']['uri']['host'] . ':' . $port . $url;
 598:             }
 599:             if (!preg_match('/^.+:\/\/|\*|^\//', $url)) {
 600:                 $url = $scheme . '://' . $url;
 601:             }
 602:         } elseif (!is_array($url) && !empty($url)) {
 603:             return false;
 604:         }
 605: 
 606:         $base = array_merge($this->config['request']['uri'], array('scheme' => array('http', 'https'), 'port' => array(80, 443)));
 607:         $url = $this->_parseUri($url, $base);
 608: 
 609:         if (empty($url)) {
 610:             $url = $this->config['request']['uri'];
 611:         }
 612: 
 613:         if (!empty($uriTemplate)) {
 614:             return $this->_buildUri($url, $uriTemplate);
 615:         }
 616:         return $this->_buildUri($url);
 617:     }
 618: 
 619:  620:  621:  622:  623:  624: 
 625:     protected function _setAuth() {
 626:         if (empty($this->_auth)) {
 627:             return;
 628:         }
 629:         $method = key($this->_auth);
 630:         list($plugin, $authClass) = pluginSplit($method, true);
 631:         $authClass = Inflector::camelize($authClass) . 'Authentication';
 632:         App::uses($authClass, $plugin . 'Network/Http');
 633: 
 634:         if (!class_exists($authClass)) {
 635:             throw new SocketException(__d('cake_dev', 'Unknown authentication method.'));
 636:         }
 637:         if (!method_exists($authClass, 'authentication')) {
 638:             throw new SocketException(__d('cake_dev', 'The %s does not support authentication.', $authClass));
 639:         }
 640:         call_user_func_array("$authClass::authentication", array($this, &$this->_auth[$method]));
 641:     }
 642: 
 643:  644:  645:  646:  647:  648: 
 649:     protected function _setProxy() {
 650:         if (empty($this->_proxy) || !isset($this->_proxy['host'], $this->_proxy['port'])) {
 651:             return;
 652:         }
 653:         $this->config['host'] = $this->_proxy['host'];
 654:         $this->config['port'] = $this->_proxy['port'];
 655:         $this->config['proxy'] = true;
 656: 
 657:         if (empty($this->_proxy['method']) || !isset($this->_proxy['user'], $this->_proxy['pass'])) {
 658:             return;
 659:         }
 660:         list($plugin, $authClass) = pluginSplit($this->_proxy['method'], true);
 661:         $authClass = Inflector::camelize($authClass) . 'Authentication';
 662:         App::uses($authClass, $plugin . 'Network/Http');
 663: 
 664:         if (!class_exists($authClass)) {
 665:             throw new SocketException(__d('cake_dev', 'Unknown authentication method for proxy.'));
 666:         }
 667:         if (!method_exists($authClass, 'proxyAuthentication')) {
 668:             throw new SocketException(__d('cake_dev', 'The %s does not support proxy authentication.', $authClass));
 669:         }
 670:         call_user_func_array("$authClass::proxyAuthentication", array($this, &$this->_proxy));
 671: 
 672:         if (!empty($this->request['header']['Proxy-Authorization'])) {
 673:             $this->config['proxyauth'] = $this->request['header']['Proxy-Authorization'];
 674:             if ($this->request['uri']['scheme'] === 'https') {
 675:                 $this->request['header'] = Hash::remove($this->request['header'], 'Proxy-Authorization');
 676:             }
 677:         }
 678:     }
 679: 
 680:  681:  682:  683:  684:  685: 
 686:     protected function _configUri($uri = null) {
 687:         if (empty($uri)) {
 688:             return false;
 689:         }
 690: 
 691:         if (is_array($uri)) {
 692:             $uri = $this->_parseUri($uri);
 693:         } else {
 694:             $uri = $this->_parseUri($uri, true);
 695:         }
 696: 
 697:         if (!isset($uri['host'])) {
 698:             return false;
 699:         }
 700:         $config = array(
 701:             'request' => array(
 702:                 'uri' => array_intersect_key($uri, $this->config['request']['uri'])
 703:             )
 704:         );
 705:         $this->config = Hash::merge($this->config, $config);
 706:         $this->config = Hash::merge($this->config, array_intersect_key($this->config['request']['uri'], $this->config));
 707:         return true;
 708:     }
 709: 
 710:  711:  712:  713:  714:  715:  716: 
 717:     protected function _buildUri($uri = array(), $uriTemplate = '%scheme://%user:%pass@%host:%port/%path?%query#%fragment') {
 718:         if (is_string($uri)) {
 719:             $uri = array('host' => $uri);
 720:         }
 721:         $uri = $this->_parseUri($uri, true);
 722: 
 723:         if (!is_array($uri) || empty($uri)) {
 724:             return false;
 725:         }
 726: 
 727:         $uri['path'] = preg_replace('/^\//', null, $uri['path']);
 728:         $uri['query'] = http_build_query($uri['query'], '', '&');
 729:         $uri['query'] = rtrim($uri['query'], '=');
 730:         $stripIfEmpty = array(
 731:             'query' => '?%query',
 732:             'fragment' => '#%fragment',
 733:             'user' => '%user:%pass@',
 734:             'host' => '%host:%port/'
 735:         );
 736: 
 737:         foreach ($stripIfEmpty as $key => $strip) {
 738:             if (empty($uri[$key])) {
 739:                 $uriTemplate = str_replace($strip, null, $uriTemplate);
 740:             }
 741:         }
 742: 
 743:         $defaultPorts = array('http' => 80, 'https' => 443);
 744:         if (array_key_exists($uri['scheme'], $defaultPorts) && $defaultPorts[$uri['scheme']] == $uri['port']) {
 745:             $uriTemplate = str_replace(':%port', null, $uriTemplate);
 746:         }
 747:         foreach ($uri as $property => $value) {
 748:             $uriTemplate = str_replace('%' . $property, $value, $uriTemplate);
 749:         }
 750: 
 751:         if ($uriTemplate === '/*') {
 752:             $uriTemplate = '*';
 753:         }
 754:         return $uriTemplate;
 755:     }
 756: 
 757:  758:  759:  760:  761:  762:  763:  764: 
 765:     protected function _parseUri($uri = null, $base = array()) {
 766:         $uriBase = array(
 767:             'scheme' => array('http', 'https'),
 768:             'host' => null,
 769:             'port' => array(80, 443),
 770:             'user' => null,
 771:             'pass' => null,
 772:             'path' => '/',
 773:             'query' => null,
 774:             'fragment' => null
 775:         );
 776: 
 777:         if (is_string($uri)) {
 778:             $uri = parse_url($uri);
 779:         }
 780:         if (!is_array($uri) || empty($uri)) {
 781:             return false;
 782:         }
 783:         if ($base === true) {
 784:             $base = $uriBase;
 785:         }
 786: 
 787:         if (isset($base['port'], $base['scheme']) && is_array($base['port']) && is_array($base['scheme'])) {
 788:             if (isset($uri['scheme']) && !isset($uri['port'])) {
 789:                 $base['port'] = $base['port'][array_search($uri['scheme'], $base['scheme'])];
 790:             } elseif (isset($uri['port']) && !isset($uri['scheme'])) {
 791:                 $base['scheme'] = $base['scheme'][array_search($uri['port'], $base['port'])];
 792:             }
 793:         }
 794: 
 795:         if (is_array($base) && !empty($base)) {
 796:             $uri = array_merge($base, $uri);
 797:         }
 798: 
 799:         if (isset($uri['scheme']) && is_array($uri['scheme'])) {
 800:             $uri['scheme'] = array_shift($uri['scheme']);
 801:         }
 802:         if (isset($uri['port']) && is_array($uri['port'])) {
 803:             $uri['port'] = array_shift($uri['port']);
 804:         }
 805: 
 806:         if (array_key_exists('query', $uri)) {
 807:             $uri['query'] = $this->_parseQuery($uri['query']);
 808:         }
 809: 
 810:         if (!array_intersect_key($uriBase, $uri)) {
 811:             return false;
 812:         }
 813:         return $uri;
 814:     }
 815: 
 816:  817:  818:  819:  820:  821:  822:  823:  824:  825:  826:  827:  828:  829: 
 830:     protected function _parseQuery($query) {
 831:         if (is_array($query)) {
 832:             return $query;
 833:         }
 834: 
 835:         $parsedQuery = array();
 836: 
 837:         if (is_string($query) && !empty($query)) {
 838:             $query = preg_replace('/^\?/', '', $query);
 839:             $items = explode('&', $query);
 840: 
 841:             foreach ($items as $item) {
 842:                 if (strpos($item, '=') !== false) {
 843:                     list($key, $value) = explode('=', $item, 2);
 844:                 } else {
 845:                     $key = $item;
 846:                     $value = null;
 847:                 }
 848: 
 849:                 $key = urldecode($key);
 850:                 $value = urldecode($value);
 851: 
 852:                 if (preg_match_all('/\[([^\[\]]*)\]/iUs', $key, $matches)) {
 853:                     $subKeys = $matches[1];
 854:                     $rootKey = substr($key, 0, strpos($key, '['));
 855:                     if (!empty($rootKey)) {
 856:                         array_unshift($subKeys, $rootKey);
 857:                     }
 858:                     $queryNode =& $parsedQuery;
 859: 
 860:                     foreach ($subKeys as $subKey) {
 861:                         if (!is_array($queryNode)) {
 862:                             $queryNode = array();
 863:                         }
 864: 
 865:                         if ($subKey === '') {
 866:                             $queryNode[] = array();
 867:                             end($queryNode);
 868:                             $subKey = key($queryNode);
 869:                         }
 870:                         $queryNode =& $queryNode[$subKey];
 871:                     }
 872:                     $queryNode = $value;
 873:                     continue;
 874:                 }
 875:                 if (!isset($parsedQuery[$key])) {
 876:                     $parsedQuery[$key] = $value;
 877:                 } else {
 878:                     $parsedQuery[$key] = (array)$parsedQuery[$key];
 879:                     $parsedQuery[$key][] = $value;
 880:                 }
 881:             }
 882:         }
 883:         return $parsedQuery;
 884:     }
 885: 
 886:  887:  888:  889:  890:  891:  892: 
 893:     protected function _buildRequestLine($request = array()) {
 894:         $asteriskMethods = array('OPTIONS');
 895: 
 896:         if (is_string($request)) {
 897:             $isValid = preg_match("/(.+) (.+) (.+)\r\n/U", $request, $match);
 898:             if (!$this->quirksMode && (!$isValid || ($match[2] === '*' && !in_array($match[3], $asteriskMethods)))) {
 899:                 throw new SocketException(__d('cake_dev', 'HttpSocket::_buildRequestLine - Passed an invalid request line string. Activate quirks mode to do this.'));
 900:             }
 901:             return $request;
 902:         } elseif (!is_array($request)) {
 903:             return false;
 904:         } elseif (!array_key_exists('uri', $request)) {
 905:             return false;
 906:         }
 907: 
 908:         $request['uri'] = $this->_parseUri($request['uri']);
 909:         $request += array('method' => 'GET');
 910:         if (!empty($this->_proxy['host']) && $request['uri']['scheme'] !== 'https') {
 911:             $request['uri'] = $this->_buildUri($request['uri'], '%scheme://%host:%port/%path?%query');
 912:         } else {
 913:             $request['uri'] = $this->_buildUri($request['uri'], '/%path?%query');
 914:         }
 915: 
 916:         if (!$this->quirksMode && $request['uri'] === '*' && !in_array($request['method'], $asteriskMethods)) {
 917:             throw new SocketException(__d('cake_dev', 'HttpSocket::_buildRequestLine - The "*" asterisk character is only allowed for the following methods: %s. Activate quirks mode to work outside of HTTP/1.1 specs.', implode(',', $asteriskMethods)));
 918:         }
 919:         $version = isset($request['version']) ? $request['version'] : '1.1';
 920:         return $request['method'] . ' ' . $request['uri'] . ' HTTP/' . $version . "\r\n";
 921:     }
 922: 
 923:  924:  925:  926:  927:  928:  929: 
 930:     protected function _buildHeader($header, $mode = 'standard') {
 931:         if (is_string($header)) {
 932:             return $header;
 933:         } elseif (!is_array($header)) {
 934:             return false;
 935:         }
 936: 
 937:         $fieldsInHeader = array();
 938:         foreach ($header as $key => $value) {
 939:             $lowKey = strtolower($key);
 940:             if (array_key_exists($lowKey, $fieldsInHeader)) {
 941:                 $header[$fieldsInHeader[$lowKey]] = $value;
 942:                 unset($header[$key]);
 943:             } else {
 944:                 $fieldsInHeader[$lowKey] = $key;
 945:             }
 946:         }
 947: 
 948:         $returnHeader = '';
 949:         foreach ($header as $field => $contents) {
 950:             if (is_array($contents) && $mode === 'standard') {
 951:                 $contents = implode(',', $contents);
 952:             }
 953:             foreach ((array)$contents as $content) {
 954:                 $contents = preg_replace("/\r\n(?![\t ])/", "\r\n ", $content);
 955:                 $field = $this->_escapeToken($field);
 956: 
 957:                 $returnHeader .= $field . ': ' . $contents . "\r\n";
 958:             }
 959:         }
 960:         return $returnHeader;
 961:     }
 962: 
 963:  964:  965:  966:  967:  968:  969:  970:  971: 
 972:     public function buildCookies($cookies) {
 973:         $header = array();
 974:         foreach ($cookies as $name => $cookie) {
 975:             if (is_array($cookie)) {
 976:                 $value = $this->_escapeToken($cookie['value'], array(';'));
 977:             } else {
 978:                 $value = $this->_escapeToken($cookie, array(';'));
 979:             }
 980:             $header[] = $name . '=' . $value;
 981:         }
 982:         return $this->_buildHeader(array('Cookie' => implode('; ', $header)), 'pragmatic');
 983:     }
 984: 
 985:  986:  987:  988:  989:  990:  991: 
 992:     protected function _escapeToken($token, $chars = null) {
 993:         $regex = '/([' . implode('', $this->_tokenEscapeChars(true, $chars)) . '])/';
 994:         $token = preg_replace($regex, '"\\1"', $token);
 995:         return $token;
 996:     }
 997: 
 998:  999: 1000: 1001: 1002: 1003: 1004: 
1005:     protected function _tokenEscapeChars($hex = true, $chars = null) {
1006:         if (!empty($chars)) {
1007:             $escape = $chars;
1008:         } else {
1009:             $escape = array('"', "(", ")", "<", ">", "@", ",", ";", ":", "\\", "/", "[", "]", "?", "=", "{", "}", " ");
1010:             for ($i = 0; $i <= 31; $i++) {
1011:                 $escape[] = chr($i);
1012:             }
1013:             $escape[] = chr(127);
1014:         }
1015: 
1016:         if (!$hex) {
1017:             return $escape;
1018:         }
1019:         foreach ($escape as $key => $char) {
1020:             $escape[$key] = '\\x' . str_pad(dechex(ord($char)), 2, '0', STR_PAD_LEFT);
1021:         }
1022:         return $escape;
1023:     }
1024: 
1025: 1026: 1027: 1028: 1029: 1030: 1031: 
1032:     public function reset($full = true) {
1033:         static $initalState = array();
1034:         if (empty($initalState)) {
1035:             $initalState = get_class_vars(__CLASS__);
1036:         }
1037:         if (!$full) {
1038:             $this->request = $initalState['request'];
1039:             $this->response = $initalState['response'];
1040:             return true;
1041:         }
1042:         parent::reset($initalState);
1043:         return true;
1044:     }
1045: 
1046: }
1047: 
1048: