Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/files/controller/PhabricatorFileImageProxyController.php
12242 views
1
<?php
2
3
final class PhabricatorFileImageProxyController
4
extends PhabricatorFileController {
5
6
public function shouldAllowPublic() {
7
return true;
8
}
9
10
public function handleRequest(AphrontRequest $request) {
11
$viewer = $request->getViewer();
12
$img_uri = $request->getStr('uri');
13
14
// Validate the URI before doing anything
15
PhabricatorEnv::requireValidRemoteURIForLink($img_uri);
16
$uri = new PhutilURI($img_uri);
17
$proto = $uri->getProtocol();
18
19
$allowed_protocols = array(
20
'http',
21
'https',
22
);
23
if (!in_array($proto, $allowed_protocols)) {
24
throw new Exception(
25
pht(
26
'The provided image URI must use one of these protocols: %s.',
27
implode(', ', $allowed_protocols)));
28
}
29
30
// Check if we already have the specified image URI downloaded
31
$cached_request = id(new PhabricatorFileExternalRequest())->loadOneWhere(
32
'uriIndex = %s',
33
PhabricatorHash::digestForIndex($img_uri));
34
35
if ($cached_request) {
36
return $this->getExternalResponse($cached_request);
37
}
38
39
$ttl = PhabricatorTime::getNow() + phutil_units('7 days in seconds');
40
$external_request = id(new PhabricatorFileExternalRequest())
41
->setURI($img_uri)
42
->setTTL($ttl);
43
44
// Cache missed, so we'll need to validate and download the image.
45
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
46
$save_request = false;
47
try {
48
// Rate limit outbound fetches to make this mechanism less useful for
49
// scanning networks and ports.
50
PhabricatorSystemActionEngine::willTakeAction(
51
array($viewer->getPHID()),
52
new PhabricatorFilesOutboundRequestAction(),
53
1);
54
55
$file = PhabricatorFile::newFromFileDownload(
56
$uri,
57
array(
58
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
59
'canCDN' => true,
60
));
61
62
if (!$file->isViewableImage()) {
63
$mime_type = $file->getMimeType();
64
$engine = new PhabricatorDestructionEngine();
65
$engine->destroyObject($file);
66
$file = null;
67
throw new Exception(
68
pht(
69
'The URI "%s" does not correspond to a valid image file (got '.
70
'a file with MIME type "%s"). You must specify the URI of a '.
71
'valid image file.',
72
$uri,
73
$mime_type));
74
}
75
76
$file->save();
77
78
$external_request
79
->setIsSuccessful(1)
80
->setFilePHID($file->getPHID());
81
82
$save_request = true;
83
} catch (HTTPFutureHTTPResponseStatus $status) {
84
$external_request
85
->setIsSuccessful(0)
86
->setResponseMessage($status->getMessage());
87
88
$save_request = true;
89
} catch (Exception $ex) {
90
// Not actually saving the request in this case
91
$external_request->setResponseMessage($ex->getMessage());
92
}
93
94
if ($save_request) {
95
try {
96
$external_request->save();
97
} catch (AphrontDuplicateKeyQueryException $ex) {
98
// We may have raced against another identical request. If we did,
99
// just throw our result away and use the winner's result.
100
$external_request = $external_request->loadOneWhere(
101
'uriIndex = %s',
102
PhabricatorHash::digestForIndex($img_uri));
103
if (!$external_request) {
104
throw new Exception(
105
pht(
106
'Hit duplicate key collision when saving proxied image, but '.
107
'failed to load duplicate row (for URI "%s").',
108
$img_uri));
109
}
110
}
111
}
112
113
unset($unguarded);
114
115
116
return $this->getExternalResponse($external_request);
117
}
118
119
private function getExternalResponse(
120
PhabricatorFileExternalRequest $request) {
121
if (!$request->getIsSuccessful()) {
122
throw new Exception(
123
pht(
124
'Request to "%s" failed: %s',
125
$request->getURI(),
126
$request->getResponseMessage()));
127
}
128
129
$file = id(new PhabricatorFileQuery())
130
->setViewer(PhabricatorUser::getOmnipotentUser())
131
->withPHIDs(array($request->getFilePHID()))
132
->executeOne();
133
if (!$file) {
134
throw new Exception(
135
pht(
136
'The underlying file does not exist, but the cached request was '.
137
'successful. This likely means the file record was manually '.
138
'deleted by an administrator.'));
139
}
140
141
return id(new AphrontAjaxResponse())
142
->setContent(
143
array(
144
'imageURI' => $file->getViewURI(),
145
));
146
}
147
}
148
149