|
1 | 1 | <?php
|
2 | 2 |
|
3 | 3 | /**
|
4 |
| - * @task data Accessing Request Data |
5 |
| - * @task cookie Managing Cookies |
6 |
| - * |
| 4 | + * @task data Accessing Request Data |
| 5 | + * @task cookie Managing Cookies |
| 6 | + * @task cluster Working With a Phabricator Cluster |
7 | 7 | */
|
8 | 8 | final class AphrontRequest {
|
9 | 9 |
|
@@ -625,4 +625,130 @@ public static function getHTTPHeader($name, $default = null, $data = null) {
|
625 | 625 | return $default;
|
626 | 626 | }
|
627 | 627 |
|
| 628 | + |
| 629 | +/* -( Working With a Phabricator Cluster )--------------------------------- */ |
| 630 | + |
| 631 | + |
| 632 | + /** |
| 633 | + * Is this a proxied request originating from within the Phabricator cluster? |
| 634 | + * |
| 635 | + * IMPORTANT: This means the request is dangerous! |
| 636 | + * |
| 637 | + * These requests are **more dangerous** than normal requests (they can not |
| 638 | + * be safely proxied, because proxying them may cause a loop). Cluster |
| 639 | + * requests are not guaranteed to come from a trusted source, and should |
| 640 | + * never be treated as safer than normal requests. They are strictly less |
| 641 | + * safe. |
| 642 | + */ |
| 643 | + public function isProxiedClusterRequest() { |
| 644 | + return (bool)AphrontRequest::getHTTPHeader('X-Phabricator-Cluster'); |
| 645 | + } |
| 646 | + |
| 647 | + |
| 648 | + /** |
| 649 | + * Build a new @{class:HTTPSFuture} which proxies this request to another |
| 650 | + * node in the cluster. |
| 651 | + * |
| 652 | + * IMPORTANT: This is very dangerous! |
| 653 | + * |
| 654 | + * The future forwards authentication information present in the request. |
| 655 | + * Proxied requests must only be sent to trusted hosts. (We attempt to |
| 656 | + * enforce this.) |
| 657 | + * |
| 658 | + * This is not a general-purpose proxying method; it is a specialized |
| 659 | + * method with niche applications and severe security implications. |
| 660 | + * |
| 661 | + * @param string URI identifying the host we are proxying the request to. |
| 662 | + * @return HTTPSFuture New proxy future. |
| 663 | + * |
| 664 | + * @phutil-external-symbol class PhabricatorStartup |
| 665 | + */ |
| 666 | + public function newClusterProxyFuture($uri) { |
| 667 | + $uri = new PhutilURI($uri); |
| 668 | + |
| 669 | + $domain = $uri->getDomain(); |
| 670 | + $ip = gethostbyname($domain); |
| 671 | + if (!$ip) { |
| 672 | + throw new Exception( |
| 673 | + pht( |
| 674 | + 'Unable to resolve domain "%s"!', |
| 675 | + $domain)); |
| 676 | + } |
| 677 | + |
| 678 | + if (!PhabricatorEnv::isClusterAddress($ip)) { |
| 679 | + throw new Exception( |
| 680 | + pht( |
| 681 | + 'Refusing to proxy a request to IP address ("%s") which is not '. |
| 682 | + 'in the cluster address block (this address was derived by '. |
| 683 | + 'resolving the domain "%s").', |
| 684 | + $ip, |
| 685 | + $domain)); |
| 686 | + } |
| 687 | + |
| 688 | + $uri->setPath($this->getPath()); |
| 689 | + $uri->setQueryParams(self::flattenData($_GET)); |
| 690 | + |
| 691 | + $input = PhabricatorStartup::getRawInput(); |
| 692 | + |
| 693 | + $future = id(new HTTPSFuture($uri)) |
| 694 | + ->addHeader('Host', self::getHost()) |
| 695 | + ->addHeader('X-Phabricator-Cluster', true) |
| 696 | + ->setMethod($_SERVER['REQUEST_METHOD']) |
| 697 | + ->write($input); |
| 698 | + |
| 699 | + if (isset($_SERVER['PHP_AUTH_USER'])) { |
| 700 | + $future->setHTTPBasicAuthCredentials( |
| 701 | + $_SERVER['PHP_AUTH_USER'], |
| 702 | + new PhutilOpaqueEnvelope(idx($_SERVER, 'PHP_AUTH_PW', ''))); |
| 703 | + } |
| 704 | + |
| 705 | + $headers = array(); |
| 706 | + $seen = array(); |
| 707 | + |
| 708 | + // NOTE: apache_request_headers() might provide a nicer way to do this, |
| 709 | + // but isn't available under FCGI until PHP 5.4.0. |
| 710 | + foreach ($_SERVER as $key => $value) { |
| 711 | + if (preg_match('/^HTTP_/', $key)) { |
| 712 | + // Unmangle the header as best we can. |
| 713 | + $key = str_replace('_', ' ', $key); |
| 714 | + $key = strtolower($key); |
| 715 | + $key = ucwords($key); |
| 716 | + $key = str_replace(' ', '-', $key); |
| 717 | + |
| 718 | + $headers[] = array($key, $value); |
| 719 | + $seen[$key] = true; |
| 720 | + } |
| 721 | + } |
| 722 | + |
| 723 | + // In some situations, this may not be mapped into the HTTP_X constants. |
| 724 | + // CONTENT_LENGTH is similarly affected, but we trust cURL to take care |
| 725 | + // of that if it matters, since we're handing off a request body. |
| 726 | + if (empty($seen['Content-Type'])) { |
| 727 | + if (isset($_SERVER['CONTENT_TYPE'])) { |
| 728 | + $headers[] = array('Content-Type', $_SERVER['CONTENT_TYPE']); |
| 729 | + } |
| 730 | + } |
| 731 | + |
| 732 | + foreach ($headers as $header) { |
| 733 | + list($key, $value) = $header; |
| 734 | + switch ($key) { |
| 735 | + case 'Host': |
| 736 | + case 'Authorization': |
| 737 | + // Don't forward these headers, we've already handled them elsewhere. |
| 738 | + unset($headers[$key]); |
| 739 | + break; |
| 740 | + default: |
| 741 | + break; |
| 742 | + } |
| 743 | + } |
| 744 | + |
| 745 | + foreach ($headers as $header) { |
| 746 | + list($key, $value) = $header; |
| 747 | + $future->addHeader($key, $value); |
| 748 | + } |
| 749 | + |
| 750 | + return $future; |
| 751 | + } |
| 752 | + |
| 753 | + |
628 | 754 | }
|
0 commit comments