Page MenuHomePhorge

No OneTemporary

diff --git a/src/aphront/response/AphrontFileResponse.php b/src/aphront/response/AphrontFileResponse.php
index 01288c50a4..08f3b82cb5 100644
--- a/src/aphront/response/AphrontFileResponse.php
+++ b/src/aphront/response/AphrontFileResponse.php
@@ -1,63 +1,83 @@
<?php
/**
* @group aphront
*/
final class AphrontFileResponse extends AphrontResponse {
private $content;
private $mimeType;
private $download;
+ private $rangeMin;
+ private $rangeMax;
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;
+ if ($this->rangeMin || $this->rangeMax) {
+ $length = ($this->rangeMax - $this->rangeMin) + 1;
+ return substr($this->content, $this->rangeMin, $length);
+ } else {
+ return $this->content;
+ }
+ }
+
+ public function setRange($min, $max) {
+ $this->rangeMin = $min;
+ $this->rangeMax = $max;
+ return $this;
}
public function getHeaders() {
$headers = array(
array('Content-Type', $this->getMimeType()),
- array('Content-Length', strlen($this->content)),
+ array('Content-Length', strlen($this->buildResponseString())),
);
+ if ($this->rangeMin || $this->rangeMax) {
+ $len = strlen($this->content);
+ $min = $this->rangeMin;
+ $max = $this->rangeMax;
+ $headers[] = array('Content-Range', "bytes {$min}-{$max}/{$len}");
+ }
+
if (strlen($this->getDownload())) {
$headers[] = array('X-Download-Options', 'noopen');
$filename = $this->getDownload();
$headers[] = array(
'Content-Disposition',
'attachment; filename='.$filename,
);
}
$headers = array_merge(parent::getHeaders(), $headers);
return $headers;
}
}
diff --git a/src/applications/files/controller/PhabricatorFileDataController.php b/src/applications/files/controller/PhabricatorFileDataController.php
index fb3189c72c..8423277955 100644
--- a/src/applications/files/controller/PhabricatorFileDataController.php
+++ b/src/applications/files/controller/PhabricatorFileDataController.php
@@ -1,65 +1,79 @@
<?php
final class PhabricatorFileDataController extends PhabricatorFileController {
private $phid;
private $key;
public function willProcessRequest(array $data) {
$this->phid = $data['phid'];
$this->key = $data['key'];
}
public function shouldRequireLogin() {
return false;
}
public function processRequest() {
$request = $this->getRequest();
$alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain');
$uri = new PhutilURI($alt);
$alt_domain = $uri->getDomain();
if ($alt_domain && ($alt_domain != $request->getHost())) {
return id(new AphrontRedirectResponse())
->setURI($uri->setPath($request->getPath()));
}
$file = id(new PhabricatorFile())->loadOneWhere(
'phid = %s',
$this->phid);
if (!$file) {
return new Aphront404Response();
}
if (!$file->validateSecretKey($this->key)) {
return new Aphront403Response();
}
$data = $file->loadFileData();
$response = new AphrontFileResponse();
$response->setContent($data);
$response->setCacheDurationInSeconds(60 * 60 * 24 * 30);
+ // NOTE: It's important to accept "Range" requests when playing audio.
+ // If we don't, Safari has difficulty figuring out how long sounds are
+ // and glitches when trying to loop them. In particular, Safari sends
+ // an initial request for bytes 0-1 of the audio file, and things go south
+ // if we can't respond with a 206 Partial Content.
+ $range = $request->getHTTPHeader('range');
+ if ($range) {
+ $matches = null;
+ if (preg_match('/^bytes=(\d+)-(\d+)$/', $range, $matches)) {
+ $response->setHTTPResponseCode(206);
+ $response->setRange((int)$matches[1], (int)$matches[2]);
+ }
+ }
+
$is_viewable = $file->isViewableInBrowser();
$force_download = $request->getExists('download');
if ($is_viewable && !$force_download) {
$response->setMimeType($file->getViewableMimeType());
} else {
if (!$request->isHTTPPost()) {
// NOTE: Require POST to download files. We'd rather go full-bore and
// do a real CSRF check, but can't currently authenticate users on the
// file domain. This should blunt any attacks based on iframes, script
// tags, applet tags, etc., at least. Send the user to the "info" page
// if they're using some other method.
return id(new AphrontRedirectResponse())
->setURI(PhabricatorEnv::getProductionURI($file->getBestURI()));
}
$response->setMimeType($file->getMimeType());
$response->setDownload($file->getName());
}
return $response;
}
}
diff --git a/src/applications/macro/remarkup/PhabricatorRemarkupRuleImageMacro.php b/src/applications/macro/remarkup/PhabricatorRemarkupRuleImageMacro.php
index 25c6ce3fc0..9fd17a5cd4 100644
--- a/src/applications/macro/remarkup/PhabricatorRemarkupRuleImageMacro.php
+++ b/src/applications/macro/remarkup/PhabricatorRemarkupRuleImageMacro.php
@@ -1,117 +1,166 @@
<?php
/**
* @group markup
*/
final class PhabricatorRemarkupRuleImageMacro
extends PhutilRemarkupRule {
- private $images;
+ private $macros;
+
+ const KEY_RULE_MACRO = 'rule.macro';
public function apply($text) {
return preg_replace_callback(
'@^([a-zA-Z0-9:_\-]+)$@m',
array($this, 'markupImageMacro'),
$text);
}
public function markupImageMacro($matches) {
- if ($this->images === null) {
- $this->images = array();
+ if ($this->macros === null) {
+ $this->macros = array();
$viewer = $this->getEngine()->getConfig('viewer');
$rows = id(new PhabricatorMacroQuery())
->setViewer($viewer)
->withStatus(PhabricatorMacroQuery::STATUS_ACTIVE)
->execute();
- foreach ($rows as $row) {
- $spec = array(
- 'image' => $row->getFilePHID(),
- );
-
- $behavior_none = PhabricatorFileImageMacro::AUDIO_BEHAVIOR_NONE;
- if ($row->getAudioPHID()) {
- if ($row->getAudioBehavior() != $behavior_none) {
- $spec += array(
- 'audio' => $row->getAudioPHID(),
- 'audioBehavior' => $row->getAudioBehavior(),
- );
- }
- }
- $this->images[$row->getName()] = $spec;
- }
+ $this->macros = mpull($rows, 'getPHID', 'getName');
}
$name = (string)$matches[1];
+ if (empty($this->macros[$name])) {
+ return $matches[1];
+ }
- if (array_key_exists($name, $this->images)) {
- $phid = $this->images[$name]['image'];
+ $engine = $this->getEngine();
- $file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $phid);
- if ($this->getEngine()->isTextMode()) {
+ $metadata_key = self::KEY_RULE_MACRO;
+ $metadata = $engine->getTextMetadata($metadata_key, array());
+
+ $token = $engine->storeText('<macro>');
+ $metadata[] = array(
+ 'token' => $token,
+ 'phid' => $this->macros[$name],
+ 'original' => $name,
+ );
+
+ $engine->setTextMetadata($metadata_key, $metadata);
+
+ return $token;
+ }
+
+ public function didMarkupText() {
+ $engine = $this->getEngine();
+ $metadata_key = self::KEY_RULE_MACRO;
+ $metadata = $engine->getTextMetadata($metadata_key, array());
+
+ if (!$metadata) {
+ return;
+ }
+
+ $phids = ipull($metadata, 'phid');
+ $viewer = $this->getEngine()->getConfig('viewer');
+
+ // Load all the macros.
+ $macros = id(new PhabricatorMacroQuery())
+ ->setViewer($viewer)
+ ->withStatus(PhabricatorMacroQuery::STATUS_ACTIVE)
+ ->withPHIDs($phids)
+ ->execute();
+ $macros = mpull($macros, null, 'getPHID');
+
+ // Load all the images and audio.
+ $file_phids = array_merge(
+ array_values(mpull($macros, 'getFilePHID')),
+ array_values(mpull($macros, 'getAudioPHID')));
+
+ $file_phids = array_filter($file_phids);
+
+ $files = array();
+ if ($file_phids) {
+ $files = id(new PhabricatorFileQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($file_phids)
+ ->execute();
+ $files = mpull($files, null, 'getPHID');
+ }
+
+ // Replace any macros that we couldn't load the macro or image for with
+ // the original text.
+ foreach ($metadata as $key => $spec) {
+ $macro = idx($macros, $spec['phid']);
+ if ($macro) {
+ $file = idx($files, $macro->getFilePHID());
if ($file) {
- $name .= ' <'.$file->getBestURI().'>';
+ continue;
}
- return $this->getEngine()->storeText($name);
}
+ $engine->overwriteStoredText($spec['token'], $spec['original']);
+ unset($metadata[$key]);
+ }
+
+ foreach ($metadata as $spec) {
+ $macro = $macros[$spec['phid']];
+ $file = $files[$macro->getFilePHID()];
+ $src_uri = $file->getBestURI();
+
+ if ($this->getEngine()->isTextMode()) {
+ $result = $spec['original'].' <'.$src_uri.'>';
+ $engine->overwriteStoredText($spec['token'], $result);
+ continue;
+ }
+
+ $file_data = $file->getMetadata();
$style = null;
- $src_uri = null;
- if ($file) {
- $src_uri = $file->getBestURI();
- $file_data = $file->getMetadata();
- $height = idx($file_data, PhabricatorFile::METADATA_IMAGE_HEIGHT);
- $width = idx($file_data, PhabricatorFile::METADATA_IMAGE_WIDTH);
- if ($height && $width) {
- $style = sprintf(
- 'height: %dpx; width: %dpx;',
- $height,
- $width);
- }
+ $height = idx($file_data, PhabricatorFile::METADATA_IMAGE_HEIGHT);
+ $width = idx($file_data, PhabricatorFile::METADATA_IMAGE_WIDTH);
+ if ($height && $width) {
+ $style = sprintf(
+ 'height: %dpx; width: %dpx;',
+ $height,
+ $width);
}
$id = null;
- $audio_phid = idx($this->images[$name], 'audio');
- if ($audio_phid) {
+ $audio = idx($files, $macro->getAudioPHID());
+ if ($audio) {
$id = celerity_generate_unique_node_id();
$loop = null;
- switch (idx($this->images[$name], 'audioBehavior')) {
+ switch ($macro->getAudioBehavior()) {
case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP:
$loop = true;
break;
}
- $file = id(new PhabricatorFile())->loadOneWhere(
- 'phid = %s',
- $audio_phid);
- if ($file) {
- Javelin::initBehavior(
- 'audio-source',
- array(
- 'sourceID' => $id,
- 'audioURI' => $file->getBestURI(),
- 'loop' => $loop,
- ));
- }
+ Javelin::initBehavior(
+ 'audio-source',
+ array(
+ 'sourceID' => $id,
+ 'audioURI' => $audio->getBestURI(),
+ 'loop' => $loop,
+ ));
}
- $img = phutil_tag(
+ $result = phutil_tag(
'img',
array(
'id' => $id,
'src' => $src_uri,
- 'alt' => $matches[1],
- 'title' => $matches[1],
+ 'alt' => $spec['original'],
+ 'title' => $spec['original'],
'style' => $style,
));
- return $this->getEngine()->storeText($img);
- } else {
- return $matches[1];
+ $engine->overwriteStoredText($spec['token'], $result);
}
+
+ $engine->setTextMetadata($metadata_key, array());
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 16:11 (2 w, 6 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126348
Default Alt Text
(11 KB)

Event Timeline