diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -15,14 +15,6 @@ 'AASTTree' => 'parser/aast/api/AASTTree.php', 'AbstractDirectedGraph' => 'utils/AbstractDirectedGraph.php', 'AbstractDirectedGraphTestCase' => 'utils/__tests__/AbstractDirectedGraphTestCase.php', - 'AphrontHTTPHeaderParser' => 'aphront/headerparser/AphrontHTTPHeaderParser.php', - 'AphrontHTTPHeaderParserTestCase' => 'aphront/headerparser/__tests__/AphrontHTTPHeaderParserTestCase.php', - 'AphrontMultipartParser' => 'aphront/multipartparser/AphrontMultipartParser.php', - 'AphrontMultipartParserTestCase' => 'aphront/multipartparser/__tests__/AphrontMultipartParserTestCase.php', - 'AphrontMultipartPart' => 'aphront/multipartparser/AphrontMultipartPart.php', - 'AphrontRequestStream' => 'aphront/requeststream/AphrontRequestStream.php', - 'AphrontScopedUnguardedWriteCapability' => 'aphront/writeguard/AphrontScopedUnguardedWriteCapability.php', - 'AphrontWriteGuard' => 'aphront/writeguard/AphrontWriteGuard.php', 'BaseHTTPFuture' => 'future/http/BaseHTTPFuture.php', 'CaseInsensitiveArray' => 'utils/CaseInsensitiveArray.php', 'CaseInsensitiveArrayTestCase' => 'utils/__tests__/CaseInsensitiveArrayTestCase.php', @@ -529,14 +521,6 @@ 'AASTTree' => 'Phobject', 'AbstractDirectedGraph' => 'Phobject', 'AbstractDirectedGraphTestCase' => 'PhutilTestCase', - 'AphrontHTTPHeaderParser' => 'Phobject', - 'AphrontHTTPHeaderParserTestCase' => 'PhutilTestCase', - 'AphrontMultipartParser' => 'Phobject', - 'AphrontMultipartParserTestCase' => 'PhutilTestCase', - 'AphrontMultipartPart' => 'Phobject', - 'AphrontRequestStream' => 'Phobject', - 'AphrontScopedUnguardedWriteCapability' => 'Phobject', - 'AphrontWriteGuard' => 'Phobject', 'BaseHTTPFuture' => 'Future', 'CaseInsensitiveArray' => 'PhutilArray', 'CaseInsensitiveArrayTestCase' => 'PhutilTestCase', diff --git a/src/aphront/headerparser/AphrontHTTPHeaderParser.php b/src/aphront/headerparser/AphrontHTTPHeaderParser.php deleted file mode 100644 --- a/src/aphront/headerparser/AphrontHTTPHeaderParser.php +++ /dev/null @@ -1,150 +0,0 @@ -name = null; - $this->content = null; - - $parts = explode(':', $raw_header, 2); - $this->name = trim($parts[0]); - if (count($parts) > 1) { - $this->content = trim($parts[1]); - } - - $this->pairs = null; - - return $this; - } - - public function getHeaderName() { - $this->requireParse(); - return $this->name; - } - - public function getHeaderContent() { - $this->requireParse(); - return $this->content; - } - - public function getHeaderContentAsPairs() { - $content = $this->getHeaderContent(); - - - $state = 'prekey'; - $length = strlen($content); - - $pair_name = null; - $pair_value = null; - - $pairs = array(); - $ii = 0; - while ($ii < $length) { - $c = $content[$ii]; - - switch ($state) { - case 'prekey'; - // We're eating space in front of a key. - if ($c == ' ') { - $ii++; - break; - } - $pair_name = ''; - $state = 'key'; - break; - case 'key'; - // We're parsing a key name until we find "=" or ";". - if ($c == ';') { - $state = 'done'; - break; - } - - if ($c == '=') { - $ii++; - $state = 'value'; - break; - } - - $ii++; - $pair_name .= $c; - break; - case 'value': - // We found an "=", so now figure out if the value is quoted - // or not. - if ($c == '"') { - $ii++; - $state = 'quoted'; - break; - } - $state = 'unquoted'; - break; - case 'quoted': - // We're in a quoted string, parse until we find the closing quote. - if ($c == '"') { - $ii++; - $state = 'done'; - break; - } - - $ii++; - $pair_value .= $c; - break; - case 'unquoted': - // We're in an unquoted string, parse until we find a space or a - // semicolon. - if ($c == ' ' || $c == ';') { - $state = 'done'; - break; - } - $ii++; - $pair_value .= $c; - break; - case 'done': - // We parsed something, so eat any trailing whitespace and semicolons - // and look for a new value. - if ($c == ' ' || $c == ';') { - $ii++; - break; - } - - $pairs[] = array( - $pair_name, - $pair_value, - ); - - $pair_name = null; - $pair_value = null; - - $state = 'prekey'; - break; - } - } - - if ($state == 'quoted') { - throw new Exception( - pht( - 'Header has unterminated double quote for key "%s".', - $pair_name)); - } - - if ($pair_name !== null) { - $pairs[] = array( - $pair_name, - $pair_value, - ); - } - - return $pairs; - } - - private function requireParse() { - if ($this->name === null) { - throw new PhutilInvalidStateException('parseRawHeader'); - } - } - -} diff --git a/src/aphront/headerparser/__tests__/AphrontHTTPHeaderParserTestCase.php b/src/aphront/headerparser/__tests__/AphrontHTTPHeaderParserTestCase.php deleted file mode 100644 --- a/src/aphront/headerparser/__tests__/AphrontHTTPHeaderParserTestCase.php +++ /dev/null @@ -1,108 +0,0 @@ -parseRawHeader($input); - - $actual_name = $parser->getHeaderName(); - $actual_content = $parser->getHeaderContent(); - - $this->assertEqual( - $expect_name, - $actual_name, - pht('Header name for: %s', $input)); - - $this->assertEqual( - $expect_content, - $actual_content, - pht('Header content for: %s', $input)); - - if (isset($case[3])) { - $expect_pairs = $case[3]; - - $caught = null; - try { - $actual_pairs = $parser->getHeaderContentAsPairs(); - } catch (Exception $ex) { - $caught = $ex; - } - - if ($expect_pairs === false) { - $this->assertEqual( - true, - ($caught instanceof Exception), - pht('Expect exception for header pairs of: %s', $input)); - } else { - $this->assertEqual( - $expect_pairs, - $actual_pairs, - pht('Header pairs for: %s', $input)); - } - } - } - } - - -} diff --git a/src/aphront/multipartparser/AphrontMultipartParser.php b/src/aphront/multipartparser/AphrontMultipartParser.php deleted file mode 100644 --- a/src/aphront/multipartparser/AphrontMultipartParser.php +++ /dev/null @@ -1,249 +0,0 @@ -contentType = $content_type; - return $this; - } - - public function getContentType() { - return $this->contentType; - } - - public function beginParse() { - $content_type = $this->getContentType(); - if ($content_type === null) { - throw new PhutilInvalidStateException('setContentType'); - } - - if (!preg_match('(^multipart/form-data)', $content_type)) { - throw new Exception( - pht( - 'Expected "multipart/form-data" content type when executing a '. - 'multipart body read.')); - } - - $type_parts = preg_split('(\s*;\s*)', $content_type); - $boundary = null; - foreach ($type_parts as $type_part) { - $matches = null; - if (preg_match('(^boundary=(.*))', $type_part, $matches)) { - $boundary = $matches[1]; - break; - } - } - - if ($boundary === null) { - throw new Exception( - pht('Received "multipart/form-data" request with no "boundary".')); - } - - $this->parts = array(); - $this->part = null; - - $this->buffer = ''; - $this->boundary = $boundary; - - // We're looking for a (usually empty) body before the first boundary. - $this->state = 'bodynewline'; - } - - public function continueParse($bytes) { - $this->buffer .= $bytes; - - $continue = true; - while ($continue) { - switch ($this->state) { - case 'endboundary': - // We've just parsed a boundary. Next, we expect either "--" (which - // indicates we've reached the end of the parts) or "\r\n" (which - // indicates we should read the headers for the next part). - - if (strlen($this->buffer) < 2) { - // We don't have enough bytes yet, so wait for more. - $continue = false; - break; - } - - if (!strncmp($this->buffer, '--', 2)) { - // This is "--" after a boundary, so we're done. We'll read the - // rest of the body (the "epilogue") and discard it. - $this->buffer = substr($this->buffer, 2); - $this->state = 'epilogue'; - - $this->part = null; - break; - } - - if (!strncmp($this->buffer, "\r\n", 2)) { - // This is "\r\n" after a boundary, so we're going to going to - // read the headers for a part. - $this->buffer = substr($this->buffer, 2); - $this->state = 'header'; - - // Create the object to hold the part we're about to read. - $part = new AphrontMultipartPart(); - $this->parts[] = $part; - $this->part = $part; - break; - } - - throw new Exception( - pht('Expected "\r\n" or "--" after multipart data boundary.')); - case 'header': - // We've just parsed a boundary, followed by "\r\n". We are going - // to read the headers for this part. They are in the form of HTTP - // headers and terminated by "\r\n". The section is terminated by - // a line with no header on it. - - if (strlen($this->buffer) < 2) { - // We don't have enough data to find a "\r\n", so wait for more. - $continue = false; - break; - } - - if (!strncmp("\r\n", $this->buffer, 2)) { - // This line immediately began "\r\n", so we're done with parsing - // headers. Start parsing the body. - $this->buffer = substr($this->buffer, 2); - $this->state = 'body'; - break; - } - - // This is an actual header, so look for the end of it. - $header_len = strpos($this->buffer, "\r\n"); - if ($header_len === false) { - // We don't have a full header yet, so wait for more data. - $continue = false; - break; - } - - $header_buf = substr($this->buffer, 0, $header_len); - $this->part->appendRawHeader($header_buf); - - $this->buffer = substr($this->buffer, $header_len + 2); - break; - case 'body': - // We've parsed a boundary and headers, and are parsing the data for - // this part. The data is terminated by "\r\n--", then the boundary. - - // We'll look for "\r\n", then switch to the "bodynewline" state if - // we find it. - - $marker = "\r"; - $marker_pos = strpos($this->buffer, $marker); - - if ($marker_pos === false) { - // There's no "\r" anywhere in the buffer, so we can just read it - // as provided. Then, since we read all the data, we're done until - // we get more. - - // Note that if we're in the preamble, we won't have a "part" - // object and will just discard the data. - if ($this->part) { - $this->part->appendData($this->buffer); - } - $this->buffer = ''; - $continue = false; - break; - } - - if ($marker_pos > 0) { - // If there are bytes before the "\r", - if ($this->part) { - $this->part->appendData(substr($this->buffer, 0, $marker_pos)); - } - $this->buffer = substr($this->buffer, $marker_pos); - } - - $expect = "\r\n"; - $expect_len = strlen($expect); - if (strlen($this->buffer) < $expect_len) { - // We don't have enough bytes yet to know if this is "\r\n" - // or not. - $continue = false; - break; - } - - if (strncmp($this->buffer, $expect, $expect_len)) { - // The next two bytes aren't "\r\n", so eat them and go looking - // for more newlines. - if ($this->part) { - $this->part->appendData(substr($this->buffer, 0, $expect_len)); - } - $this->buffer = substr($this->buffer, $expect_len); - break; - } - - // Eat the "\r\n". - $this->buffer = substr($this->buffer, $expect_len); - $this->state = 'bodynewline'; - break; - case 'bodynewline': - // We've parsed a newline in a body, or we just started parsing the - // request. In either case, we're looking for "--", then the boundary. - // If we find it, this section is done. If we don't, we consume the - // bytes and move on. - - $expect = '--'.$this->boundary; - $expect_len = strlen($expect); - - if (strlen($this->buffer) < $expect_len) { - // We don't have enough bytes yet, so wait for more. - $continue = false; - break; - } - - if (strncmp($this->buffer, $expect, $expect_len)) { - // This wasn't the boundary, so return to the "body" state and - // consume it. (But first, we need to append the "\r\n" which we - // ate earlier.) - if ($this->part) { - $this->part->appendData("\r\n"); - } - $this->state = 'body'; - break; - } - - // This is the boundary, so toss it and move on. - $this->buffer = substr($this->buffer, $expect_len); - $this->state = 'endboundary'; - break; - case 'epilogue': - // We just discard any epilogue. - $this->buffer = ''; - $continue = false; - break; - default: - throw new Exception( - pht( - 'Unknown parser state "%s".\n', - $this->state)); - } - } - } - - public function endParse() { - if ($this->state !== 'epilogue') { - throw new Exception( - pht( - 'Expected "multipart/form-data" parse to end '. - 'in state "epilogue".')); - } - - return $this->parts; - } - - -} diff --git a/src/aphront/multipartparser/AphrontMultipartPart.php b/src/aphront/multipartparser/AphrontMultipartPart.php deleted file mode 100644 --- a/src/aphront/multipartparser/AphrontMultipartPart.php +++ /dev/null @@ -1,96 +0,0 @@ -parseRawHeader($bytes); - - $header_name = $parser->getHeaderName(); - - $this->headers[] = array( - $header_name, - $parser->getHeaderContent(), - ); - - if (strtolower($header_name) === 'content-disposition') { - $pairs = $parser->getHeaderContentAsPairs(); - foreach ($pairs as $pair) { - list($key, $value) = $pair; - switch ($key) { - case 'filename': - $this->filename = $value; - break; - case 'name': - $this->name = $value; - break; - } - } - } - - return $this; - } - - public function appendData($bytes) { - $this->byteSize += strlen($bytes); - - if ($this->isVariable()) { - $this->value .= $bytes; - } else { - if (!$this->tempFile) { - $this->tempFile = new TempFile(getmypid().'.upload'); - } - Filesystem::appendFile($this->tempFile, $bytes); - } - - return $this; - } - - public function isVariable() { - return ($this->filename === null); - } - - public function getName() { - return $this->name; - } - - public function getVariableValue() { - if (!$this->isVariable()) { - throw new Exception(pht('This part is not a variable!')); - } - - return $this->value; - } - - public function getPHPFileDictionary() { - if (!$this->tempFile) { - $this->appendData(''); - } - - $mime_type = 'application/octet-stream'; - foreach ($this->headers as $header) { - list($name, $value) = $header; - if (strtolower($name) == 'content-type') { - $mime_type = $value; - break; - } - } - - return array( - 'name' => $this->filename, - 'type' => $mime_type, - 'tmp_name' => (string)$this->tempFile, - 'error' => 0, - 'size' => $this->byteSize, - ); - } - -} diff --git a/src/aphront/multipartparser/__tests__/AphrontMultipartParserTestCase.php b/src/aphront/multipartparser/__tests__/AphrontMultipartParserTestCase.php deleted file mode 100644 --- a/src/aphront/multipartparser/__tests__/AphrontMultipartParserTestCase.php +++ /dev/null @@ -1,45 +0,0 @@ - 'simple.txt', - 'variables' => array( - array('a', 'b'), - ), - ), - ); - - $data_dir = dirname(__FILE__).'/data/'; - foreach ($map as $test_case) { - $data = Filesystem::readFile($data_dir.$test_case['data']); - $data = str_replace("\n", "\r\n", $data); - - $parser = id(new AphrontMultipartParser()) - ->setContentType('multipart/form-data; boundary=ABCDEFG'); - $parser->beginParse(); - $parser->continueParse($data); - $parts = $parser->endParse(); - - $variables = array(); - foreach ($parts as $part) { - if (!$part->isVariable()) { - continue; - } - - $variables[] = array( - $part->getName(), - $part->getVariableValue(), - ); - } - - $expect_variables = idx($test_case, 'variables', array()); - $this->assertEqual($expect_variables, $variables); - } - } - - - -} diff --git a/src/aphront/multipartparser/__tests__/data/simple.txt b/src/aphront/multipartparser/__tests__/data/simple.txt deleted file mode 100644 --- a/src/aphront/multipartparser/__tests__/data/simple.txt +++ /dev/null @@ -1,5 +0,0 @@ ---ABCDEFG -Content-Disposition: form-data; name="a" - -b ---ABCDEFG-- diff --git a/src/aphront/requeststream/AphrontRequestStream.php b/src/aphront/requeststream/AphrontRequestStream.php deleted file mode 100644 --- a/src/aphront/requeststream/AphrontRequestStream.php +++ /dev/null @@ -1,92 +0,0 @@ -encoding = $encoding; - return $this; - } - - public function getEncoding() { - return $this->encoding; - } - - public function getIterator() { - if (!$this->iterator) { - $this->iterator = new PhutilStreamIterator($this->getStream()); - } - return $this->iterator; - } - - public function readData() { - if (!$this->iterator) { - $iterator = $this->getIterator(); - $iterator->rewind(); - } else { - $iterator = $this->getIterator(); - } - - if (!$iterator->valid()) { - return null; - } - - $data = $iterator->current(); - $iterator->next(); - - return $data; - } - - private function getStream() { - if (!$this->stream) { - $this->stream = $this->newStream(); - } - - return $this->stream; - } - - private function newStream() { - $stream = fopen('php://input', 'rb'); - if (!$stream) { - throw new Exception( - pht( - 'Failed to open stream "%s" for reading.', - 'php://input')); - } - - $encoding = $this->getEncoding(); - if ($encoding === 'gzip') { - // This parameter is magic. Values 0-15 express a time/memory tradeoff, - // but the largest value (15) corresponds to only 32KB of memory and - // data encoded with a smaller window size than the one we pass can not - // be decompressed. Always pass the maximum window size. - - // Additionally, you can add 16 (to enable gzip) or 32 (to enable both - // gzip and zlib). Add 32 to support both. - $zlib_window = 15 + 32; - - $ok = stream_filter_append( - $stream, - 'zlib.inflate', - STREAM_FILTER_READ, - array( - 'window' => $zlib_window, - )); - if (!$ok) { - throw new Exception( - pht( - 'Failed to append filter "%s" to input stream while processing '. - 'a request with "%s" encoding.', - 'zlib.inflate', - $encoding)); - } - } - - return $stream; - } - -} diff --git a/src/aphront/writeguard/AphrontScopedUnguardedWriteCapability.php b/src/aphront/writeguard/AphrontScopedUnguardedWriteCapability.php deleted file mode 100644 --- a/src/aphront/writeguard/AphrontScopedUnguardedWriteCapability.php +++ /dev/null @@ -1,9 +0,0 @@ -dispose(); - * - * Normally, you do not need to manage guards yourself -- the Aphront stack - * handles it for you. - * - * This class accepts a callback, which will be invoked when a write is - * attempted. The callback should validate the presence of a CSRF token in - * the request, or abort the request (e.g., by throwing an exception) if a - * valid token isn't present. - * - * @param callable CSRF callback. - * @return this - * @task manage - */ - public function __construct($callback) { - if (self::$instance) { - throw new Exception( - pht( - 'An %s already exists. Dispose of the previous guard '. - 'before creating a new one.', - __CLASS__)); - } - if (self::$allowUnguardedWrites) { - throw new Exception( - pht( - 'An %s is being created in a context which permits '. - 'unguarded writes unconditionally. This is not allowed and '. - 'indicates a serious error.', - __CLASS__)); - } - $this->callback = $callback; - self::$instance = $this; - } - - - /** - * Dispose of the active write guard. You must call this method when you are - * done with a write guard. You do not normally need to call this yourself. - * - * @return void - * @task manage - */ - public function dispose() { - if (!self::$instance) { - throw new Exception(pht( - 'Attempting to dispose of write guard, but no write guard is active!')); - } - - if ($this->allowDepth > 0) { - throw new Exception( - pht( - 'Imbalanced %s: more %s calls than %s calls.', - __CLASS__, - 'beginUnguardedWrites()', - 'endUnguardedWrites()')); - } - self::$instance = null; - } - - - /** - * Determine if there is an active write guard. - * - * @return bool - * @task manage - */ - public static function isGuardActive() { - return (bool)self::$instance; - } - - /** - * Return on instance of AphrontWriteGuard if it's active, or null - * - * @return AphrontWriteGuard|null - */ - public static function getInstance() { - return self::$instance; - } - - -/* -( Protecting Writes )-------------------------------------------------- */ - - - /** - * Declare intention to perform a write, validating that writes are allowed. - * You should call this method before executing a write whenever you implement - * a new storage engine where information can be permanently kept. - * - * Writes are permitted if: - * - * - The request has valid CSRF tokens. - * - Unguarded writes have been temporarily enabled by a call to - * @{method:beginUnguardedWrites}. - * - All write guarding has been disabled with - * @{method:allowDangerousUnguardedWrites}. - * - * If none of these conditions are true, this method will throw and prevent - * the write. - * - * @return void - * @task protect - */ - public static function willWrite() { - if (!self::$instance) { - if (!self::$allowUnguardedWrites) { - throw new Exception( - pht( - 'Unguarded write! There must be an active %s to perform writes.', - __CLASS__)); - } else { - // Unguarded writes are being allowed unconditionally. - return; - } - } - - $instance = self::$instance; - if ($instance->allowDepth == 0) { - call_user_func($instance->callback); - } - } - - -/* -( Disabling Write Protection )----------------------------------------- */ - - - /** - * Enter a scope which permits unguarded writes. This works like - * @{method:beginUnguardedWrites} but returns an object which will end - * the unguarded write scope when its __destruct() method is called. This - * is useful to more easily handle exceptions correctly in unguarded write - * blocks: - * - * // Restores the guard even if do_logging() throws. - * function unguarded_scope() { - * $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - * do_logging(); - * } - * - * @return AphrontScopedUnguardedWriteCapability Object which ends unguarded - * writes when it leaves scope. - * @task disable - */ - public static function beginScopedUnguardedWrites() { - self::beginUnguardedWrites(); - return new AphrontScopedUnguardedWriteCapability(); - } - - - /** - * Begin a block which permits unguarded writes. You should use this very - * sparingly, and only for things like logging where CSRF is not a concern. - * - * You must pair every call to @{method:beginUnguardedWrites} with a call to - * @{method:endUnguardedWrites}: - * - * AphrontWriteGuard::beginUnguardedWrites(); - * do_logging(); - * AphrontWriteGuard::endUnguardedWrites(); - * - * @return void - * @task disable - */ - public static function beginUnguardedWrites() { - if (!self::$instance) { - return; - } - self::$instance->allowDepth++; - } - - /** - * Declare that you have finished performing unguarded writes. You must - * call this exactly once for each call to @{method:beginUnguardedWrites}. - * - * @return void - * @task disable - */ - public static function endUnguardedWrites() { - if (!self::$instance) { - return; - } - if (self::$instance->allowDepth <= 0) { - throw new Exception( - pht( - 'Imbalanced %s: more %s calls than %s calls.', - __CLASS__, - 'endUnguardedWrites()', - 'beginUnguardedWrites()')); - } - self::$instance->allowDepth--; - } - - - /** - * Allow execution of unguarded writes. This is ONLY appropriate for use in - * script contexts or other contexts where you are guaranteed to never be - * vulnerable to CSRF concerns. Calling this method is EXTREMELY DANGEROUS - * if you do not understand the consequences. - * - * If you need to perform unguarded writes on an otherwise guarded workflow - * which is vulnerable to CSRF, use @{method:beginUnguardedWrites}. - * - * @return void - * @task disable - */ - public static function allowDangerousUnguardedWrites($allow) { - if (self::$instance) { - throw new Exception( - pht( - 'You can not unconditionally disable %s by calling %s while a write '. - 'guard is active. Use %s to temporarily allow unguarded writes.', - __CLASS__, - __FUNCTION__.'()', - 'beginUnguardedWrites()')); - } - self::$allowUnguardedWrites = true; - } - -}