Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2892064
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
11 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment