MediaWiki  master
img_auth.php
Go to the documentation of this file.
1 <?php
41 define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
42 define( 'MW_ENTRY_POINT', 'img_auth' );
43 require __DIR__ . '/includes/WebStart.php';
44 
45 # Set action base paths so that WebRequest::getPathInfo()
46 # recognizes the "X" as the 'title' in ../img_auth.php/X urls.
47 $wgArticlePath = false; # Don't let a "/*" article path clober our action path
48 $wgActionPaths = [ "$wgUploadPath/" ];
49 
50 wfImageAuthMain();
51 
52 $mediawiki = new MediaWiki();
53 $mediawiki->doPostOutputShutdown();
54 
55 function wfImageAuthMain() {
56  global $wgImgAuthUrlPathMap;
57  $permissionManager = \MediaWiki\MediaWikiServices::getInstance()->getPermissionManager();
58 
59  $request = RequestContext::getMain()->getRequest();
60  $publicWiki = in_array( 'read', $permissionManager->getGroupPermissions( [ '*' ] ), true );
61 
62  // Get the requested file path (source file or thumbnail)
63  $matches = WebRequest::getPathInfo();
64  if ( !isset( $matches['title'] ) ) {
65  wfForbidden( 'img-auth-accessdenied', 'img-auth-nopathinfo' );
66  return;
67  }
68  $path = $matches['title'];
69  if ( $path && $path[0] !== '/' ) {
70  // Make sure $path has a leading /
71  $path = "/" . $path;
72  }
73 
74  // Check for T30235: QUERY_STRING overriding the correct extension
75  $whitelist = [];
76  $extension = FileBackend::extensionFromPath( $path, 'rawcase' );
77  if ( $extension != '' ) {
78  $whitelist[] = $extension;
79  }
80  if ( !$request->checkUrlExtension( $whitelist ) ) {
81  return;
82  }
83 
84  $user = RequestContext::getMain()->getUser();
85 
86  // Various extensions may have their own backends that need access.
87  // Check if there is a special backend and storage base path for this file.
88  foreach ( $wgImgAuthUrlPathMap as $prefix => $storageDir ) {
89  $prefix = rtrim( $prefix, '/' ) . '/'; // implicit trailing slash
90  if ( strpos( $path, $prefix ) === 0 ) {
91  $be = FileBackendGroup::singleton()->backendFromPath( $storageDir );
92  $filename = $storageDir . substr( $path, strlen( $prefix ) ); // strip prefix
93  // Check basic user authorization
94  if ( !$user->isAllowed( 'read' ) ) {
95  wfForbidden( 'img-auth-accessdenied', 'img-auth-noread', $path );
96  return;
97  }
98  if ( $be->fileExists( [ 'src' => $filename ] ) ) {
99  wfDebugLog( 'img_auth', "Streaming `" . $filename . "`." );
100  $be->streamFile( [ 'src' => $filename ],
101  [ 'Cache-Control: private', 'Vary: Cookie' ] );
102  } else {
103  wfForbidden( 'img-auth-accessdenied', 'img-auth-nofile', $path );
104  }
105  return;
106  }
107  }
108 
109  // Get the local file repository
110  $repo = RepoGroup::singleton()->getRepo( 'local' );
111  $zone = strstr( ltrim( $path, '/' ), '/', true );
112 
113  // Get the full file storage path and extract the source file name.
114  // (e.g. 120px-Foo.png => Foo.png or page2-120px-Foo.png => Foo.png).
115  // This only applies to thumbnails/transcoded, and each of them should
116  // be under a folder that has the source file name.
117  if ( $zone === 'thumb' || $zone === 'transcoded' ) {
118  $name = wfBaseName( dirname( $path ) );
119  $filename = $repo->getZonePath( $zone ) . substr( $path, strlen( "/" . $zone ) );
120  // Check to see if the file exists
121  if ( !$repo->fileExists( $filename ) ) {
122  wfForbidden( 'img-auth-accessdenied', 'img-auth-nofile', $filename );
123  return;
124  }
125  } else {
126  $name = wfBaseName( $path ); // file is a source file
127  $filename = $repo->getZonePath( 'public' ) . $path;
128  // Check to see if the file exists and is not deleted
129  $bits = explode( '!', $name, 2 );
130  if ( substr( $path, 0, 9 ) === '/archive/' && count( $bits ) == 2 ) {
131  $file = $repo->newFromArchiveName( $bits[1], $name );
132  } else {
133  $file = $repo->newFile( $name );
134  }
135  if ( !$file->exists() || $file->isDeleted( File::DELETED_FILE ) ) {
136  wfForbidden( 'img-auth-accessdenied', 'img-auth-nofile', $filename );
137  return;
138  }
139  }
140 
141  $headers = []; // extra HTTP headers to send
142 
143  $title = Title::makeTitleSafe( NS_FILE, $name );
144 
145  if ( !$publicWiki ) {
146  // For private wikis, run extra auth checks and set cache control headers
147  $headers['Cache-Control'] = 'private';
148  $headers['Vary'] = 'Cookie';
149 
150  if ( !$title instanceof Title ) { // files have valid titles
151  wfForbidden( 'img-auth-accessdenied', 'img-auth-badtitle', $name );
152  return;
153  }
154 
155  // Run hook for extension authorization plugins
157  $result = null;
158  if ( !Hooks::run( 'ImgAuthBeforeStream', [ &$title, &$path, &$name, &$result ] ) ) {
159  wfForbidden( $result[0], $result[1], array_slice( $result, 2 ) );
160  return;
161  }
162 
163  // Check user authorization for this title
164  // Checks Whitelist too
165 
166  if ( !$permissionManager->userCan( 'read', $user, $title ) ) {
167  wfForbidden( 'img-auth-accessdenied', 'img-auth-noread', $name );
168  return;
169  }
170  }
171 
172  if ( isset( $_SERVER['HTTP_RANGE'] ) ) {
173  $headers['Range'] = $_SERVER['HTTP_RANGE'];
174  }
175  if ( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
176  $headers['If-Modified-Since'] = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
177  }
178 
179  if ( $request->getCheck( 'download' ) ) {
180  $headers['Content-Disposition'] = 'attachment';
181  }
182 
183  // Allow modification of headers before streaming a file
184  Hooks::run( 'ImgAuthModifyHeaders', [ $title->getTitleValue(), &$headers ] );
185 
186  // Stream the requested file
187  list( $headers, $options ) = HTTPFileStreamer::preprocessHeaders( $headers );
188  wfDebugLog( 'img_auth', "Streaming `" . $filename . "`." );
189  $repo->streamFileWithStatus( $filename, $headers, $options );
190 }
191 
201 function wfForbidden( $msg1, $msg2, ...$args ) {
202  global $wgImgAuthDetails;
203 
204  $args = ( isset( $args[0] ) && is_array( $args[0] ) ) ? $args[0] : $args;
205 
206  $msgHdr = wfMessage( $msg1 )->text();
207  $detailMsgKey = $wgImgAuthDetails ? $msg2 : 'badaccess-group0';
208  $detailMsg = wfMessage( $detailMsgKey, $args )->text();
209 
210  wfDebugLog( 'img_auth',
211  "wfForbidden Hdr: " . wfMessage( $msg1 )->inLanguage( 'en' )->text() . " Msg: " .
212  wfMessage( $msg2, $args )->inLanguage( 'en' )->text()
213  );
214 
215  HttpStatus::header( 403 );
216  header( 'Cache-Control: no-cache' );
217  header( 'Content-Type: text/html; charset=utf-8' );
218  $templateParser = new TemplateParser();
219  echo $templateParser->processTemplate( 'ImageAuthForbidden', [
220  'msgHdr' => $msgHdr,
221  'detailMsg' => $detailMsg,
222  ] );
223 }
$wgArticlePath
Definition: img_auth.php:47