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