MediaWiki  master
img_auth.php
Go to the documentation of this file.
1 <?php
46 
47 define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
48 define( 'MW_ENTRY_POINT', 'img_auth' );
49 require __DIR__ . '/includes/WebStart.php';
50 
52 
54 $mediawiki->doPostOutputShutdown();
55 
56 function wfImageAuthMain() {
58 
60  $permissionManager = $services->getPermissionManager();
61 
62  $request = RequestContext::getMain()->getRequest();
63  $publicWiki = $services->getGroupPermissionsLookup()->groupHasPermission( '*', 'read' );
64 
65  // Find the path assuming the request URL is relative to the local public zone URL
66  $baseUrl = $services->getRepoGroup()->getLocalRepo()->getZoneUrl( 'public' );
67  if ( $baseUrl[0] === '/' ) {
68  $basePath = $baseUrl;
69  } else {
70  $basePath = parse_url( $baseUrl, PHP_URL_PATH );
71  }
72  $path = WebRequest::getRequestPathSuffix( $basePath );
73 
74  if ( $path === false ) {
75  // Try instead assuming img_auth.php is the base path
76  $basePath = $wgImgAuthPath ?: "$wgScriptPath/img_auth.php";
77  $path = WebRequest::getRequestPathSuffix( $basePath );
78  }
79 
80  if ( $path === false ) {
81  wfForbidden( 'img-auth-accessdenied', 'img-auth-notindir' );
82  return;
83  }
84 
85  if ( $path === '' || $path[0] !== '/' ) {
86  // Make sure $path has a leading /
87  $path = "/" . $path;
88  }
89 
90  $user = RequestContext::getMain()->getUser();
91 
92  // Various extensions may have their own backends that need access.
93  // Check if there is a special backend and storage base path for this file.
94  foreach ( $wgImgAuthUrlPathMap as $prefix => $storageDir ) {
95  $prefix = rtrim( $prefix, '/' ) . '/'; // implicit trailing slash
96  if ( strpos( $path, $prefix ) === 0 ) {
97  $be = $services->getFileBackendGroup()->backendFromPath( $storageDir );
98  $filename = $storageDir . substr( $path, strlen( $prefix ) ); // strip prefix
99  // Check basic user authorization
100  $isAllowedUser = $permissionManager->userHasRight( $user, 'read' );
101  if ( !$isAllowedUser ) {
102  wfForbidden( 'img-auth-accessdenied', 'img-auth-noread', $path );
103  return;
104  }
105  if ( $be->fileExists( [ 'src' => $filename ] ) ) {
106  wfDebugLog( 'img_auth', "Streaming `" . $filename . "`." );
107  $be->streamFile( [
108  'src' => $filename,
109  'headers' => [ 'Cache-Control: private', 'Vary: Cookie' ]
110  ] );
111  } else {
112  wfForbidden( 'img-auth-accessdenied', 'img-auth-nofile', $path );
113  }
114  return;
115  }
116  }
117 
118  // Get the local file repository
119  $repo = $services->getRepoGroup()->getRepo( 'local' );
120  $zone = strstr( ltrim( $path, '/' ), '/', true );
121 
122  // Get the full file storage path and extract the source file name.
123  // (e.g. 120px-Foo.png => Foo.png or page2-120px-Foo.png => Foo.png).
124  // This only applies to thumbnails/transcoded, and each of them should
125  // be under a folder that has the source file name.
126  if ( $zone === 'thumb' || $zone === 'transcoded' ) {
127  $name = wfBaseName( dirname( $path ) );
128  $filename = $repo->getZonePath( $zone ) . substr( $path, strlen( "/" . $zone ) );
129  // Check to see if the file exists
130  if ( !$repo->fileExists( $filename ) ) {
131  wfForbidden( 'img-auth-accessdenied', 'img-auth-nofile', $filename );
132  return;
133  }
134  } else {
135  $name = wfBaseName( $path ); // file is a source file
136  $filename = $repo->getZonePath( 'public' ) . $path;
137  // Check to see if the file exists and is not deleted
138  $bits = explode( '!', $name, 2 );
139  if ( str_starts_with( $path, '/archive/' ) && count( $bits ) == 2 ) {
140  $file = $repo->newFromArchiveName( $bits[1], $name );
141  } else {
142  $file = $repo->newFile( $name );
143  }
144  if ( !$file->exists() || $file->isDeleted( File::DELETED_FILE ) ) {
145  wfForbidden( 'img-auth-accessdenied', 'img-auth-nofile', $filename );
146  return;
147  }
148  }
149 
150  $headers = []; // extra HTTP headers to send
151 
152  $title = Title::makeTitleSafe( NS_FILE, $name );
153 
154  $hookRunner = new HookRunner( $services->getHookContainer() );
155  if ( !$publicWiki ) {
156  // For private wikis, run extra auth checks and set cache control headers
157  $headers['Cache-Control'] = 'private';
158  $headers['Vary'] = 'Cookie';
159 
160  if ( !$title instanceof Title ) { // files have valid titles
161  wfForbidden( 'img-auth-accessdenied', 'img-auth-badtitle', $name );
162  return;
163  }
164 
165  // Run hook for extension authorization plugins
167  $result = null;
168  if ( !$hookRunner->onImgAuthBeforeStream( $title, $path, $name, $result ) ) {
169  wfForbidden( $result[0], $result[1], array_slice( $result, 2 ) );
170  return;
171  }
172 
173  // Check user authorization for this title
174  // Checks Whitelist too
175 
176  if ( !$permissionManager->userCan( 'read', $user, $title ) ) {
177  wfForbidden( 'img-auth-accessdenied', 'img-auth-noread', $name );
178  return;
179  }
180  }
181 
182  if ( isset( $_SERVER['HTTP_RANGE'] ) ) {
183  $headers['Range'] = $_SERVER['HTTP_RANGE'];
184  }
185  if ( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
186  $headers['If-Modified-Since'] = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
187  }
188 
189  if ( $request->getCheck( 'download' ) ) {
190  $headers['Content-Disposition'] = 'attachment';
191  }
192 
193  // Allow modification of headers before streaming a file
194  $hookRunner->onImgAuthModifyHeaders( $title->getTitleValue(), $headers );
195 
196  // Stream the requested file
197  [ $headers, $options ] = HTTPFileStreamer::preprocessHeaders( $headers );
198  wfDebugLog( 'img_auth', "Streaming `" . $filename . "`." );
199  $repo->streamFileWithStatus( $filename, $headers, $options );
200 }
201 
211 function wfForbidden( $msg1, $msg2, ...$args ) {
212  global $wgImgAuthDetails;
213 
214  $args = ( isset( $args[0] ) && is_array( $args[0] ) ) ? $args[0] : $args;
215 
216  $msgHdr = wfMessage( $msg1 )->text();
217  $detailMsgKey = $wgImgAuthDetails ? $msg2 : 'badaccess-group0';
218  $detailMsg = wfMessage( $detailMsgKey, $args )->text();
219 
220  wfDebugLog( 'img_auth',
221  "wfForbidden Hdr: " . wfMessage( $msg1 )->inLanguage( 'en' )->text() . " Msg: " .
222  wfMessage( $msg2, $args )->inLanguage( 'en' )->text()
223  );
224 
225  HttpStatus::header( 403 );
226  header( 'Cache-Control: no-cache' );
227  header( 'Content-Type: text/html; charset=utf-8' );
229  echo $templateParser->processTemplate( 'ImageAuthForbidden', [
230  'msgHdr' => $msgHdr,
231  'detailMsg' => $detailMsg,
232  ] );
233 }
const NS_FILE
Definition: Defines.php:70
wfBaseName( $path, $suffix='')
Return the final portion of a pathname.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
$templateParser
const DELETED_FILE
Definition: File.php:74
static preprocessHeaders( $headers)
Takes HTTP headers in a name => value format and converts them to the weird format expected by stream...
static header( $code)
Output an HTTP status code header.
Definition: HttpStatus.php:96
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:568
static getInstance()
Returns the global default instance of the top level service locator.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:50
Represents a title within MediaWiki.
Definition: Title.php:76
The MediaWiki class is the helper class for the index.php entry point.
Definition: MediaWiki.php:50
static getMain()
Get the RequestContext object associated with the main request.
$wgImgAuthPath
Config variable stub for the ImgAuthPath setting, for use by phpdoc and IDEs.
$wgImgAuthDetails
Config variable stub for the ImgAuthDetails setting, for use by phpdoc and IDEs.
$wgImgAuthUrlPathMap
Config variable stub for the ImgAuthUrlPathMap setting, for use by phpdoc and IDEs.
$wgScriptPath
Config variable stub for the ScriptPath setting, for use by phpdoc and IDEs.
Definition: config-vars.php:61
wfForbidden( $msg1, $msg2,... $args)
Issue a standard HTTP 403 Forbidden header ($msg1-a message index, not a message) and an error messag...
Definition: img_auth.php:211
wfImageAuthMain()
Definition: img_auth.php:56
$mediawiki
Definition: img_auth.php:53
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42