Page MenuHomePhorge

No OneTemporary

diff --git a/src/infrastructure/PhabricatorRequestOverseer.php b/src/infrastructure/PhabricatorRequestOverseer.php
index dfa099a8c3..6ae4b8bdbc 100644
--- a/src/infrastructure/PhabricatorRequestOverseer.php
+++ b/src/infrastructure/PhabricatorRequestOverseer.php
@@ -1,113 +1,122 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorRequestOverseer {
public function didStartup() {
$this->detectPostMaxSizeTriggered();
}
/**
* Detect if this request has had its POST data stripped by exceeding the
* 'post_max_size' PHP configuration limit.
*
* PHP has a setting called 'post_max_size'. If a POST request arrives with
* a body larger than the limit, PHP doesn't generate $_POST but processes
* the request anyway, and provides no formal way to detect that this
* happened.
*
- * We can still read the entire body out of `php://input`. However, this
- * stream can't be rewound, and according to the documentation isn't available
- * for "multipart/form-data" (on nginx + php-fpm it appears that it is
- * available, though, at least) so any attempt to generate $_POST would create
- * side effects and be fragile.
+ * We can still read the entire body out of `php://input`. However according
+ * to the documentation the stream isn't available for "multipart/form-data"
+ * (on nginx + php-fpm it appears that it is available, though, at least) so
+ * any attempt to generate $_POST would be fragile.
*/
private function detectPostMaxSizeTriggered() {
// If this wasn't a POST, we're fine.
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
return;
}
// If there's POST data, clearly we're in good shape.
if ($_POST) {
return;
}
+ // For HTML5 drag-and-drop file uploads, Safari submits the data as
+ // "application/x-www-form-urlencoded". For most files this generates
+ // something in POST because most files decode to some nonempty (albeit
+ // meaningless) value. However, some files (particularly small images)
+ // don't decode to anything. If we know this is a drag-and-drop upload,
+ // we can skip this check.
+ if (isset($_REQUEST['__upload__'])) {
+ return;
+ }
+
// PHP generates $_POST only for two content types. This routing happens
// in `main/php_content_types.c` in PHP. Normally, all forms use one of
// these content types, but some requests may not -- for example, Firefox
// submits files sent over HTML5 XMLHTTPRequest APIs with the Content-Type
// of the file itself. If we don't have a recognized content type, we
// don't need $_POST.
//
// NOTE: We use strncmp() because the actual content type may be something
// like "multipart/form-data; boundary=...".
//
// NOTE: Chrome sometimes omits this header, see some discussion in T1762
// and http://code.google.com/p/chromium/issues/detail?id=6800
$content_type = idx($_SERVER, 'CONTENT_TYPE', '');
$parsed_types = array(
'application/x-www-form-urlencoded',
'multipart/form-data',
);
$is_parsed_type = false;
foreach ($parsed_types as $parsed_type) {
if (strncmp($content_type, $parsed_type, strlen($parsed_type)) === 0) {
$is_parsed_type = true;
break;
}
}
if (!$is_parsed_type) {
return;
}
// Check for 'Content-Length'. If there's no data, we don't expect $_POST
// to exist.
$length = (int)$_SERVER['CONTENT_LENGTH'];
if (!$length) {
return;
}
// Time to fatal: we know this was a POST with data that should have been
// populated into $_POST, but it wasn't.
$config = ini_get('post_max_size');
$this->fatal(
"As received by the server, this request had a nonzero content length ".
"but no POST data.\n\n".
"Normally, this indicates that it exceeds the 'post_max_size' setting ".
"in the PHP configuration on the server. Increase the 'post_max_size' ".
"setting or reduce the size of the request.\n\n".
"Request size according to 'Content-Length' was '{$length}', ".
"'post_max_size' is set to '{$config}'.");
}
/**
* Defined in webroot/index.php.
* TODO: Move here.
*
* @phutil-external-symbol function phabricator_fatal
*/
public function fatal($message) {
phabricator_fatal('FATAL ERROR: '.$message);
}
}
diff --git a/webroot/rsrc/js/application/core/DragAndDropFileUpload.js b/webroot/rsrc/js/application/core/DragAndDropFileUpload.js
index 00c3c05ed9..d77e7c2986 100644
--- a/webroot/rsrc/js/application/core/DragAndDropFileUpload.js
+++ b/webroot/rsrc/js/application/core/DragAndDropFileUpload.js
@@ -1,172 +1,173 @@
/**
* @requires javelin-install
* javelin-util
* javelin-request
* javelin-dom
* javelin-uri
* phabricator-file-upload
* @provides phabricator-drag-and-drop-file-upload
* @javelin
*/
JX.install('PhabricatorDragAndDropFileUpload', {
construct : function(node) {
this._node = node;
},
events : ['willUpload', 'progress', 'didUpload', 'didError'],
statics : {
isSupported : function() {
// TODO: Is there a better capability test for this? This seems okay in
// Safari, Firefox and Chrome.
return !!window.FileList;
}
},
members : {
_node : null,
_depth : 0,
_updateDepth : function(delta) {
this._depth += delta;
JX.DOM.alterClass(
this._node,
this.getActivatedClass(),
(this._depth > 0));
},
start : function() {
// TODO: move this to JX.DOM.contains()?
function contains(container, child) {
do {
if (child === container) {
return true;
}
child = child.parentNode;
} while (child);
return false;
}
// We track depth so that the _node may have children inside of it and
// not become unselected when they are dragged over.
JX.DOM.listen(
this._node,
'dragenter',
null,
JX.bind(this, function(e) {
if (contains(this._node, e.getTarget())) {
this._updateDepth(1);
}
}));
JX.DOM.listen(
this._node,
'dragleave',
null,
JX.bind(this, function(e) {
if (contains(this._node, e.getTarget())) {
this._updateDepth(-1);
}
}));
JX.DOM.listen(
this._node,
'dragover',
null,
function(e) {
e.kill();
});
JX.DOM.listen(
this._node,
'drop',
null,
JX.bind(this, function(e) {
e.kill();
var files = e.getRawEvent().dataTransfer.files;
for (var ii = 0; ii < files.length; ii++) {
this._sendRequest(files[ii]);
}
// Force depth to 0.
this._updateDepth(-this._depth);
}));
},
_sendRequest : function(spec) {
var file = new JX.PhabricatorFileUpload()
.setName(spec.name)
.setTotalBytes(spec.size)
.setStatus('uploading')
.update();
this.invoke('willUpload', file);
var up_uri = JX.$U(this.getURI())
.setQueryParam('name', file.getName())
+ .setQueryParam('__upload__', 1)
.toString();
var onupload = JX.bind(this, function(r) {
if (r.error) {
file
.setStatus('error')
.setError(r.error)
.update();
this.invoke('didError', file);
} else {
file
.setID(r.id)
.setPHID(r.phid)
.setURI(r.uri)
.setMarkup(r.html)
.setStatus('done')
.update();
this.invoke('didUpload', file);
}
});
var req = new JX.Request(up_uri, onupload);
var onerror = JX.bind(this, function(error) {
file.setStatus('error');
if (error) {
file.setError(error.code + ': ' + error.info);
} else {
var xhr = req.getTransport();
if (xhr.responseText) {
file.setError('Server responded: ' + xhr.responseText);
}
}
file.update();
this.invoke('didError', file);
});
var onprogress = JX.bind(this, function(progress) {
file
.setTotalBytes(progress.total)
.setUploadedBytes(progress.loaded)
.update();
this.invoke('progress', file);
});
req.listen('error', onerror);
req.listen('uploadprogress', onprogress);
req
.setRawData(spec)
.send();
}
},
properties: {
URI : null,
activatedClass : null
}
});
diff --git a/webroot/rsrc/js/application/core/PasteFileUpload.js b/webroot/rsrc/js/application/core/PasteFileUpload.js
index 2869087665..d3e7c79703 100644
--- a/webroot/rsrc/js/application/core/PasteFileUpload.js
+++ b/webroot/rsrc/js/application/core/PasteFileUpload.js
@@ -1,68 +1,69 @@
/**
* @requires javelin-install
* javelin-util
* javelin-request
* javelin-dom
* javelin-uri
* @provides phabricator-paste-file-upload
* @javelin
*/
JX.install('PhabricatorPasteFileUpload', {
construct : function(node) {
this._node = node;
},
events : ['willUpload', 'didUpload'],
statics : {
isSupported : function() {
// TODO: Needs to check if event.clipboardData is available.
// Works in Chrome, doesn't work in Firefox 10.
return !!window.FileList;
}
},
members : {
_node : null,
start : function() {
JX.DOM.listen(
this._node,
'paste',
null,
JX.bind(this, function(e) {
var clipboardData = e.getRawEvent().clipboardData;
if (!clipboardData) {
return;
}
for (var ii = 0; ii < clipboardData.types.length; ii++) {
if (/^image\//.test(clipboardData.types[ii])) {
var file = clipboardData.items[ii].getAsFile();
this.invoke('willUpload', file);
var up_uri = JX.$U(this.getURI())
.setQueryParam('name', 'clipboard.png')
+ .setQueryParam('__upload__', 1)
.toString();
new JX.Request(up_uri, JX.bind(this, function(r) {
this.invoke('didUpload', r);
}))
.setRawData(file)
.send();
e.kill();
break;
}
}
}));
}
},
properties: {
URI : null
}
});

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 15:20 (3 w, 1 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1125937
Default Alt Text
(11 KB)

Event Timeline