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