62privateconst MAX_SERVE_BYTES = 1_048_576;
// 1 MiB 70 parent::__construct(
'UploadStash',
'upload' );
72 $this->httpRequestFactory = $httpRequestFactory;
73 $this->urlUtils = $urlUtils;
74 $this->dbProvider = $dbProvider;
91// This is not set in constructor, because the context with the user is not safe to be set 92 $this->stash = $this->localRepo->getUploadStash( $this->
getUser() );
95if ( $subPage ===
null || $subPage ===
'' ) {
110// prevent callers from doing standard HTML output -- we'll take it from here 114 $params = $this->parseKey( $key );
115if ( $params[
'type'] ===
'thumb' ) {
116 $this->outputThumbFromStash( $params[
'file'], $params[
'params'] );
118 $this->outputLocalFile( $params[
'file'] );
123 $message = $e->getMessage();
124 }
catch ( Exception $e ) {
126 $message = $e->getMessage();
141privatefunction parseKey( $key ) {
142 $type = strtok( $key,
'/' );
144if ( $type !==
'file' && $type !==
'thumb' ) {
146 $this->
msg(
'uploadstash-bad-path-unknown-type', $type )
149 $fileName = strtok(
'/' );
150 $thumbPart = strtok(
'/' );
151 $file = $this->stash->getFile( $fileName );
152if ( $type ===
'thumb' ) {
153 $srcNamePos = strrpos( $thumbPart, $fileName );
154if ( $srcNamePos ===
false || $srcNamePos < 1 ) {
156 $this->
msg(
'uploadstash-bad-path-unrecognized-thumb-name' )
159 $paramString = substr( $thumbPart, 0, $srcNamePos - 1 );
161 $handler = $file->getHandler();
163 $params = $handler->parseParamString( $paramString );
164if ( $params ===
false ) {
165// The params are invalid, but still try to show a thumb 169return [
'file' => $file,
'type' => $type,
'params' =>
$params ];
172 $this->
msg(
'uploadstash-bad-path-no-handler', $file->getMimeType(), $file->getPath() )
177return [
'file' => $file,
'type' => $type ];
186privatefunction outputThumbFromStash( $file, $params ) {
187// this config option, if it exists, points to a "scaler", as you might find in 188// the Wikimedia Foundation cluster. See outputRemoteScaledThumb(). This 189// is part of our horrible NFS-based system, we create a file on a mount 190// point here, but fetch the scaled file from somewhere else that 191// happens to share it over NFS. 192if ( $file->getRepo()->getThumbProxyUrl()
195 $this->outputRemoteScaledThumb( $file, $params );
197 $this->outputLocallyScaledThumb( $file, $params );
207privatefunction outputLocallyScaledThumb( $file, $params ) {
208// n.b. this is stupid, we insist on re-transforming the file every time we are invoked. We rely 209// on HTTP caching to ensure this doesn't happen. 211 $thumbnailImage = $file->transform( $params, File::RENDER_NOW );
212if ( !$thumbnailImage ) {
214 $this->
msg(
'uploadstash-file-not-found-no-thumb' )
218// we should have just generated it locally 219if ( !$thumbnailImage->getStoragePath() ) {
221 $this->
msg(
'uploadstash-file-not-found-no-local-path' )
225// now we should construct a File, so we can get MIME and other such info in a standard way 226// n.b. MIME type may be different from original (ogx original -> jpeg thumb) 227 $thumbFile =
new UnregisteredLocalFile(
false,
228 $this->stash->repo, $thumbnailImage->getStoragePath(),
false );
230 $this->outputLocalFile( $thumbFile );
249privatefunction outputRemoteScaledThumb( $file, $params ) {
250// We need to use generateThumbName() instead of thumbName(), because 251// the suffix needs to match the file name for the remote thumbnailer 253 $scalerThumbName = $file->generateThumbName( $file->getName(), $params );
255// If a thumb proxy is set up for the repo, we favor that, as that will 256// keep the request internal 257 $thumbProxyUrl = $file->getRepo()->getThumbProxyUrl();
258if ( $thumbProxyUrl !==
null ) {
259 $scalerThumbUrl = $thumbProxyUrl .
'temp/' . $file->getUrlRel() .
260'/' . rawurlencode( $scalerThumbName );
261 $secret = $file->getRepo()->getThumbProxySecret();
263// This option probably looks something like 264// '//upload.wikimedia.org/wikipedia/test/thumb/temp'. Do not use 268if ( preg_match(
'/^\/\//', $scalerBaseUrl ) ) {
269// this is apparently a protocol-relative URL, which makes no sense in this context, 270// since this is used for communication that's internal to the application. 272 $scalerBaseUrl = $this->urlUtils->expand( $scalerBaseUrl,
PROTO_CANONICAL );
275 $scalerThumbUrl = $scalerBaseUrl .
'/' . $file->getUrlRel() .
276'/' . rawurlencode( $scalerThumbName );
280// make an http request based on wgUploadStashScalerBaseUrl to lazy-create 284'timeout' => 5
// T90599 attempt to time out cleanly 286 $req = $this->httpRequestFactory->create( $scalerThumbUrl, $httpOptions, __METHOD__ );
288// Pass a secret key shared with the proxied service if any 289if ( $secret !==
null ) {
290 $req->setHeader(
'X-Swift-Secret', $secret );
293 $status = $req->execute();
294if ( !$status->isOK() ) {
297'uploadstash-file-not-found-no-remote-thumb',
298 $status->getMessage(),
303 $contentType = $req->getResponseHeader(
"content-type" );
304if ( !$contentType ) {
306 $this->
msg(
'uploadstash-file-not-found-missing-content-type' )
310 $this->outputContents( $req->getContent(), $contentType );
321privatefunction outputLocalFile( File $file ) {
322if ( $file->getSize() > self::MAX_SERVE_BYTES ) {
324 $this->
msg(
'uploadstash-file-too-large', self::MAX_SERVE_BYTES )
328 $file->getRepo()->streamFileWithStatus( $file->getPath(),
329 [
'Content-Transfer-Encoding: binary',
330'Expires: Sun, 17-Jan-2038 19:14:07 GMT' ]
341privatefunction outputContents( $content, $contentType ) {
342 $size = strlen( $content );
343if ( $size > self::MAX_SERVE_BYTES ) {
345 $this->
msg(
'uploadstash-file-too-large', self::MAX_SERVE_BYTES )
348// Cancel output buffering and gzipping if set 350 self::outputFileHeaders( $contentType, $size );
363privatestaticfunction outputFileHeaders( $contentType, $size ) {
364 header(
"Content-Type: $contentType",
true );
365 header(
'Content-Transfer-Encoding: binary',
true );
366 header(
'Expires: Sun, 17-Jan-2038 19:14:07 GMT',
true );
367// T55032 - It shouldn't be a problem here, but let's be safe and not cache 368 header(
'Cache-Control: private' );
369 header(
"Content-Length: $size",
true );
376privatefunction showUploads() {
377// sets the title, etc. 380 $this->
getOutput()->addModuleStyles(
'mediawiki.special' );
382// create the form, which will also be used to execute a callback to process incoming form data 383// this design is extremely dubious, but supposedly HTMLForm is our standard now? 385 $form = HTMLForm::factory(
'ooui', [
391 ], $this->
getContext(),
'clearStashedUploads' );
392 $form->setTitle( $this->
getPageTitle() );
// Remove subpage 393 $form->setSubmitDestructive();
394 $form->setSubmitCallback(
function ( $formData, $form ) {
395if ( isset( $formData[
'Clear'] ) ) {
396wfDebug(
'stash has: ' . print_r( $this->stash->listFiles(),
true ) );
398if ( !$this->stash->clear() ) {
399return Status::newFatal(
'uploadstash-errclear' );
403return Status::newGood();
405 $form->setSubmitTextMsg(
'uploadstash-clear' );
407 $form->prepareForm();
408 $formResult = $form->tryAuthorizedSubmit();
410// show the files + form, if there are any, or just say there are none 412 $refreshHtml = $linkRenderer->makeKnownLink(
414 $this->
msg(
'uploadstash-refresh' )->text()
416 $pager =
new UploadStashPager(
423if ( $pager->getNumRows() ) {
425 $this->
getOutput()->addParserOutputContent(
426 $pager->getFullOutput(),
427 ParserOptions::newFromContext( $this->getContext() )
429 $form->displayForm( $formResult );
430 $this->
getOutput()->addHTML( Html::rawElement(
'p', [], $refreshHtml ) );
432 $this->
getOutput()->addHTML( Html::rawElement(
'p', [],