This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Proxy backend that manages file layout rewriting for FileRepo. |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | * @ingroup FileRepo |
||
22 | * @ingroup FileBackend |
||
23 | * @author Aaron Schulz |
||
24 | */ |
||
25 | |||
26 | /** |
||
27 | * @brief Proxy backend that manages file layout rewriting for FileRepo. |
||
28 | * |
||
29 | * LocalRepo may be configured to store files under their title names or by SHA-1. |
||
30 | * This acts as a shim in the latter case, providing backwards compatability for |
||
31 | * most callers. All "public"/"deleted" zone files actually go in an "original" |
||
32 | * container and are never changed. |
||
33 | * |
||
34 | * This requires something like thumb_handler.php and img_auth.php for client viewing of files. |
||
35 | * |
||
36 | * @ingroup FileRepo |
||
37 | * @ingroup FileBackend |
||
38 | * @since 1.25 |
||
39 | */ |
||
40 | class FileBackendDBRepoWrapper extends FileBackend { |
||
41 | /** @var FileBackend */ |
||
42 | protected $backend; |
||
43 | /** @var string */ |
||
44 | protected $repoName; |
||
45 | /** @var Closure */ |
||
46 | protected $dbHandleFunc; |
||
47 | /** @var ProcessCacheLRU */ |
||
48 | protected $resolvedPathCache; |
||
49 | /** @var DBConnRef[] */ |
||
50 | protected $dbs; |
||
51 | |||
52 | public function __construct( array $config ) { |
||
53 | /** @var FileBackend $backend */ |
||
54 | $backend = $config['backend']; |
||
55 | $config['name'] = $backend->getName(); |
||
56 | $config['wikiId'] = $backend->getWikiId(); |
||
57 | parent::__construct( $config ); |
||
58 | $this->backend = $config['backend']; |
||
59 | $this->repoName = $config['repoName']; |
||
60 | $this->dbHandleFunc = $config['dbHandleFactory']; |
||
61 | $this->resolvedPathCache = new ProcessCacheLRU( 100 ); |
||
62 | } |
||
63 | |||
64 | /** |
||
65 | * Get the underlying FileBackend that is being wrapped |
||
66 | * |
||
67 | * @return FileBackend |
||
68 | */ |
||
69 | public function getInternalBackend() { |
||
70 | return $this->backend; |
||
71 | } |
||
72 | |||
73 | /** |
||
74 | * Translate a legacy "title" path to it's "sha1" counterpart |
||
75 | * |
||
76 | * E.g. mwstore://local-backend/local-public/a/ab/<name>.jpg |
||
77 | * => mwstore://local-backend/local-original/x/y/z/<sha1>.jpg |
||
78 | * |
||
79 | * @param string $path |
||
80 | * @param bool $latest |
||
81 | * @return string |
||
82 | */ |
||
83 | public function getBackendPath( $path, $latest = true ) { |
||
84 | $paths = $this->getBackendPaths( [ $path ], $latest ); |
||
85 | return current( $paths ); |
||
86 | } |
||
87 | |||
88 | /** |
||
89 | * Translate legacy "title" paths to their "sha1" counterparts |
||
90 | * |
||
91 | * E.g. mwstore://local-backend/local-public/a/ab/<name>.jpg |
||
92 | * => mwstore://local-backend/local-original/x/y/z/<sha1>.jpg |
||
93 | * |
||
94 | * @param array $paths |
||
95 | * @param bool $latest |
||
96 | * @return array Translated paths in same order |
||
97 | */ |
||
98 | public function getBackendPaths( array $paths, $latest = true ) { |
||
99 | $db = $this->getDB( $latest ? DB_MASTER : DB_REPLICA ); |
||
100 | |||
101 | // @TODO: batching |
||
102 | $resolved = []; |
||
103 | foreach ( $paths as $i => $path ) { |
||
104 | if ( !$latest && $this->resolvedPathCache->has( $path, 'target', 10 ) ) { |
||
105 | $resolved[$i] = $this->resolvedPathCache->get( $path, 'target' ); |
||
106 | continue; |
||
107 | } |
||
108 | |||
109 | list( , $container ) = FileBackend::splitStoragePath( $path ); |
||
110 | |||
111 | if ( $container === "{$this->repoName}-public" ) { |
||
112 | $name = basename( $path ); |
||
113 | if ( strpos( $path, '!' ) !== false ) { |
||
114 | $sha1 = $db->selectField( 'oldimage', 'oi_sha1', |
||
115 | [ 'oi_archive_name' => $name ], |
||
116 | __METHOD__ |
||
117 | ); |
||
118 | } else { |
||
119 | $sha1 = $db->selectField( 'image', 'img_sha1', |
||
120 | [ 'img_name' => $name ], |
||
121 | __METHOD__ |
||
122 | ); |
||
123 | } |
||
124 | if ( !strlen( $sha1 ) ) { |
||
125 | $resolved[$i] = $path; // give up |
||
126 | continue; |
||
127 | } |
||
128 | $resolved[$i] = $this->getPathForSHA1( $sha1 ); |
||
129 | $this->resolvedPathCache->set( $path, 'target', $resolved[$i] ); |
||
130 | } elseif ( $container === "{$this->repoName}-deleted" ) { |
||
131 | $name = basename( $path ); // <hash>.<ext> |
||
132 | $sha1 = substr( $name, 0, strpos( $name, '.' ) ); // ignore extension |
||
133 | $resolved[$i] = $this->getPathForSHA1( $sha1 ); |
||
134 | $this->resolvedPathCache->set( $path, 'target', $resolved[$i] ); |
||
135 | } else { |
||
136 | $resolved[$i] = $path; |
||
137 | } |
||
138 | } |
||
139 | |||
140 | $res = []; |
||
141 | foreach ( $paths as $i => $path ) { |
||
142 | $res[$i] = $resolved[$i]; |
||
143 | } |
||
144 | |||
145 | return $res; |
||
146 | } |
||
147 | |||
148 | protected function doOperationsInternal( array $ops, array $opts ) { |
||
149 | return $this->backend->doOperationsInternal( $this->mungeOpPaths( $ops ), $opts ); |
||
150 | } |
||
151 | |||
152 | protected function doQuickOperationsInternal( array $ops ) { |
||
153 | return $this->backend->doQuickOperationsInternal( $this->mungeOpPaths( $ops ) ); |
||
154 | } |
||
155 | |||
156 | protected function doPrepare( array $params ) { |
||
157 | return $this->backend->doPrepare( $params ); |
||
158 | } |
||
159 | |||
160 | protected function doSecure( array $params ) { |
||
161 | return $this->backend->doSecure( $params ); |
||
162 | } |
||
163 | |||
164 | protected function doPublish( array $params ) { |
||
165 | return $this->backend->doPublish( $params ); |
||
166 | } |
||
167 | |||
168 | protected function doClean( array $params ) { |
||
169 | return $this->backend->doClean( $params ); |
||
170 | } |
||
171 | |||
172 | public function concatenate( array $params ) { |
||
173 | return $this->translateSrcParams( __FUNCTION__, $params ); |
||
174 | } |
||
175 | |||
176 | public function fileExists( array $params ) { |
||
177 | return $this->translateSrcParams( __FUNCTION__, $params ); |
||
178 | } |
||
179 | |||
180 | public function getFileTimestamp( array $params ) { |
||
181 | return $this->translateSrcParams( __FUNCTION__, $params ); |
||
182 | } |
||
183 | |||
184 | public function getFileSize( array $params ) { |
||
185 | return $this->translateSrcParams( __FUNCTION__, $params ); |
||
186 | } |
||
187 | |||
188 | public function getFileStat( array $params ) { |
||
189 | return $this->translateSrcParams( __FUNCTION__, $params ); |
||
190 | } |
||
191 | |||
192 | public function getFileXAttributes( array $params ) { |
||
193 | return $this->translateSrcParams( __FUNCTION__, $params ); |
||
194 | } |
||
195 | |||
196 | public function getFileSha1Base36( array $params ) { |
||
197 | return $this->translateSrcParams( __FUNCTION__, $params ); |
||
198 | } |
||
199 | |||
200 | public function getFileProps( array $params ) { |
||
201 | return $this->translateSrcParams( __FUNCTION__, $params ); |
||
202 | } |
||
203 | |||
204 | public function streamFile( array $params ) { |
||
205 | // The stream methods use the file extension to determine the |
||
206 | // Content-Type (as MediaWiki should already validate it on upload). |
||
207 | // The translated SHA1 path has no extension, so this needs to use |
||
208 | // the untranslated path extension. |
||
209 | $type = StreamFile::contentTypeFromPath( $params['src'] ); |
||
210 | if ( $type && $type != 'unknown/unknown' ) { |
||
0 ignored issues
–
show
|
|||
211 | $params['headers'][] = "Content-type: $type"; |
||
212 | } |
||
213 | return $this->translateSrcParams( __FUNCTION__, $params ); |
||
214 | } |
||
215 | |||
216 | public function getFileContentsMulti( array $params ) { |
||
217 | return $this->translateArrayResults( __FUNCTION__, $params ); |
||
218 | } |
||
219 | |||
220 | public function getLocalReferenceMulti( array $params ) { |
||
221 | return $this->translateArrayResults( __FUNCTION__, $params ); |
||
222 | } |
||
223 | |||
224 | public function getLocalCopyMulti( array $params ) { |
||
225 | return $this->translateArrayResults( __FUNCTION__, $params ); |
||
226 | } |
||
227 | |||
228 | public function getFileHttpUrl( array $params ) { |
||
229 | return $this->translateSrcParams( __FUNCTION__, $params ); |
||
230 | } |
||
231 | |||
232 | public function directoryExists( array $params ) { |
||
233 | return $this->backend->directoryExists( $params ); |
||
234 | } |
||
235 | |||
236 | public function getDirectoryList( array $params ) { |
||
237 | return $this->backend->getDirectoryList( $params ); |
||
238 | } |
||
239 | |||
240 | public function getFileList( array $params ) { |
||
241 | return $this->backend->getFileList( $params ); |
||
242 | } |
||
243 | |||
244 | public function getFeatures() { |
||
245 | return $this->backend->getFeatures(); |
||
246 | } |
||
247 | |||
248 | public function clearCache( array $paths = null ) { |
||
249 | $this->backend->clearCache( null ); // clear all |
||
250 | } |
||
251 | |||
252 | public function preloadCache( array $paths ) { |
||
253 | $paths = $this->getBackendPaths( $paths ); |
||
254 | $this->backend->preloadCache( $paths ); |
||
255 | } |
||
256 | |||
257 | public function preloadFileStat( array $params ) { |
||
258 | return $this->translateSrcParams( __FUNCTION__, $params ); |
||
259 | } |
||
260 | |||
261 | public function getScopedLocksForOps( array $ops, StatusValue $status ) { |
||
262 | return $this->backend->getScopedLocksForOps( $ops, $status ); |
||
263 | } |
||
264 | |||
265 | /** |
||
266 | * Get the ultimate original storage path for a file |
||
267 | * |
||
268 | * Use this when putting a new file into the system |
||
269 | * |
||
270 | * @param string $sha1 File SHA-1 base36 |
||
271 | * @return string |
||
272 | */ |
||
273 | public function getPathForSHA1( $sha1 ) { |
||
274 | if ( strlen( $sha1 ) < 3 ) { |
||
275 | throw new InvalidArgumentException( "Invalid file SHA-1." ); |
||
276 | } |
||
277 | return $this->backend->getContainerStoragePath( "{$this->repoName}-original" ) . |
||
278 | "/{$sha1[0]}/{$sha1[1]}/{$sha1[2]}/{$sha1}"; |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * Get a connection to the repo file registry DB |
||
283 | * |
||
284 | * @param integer $index |
||
285 | * @return DBConnRef |
||
286 | */ |
||
287 | protected function getDB( $index ) { |
||
288 | if ( !isset( $this->dbs[$index] ) ) { |
||
289 | $func = $this->dbHandleFunc; |
||
290 | $this->dbs[$index] = $func( $index ); |
||
291 | } |
||
292 | return $this->dbs[$index]; |
||
293 | } |
||
294 | |||
295 | /** |
||
296 | * Translates paths found in the "src" or "srcs" keys of a params array |
||
297 | * |
||
298 | * @param string $function |
||
299 | * @param array $params |
||
300 | */ |
||
301 | protected function translateSrcParams( $function, array $params ) { |
||
302 | $latest = !empty( $params['latest'] ); |
||
303 | |||
304 | if ( isset( $params['src'] ) ) { |
||
305 | $params['src'] = $this->getBackendPath( $params['src'], $latest ); |
||
306 | } |
||
307 | |||
308 | if ( isset( $params['srcs'] ) ) { |
||
309 | $params['srcs'] = $this->getBackendPaths( $params['srcs'], $latest ); |
||
310 | } |
||
311 | |||
312 | return $this->backend->$function( $params ); |
||
313 | } |
||
314 | |||
315 | /** |
||
316 | * Translates paths when the backend function returns results keyed by paths |
||
317 | * |
||
318 | * @param string $function |
||
319 | * @param array $params |
||
320 | * @return array |
||
321 | */ |
||
322 | protected function translateArrayResults( $function, array $params ) { |
||
323 | $origPaths = $params['srcs']; |
||
324 | $params['srcs'] = $this->getBackendPaths( $params['srcs'], !empty( $params['latest'] ) ); |
||
325 | $pathMap = array_combine( $params['srcs'], $origPaths ); |
||
326 | |||
327 | $results = $this->backend->$function( $params ); |
||
328 | |||
329 | $contents = []; |
||
330 | foreach ( $results as $path => $result ) { |
||
331 | $contents[$pathMap[$path]] = $result; |
||
332 | } |
||
333 | |||
334 | return $contents; |
||
335 | } |
||
336 | |||
337 | /** |
||
338 | * Translate legacy "title" source paths to their "sha1" counterparts |
||
339 | * |
||
340 | * This leaves destination paths alone since we don't want those to mutate |
||
341 | * |
||
342 | * @param array $ops |
||
343 | * @return array |
||
344 | */ |
||
345 | protected function mungeOpPaths( array $ops ) { |
||
346 | // Ops that use 'src' and do not mutate core file data there |
||
347 | static $srcRefOps = [ 'store', 'copy', 'describe' ]; |
||
348 | foreach ( $ops as &$op ) { |
||
349 | if ( isset( $op['src'] ) && in_array( $op['op'], $srcRefOps ) ) { |
||
350 | $op['src'] = $this->getBackendPath( $op['src'], true ); |
||
351 | } |
||
352 | if ( isset( $op['srcs'] ) ) { |
||
353 | $op['srcs'] = $this->getBackendPaths( $op['srcs'], true ); |
||
354 | } |
||
355 | } |
||
356 | return $ops; |
||
357 | } |
||
358 | } |
||
359 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: