diff --git a/src/parser/PhutilURI.php b/src/parser/PhutilURI.php --- a/src/parser/PhutilURI.php +++ b/src/parser/PhutilURI.php @@ -45,6 +45,16 @@ $type = self::TYPE_URI; + // Reject ambiguous URIs outright. Different versions of different clients + // parse these in different ways. See T12526 for discussion. + if (preg_match('(^[^/:]*://[^/]*[#?].*:)', $uri)) { + throw new Exception( + pht( + 'Rejecting ambiguous URI "%s". This URI is not formatted or '. + 'encoded properly.', + $uri)); + } + $matches = null; if (preg_match('(^([^/:]*://[^/]*)(\\?.*)\z)', $uri, $matches)) { // If the URI is something like `idea://open?file=/path/to/file`, the diff --git a/src/parser/__tests__/PhutilURITestCase.php b/src/parser/__tests__/PhutilURITestCase.php --- a/src/parser/__tests__/PhutilURITestCase.php +++ b/src/parser/__tests__/PhutilURITestCase.php @@ -83,17 +83,38 @@ $this->assertEqual('evil.com', $uri->getDomain()); $this->assertEqual('http://u:p@evil.com?%40good.com=', (string)$uri); - $uri = new PhutilURI('http://good.com#u:p@evil.com/'); - $this->assertEqual('good.com#u', $uri->getUser()); - $this->assertEqual('p', $uri->getPass()); - $this->assertEqual('evil.com', $uri->getDomain()); - $this->assertEqual('http://good.com%23u:p@evil.com/', (string)$uri); - - $uri = new PhutilURI('http://good.com?u:p@evil.com/'); - $this->assertEqual('', $uri->getUser()); - $this->assertEqual('', $uri->getPass()); - $this->assertEqual('good.com', $uri->getDomain()); - $this->assertEqual('http://good.com?u%3Ap%40evil.com%2F=', (string)$uri); + // The behavior of URLs in these forms differs for different versions + // of cURL, PHP, and other software. Because safe parsing is a tricky + // proposition and these URIs are almost certainly malicious, we just + // reject them. See T12526 for discussion. + + $dangerous = array( + // Ambiguous encoding. + 'http://good.com#u:p@evil.com/' => true, + 'http://good.com?u:p@evil.com/' => true, + + // Unambiguous encoding: with a trailing slash. + 'http://good.com/#u:p@evil.com/' => false, + 'http://good.com/?u:p@evil.com/' => false, + + // Unambiguous encoding: with escaping. + 'http://good.com%23u:p@evil.com/' => false, + 'http://good.com%40u:p@evil.com/' => false, + ); + + foreach ($dangerous as $input => $expect) { + $caught = null; + try { + new PhutilURI($input); + } catch (Exception $ex) { + $caught = $ex; + } + + $this->assertEqual( + $expect, + ($caught instanceof $ex), + pht('Unexpected parse result for dangerous URI "%s".', $input)); + } $uri = new PhutilURI('www.example.com'); $this->assertEqual('', $uri->getProtocol());