Page MenuHomePhabricator

D20974.id49974.diff
No OneTemporary

D20974.id49974.diff

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 @@
-<?php
-
-final class AphrontHTTPHeaderParser extends Phobject {
-
- private $name;
- private $content;
- private $pairs;
-
- public function parseRawHeader($raw_header) {
- $this->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 @@
-<?php
-
-final class AphrontHTTPHeaderParserTestCase extends PhutilTestCase {
-
- public function testHeaderParser() {
- $cases = array(
- array(
- 'Key: x; y; z',
- 'Key',
- 'x; y; z',
- array(
- array('x', null),
- array('y', null),
- array('z', null),
- ),
- ),
- array(
- 'Content-Disposition: form-data; name="label"',
- 'Content-Disposition',
- 'form-data; name="label"',
- array(
- array('form-data', null),
- array('name', 'label'),
- ),
- ),
- array(
- 'Content-Type: multipart/form-data; charset=utf-8',
- 'Content-Type',
- 'multipart/form-data; charset=utf-8',
- array(
- array('multipart/form-data', null),
- array('charset', 'utf-8'),
- ),
- ),
- array(
- 'Content-Type: application/octet-stream; charset="ut',
- 'Content-Type',
- 'application/octet-stream; charset="ut',
- false,
- ),
- array(
- 'Content-Type: multipart/form-data; boundary=ABCDEFG',
- 'Content-Type',
- 'multipart/form-data; boundary=ABCDEFG',
- array(
- array('multipart/form-data', null),
- array('boundary', 'ABCDEFG'),
- ),
- ),
- array(
- 'Content-Type: multipart/form-data; boundary="ABCDEFG"',
- 'Content-Type',
- 'multipart/form-data; boundary="ABCDEFG"',
- array(
- array('multipart/form-data', null),
- array('boundary', 'ABCDEFG'),
- ),
- ),
- );
-
- foreach ($cases as $case) {
- $input = $case[0];
- $expect_name = $case[1];
- $expect_content = $case[2];
-
- $parser = id(new AphrontHTTPHeaderParser())
- ->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 @@
-<?php
-
-final class AphrontMultipartParser extends Phobject {
-
- private $contentType;
- private $boundary;
-
- private $buffer;
- private $body;
- private $state;
-
- private $part;
- private $parts;
-
- public function setContentType($content_type) {
- $this->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 @@
-<?php
-
-final class AphrontMultipartPart extends Phobject {
-
- private $headers = array();
- private $value = '';
-
- private $name;
- private $filename;
- private $tempFile;
- private $byteSize = 0;
-
- public function appendRawHeader($bytes) {
- $parser = id(new AphrontHTTPHeaderParser())
- ->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 @@
-<?php
-
-final class AphrontMultipartParserTestCase extends PhutilTestCase {
-
- public function testParser() {
- $map = array(
- array(
- 'data' => '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 @@
-<?php
-
-final class AphrontRequestStream extends Phobject {
-
- private $encoding;
- private $stream;
- private $closed;
- private $iterator;
-
- public function setEncoding($encoding) {
- $this->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 @@
-<?php
-
-final class AphrontScopedUnguardedWriteCapability extends Phobject {
-
- public function __destruct() {
- AphrontWriteGuard::endUnguardedWrites();
- }
-
-}
diff --git a/src/aphront/writeguard/AphrontWriteGuard.php b/src/aphront/writeguard/AphrontWriteGuard.php
deleted file mode 100644
--- a/src/aphront/writeguard/AphrontWriteGuard.php
+++ /dev/null
@@ -1,267 +0,0 @@
-<?php
-
-/**
- * Guard writes against CSRF. The Aphront structure takes care of most of this
- * for you, you just need to call:
- *
- * AphrontWriteGuard::willWrite();
- *
- * ...before executing a write against any new kind of storage engine. MySQL
- * databases and the default file storage engines are already covered, but if
- * you introduce new types of datastores make sure their writes are guarded. If
- * you don't guard writes and make a mistake doing CSRF checks in a controller,
- * a CSRF vulnerability can escape undetected.
- *
- * If you need to execute writes on a page which doesn't have CSRF tokens (for
- * example, because you need to do logging), you can temporarily disable the
- * write guard by calling:
- *
- * AphrontWriteGuard::beginUnguardedWrites();
- * do_logging_write();
- * AphrontWriteGuard::endUnguardedWrites();
- *
- * This is dangerous, because it disables the backup layer of CSRF protection
- * this class provides. You should need this only very, very rarely.
- *
- * @task protect Protecting Writes
- * @task disable Disabling Protection
- * @task manage Managing Write Guards
- * @task internal Internals
- */
-final class AphrontWriteGuard extends Phobject {
-
- private static $instance;
- private static $allowUnguardedWrites = false;
-
- private $callback;
- private $allowDepth = 0;
-
-
-/* -( Managing Write Guards )---------------------------------------------- */
-
-
- /**
- * Construct a new write guard for a request. Only one write guard may be
- * active at a time. You must explicitly call @{method:dispose} when you are
- * done with a write guard:
- *
- * $guard = new AphrontWriteGuard($callback);
- * // ...
- * $guard->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;
- }
-
-}

File Metadata

Mime Type
text/plain
Expires
Mon, Nov 25, 9:13 AM (20 h, 44 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6785537
Default Alt Text
D20974.id49974.diff (31 KB)

Event Timeline