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