Followup r76782: is_a() -> instanceof (we dropped PHP 4 support over 3 years ago...
[lhc/web/wiklou.git] / includes / specials / SpecialUploadStash.php
1 <?php
2 /**
3 * Implements Special:UploadStash
4 *
5 * Web access for files temporarily stored by UploadStash.
6 *
7 * For example -- files that were uploaded with the UploadWizard extension are stored temporarily
8 * before committing them to the db. But we want to see their thumbnails and get other information
9 * about them.
10 *
11 * Since this is based on the user's session, in effect this creates a private temporary file area.
12 * However, the URLs for the files cannot be shared.
13 *
14 * @file
15 * @ingroup SpecialPage
16 * @ingroup Upload
17 */
18
19 class SpecialUploadStash extends UnlistedSpecialPage {
20 // UploadStash
21 private $stash;
22
23 // Since we are directly writing the file to STDOUT,
24 // we should not be reading in really big files and serving them out.
25 //
26 // We also don't want people using this as a file drop, even if they
27 // share credentials.
28 //
29 // This service is really for thumbnails and other such previews while
30 // uploading.
31 const MAX_SERVE_BYTES = 262144; // 256K
32
33 public function __construct( ) {
34 parent::__construct( 'UploadStash', 'upload' );
35 try {
36 $this->stash = new UploadStash( );
37 } catch (UploadStashNotAvailableException $e) {
38 return null;
39 }
40 }
41
42 /**
43 * If file available in stash, cats it out to the client as a simple HTTP response.
44 * n.b. Most sanity checking done in UploadStashLocalFile, so this is straightforward.
45 *
46 * @param $subPage String: subpage, e.g. in http://example.com/wiki/Special:UploadStash/foo.jpg, the "foo.jpg" part
47 * @return Boolean: success
48 */
49 public function execute( $subPage ) {
50 global $wgOut, $wgUser;
51
52 if ( !$this->userCanExecute( $wgUser ) ) {
53 $this->displayRestrictionError();
54 return;
55 }
56
57 // prevent callers from doing standard HTML output -- we'll take it from here
58 $wgOut->disable();
59
60 $code = 500;
61 $message = 'Unknown error';
62
63 if ( !isset( $subPage ) || $subPage === '' ) {
64 // the user probably visited the page just to see what would happen, so explain it a bit.
65 $code = '400';
66 $message = "Missing key\n\n"
67 . 'This page provides access to temporarily stashed files for the user that '
68 . 'uploaded those files. See the upload API documentation. To access a stashed file, '
69 . 'use the URL of this page, with a slash and the key of the stashed file appended.';
70 } else {
71 try {
72 $file = $this->getStashFile( $subPage );
73 $size = $file->getSize();
74 if ( $size === 0 ) {
75 $code = 500;
76 $message = 'File is zero length';
77 } else if ( $size > self::MAX_SERVE_BYTES ) {
78 $code = 500;
79 $message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES . ' bytes';
80 } else {
81 $this->outputFile( $file );
82 return true;
83 }
84 } catch( UploadStashFileNotFoundException $e ) {
85 $code = 404;
86 $message = $e->getMessage();
87 } catch( UploadStashBadPathException $e ) {
88 $code = 500;
89 $message = $e->getMessage();
90 } catch( Exception $e ) {
91 $code = 500;
92 $message = $e->getMessage();
93 }
94 }
95
96 wfHttpError( $code, OutputPage::getStatusMessage( $code ), $message );
97 return false;
98 }
99
100
101 /**
102 * Convert the incoming url portion (subpage of Special page) into a stashed file,
103 * if available.
104 *
105 * @param $subPage String
106 * @return File object
107 * @throws MWException, UploadStashFileNotFoundException, UploadStashBadPathException
108 */
109 private function getStashFile( $subPage ) {
110 // due to an implementation quirk (and trying to be compatible with older method)
111 // the stash key doesn't have an extension
112 $key = $subPage;
113 $n = strrpos( $subPage, '.' );
114 if ( $n !== false ) {
115 $key = $n ? substr( $subPage, 0, $n ) : $subPage;
116 }
117
118 try {
119 $file = $this->stash->getFile( $key );
120 } catch ( UploadStashFileNotFoundException $e ) {
121 // if we couldn't find it, and it looks like a thumbnail,
122 // and it looks like we have the original, go ahead and generate it
123 $matches = array();
124 if ( ! preg_match( '/^(\d+)px-(.*)$/', $key, $matches ) ) {
125 // that doesn't look like a thumbnail. re-raise exception
126 throw $e;
127 }
128
129 list( $dummy, $width, $origKey ) = $matches;
130
131 // do not trap exceptions, if key is in bad format, or file not found,
132 // let exceptions propagate to caller.
133 $origFile = $this->stash->getFile( $origKey );
134
135 // ok we're here so the original must exist. Generate the thumbnail.
136 // because the file is a UploadStashFile, this thumbnail will also be stashed,
137 // and a thumbnailFile will be created in the thumbnailImage composite object
138 $thumbnailImage = $origFile->transform( array( 'width' => $width ) );
139 if ( !$thumbnailImage ) {
140 throw new MWException( 'Could not obtain thumbnail' );
141 }
142 $file = $thumbnailImage->thumbnailFile;
143 }
144
145 return $file;
146 }
147
148 /**
149 * Output HTTP response for file
150 * Side effects, obviously, of echoing lots of stuff to stdout.
151 *
152 * @param $file File object
153 */
154 private function outputFile( $file ) {
155 header( 'Content-Type: ' . $file->getMimeType(), true );
156 header( 'Content-Transfer-Encoding: binary', true );
157 header( 'Expires: Sun, 17-Jan-2038 19:14:07 GMT', true );
158 header( 'Content-Length: ' . $file->getSize(), true );
159 readfile( $file->getPath() );
160 }
161 }