Straighten out Diffusion file integration
Summary: This is in preparation for getting the "View Options" dropdown working on audits. - Use Files to serve raw data so we get all the security benefits of the alternate file domain. Although the difficulty of exploiting this is high (you need commit access to the repo) there's no reason to leave it dangling. - Add a "contentHash" to Files so we can lookup files by content rather than adding some weird linker table. We can do other things with this later, potentially. - Don't use 'data' URIs since they're crazy and we can just link to the file URI. - When showing a binary file or an image, don't give options like "show highlighted text with blame" or "edit in external editor" since they don't make any sense. - Use the existing infrastructure to figure out if things are images or binaries instead of an ad-hoc thing in this class. Test Plan: Looked at text, image and binary files in Diffusion. Verified we reuse existing files if we've already generated them. Reviewers: btrahan, vrana Reviewed By: btrahan CC: aran, epriestley Maniphest Tasks: T904 Differential Revision: https://secure.phabricator.com/D1899
This commit is contained in:
@@ -18,32 +18,10 @@
|
||||
|
||||
final class DiffusionBrowseFileController extends DiffusionController {
|
||||
|
||||
// Image types we want to display inline using <img> tags
|
||||
protected $imageTypes = array(
|
||||
'png' => 'image/png',
|
||||
'gif' => 'image/gif',
|
||||
'ico' => 'image/png',
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg'=> 'image/jpeg'
|
||||
);
|
||||
|
||||
// Document types that should trigger link to ?view=raw
|
||||
protected $documentTypes = array(
|
||||
'pdf'=> 'application/pdf',
|
||||
'ps' => 'application/postscript',
|
||||
);
|
||||
private $corpusType = 'text';
|
||||
|
||||
public function processRequest() {
|
||||
|
||||
// Build the view selection form.
|
||||
$select_map = array(
|
||||
'highlighted' => 'View as Highlighted Text',
|
||||
'blame' => 'View as Highlighted Text with Blame',
|
||||
'plain' => 'View as Plain Text',
|
||||
'plainblame' => 'View as Plain Text with Blame',
|
||||
'raw' => 'View as raw document',
|
||||
);
|
||||
|
||||
$request = $this->getRequest();
|
||||
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
@@ -55,67 +33,11 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
||||
$file_query->setNeedsBlame($needs_blame);
|
||||
$file_query->loadFileContent();
|
||||
$data = $file_query->getRawData();
|
||||
|
||||
if ($selected === 'raw') {
|
||||
$response = new AphrontFileResponse();
|
||||
$response->setContent($data);
|
||||
$mime_type = $this->getDocumentType($path);
|
||||
if ($mime_type) {
|
||||
$response->setMimeType($mime_type);
|
||||
} else {
|
||||
$as_filename = idx(pathinfo($path), 'basename');
|
||||
$response->setDownload($as_filename);
|
||||
}
|
||||
return $response;
|
||||
return $this->buildRawResponse($path, $data);
|
||||
}
|
||||
|
||||
$select = '<select name="view">';
|
||||
foreach ($select_map as $k => $v) {
|
||||
$option = phutil_render_tag(
|
||||
'option',
|
||||
array(
|
||||
'value' => $k,
|
||||
'selected' => ($k == $selected) ? 'selected' : null,
|
||||
),
|
||||
phutil_escape_html($v));
|
||||
|
||||
$select .= $option;
|
||||
}
|
||||
$select .= '</select>';
|
||||
|
||||
require_celerity_resource('diffusion-source-css');
|
||||
|
||||
$edit_button = '';
|
||||
$user = $request->getUser();
|
||||
if ($user) {
|
||||
$line = 1;
|
||||
$repository = $this->getDiffusionRequest()->getRepository();
|
||||
$editor_link = $user->loadEditorLink($path, $line, $repository);
|
||||
if ($editor_link) {
|
||||
$edit_button = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $editor_link,
|
||||
'class' => 'button',
|
||||
),
|
||||
'Edit');
|
||||
}
|
||||
}
|
||||
|
||||
$view_select_panel = new AphrontPanelView();
|
||||
$view_select_form = phutil_render_tag(
|
||||
'form',
|
||||
array(
|
||||
'action' => $request->getRequestURI(),
|
||||
'method' => 'get',
|
||||
'class' => 'diffusion-browse-type-form',
|
||||
),
|
||||
$select.
|
||||
' <button>View</button> '.
|
||||
$edit_button);
|
||||
$view_select_panel->appendChild($view_select_form);
|
||||
|
||||
$view_select_panel->appendChild('<div style="clear: both;"></div>');
|
||||
|
||||
// Build the content of the file.
|
||||
$corpus = $this->buildCorpus(
|
||||
$selected,
|
||||
@@ -123,8 +45,15 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
||||
$needs_blame,
|
||||
$drequest,
|
||||
$path,
|
||||
$data
|
||||
);
|
||||
$data);
|
||||
|
||||
require_celerity_resource('diffusion-source-css');
|
||||
|
||||
if ($this->corpusType == 'text') {
|
||||
$view_select_panel = $this->renderViewSelectPanel();
|
||||
} else {
|
||||
$view_select_panel = null;
|
||||
}
|
||||
|
||||
// Render the page.
|
||||
$content = array();
|
||||
@@ -141,7 +70,6 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
||||
$nav = $this->buildSideNav('browse', true);
|
||||
$nav->appendChild($content);
|
||||
|
||||
|
||||
$basename = basename($this->getDiffusionRequest()->getPath());
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
@@ -151,71 +79,24 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a content-type corrsponding to an image file extension
|
||||
*
|
||||
* @param string $path File path
|
||||
* @return mixed A content-type string or NULL if path doesn't end with a
|
||||
* recognized image extension
|
||||
*/
|
||||
public function getImageType($path) {
|
||||
$ext = pathinfo($path);
|
||||
$ext = idx($ext, 'extension');
|
||||
return idx($this->imageTypes, $ext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a content-type corresponding to an document file extension
|
||||
*
|
||||
* @param string $path File path
|
||||
* @return mixed A content-type string or NULL if path doesn't end with a
|
||||
* recognized document extension
|
||||
*/
|
||||
public function getDocumentType($path) {
|
||||
$ext = pathinfo($path);
|
||||
$ext = idx($ext, 'extension');
|
||||
return idx($this->documentTypes, $ext);
|
||||
}
|
||||
|
||||
|
||||
private function buildCorpus($selected,
|
||||
$file_query,
|
||||
$needs_blame,
|
||||
$drequest,
|
||||
$path,
|
||||
$data) {
|
||||
$image_type = $this->getImageType($path);
|
||||
if ($image_type && !$selected) {
|
||||
$corpus = phutil_render_tag(
|
||||
'img',
|
||||
array(
|
||||
'style' => 'padding-bottom: 10px',
|
||||
'src' => 'data:'.$image_type.';base64,'.base64_encode($data),
|
||||
)
|
||||
);
|
||||
return $corpus;
|
||||
}
|
||||
|
||||
$document_type = $this->getDocumentType($path);
|
||||
if (($document_type && !$selected) || !phutil_is_utf8($data)) {
|
||||
$data = $file_query->getRawData();
|
||||
$document_type_description = $document_type ? $document_type : 'binary';
|
||||
$corpus = phutil_render_tag(
|
||||
'p',
|
||||
array(
|
||||
'style' => 'text-align: center;'
|
||||
),
|
||||
phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '?view=raw',
|
||||
'class' => 'button'
|
||||
),
|
||||
"View $document_type_description"
|
||||
)
|
||||
);
|
||||
return $corpus;
|
||||
if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) {
|
||||
$file = $this->loadFileForData($path, $data);
|
||||
$file_uri = $file->getBestURI();
|
||||
|
||||
if ($file->isViewableImage()) {
|
||||
$this->corpusType = 'image';
|
||||
return $this->buildImageCorpus($file_uri);
|
||||
} else {
|
||||
$this->corpusType = 'binary';
|
||||
return $this->buildBinaryCorpus($file_uri, $data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -295,6 +176,64 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
||||
return $corpus;
|
||||
}
|
||||
|
||||
private function renderViewSelectPanel() {
|
||||
|
||||
$request = $this->getRequest();
|
||||
|
||||
$select = AphrontFormSelectControl::renderSelectTag(
|
||||
$request->getStr('view'),
|
||||
array(
|
||||
'highlighted' => 'View as Highlighted Text',
|
||||
'blame' => 'View as Highlighted Text with Blame',
|
||||
'plain' => 'View as Plain Text',
|
||||
'plainblame' => 'View as Plain Text with Blame',
|
||||
'raw' => 'View as raw document',
|
||||
),
|
||||
array(
|
||||
'name' => 'view',
|
||||
));
|
||||
|
||||
$view_select_panel = new AphrontPanelView();
|
||||
$view_select_form = phutil_render_tag(
|
||||
'form',
|
||||
array(
|
||||
'action' => $request->getRequestURI(),
|
||||
'method' => 'get',
|
||||
'class' => 'diffusion-browse-type-form',
|
||||
),
|
||||
$select.
|
||||
' <button>View</button> '.
|
||||
$this->renderEditButton());
|
||||
|
||||
$view_select_panel->appendChild($view_select_form);
|
||||
$view_select_panel->appendChild('<div style="clear: both;"></div>');
|
||||
|
||||
return $view_select_panel;
|
||||
}
|
||||
|
||||
private function renderEditButton() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
|
||||
$repository = $drequest->getRepository();
|
||||
$path = $drequest->getPath();
|
||||
$line = 1;
|
||||
|
||||
$editor_link = $user->loadEditorLink($path, $line, $repository);
|
||||
if (!$editor_link) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $editor_link,
|
||||
'class' => 'button',
|
||||
),
|
||||
'Edit');
|
||||
}
|
||||
|
||||
private function buildDisplayRows($text_list, $rev_list, $blame_dict,
|
||||
$needs_blame, DiffusionRequest $drequest, $file_query, $selected) {
|
||||
@@ -486,5 +425,65 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
||||
);
|
||||
}
|
||||
|
||||
private function loadFileForData($path, $data) {
|
||||
$hash = PhabricatorHash::digest($data);
|
||||
|
||||
$file = id(new PhabricatorFile())->loadOneWhere(
|
||||
'contentHash = %s LIMIT 1',
|
||||
$hash);
|
||||
if (!$file) {
|
||||
// We're just caching the data; this is always safe.
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
|
||||
$file = PhabricatorFile::newFromFileData(
|
||||
$data,
|
||||
array(
|
||||
'name' => basename($path),
|
||||
));
|
||||
|
||||
unset($unguarded);
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function buildRawResponse($path, $data) {
|
||||
$file = $this->loadFileForData($path, $data);
|
||||
return id(new AphrontRedirectResponse())->setURI($file->getBestURI());
|
||||
}
|
||||
|
||||
private function buildImageCorpus($file_uri) {
|
||||
$panel = new AphrontPanelView();
|
||||
$panel->setHeader('Image');
|
||||
$panel->addButton($this->renderEditButton());
|
||||
$panel->appendChild(
|
||||
phutil_render_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => $file_uri,
|
||||
)));
|
||||
return $panel;
|
||||
}
|
||||
|
||||
private function buildBinaryCorpus($file_uri, $data) {
|
||||
$panel = new AphrontPanelView();
|
||||
$panel->setHeader('Binary File');
|
||||
$panel->addButton($this->renderEditButton());
|
||||
$panel->appendChild(
|
||||
'<p>'.
|
||||
'This is a binary file. '.
|
||||
'It is '.number_format(strlen($data)).' bytes in length.'.
|
||||
'</p>');
|
||||
$panel->addButton(
|
||||
phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $file_uri,
|
||||
'class' => 'button green',
|
||||
),
|
||||
'Download Binary File...'));
|
||||
return $panel;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user