[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/files/controller/ -> PhabricatorFileDataController.php (source)

   1  <?php
   2  
   3  final class PhabricatorFileDataController extends PhabricatorFileController {
   4  
   5    private $phid;
   6    private $key;
   7    private $token;
   8  
   9    public function willProcessRequest(array $data) {
  10      $this->phid = $data['phid'];
  11      $this->key  = $data['key'];
  12      $this->token = idx($data, 'token');
  13    }
  14  
  15    public function shouldRequireLogin() {
  16      return false;
  17    }
  18  
  19    protected function checkFileAndToken($file) {
  20      if (!$file) {
  21        return new Aphront404Response();
  22      }
  23  
  24      if (!$file->validateSecretKey($this->key)) {
  25        return new Aphront403Response();
  26      }
  27  
  28      return null;
  29    }
  30  
  31    public function processRequest() {
  32      $request = $this->getRequest();
  33  
  34      $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain');
  35      $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
  36      $alt_uri = new PhutilURI($alt);
  37      $alt_domain = $alt_uri->getDomain();
  38      $req_domain = $request->getHost();
  39      $main_domain = id(new PhutilURI($base_uri))->getDomain();
  40  
  41      $cache_response = true;
  42  
  43      if (empty($alt) || $main_domain == $alt_domain) {
  44        // Alternate files domain isn't configured or it's set
  45        // to the same as the default domain
  46  
  47        // load the file with permissions checks;
  48        $file = id(new PhabricatorFileQuery())
  49          ->setViewer($request->getUser())
  50          ->withPHIDs(array($this->phid))
  51          ->executeOne();
  52  
  53        $error_response = $this->checkFileAndToken($file);
  54        if ($error_response) {
  55          return $error_response;
  56        }
  57  
  58        // when the file is not CDNable, don't allow cache
  59        $cache_response = $file->getCanCDN();
  60      } else if ($req_domain != $alt_domain) {
  61        // Alternate domain is configured but this request isn't using it
  62  
  63        // load the file with permissions checks;
  64        $file = id(new PhabricatorFileQuery())
  65          ->setViewer($request->getUser())
  66          ->withPHIDs(array($this->phid))
  67          ->executeOne();
  68  
  69        $error_response = $this->checkFileAndToken($file);
  70        if ($error_response) {
  71          return $error_response;
  72        }
  73  
  74        // if the user can see the file, generate a token;
  75        // redirect to the alt domain with the token;
  76        return id(new AphrontRedirectResponse())
  77          ->setIsExternal(true)
  78          ->setURI($file->getCDNURIWithToken());
  79  
  80      } else {
  81        // We are using the alternate domain
  82  
  83        // load the file, bypassing permission checks;
  84        $file = id(new PhabricatorFileQuery())
  85          ->setViewer(PhabricatorUser::getOmnipotentUser())
  86          ->withPHIDs(array($this->phid))
  87          ->executeOne();
  88  
  89        $error_response = $this->checkFileAndToken($file);
  90        if ($error_response) {
  91          return $error_response;
  92        }
  93  
  94        $acquire_token_uri = id(new PhutilURI($file->getViewURI()))
  95          ->setDomain($main_domain);
  96  
  97  
  98        if ($this->token) {
  99          // validate the token, if it is valid, continue
 100          $validated_token = $file->validateOneTimeToken($this->token);
 101  
 102          if (!$validated_token) {
 103            $dialog = $this->newDialog()
 104              ->setShortTitle(pht('Expired File'))
 105              ->setTitle(pht('File Link Has Expired'))
 106              ->appendParagraph(
 107                pht(
 108                  'The link you followed to view this file is invalid or '.
 109                  'expired.'))
 110              ->appendParagraph(
 111                pht(
 112                  'Continue to generate a new link to the file. You may be '.
 113                  'required to log in.'))
 114              ->addCancelButton(
 115                $acquire_token_uri,
 116                pht('Continue'));
 117  
 118            // Build an explicit response so we can respond with HTTP/403 instead
 119            // of HTTP/200.
 120            $response = id(new AphrontDialogResponse())
 121              ->setDialog($dialog)
 122              ->setHTTPResponseCode(403);
 123  
 124            return $response;
 125          }
 126          // return the file data without cache headers
 127          $cache_response = false;
 128        } else if (!$file->getCanCDN()) {
 129          // file cannot be served via cdn, and no token given
 130          // redirect to the main domain to aquire a token
 131  
 132          // This is marked as an "external" URI because it is fully qualified.
 133          return id(new AphrontRedirectResponse())
 134            ->setIsExternal(true)
 135            ->setURI($acquire_token_uri);
 136        }
 137      }
 138  
 139      $data = $file->loadFileData();
 140      $response = new AphrontFileResponse();
 141      $response->setContent($data);
 142      if ($cache_response) {
 143        $response->setCacheDurationInSeconds(60 * 60 * 24 * 30);
 144      }
 145  
 146      // NOTE: It's important to accept "Range" requests when playing audio.
 147      // If we don't, Safari has difficulty figuring out how long sounds are
 148      // and glitches when trying to loop them. In particular, Safari sends
 149      // an initial request for bytes 0-1 of the audio file, and things go south
 150      // if we can't respond with a 206 Partial Content.
 151      $range = $request->getHTTPHeader('range');
 152      if ($range) {
 153        $matches = null;
 154        if (preg_match('/^bytes=(\d+)-(\d+)$/', $range, $matches)) {
 155          $response->setHTTPResponseCode(206);
 156          $response->setRange((int)$matches[1], (int)$matches[2]);
 157        }
 158      } else if (isset($validated_token)) {
 159        // consume the one-time token if we have one.
 160        $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
 161          $validated_token->delete();
 162        unset($unguarded);
 163      }
 164  
 165      $is_viewable = $file->isViewableInBrowser();
 166      $force_download = $request->getExists('download');
 167  
 168      if ($is_viewable && !$force_download) {
 169        $response->setMimeType($file->getViewableMimeType());
 170      } else {
 171        if (!$request->isHTTPPost() && !$alt_domain) {
 172          // NOTE: Require POST to download files from the primary domain. We'd
 173          // rather go full-bore and do a real CSRF check, but can't currently
 174          // authenticate users on the file domain. This should blunt any
 175          // attacks based on iframes, script tags, applet tags, etc., at least.
 176          // Send the user to the "info" page if they're using some other method.
 177  
 178          // This is marked as "external" because it is fully qualified.
 179          return id(new AphrontRedirectResponse())
 180            ->setIsExternal(true)
 181            ->setURI(PhabricatorEnv::getProductionURI($file->getBestURI()));
 182        }
 183        $response->setMimeType($file->getMimeType());
 184        $response->setDownload($file->getName());
 185      }
 186  
 187      return $response;
 188    }
 189  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1