Index: branches/wmf/1.16wmf4/includes/upload/UploadStash.php |
— | — | @@ -12,14 +12,13 @@ |
13 | 13 | * |
14 | 14 | */ |
15 | 15 | class UploadStash { |
16 | | - // Format of the key for files -- has to be suitable as a filename itself in some cases. |
17 | | - // This should encompass a sha1 content hash in hex (new style), or an integer (old style), |
18 | | - // and also thumbnails with prepended strings like "120px-". |
19 | | - // The file extension should not be part of the key. |
20 | | - const KEY_FORMAT_REGEX = '/^[\w-]+$/'; |
21 | 16 | |
| 17 | + // Format of the key for files -- has to be suitable as a filename itself (e.g. ab12cd34ef.jpg) |
| 18 | + const KEY_FORMAT_REGEX = '/^[\w-]+\.\w+$/'; |
| 19 | + |
22 | 20 | // repository that this uses to store temp files |
23 | | - protected $repo; |
| 21 | + // public because we sometimes need to get a LocalFile within the same repo. |
| 22 | + public $repo; |
24 | 23 | |
25 | 24 | // array of initialized objects obtained from session (lazily initialized upon getFile()) |
26 | 25 | private $files = array(); |
— | — | @@ -80,7 +79,9 @@ |
81 | 80 | unset( $data['mTempPath'] ); |
82 | 81 | |
83 | 82 | $file = new UploadStashFile( $this, $this->repo, $path, $key, $data ); |
84 | | - |
| 83 | + if ( $file->getSize === 0 ) { |
| 84 | + throw new UploadStashZeroLengthFileException( "File is zero length" ); |
| 85 | + } |
85 | 86 | $this->files[$key] = $file; |
86 | 87 | |
87 | 88 | } |
— | — | @@ -106,18 +107,31 @@ |
107 | 108 | } |
108 | 109 | $fileProps = File::getPropsFromPath( $path ); |
109 | 110 | |
| 111 | + // we will be initializing from some tmpnam files that don't have extensions. |
| 112 | + // most of MediaWiki assumes all uploaded files have good extensions. So, we fix this. |
| 113 | + $extension = self::getExtensionForPath( $path ); |
| 114 | + if ( ! preg_match( "/\\.\\Q$extension\\E$/", $path ) ) { |
| 115 | + $pathWithGoodExtension = "$path.$extension"; |
| 116 | + if ( ! rename( $path, $pathWithGoodExtension ) ) { |
| 117 | + throw new UploadStashFileException( "couldn't rename $path to have a better extension at $pathWithGoodExtension" ); |
| 118 | + } |
| 119 | + $path = $pathWithGoodExtension; |
| 120 | + } |
| 121 | + |
110 | 122 | // If no key was supplied, use content hash. Also has the nice property of collapsing multiple identical files |
111 | 123 | // uploaded this session, which could happen if uploads had failed. |
112 | 124 | if ( is_null( $key ) ) { |
113 | | - $key = $fileProps['sha1']; |
| 125 | + $key = $fileProps['sha1'] . "." . $extension; |
114 | 126 | } |
115 | 127 | |
116 | 128 | if ( ! preg_match( self::KEY_FORMAT_REGEX, $key ) ) { |
117 | 129 | throw new UploadStashBadPathException( "key '$key' is not in a proper format" ); |
118 | 130 | } |
119 | 131 | |
120 | | - // if not already in a temporary area, put it there |
| 132 | + |
| 133 | + // if not already in a temporary area, put it there |
121 | 134 | $status = $this->repo->storeTemp( basename( $path ), $path ); |
| 135 | + |
122 | 136 | if( ! $status->isOK() ) { |
123 | 137 | // It is a convention in MediaWiki to only return one error per API exception, even if multiple errors |
124 | 138 | // are available. We use reset() to pick the "first" thing that was wrong, preferring errors to warnings. |
— | — | @@ -134,7 +148,7 @@ |
135 | 149 | throw new UploadStashFileException( "error storing file in '$path': " . implode( '; ', $error ) ); |
136 | 150 | } |
137 | 151 | $stashPath = $status->value; |
138 | | - |
| 152 | + |
139 | 153 | // required info we always store. Must trump any other application info in $data |
140 | 154 | // 'mTempPath', 'mFileSize', and 'mFileProps' are arbitrary names |
141 | 155 | // chosen for compatibility with UploadBase's way of doing this. |
— | — | @@ -147,11 +161,42 @@ |
148 | 162 | |
149 | 163 | // now, merge required info and extra data into the session. (The extra data changes from application to application. |
150 | 164 | // UploadWizard wants different things than say FirefoggChunkedUpload.) |
| 165 | + wfDebug( __METHOD__ . " storing under $key\n" ); |
151 | 166 | $_SESSION[UploadBase::SESSION_KEYNAME][$key] = array_merge( $data, $requiredData ); |
152 | 167 | |
153 | 168 | return $this->getFile( $key ); |
154 | 169 | } |
155 | 170 | |
| 171 | + /** |
| 172 | + * Find or guess extension -- ensuring that our extension matches our mime type. |
| 173 | + * Since these files are constructed from php tempnames they may not start off |
| 174 | + * with an extension. |
| 175 | + * XXX this is somewhat redundant with the checks that ApiUpload.php does with incoming |
| 176 | + * uploads versus the desired filename. Maybe we can get that passed to us... |
| 177 | + */ |
| 178 | + public static function getExtensionForPath( $path ) { |
| 179 | + // Does this have an extension? |
| 180 | + $n = strrpos( $path, '.' ); |
| 181 | + $extension = null; |
| 182 | + if ( $n !== false ) { |
| 183 | + $extension = $n ? substr( $path, $n + 1 ) : ''; |
| 184 | + } else { |
| 185 | + // If not, assume that it should be related to the mime type of the original file. |
| 186 | + $magic = MimeMagic::singleton(); |
| 187 | + $mimeType = $magic->guessMimeType( $path ); |
| 188 | + $extensions = explode( ' ', MimeMagic::singleton()->getExtensionsForType( $mimeType ) ); |
| 189 | + if ( count( $extensions ) ) { |
| 190 | + $extension = $extensions[0]; |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + if ( is_null( $extension ) ) { |
| 195 | + throw new UploadStashFileException( "extension is null" ); |
| 196 | + } |
| 197 | + |
| 198 | + return File::normalizeExtension( $extension ); |
| 199 | + } |
| 200 | + |
156 | 201 | } |
157 | 202 | |
158 | 203 | class UploadStashFile extends UnregisteredLocalFile { |
— | — | @@ -195,13 +240,11 @@ |
196 | 241 | throw new UploadStashFileNotFoundException( 'cannot find path, or not a plain file' ); |
197 | 242 | } |
198 | 243 | |
| 244 | + |
| 245 | + |
199 | 246 | parent::__construct( false, $repo, $path, false ); |
200 | 247 | |
201 | | - // we will be initializing from some tmpnam files that don't have extensions. |
202 | | - // most of MediaWiki assumes all uploaded files have good extensions. So, we fix this. |
203 | 248 | $this->name = basename( $this->path ); |
204 | | - $this->setExtension(); |
205 | | - |
206 | 249 | } |
207 | 250 | |
208 | 251 | /** |
— | — | @@ -216,40 +259,6 @@ |
217 | 260 | } |
218 | 261 | |
219 | 262 | /** |
220 | | - * Find or guess extension -- ensuring that our extension matches our mime type. |
221 | | - * Since these files are constructed from php tempnames they may not start off |
222 | | - * with an extension. |
223 | | - * This does not override getExtension() because things like getMimeType() already call getExtension(), |
224 | | - * and that results in infinite recursion. So, we preemptively *set* the extension so getExtension() can find it. |
225 | | - * For obvious reasons this should be called as early as possible, as part of initialization |
226 | | - */ |
227 | | - public function setExtension() { |
228 | | - // Does this have an extension? |
229 | | - $n = strrpos( $this->path, '.' ); |
230 | | - $extension = null; |
231 | | - if ( $n !== false ) { |
232 | | - $extension = $n ? substr( $this->path, $n + 1 ) : ''; |
233 | | - } else { |
234 | | - // If not, assume that it should be related to the mime type of the original file. |
235 | | - // |
236 | | - // This entire thing is backwards -- we *should* just create an extension based on |
237 | | - // the mime type of the transformed file, *after* transformation. But File.php demands |
238 | | - // to know the name of the transformed file before creating it. |
239 | | - $mimeType = $this->getMimeType(); |
240 | | - $extensions = explode( ' ', MimeMagic::singleton()->getExtensionsForType( $mimeType ) ); |
241 | | - if ( count( $extensions ) ) { |
242 | | - $extension = $extensions[0]; |
243 | | - } |
244 | | - } |
245 | | - |
246 | | - if ( is_null( $extension ) ) { |
247 | | - throw new UploadStashFileException( "extension is null" ); |
248 | | - } |
249 | | - |
250 | | - $this->extension = parent::normalizeExtension( $extension ); |
251 | | - } |
252 | | - |
253 | | - /** |
254 | 263 | * Get the path for the thumbnail (actually any transformation of this file) |
255 | 264 | * The actual argument is the result of thumbName although we seem to have |
256 | 265 | * buggy code elsewhere that expects a boolean 'suffix' |
— | — | @@ -272,12 +281,26 @@ |
273 | 282 | * @return {String|null} base name for URL, like '120px-12345.jpg', or null if there is no handler |
274 | 283 | */ |
275 | 284 | function thumbName( $params ) { |
| 285 | + return $this->getParamThumbName( $this->getUrlName(), $params ); |
| 286 | + } |
| 287 | + |
| 288 | + |
| 289 | + /** |
| 290 | + * Given the name of the original, i.e. Foo.jpg, and scaling parameters, returns filename with appropriate extension |
| 291 | + * This is abstracted from getThumbName because we also use it to calculate the thumbname the file should have on |
| 292 | + * remote image scalers |
| 293 | + * |
| 294 | + * @param String $urlName: A filename, like MyMovie.ogx |
| 295 | + * @param Array $parameters: scaling parameters, like array( 'width' => '120' ); |
| 296 | + * @return String|null parameterized thumb name, like 120px-MyMovie.ogx.jpg, or null if no handler found |
| 297 | + */ |
| 298 | + function getParamThumbName( $urlName, $params ) { |
276 | 299 | if ( !$this->getHandler() ) { |
277 | 300 | return null; |
278 | 301 | } |
279 | 302 | $extension = $this->getExtension(); |
280 | 303 | list( $thumbExt, $thumbMime ) = $this->handler->getThumbType( $extension, $this->getMimeType(), $params ); |
281 | | - $thumbName = $this->getHandler()->makeParamString( $params ) . '-' . $this->getUrlName(); |
| 304 | + $thumbName = $this->getHandler()->makeParamString( $params ) . '-' . $urlName; |
282 | 305 | if ( $thumbExt != $extension ) { |
283 | 306 | $thumbName .= ".$thumbExt"; |
284 | 307 | } |
— | — | @@ -304,7 +327,8 @@ |
305 | 328 | * @return {String} URL to access thumbnail, or URL with partial path |
306 | 329 | */ |
307 | 330 | public function getThumbUrl( $thumbName = false ) { |
308 | | - return self::getSpecialUrl( $thumbName ); |
| 331 | + wfDebug( __METHOD__ . " getting for $thumbName \n" ); |
| 332 | + return $this->getSpecialUrl( $thumbName ); |
309 | 333 | } |
310 | 334 | |
311 | 335 | /** |
— | — | @@ -314,7 +338,7 @@ |
315 | 339 | */ |
316 | 340 | public function getUrlName() { |
317 | 341 | if ( ! $this->urlName ) { |
318 | | - $this->urlName = $this->sessionKey . '.' . $this->getExtension(); |
| 342 | + $this->urlName = $this->sessionKey; |
319 | 343 | } |
320 | 344 | return $this->urlName; |
321 | 345 | } |
— | — | @@ -326,7 +350,7 @@ |
327 | 351 | */ |
328 | 352 | public function getUrl() { |
329 | 353 | if ( !isset( $this->url ) ) { |
330 | | - $this->url = self::getSpecialUrl( $this->getUrlName() ); |
| 354 | + $this->url = $this->getSpecialUrl( $this->getUrlName() ); |
331 | 355 | } |
332 | 356 | return $this->url; |
333 | 357 | } |
— | — | @@ -351,43 +375,6 @@ |
352 | 376 | } |
353 | 377 | |
354 | 378 | /** |
355 | | - * Typically, transform() returns a ThumbnailImage, which you can think of as being the exact |
356 | | - * equivalent of an HTML thumbnail on Wikipedia. So its URL is the full-size file, not the thumbnail's URL. |
357 | | - * |
358 | | - * Here we override transform() to stash the thumbnail file, and then |
359 | | - * provide a way to get at the stashed thumbnail file to extract properties such as its URL |
360 | | - * |
361 | | - * @param {Array} $params: parameters suitable for File::transform() |
362 | | - * @param {Bitmask} $flags: flags suitable for File::transform() |
363 | | - * @return {ThumbnailImage} with additional File thumbnailFile property |
364 | | - */ |
365 | | - public function transform( $params, $flags = 0 ) { |
366 | | - |
367 | | - // force it to get a thumbnail right away |
368 | | - $flags |= self::RENDER_NOW; |
369 | | - |
370 | | - // returns a ThumbnailImage object containing the url and path. Note. NOT A FILE OBJECT. |
371 | | - $thumb = parent::transform( $params, $flags ); |
372 | | - wfDebug( "UploadStash: generating thumbnail\n" ); |
373 | | - wfDebug( print_r( $thumb, 1 ) ); |
374 | | - $key = $this->thumbName($params); |
375 | | - |
376 | | - // remove extension, so it's stored in the session under '120px-123456' |
377 | | - // this makes it uniform with the other session key for the original, '123456' |
378 | | - $n = strrpos( $key, '.' ); |
379 | | - if ( $n !== false ) { |
380 | | - $key = substr( $key, 0, $n ); |
381 | | - } |
382 | | - |
383 | | - // stash the thumbnail File, and provide our caller with a way to get at its properties |
384 | | - $stashedThumbFile = $this->sessionStash->stashFile( $thumb->getPath(), array(), $key ); |
385 | | - $thumb->thumbnailFile = $stashedThumbFile; |
386 | | - |
387 | | - return $thumb; |
388 | | - |
389 | | - } |
390 | | - |
391 | | - /** |
392 | 379 | * Remove the associated temporary file |
393 | 380 | * @return {Status} success |
394 | 381 | */ |
— | — | @@ -402,4 +389,5 @@ |
403 | 390 | class UploadStashBadPathException extends MWException {}; |
404 | 391 | class UploadStashBadVersionException extends MWException {}; |
405 | 392 | class UploadStashFileException extends MWException {}; |
| 393 | +class UploadStashZeroLengthFileException extends MWException {}; |
406 | 394 | |
Property changes on: branches/wmf/1.16wmf4/includes/upload/UploadStash.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
407 | 395 | Merged /trunk/phase3/includes/upload/UploadStash.php:r77391,77451,77453 |
Index: branches/wmf/1.16wmf4/includes/specials/SpecialUploadStash.php |
— | — | @@ -57,9 +57,6 @@ |
58 | 58 | // prevent callers from doing standard HTML output -- we'll take it from here |
59 | 59 | $wgOut->disable(); |
60 | 60 | |
61 | | - $code = 500; |
62 | | - $message = 'Unknown error'; |
63 | | - |
64 | 61 | if ( !isset( $subPage ) || $subPage === '' ) { |
65 | 62 | // the user probably visited the page just to see what would happen, so explain it a bit. |
66 | 63 | $code = '400'; |
— | — | @@ -69,24 +66,24 @@ |
70 | 67 | . 'use the URL of this page, with a slash and the key of the stashed file appended.'; |
71 | 68 | } else { |
72 | 69 | try { |
73 | | - $file = $this->getStashFile( $subPage ); |
74 | | - $size = $file->getSize(); |
75 | | - if ( $size === 0 ) { |
76 | | - $code = 500; |
77 | | - $message = 'File is zero length'; |
78 | | - } else if ( $size > self::MAX_SERVE_BYTES ) { |
79 | | - $code = 500; |
80 | | - $message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES . ' bytes'; |
| 70 | + if ( preg_match( '/^(\d+)px-(.*)$/', $subPage, $matches ) ) { |
| 71 | + list( /* full match */, $width, $key ) = $matches; |
| 72 | + return $this->outputThumbFromStash( $key, $width ); |
81 | 73 | } else { |
82 | | - $this->outputFile( $file ); |
83 | | - return true; |
| 74 | + return $this->outputFileFromStash( $subPage ); |
84 | 75 | } |
85 | 76 | } catch( UploadStashFileNotFoundException $e ) { |
86 | 77 | $code = 404; |
87 | 78 | $message = $e->getMessage(); |
| 79 | + } catch( UploadStashZeroLengthFileException $e ) { |
| 80 | + $code = 500; |
| 81 | + $message = $e->getMessage(); |
88 | 82 | } catch( UploadStashBadPathException $e ) { |
89 | 83 | $code = 500; |
90 | 84 | $message = $e->getMessage(); |
| 85 | + } catch( SpecialUploadStashTooLargeException $e ) { |
| 86 | + $code = 500; |
| 87 | + $message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES . ' bytes. ' . $e->getMessage(); |
91 | 88 | } catch( Exception $e ) { |
92 | 89 | $code = 500; |
93 | 90 | $message = $e->getMessage(); |
— | — | @@ -96,63 +93,170 @@ |
97 | 94 | wfHttpError( $code, OutputPage::getStatusMessage( $code ), $message ); |
98 | 95 | return false; |
99 | 96 | } |
| 97 | + |
| 98 | + /** |
| 99 | + * Get a file from stash and stream it out. Rely on parent to catch exceptions and transform them into HTTP |
| 100 | + * @param String: $key - key of this file in the stash, which probably looks like a filename with extension. |
| 101 | + * @throws ....? |
| 102 | + * @return boolean |
| 103 | + */ |
| 104 | + private function outputFileFromStash( $key ) { |
| 105 | + $file = $this->stash->getFile( $key ); |
| 106 | + $this->outputLocalFile( $file ); |
| 107 | + return true; |
| 108 | + } |
100 | 109 | |
101 | 110 | |
102 | | - /** |
103 | | - * Convert the incoming url portion (subpage of Special page) into a stashed file, if available. |
104 | | - * @param {String} $subPage |
105 | | - * @return {File} file object |
106 | | - * @throws MWException, UploadStashFileNotFoundException, UploadStashBadPathException |
| 111 | + /** |
| 112 | + * Get a thumbnail for file, either generated locally or remotely, and stream it out |
| 113 | + * @param String $key: key for the file in the stash |
| 114 | + * @param int $width: width of desired thumbnail |
| 115 | + * @return ?? |
| 116 | + */ |
| 117 | + private function outputThumbFromStash( $key, $width ) { |
| 118 | + |
| 119 | + // this global, if it exists, points to a "scaler", as you might find in the Wikimedia Foundation cluster. See outputRemoteScaledThumb() |
| 120 | + global $wgUploadStashScalerBaseUrl; |
| 121 | + |
| 122 | + // let exceptions propagate to caller. |
| 123 | + $file = $this->stash->getFile( $key ); |
| 124 | + |
| 125 | + // OK, we're here and no exception was thrown, |
| 126 | + // so the original file must exist. |
| 127 | + |
| 128 | + // let's get ready to transform the original -- these are standard |
| 129 | + $params = array( 'width' => $width ); |
| 130 | + $flags = 0; |
| 131 | + |
| 132 | + return $wgUploadStashScalerBaseUrl ? $this->outputRemoteScaledThumb( $file, $params, $flags ) |
| 133 | + : $this->outputLocallyScaledThumb( $file, $params, $flags ); |
| 134 | + |
| 135 | + } |
| 136 | + |
| 137 | + |
| 138 | + /** |
| 139 | + * Scale a file (probably with a locally installed imagemagick, or similar) and output it to STDOUT. |
| 140 | + * @param $file: File object |
| 141 | + * @param $params: scaling parameters ( e.g. array( width => '50' ) ); |
| 142 | + * @param $flags: scaling flags ( see File:: constants ) |
| 143 | + * @throws MWException |
| 144 | + * @return boolean success |
107 | 145 | */ |
108 | | - private function getStashFile( $subPage ) { |
109 | | - // due to an implementation quirk (and trying to be compatible with older method) |
110 | | - // the stash key doesn't have an extension |
111 | | - $key = $subPage; |
112 | | - $n = strrpos( $subPage, '.' ); |
113 | | - if ( $n !== false ) { |
114 | | - $key = $n ? substr( $subPage, 0, $n ) : $subPage; |
115 | | - } |
| 146 | + private function outputLocallyScaledThumb( $file, $params, $flags ) { |
116 | 147 | |
117 | | - try { |
118 | | - $file = $this->stash->getFile( $key ); |
119 | | - } catch ( UploadStashFileNotFoundException $e ) { |
120 | | - // if we couldn't find it, and it looks like a thumbnail, |
121 | | - // and it looks like we have the original, go ahead and generate it |
122 | | - $matches = array(); |
123 | | - if ( ! preg_match( '/^(\d+)px-(.*)$/', $key, $matches ) ) { |
124 | | - // that doesn't look like a thumbnail. re-raise exception |
125 | | - throw $e; |
126 | | - } |
| 148 | + // n.b. this is stupid, we insist on re-transforming the file every time we are invoked. We rely |
| 149 | + // on HTTP caching to ensure this doesn't happen. |
| 150 | + |
| 151 | + $flags |= File::RENDER_NOW; |
127 | 152 | |
128 | | - list( $dummy, $width, $origKey ) = $matches; |
| 153 | + $thumbnailImage = $file->transform( $params, $flags ); |
| 154 | + if ( !$thumbnailImage ) { |
| 155 | + throw new MWException( 'Could not obtain thumbnail' ); |
| 156 | + } |
129 | 157 | |
130 | | - // do not trap exceptions, if key is in bad format, or file not found, |
131 | | - // let exceptions propagate to caller. |
132 | | - $origFile = $this->stash->getFile( $origKey ); |
| 158 | + // we should have just generated it locally |
| 159 | + if ( ! $thumbnailImage->getPath() ) { |
| 160 | + throw new UploadStashFileNotFoundException( "no local path for scaled item" ); |
| 161 | + } |
133 | 162 | |
134 | | - // ok we're here so the original must exist. Generate the thumbnail. |
135 | | - // because the file is a UploadStashFile, this thumbnail will also be stashed, |
136 | | - // and a thumbnailFile will be created in the thumbnailImage composite object |
137 | | - $thumbnailImage = $origFile->transform( array( 'width' => $width ) ); |
138 | | - if ( !$thumbnailImage ) { |
139 | | - throw new MWException( 'Could not obtain thumbnail' ); |
140 | | - } |
141 | | - $file = $thumbnailImage->thumbnailFile; |
| 163 | + // now we should construct a File, so we can get mime and other such info in a standard way |
| 164 | + // n.b. mimetype may be different from original (ogx original -> jpeg thumb) |
| 165 | + $thumbFile = new UnregisteredLocalFile( false, $this->stash->repo, $thumbnailImage->getPath(), false ); |
| 166 | + if ( ! $thumbFile ) { |
| 167 | + throw new UploadStashFileNotFoundException( "couldn't create local file object for thumbnail" ); |
142 | 168 | } |
143 | 169 | |
144 | | - return $file; |
| 170 | + return $this->outputLocalFile( $thumbFile ); |
| 171 | + |
145 | 172 | } |
| 173 | + |
| 174 | + /** |
| 175 | + * Scale a file with a remote "scaler", as exists on the Wikimedia Foundation cluster, and output it to STDOUT. |
| 176 | + * Note: unlike the usual thumbnail process, the web client never sees the cluster URL; we do the whole HTTP transaction to the scaler ourselves |
| 177 | + * and cat the results out. |
| 178 | + * Note: We rely on NFS to have propagated the file contents to the scaler. However, we do not rely on the thumbnail being created in NFS and then |
| 179 | + * propagated back to our filesystem. Instead we take the results of the HTTP request instead. |
| 180 | + * Note: no caching is being done here, although we are instructing the client to cache it forever. |
| 181 | + * @param $file: File object |
| 182 | + * @param $params: scaling parameters ( e.g. array( width => '50' ) ); |
| 183 | + * @param $flags: scaling flags ( see File:: constants ) |
| 184 | + * @throws MWException |
| 185 | + * @return boolean success |
| 186 | + */ |
| 187 | + private function outputRemoteScaledThumb( $file, $params, $flags ) { |
| 188 | + |
| 189 | + // this global probably looks something like 'http://upload.wikimedia.org/wikipedia/test/thumb/temp' |
| 190 | + // do not use trailing slash |
| 191 | + global $wgUploadStashScalerBaseUrl; |
146 | 192 | |
| 193 | + $scalerThumbName = $file->getParamThumbName( $file->name, $params ); |
| 194 | + $scalerThumbUrl = $wgUploadStashScalerBaseUrl . '/' . $file->getRel() . '/' . $scalerThumbName; |
| 195 | + |
| 196 | + // make a curl call to the scaler to create a thumbnail |
| 197 | + $httpOptions = array( |
| 198 | + 'method' => 'GET', |
| 199 | + 'timeout' => 'default' |
| 200 | + ); |
| 201 | + $req = MWHttpRequest::factory( $scalerThumbUrl, $httpOptions ); |
| 202 | + $status = $req->execute(); |
| 203 | + if ( ! $status->isOK() ) { |
| 204 | + $errors = $status->getErrorsArray(); |
| 205 | + throw new MWException( "Fetching thumbnail failed: " . join( ", ", $errors ) ); |
| 206 | + } |
| 207 | + $contentType = $req->getResponseHeader( "content-type" ); |
| 208 | + if ( ! $contentType ) { |
| 209 | + throw new MWException( "Missing content-type header" ); |
| 210 | + } |
| 211 | + return $this->outputContents( $req->getContent(), $contentType ); |
| 212 | + } |
| 213 | + |
147 | 214 | /** |
148 | 215 | * Output HTTP response for file |
149 | | - * Side effects, obviously, of echoing lots of stuff to stdout. |
150 | | - * @param {File} file |
| 216 | + * Side effect: writes HTTP response to STDOUT. |
| 217 | + * XXX could use wfStreamfile (in includes/Streamfile.php), but for consistency with outputContents() doing it this way. |
| 218 | + * XXX is mimeType really enough, or do we need encoding for full Content-Type header? |
| 219 | + * |
| 220 | + * @param $file File object with a local path (e.g. UnregisteredLocalFile, LocalFile. Oddly these don't share an ancestor!) |
151 | 221 | */ |
152 | | - private function outputFile( $file ) { |
153 | | - header( 'Content-Type: ' . $file->getMimeType(), true ); |
| 222 | + private function outputLocalFile( $file ) { |
| 223 | + if ( $file->getSize() > self::MAX_SERVE_BYTES ) { |
| 224 | + throw new SpecialUploadStashTooLargeException(); |
| 225 | + } |
| 226 | + self::outputHeaders( $file->getMimeType(), $file->getSize() ); |
| 227 | + readfile( $file->getPath() ); |
| 228 | + return true; |
| 229 | + } |
| 230 | + |
| 231 | + /** |
| 232 | + * Output HTTP response of raw content |
| 233 | + * Side effect: writes HTTP response to STDOUT. |
| 234 | + * @param String $content: content |
| 235 | + * @param String $mimeType: mime type |
| 236 | + */ |
| 237 | + private function outputContents( $content, $contentType ) { |
| 238 | + $size = strlen( $content ); |
| 239 | + if ( $size > self::MAX_SERVE_BYTES ) { |
| 240 | + throw new SpecialUploadStashTooLargeException(); |
| 241 | + } |
| 242 | + self::outputHeaders( $contentType, $size ); |
| 243 | + print $content; |
| 244 | + return true; |
| 245 | + } |
| 246 | + |
| 247 | + /** |
| 248 | + * Output headers for streaming |
| 249 | + * XXX unsure about encoding as binary; if we received from HTTP perhaps we should use that encoding, concatted with semicolon to mimeType as it usually is. |
| 250 | + * Side effect: preps PHP to write headers to STDOUT. |
| 251 | + * @param String $contentType : string suitable for content-type header |
| 252 | + * @param String $size: length in bytes |
| 253 | + */ |
| 254 | + private static function outputHeaders( $contentType, $size ) { |
| 255 | + header( "Content-Type: $contentType", true ); |
154 | 256 | header( 'Content-Transfer-Encoding: binary', true ); |
155 | 257 | header( 'Expires: Sun, 17-Jan-2038 19:14:07 GMT', true ); |
156 | | - header( 'Content-Length: ' . $file->getSize(), true ); |
157 | | - readfile( $file->getPath() ); |
| 258 | + header( "Content-Length: $size", true ); |
158 | 259 | } |
| 260 | + |
159 | 261 | } |
| 262 | + |
| 263 | +class SpecialUploadStashTooLargeException extends MWException {}; |
Property changes on: branches/wmf/1.16wmf4/includes/specials/SpecialUploadStash.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
160 | 264 | Merged /trunk/phase3/includes/specials/SpecialUploadStash.php:r77239-77240,77393,77451,77454-77455,77461 |