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);
+  }
 
 }