@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class PhabricatorFileTransformController
4 extends PhabricatorFileController {
5
6 public function shouldRequireLogin() {
7 return false;
8 }
9
10 public function handleRequest(AphrontRequest $request) {
11 $viewer = $this->getViewer();
12
13 // NOTE: This is a public/CDN endpoint, and permission to see files is
14 // controlled by knowing the secret key, not by authentication.
15
16 $is_regenerate = $request->getBool('regenerate');
17
18 $source_phid = $request->getURIData('phid');
19 $file = id(new PhabricatorFileQuery())
20 ->setViewer(PhabricatorUser::getOmnipotentUser())
21 ->withPHIDs(array($source_phid))
22 ->executeOne();
23 if (!$file) {
24 return new Aphront404Response();
25 }
26
27 $secret_key = $request->getURIData('key');
28 if (!$file->validateSecretKey($secret_key)) {
29 return new Aphront403Response();
30 }
31
32 $transform = $request->getURIData('transform');
33 $xform = $this->loadTransform($source_phid, $transform);
34
35 if ($xform) {
36 if ($is_regenerate) {
37 $this->destroyTransform($xform);
38 } else {
39 return $this->buildTransformedFileResponse($xform);
40 }
41 }
42
43 $xforms = PhabricatorFileTransform::getAllTransforms();
44 if (!isset($xforms[$transform])) {
45 return new Aphront404Response();
46 }
47
48 $xform = $xforms[$transform];
49
50 // We're essentially just building a cache here and don't need CSRF
51 // protection.
52 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
53
54 $xformed_file = null;
55 if ($xform->canApplyTransform($file)) {
56 try {
57 $xformed_file = $xforms[$transform]->applyTransform($file);
58 } catch (Exception $ex) {
59 // In normal transform mode, we ignore failures and generate a
60 // default transform below. If we're explicitly regenerating the
61 // thumbnail, rethrow the exception.
62 if ($is_regenerate) {
63 throw $ex;
64 }
65 }
66 }
67
68 if (!$xformed_file) {
69 $xformed_file = $xform->getDefaultTransform($file);
70 }
71
72 if (!$xformed_file) {
73 return new Aphront400Response();
74 }
75
76 $xform = id(new PhabricatorTransformedFile())
77 ->setOriginalPHID($source_phid)
78 ->setTransform($transform)
79 ->setTransformedPHID($xformed_file->getPHID());
80
81 try {
82 $xform->save();
83 } catch (AphrontDuplicateKeyQueryException $ex) {
84 // If we collide when saving, we've raced another endpoint which was
85 // transforming the same file. Just throw our work away and use that
86 // transform instead.
87 $this->destroyTransform($xform);
88 $xform = $this->loadTransform($source_phid, $transform);
89 if (!$xform) {
90 return new Aphront404Response();
91 }
92 }
93
94 return $this->buildTransformedFileResponse($xform);
95 }
96
97 private function buildTransformedFileResponse(
98 PhabricatorTransformedFile $xform) {
99
100 $file = id(new PhabricatorFileQuery())
101 ->setViewer(PhabricatorUser::getOmnipotentUser())
102 ->withPHIDs(array($xform->getTransformedPHID()))
103 ->executeOne();
104 if (!$file) {
105 return new Aphront404Response();
106 }
107
108 // TODO: We could just delegate to the file view controller instead,
109 // which would save the client a roundtrip, but is slightly more complex.
110
111 return $file->getRedirectResponse();
112 }
113
114 private function destroyTransform(PhabricatorTransformedFile $xform) {
115 $engine = new PhabricatorDestructionEngine();
116 $file = id(new PhabricatorFileQuery())
117 ->setViewer($engine->getViewer())
118 ->withPHIDs(array($xform->getTransformedPHID()))
119 ->executeOne();
120
121 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
122
123 if (!$file) {
124 if ($xform->getID()) {
125 $xform->delete();
126 }
127 } else {
128 $engine->destroyObject($file);
129 }
130
131 unset($unguarded);
132 }
133
134 private function loadTransform($source_phid, $transform) {
135 return id(new PhabricatorTransformedFile())->loadOneWhere(
136 'originalPHID = %s AND transform = %s',
137 $source_phid,
138 $transform);
139 }
140
141}