Page MenuHomestyx hydra

No OneTemporary

diff --git a/conf/default.conf.php b/conf/default.conf.php
index 3261b22c6a..5c9ff4ba91 100644
--- a/conf/default.conf.php
+++ b/conf/default.conf.php
@@ -1,237 +1,257 @@
<?php
/*
* Copyright 2011 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.
*/
return array(
// The root URI which Phabricator is installed on.
// Example: "http://phabricator.example.com/"
'phabricator.base-uri' => null,
// The Conduit URI for API access to this install. Normally this is just
// the 'base-uri' plus "/api/" (e.g. "http://phabricator.example.com/api/"),
// but make sure you specify 'https' if you have HTTPS configured.
'phabricator.conduit-uri' => null,
// The default PHID for users who haven't uploaded a profile image. It should
// be 50x50px.
'user.default-profile-image-phid' => 'PHID-FILE-f57aaefce707fc4060ef',
// -- Access Control -------------------------------------------------------- //
// Phabricator users have one of three access levels: "anyone", "verified",
// or "admin". "anyone" means every user, including users who do not have
// accounts or are not logged into the system. "verified" is users who have
// accounts, are logged in, and have satisfied whatever verification steps
// the configuration requires (e.g., email verification and/or manual
// approval). "admin" is verified users with the "administrator" flag set.
// These configuration options control which access level is required to read
// data from Phabricator (e.g., view revisions and comments in Differential)
// and write data to Phabricator (e.g., upload files and create diffs). By
// default they are both set to "verified", meaning only verified user
// accounts can interact with the system in any meaningful way.
// If you are configuring an install for an open source project, you may
// want to reduce the "phabricator.read-access" requirement to "anyone". This
// will allow anyone to browse Phabricator content, even without logging in.
// Alternatively, you could raise the "phabricator.write-access" requirement
// to "admin", effectively creating a read-only install.
// Controls the minimum access level required to read data from Phabricator
// (e.g., view revisions in Differential). Allowed values are "anyone",
// "verified", or "admin". Note that "anyone" includes users who are not
// logged in! You should leave this at 'verified' unless you want your data
// to be publicly readable (e.g., you are developing open source software).
'phabricator.read-access' => 'verified',
// Controls the minimum access level required to write data to Phabricator
// (e.g., create new revisions in Differential). Allowed values are
// "verified" or "admin". Setting this to "admin" will effectively create a
// read-only install.
'phabricator.write-access' => 'verified',
// -- DarkConsole ----------------------------------------------------------- //
// DarkConsole is a administrative debugging/profiling tool built into
// Phabricator. You can leave it disabled unless you're developing against
// Phabricator.
// Determines whether or not DarkConsole is available. DarkConsole exposes
// some data like queries and stack traces, so you should be careful about
// turning it on in production (although users can not normally see it, even
// if the deployment configuration enables it).
'darkconsole.enabled' => true,
// Always enable DarkConsole, even for logged out users. This potentially
// exposes sensitive information to users, so make sure untrusted users can
// not access an install running in this mode. You should definitely leave
// this off in production. It is only really useful for using DarkConsole
// utilties to debug or profile logged-out pages. You must set
// 'darkconsole.enabled' to use this option.
'darkconsole.always-on' => false,
// Allows you to mask certain configuration values from appearing in the
// "Config" tab of DarkConsole.
'darkconsole.config-mask' => array(
'mysql.pass',
'amazon-ses.secret-key',
'recaptcha.private-key',
'phabricator.csrf-key',
'facebook.application-secret',
'github.secret',
),
// -- MySQL --------------------------------------------------------------- //
// The username to use when connecting to MySQL.
'mysql.user' => 'root',
// The password to use when connecting to MySQL.
'mysql.pass' => '',
// The MySQL server to connect to.
'mysql.host' => 'localhost',
// -- Email ----------------------------------------------------------------- //
// Some Phabricator tools send email notifications, e.g. when Differential
// revisions are updated or Maniphest tasks are changed. These options allow
// you to configure how email is delivered.
// You can test your mail setup by going to "MetaMTA" in the web interface,
// clicking "Send New Message", and then composing a message.
// Default address to send mail "From".
'metamta.default-address' => 'noreply@example.com',
// When a user takes an action which generates an email notification (like
// commenting on a Differential revision), Phabricator can either send that
// mail "From" the user's email address (like "alincoln@logcabin.com") or
// "From" the 'metamta.default-address' address. The user experience is
// generally better if Phabricator uses the user's real address as the "From"
// since the messages are easier to organize when they appear in mail clients,
// but this will only work if the server is authorized to send email on behalf
// of the "From" domain. Practically, this means:
// - If you are doing an install for Example Corp and all the users will
// have corporate @corp.example.com addresses and any hosts Phabricator
// is running on are authorized to send email from corp.example.com,
// you can enable this to make the user experience a little better.
// - If you are doing an install for an open source project and your
// users will be registering via Facebook and using personal email
// addresses, you MUST NOT enable this or virtually all of your outgoing
// email will vanish into SFP blackholes.
// - If your install is anything else, you're much safer leaving this
// off since the risk in turning it on is that your outgoing mail will
// mostly never arrive.
'metamta.can-send-as-user' => false,
// Adapter class to use to transmit mail to the MTA. The default uses
// PHPMailerLite, which will invoke PHP's mail() function. This is appropriate
// if mail() actually works on your host, but if you haven't configured mail
// it may not be so great. You can also use Amazon SES, by changing this to
// 'PhabricatorMailImplementationAmazonSESAdapter', signing up for SES, and
// filling in your 'amazon-ses.access-key' and 'amazon-ses.secret-key' below.
'metamta.mail-adapter' =>
'PhabricatorMailImplementationPHPMailerLiteAdapter',
// When email is sent, try to hand it off to the MTA immediately. This may
// be worth disabling if your MTA infrastructure is slow or unreliable. If you
// disable this option, you must run the 'metamta_mta.php' daemon or mail
// won't be handed off to the MTA. If you're using Amazon SES it can be a
// little slugish sometimes so it may be worth disabling this and moving to
// the daemon after you've got your install up and running. If you have a
// properly configured local MTA it should not be necessary to disable this.
'metamta.send-immediately' => true,
// If you're using Amazon SES to send email, provide your AWS access key
// and AWS secret key here. To set up Amazon SES with Phabricator, you need
// to:
// - Make sure 'metamta.mail-adapter' is set to:
// "PhabricatorMailImplementationAmazonSESAdapter"
// - Make sure 'metamta.can-send-as-user' is false.
// - Make sure 'metamta.default-address' is configured to something sensible.
// - Make sure 'metamta.default-address' is a validated SES "From" address.
'amazon-ses.access-key' => null,
'amazon-ses.secret-key' => null,
// -- Facebook ------------------------------------------------------------ //
// Can users use Facebook credentials to login to Phabricator?
'facebook.auth-enabled' => false,
// The Facebook "Application ID" to use for Facebook API access.
'facebook.application-id' => null,
// The Facebook "Application Secret" to use for Facebook API access.
'facebook.application-secret' => null,
// -- Github ---------------------------------------------------------------- //
// Can users use Github credentials to login to Phabricator?
'github.auth-enabled' => false,
// The Github "Client ID" to use for Github API access.
'github.application-id' => null,
// The Github "Secret" to use for Github API access.
'github.application-secret' => null,
// Github Authorize URI. You don't need to change this unless Github changes
// its API in the future (this is unlikely).
'github.authorize-uri' => 'https://github.com/login/oauth/authorize',
// Github Access Token URI. You don't need to change this unless Github
// changes its API in the future (this is unlikely).
'github.access-token-uri' => 'https://github.com/login/oauth/access_token',
// -- Recaptcha ------------------------------------------------------------- //
// Is Recaptcha enabled? If disabled, captchas will not appear.
'recaptcha.enabled' => false,
// Your Recaptcha public key, obtained from Recaptcha.
'recaptcha.public-key' => null,
// Your Recaptcha private key, obtained from Recaptcha.
'recaptcha.private-key' => null,
// -- Misc ------------------------------------------------------------------ //
// This is hashed with other inputs to generate CSRF tokens. If you want, you
// can change it to some other string which is unique to your install. This
// will make your install more secure in a vague, mostly theoretical way. But
// it will take you like 3 seconds of mashing on your keyboard to set it up so
// you might as well.
'phabricator.csrf-key' => '0b7ec0592e0a2829d8b71df2fa269b2c6172eca3',
// Version string displayed in the footer. You probably should leave this
// alone.
'phabricator.version' => 'UNSTABLE',
+
+
+// -- Files ----------------------------------------------------------------- //
+
+ // Lists which uploaded file types may be viewed in the browser. If a file
+ // has a mime type which does not appear in this list, it will always be
+ // downloaded instead of displayed. This is a security consideration: if a
+ // user uploads a file of type "text/html" and it is displayed as
+ // "text/html", they can eaily execute XSS attacks. This is also a usability
+ // consideration, since browsers tend to freak out when viewing enormous
+ // binary files.
+ //
+ // The keys in this array are viewable mime types; the values are the mime
+ // types they will be delivered as when they are viewed in the browser.
+ 'files.viewable-mime-types' => array(
+ 'image/jpeg' => 'image/jpeg',
+ 'image/jpg' => 'image/jpg',
+ 'image/png' => 'image/png',
+ 'text/plain' => 'text/plain; charset=utf-8',
+ ),
);
diff --git a/src/aphront/response/file/AphrontFileResponse.php b/src/aphront/response/file/AphrontFileResponse.php
index 0e60435951..4bd2831afd 100644
--- a/src/aphront/response/file/AphrontFileResponse.php
+++ b/src/aphront/response/file/AphrontFileResponse.php
@@ -1,70 +1,82 @@
<?php
/*
* Copyright 2011 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.
*/
/**
* @group aphront
*/
class AphrontFileResponse extends AphrontResponse {
private $content;
private $mimeType;
private $download;
public function setDownload($download) {
+ $download = preg_replace('/[^A-Za-z0-9_.-]/', '_', $download);
+ if (!strlen($download)) {
+ $download = 'untitled_document.txt';
+ }
$this->download = $download;
return $this;
}
public function getDownload() {
return $this->download;
}
public function setMimeType($mime_type) {
$this->mimeType = $mime_type;
return $this;
}
public function getMimeType() {
return $this->mimeType;
}
public function setContent($content) {
$this->content = $content;
return $this;
}
public function buildResponseString() {
return $this->content;
}
public function getHeaders() {
$headers = array(
array('Content-Type', $this->getMimeType()),
+ // Without this, IE can decide that we surely meant "text/html" when
+ // delivering another content type since, you know, it looks like it's
+ // probably an HTML document. This closes the security hole that policy
+ // creates.
+ array('X-Content-Type-Options', 'nosniff'),
);
- if ($this->getDownload()) {
+ if (strlen($this->getDownload())) {
+ $headers[] = array('X-Download-Options', 'noopen');
+
+ $filename = $this->getDownload();
$headers[] = array(
'Content-Disposition',
- 'attachment; filename='.$this->getDownload(),
+ 'attachment; filename='.$filename,
);
}
return $headers;
}
}
diff --git a/src/applications/files/controller/list/PhabricatorFileListController.php b/src/applications/files/controller/list/PhabricatorFileListController.php
index ac2edab897..8e4435baf4 100644
--- a/src/applications/files/controller/list/PhabricatorFileListController.php
+++ b/src/applications/files/controller/list/PhabricatorFileListController.php
@@ -1,85 +1,90 @@
<?php
/*
* Copyright 2011 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.
*/
class PhabricatorFileListController extends PhabricatorFileController {
public function processRequest() {
$files = id(new PhabricatorFile())->loadAllWhere(
'1 = 1 ORDER BY id DESC LIMIT 100');
$rows = array();
foreach ($files as $file) {
+ if ($file->isViewableInBrowser()) {
+ $view_button = phutil_render_tag(
+ 'a',
+ array(
+ 'class' => 'small button grey',
+ 'href' => '/file/view/'.$file->getPHID().'/',
+ ),
+ 'View');
+ } else {
+ $view_button = null;
+ }
$rows[] = array(
phutil_escape_html($file->getPHID()),
phutil_escape_html($file->getName()),
phutil_escape_html($file->getByteSize()),
phutil_render_tag(
'a',
array(
'class' => 'small button grey',
'href' => '/file/info/'.$file->getPHID().'/',
),
'Info'),
- phutil_render_tag(
- 'a',
- array(
- 'class' => 'small button grey',
- 'href' => '/file/view/'.$file->getPHID().'/',
- ),
- 'View'),
+ $view_button,
phutil_render_tag(
'a',
array(
'class' => 'small button grey',
'href' => '/file/download/'.$file->getPHID().'/',
),
'Download'),
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'PHID',
'Name',
'Size',
'',
'',
'',
));
$table->setColumnClasses(
array(
null,
'wide',
null,
'action',
'action',
'action',
));
$panel = new AphrontPanelView();
$panel->appendChild($table);
$panel->setHeader('Files');
$panel->setCreateButton('Upload File', '/file/upload/');
return $this->buildStandardPageResponse($panel, array(
'title' => 'Files',
'tab' => 'files',
));
}
}
diff --git a/src/applications/files/controller/view/PhabricatorFileViewController.php b/src/applications/files/controller/view/PhabricatorFileViewController.php
index 38e9b92654..3dd93248c1 100644
--- a/src/applications/files/controller/view/PhabricatorFileViewController.php
+++ b/src/applications/files/controller/view/PhabricatorFileViewController.php
@@ -1,113 +1,137 @@
<?php
/*
* Copyright 2011 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.
*/
class PhabricatorFileViewController extends PhabricatorFileController {
private $phid;
private $view;
public function willProcessRequest(array $data) {
$this->phid = $data['phid'];
$this->view = $data['view'];
}
public function processRequest() {
$file = id(new PhabricatorFile())->loadOneWhere(
'phid = %s',
$this->phid);
if (!$file) {
return new Aphront404Response();
}
-
+
switch ($this->view) {
case 'download':
case 'view':
$data = $file->loadFileData();
$response = new AphrontFileResponse();
$response->setContent($data);
- $response->setMimeType($file->getMimeType());
- if ($this->view == 'download') {
+
+ if ($this->view == 'view') {
+ if (!$file->isViewableInBrowser()) {
+ return new Aphront400Response();
+ }
+ $download = false;
+ } else {
+ $download = true;
+ }
+
+ if ($download) {
+ $mime_type = $file->getMimeType();
+ } else {
+ $mime_type = $file->getViewableMimeType();
+ }
+
+ $response->setMimeType($mime_type);
+
+ if ($download) {
$response->setDownload($file->getName());
}
return $response;
default:
break;
}
$form = new AphrontFormView();
- $form->setAction('/file/view/'.$file->getPHID().'/');
+
+ if ($file->isViewableInBrowser()) {
+ $form->setAction('/file/view/'.$file->getPHID().'/');
+ $button_name = 'View File';
+ } else {
+ $form->setAction('/file/download/'.$file->getPHID().'/');
+ $button_name = 'Download File';
+ }
$form->setUser($this->getRequest()->getUser());
$form
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Name')
->setName('name')
->setValue($file->getName()))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('PHID')
->setName('phid')
->setValue($file->getPHID()))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Created')
->setName('created')
->setValue(date('Y-m-d g:i:s A', $file->getDateCreated())))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Mime Type')
->setName('mime')
->setValue($file->getMimeType()))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Size')
->setName('size')
->setValue($file->getByteSize().' bytes'))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Engine')
->setName('storageEngine')
->setValue($file->getStorageEngine()))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Format')
->setName('storageFormat')
->setValue($file->getStorageFormat()))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Handle')
->setName('storageHandle')
->setValue($file->getStorageHandle()))
->appendChild(
id(new AphrontFormSubmitControl())
- ->setValue('View File'));
+ ->setValue($button_name));
$panel = new AphrontPanelView();
$panel->setHeader('File Info - '.$file->getName());
$panel->appendChild($form);
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
return $this->buildStandardPageResponse(
array($panel),
array(
'title' => 'File Info - '.$file->getName(),
));
}
}
diff --git a/src/applications/files/storage/file/PhabricatorFile.php b/src/applications/files/storage/file/PhabricatorFile.php
index d2ab45826e..e365706304 100644
--- a/src/applications/files/storage/file/PhabricatorFile.php
+++ b/src/applications/files/storage/file/PhabricatorFile.php
@@ -1,178 +1,192 @@
<?php
/*
* Copyright 2011 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.
*/
class PhabricatorFile extends PhabricatorFileDAO {
const STORAGE_ENGINE_BLOB = 'blob';
const STORAGE_FORMAT_RAW = 'raw';
const PHID_TYPE = 'FILE';
// TODO: We need to reconcile this with MySQL packet size.
const FILE_SIZE_BYTE_LIMIT = 12582912;
protected $phid;
protected $name;
protected $mimeType;
protected $byteSize;
protected $storageEngine;
protected $storageFormat;
protected $storageHandle;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(self::PHID_TYPE);
}
public static function newFromPHPUpload($spec, array $params = array()) {
if (!$spec) {
throw new Exception("No file was uploaded!");
}
$err = idx($spec, 'error');
if ($err) {
throw new Exception("File upload failed with error '{$err}'.");
}
$tmp_name = idx($spec, 'tmp_name');
$is_valid = @is_uploaded_file($tmp_name);
if (!$is_valid) {
throw new Exception("File is not an uploaded file.");
}
$file_data = Filesystem::readFile($tmp_name);
$file_size = idx($spec, 'size');
if (strlen($file_data) != $file_size) {
throw new Exception("File size disagrees with uploaded size.");
}
$file_name = nonempty(
idx($params, 'name'),
idx($spec, 'name'));
$params = array(
'name' => $file_name,
) + $params;
return self::newFromFileData($file_data, $params);
}
public static function newFromFileData($data, array $params = array()) {
$file_size = strlen($data);
if ($file_size > self::FILE_SIZE_BYTE_LIMIT) {
throw new Exception("File is too large to store.");
}
$file_name = idx($params, 'name');
$file_name = self::normalizeFileName($file_name);
$file = new PhabricatorFile();
$file->setName($file_name);
$file->setByteSize(strlen($data));
$blob = new PhabricatorFileStorageBlob();
$blob->setData($data);
$blob->save();
// TODO: This stuff is almost certainly YAGNI, but we could imagine having
// an alternate disk store and gzipping or encrypting things or something
// crazy like that and this isn't toooo much extra code.
$file->setStorageEngine(self::STORAGE_ENGINE_BLOB);
$file->setStorageFormat(self::STORAGE_FORMAT_RAW);
$file->setStorageHandle($blob->getID());
if (isset($params['mime-type'])) {
$file->setMimeType($params['mime-type']);
} else {
try {
$tmp = new TempFile();
Filesystem::writeFile($tmp, $data);
list($stdout) = execx('file -b --mime %s', $tmp);
$file->setMimeType($stdout);
} catch (Exception $ex) {
// Be robust here since we don't really care that much about mime types.
}
}
$file->save();
return $file;
}
public static function normalizeFileName($file_name) {
return preg_replace('/[^a-zA-Z0-9.~_-]/', '_', $file_name);
}
public function delete() {
$this->openTransaction();
switch ($this->getStorageEngine()) {
case self::STORAGE_ENGINE_BLOB:
$handle = $this->getStorageHandle();
$blob = id(new PhabricatorFileStorageBlob())->load($handle);
$blob->delete();
break;
default:
throw new Exception("Unknown storage engine!");
}
$ret = parent::delete();
$this->saveTransaction();
return $ret;
}
public function loadFileData() {
$handle = $this->getStorageHandle();
$data = null;
switch ($this->getStorageEngine()) {
case self::STORAGE_ENGINE_BLOB:
$blob = id(new PhabricatorFileStorageBlob())->load($handle);
if (!$blob) {
throw new Exception("Failed to load file blob data.");
}
$data = $blob->getData();
break;
default:
throw new Exception("Unknown storage engine.");
}
switch ($this->getStorageFormat()) {
case self::STORAGE_FORMAT_RAW:
$data = $data;
break;
default:
throw new Exception("Unknown storage format.");
}
return $data;
}
public function getViewURI() {
return PhabricatorFileURI::getViewURIForPHID($this->getPHID());
}
+
+ public function isViewableInBrowser() {
+ return ($this->getViewableMimeType() !== null);
+ }
+
+ public function getViewableMimeType() {
+ $mime_map = PhabricatorEnv::getEnvConfig('files.viewable-mime-types');
+
+ $mime_type = $this->getMimeType();
+ $mime_parts = explode(';', $mime_type);
+ $mime_type = reset($mime_parts);
+
+ return idx($mime_map, $mime_type);
+ }
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 14, 3:30 AM (1 d, 3 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
336747
Default Alt Text
(27 KB)

Event Timeline