Completed
Branch master (939199)
by
unknown
39:35
created

img_auth.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Image authorisation script
4
 *
5
 * To use this, see https://www.mediawiki.org/wiki/Manual:Image_Authorization
6
 *
7
 * - Set $wgUploadDirectory to a non-public directory (not web accessible)
8
 * - Set $wgUploadPath to point to this file
9
 *
10
 * Optional Parameters
11
 *
12
 * - Set $wgImgAuthDetails = true if you want the reason the access was denied messages to
13
 *       be displayed instead of just the 403 error (doesn't work on IE anyway),
14
 *       otherwise it will only appear in error logs
15
 *
16
 *  For security reasons, you usually don't want your user to know *why* access was denied,
17
 *  just that it was. If you want to change this, you can set $wgImgAuthDetails to 'true'
18
 *  in localsettings.php and it will give the user the reason why access was denied.
19
 *
20
 * Your server needs to support PATH_INFO; CGI-based configurations usually don't.
21
 *
22
 * This program is free software; you can redistribute it and/or modify
23
 * it under the terms of the GNU General Public License as published by
24
 * the Free Software Foundation; either version 2 of the License, or
25
 * (at your option) any later version.
26
 *
27
 * This program is distributed in the hope that it will be useful,
28
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30
 * GNU General Public License for more details.
31
 *
32
 * You should have received a copy of the GNU General Public License along
33
 * with this program; if not, write to the Free Software Foundation, Inc.,
34
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
35
 * http://www.gnu.org/copyleft/gpl.html
36
 *
37
 * @file
38
 */
39
40
define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
41
require __DIR__ . '/includes/WebStart.php';
42
43
# Set action base paths so that WebRequest::getPathInfo()
44
# recognizes the "X" as the 'title' in ../img_auth.php/X urls.
45
$wgArticlePath = false; # Don't let a "/*" article path clober our action path
46
$wgActionPaths = [ "$wgUploadPath/" ];
47
48
wfImageAuthMain();
49
50
$mediawiki = new MediaWiki();
51
$mediawiki->doPostOutputShutdown( 'fast' );
52
53
function wfImageAuthMain() {
0 ignored issues
show
wfImageAuthMain uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
54
	global $wgImgAuthUrlPathMap;
55
56
	$request = RequestContext::getMain()->getRequest();
57
	$publicWiki = in_array( 'read', User::getGroupPermissions( [ '*' ] ), true );
58
59
	// Get the requested file path (source file or thumbnail)
60
	$matches = WebRequest::getPathInfo();
61
	if ( !isset( $matches['title'] ) ) {
62
		wfForbidden( 'img-auth-accessdenied', 'img-auth-nopathinfo' );
63
		return;
64
	}
65
	$path = $matches['title'];
66
	if ( $path && $path[0] !== '/' ) {
67
		// Make sure $path has a leading /
68
		$path = "/" . $path;
69
	}
70
71
	// Check for bug 28235: QUERY_STRING overriding the correct extension
72
	$whitelist = [];
73
	$extension = FileBackend::extensionFromPath( $path, 'rawcase' );
74
	if ( $extension != '' ) {
75
		$whitelist[] = $extension;
76
	}
77
	if ( !$request->checkUrlExtension( $whitelist ) ) {
78
		return;
79
	}
80
81
	// Various extensions may have their own backends that need access.
82
	// Check if there is a special backend and storage base path for this file.
83
	foreach ( $wgImgAuthUrlPathMap as $prefix => $storageDir ) {
84
		$prefix = rtrim( $prefix, '/' ) . '/'; // implicit trailing slash
85
		if ( strpos( $path, $prefix ) === 0 ) {
86
			$be = FileBackendGroup::singleton()->backendFromPath( $storageDir );
87
			$filename = $storageDir . substr( $path, strlen( $prefix ) ); // strip prefix
88
			// Check basic user authorization
89
			if ( !RequestContext::getMain()->getUser()->isAllowed( 'read' ) ) {
90
				wfForbidden( 'img-auth-accessdenied', 'img-auth-noread', $path );
91
				return;
92
			}
93
			if ( $be->fileExists( [ 'src' => $filename ] ) ) {
94
				wfDebugLog( 'img_auth', "Streaming `" . $filename . "`." );
95
				$be->streamFile( [ 'src' => $filename ],
96
					[ 'Cache-Control: private', 'Vary: Cookie' ] );
97
			} else {
98
				wfForbidden( 'img-auth-accessdenied', 'img-auth-nofile', $path );
99
			}
100
			return;
101
		}
102
	}
103
104
	// Get the local file repository
105
	$repo = RepoGroup::singleton()->getRepo( 'local' );
106
	$zone = strstr( ltrim( $path, '/' ), '/', true );
107
108
	// Get the full file storage path and extract the source file name.
109
	// (e.g. 120px-Foo.png => Foo.png or page2-120px-Foo.png => Foo.png).
110
	// This only applies to thumbnails/transcoded, and each of them should
111
	// be under a folder that has the source file name.
112
	if ( $zone === 'thumb' || $zone === 'transcoded' ) {
113
		$name = wfBaseName( dirname( $path ) );
114
		$filename = $repo->getZonePath( $zone ) . substr( $path, strlen( "/" . $zone ) );
115
		// Check to see if the file exists
116
		if ( !$repo->fileExists( $filename ) ) {
117
			wfForbidden( 'img-auth-accessdenied', 'img-auth-nofile', $filename );
118
			return;
119
		}
120
	} else {
121
		$name = wfBaseName( $path ); // file is a source file
122
		$filename = $repo->getZonePath( 'public' ) . $path;
123
		// Check to see if the file exists and is not deleted
124
		$bits = explode( '!', $name, 2 );
125
		if ( substr( $path, 0, 9 ) === '/archive/' && count( $bits ) == 2 ) {
126
			$file = $repo->newFromArchiveName( $bits[1], $name );
127
		} else {
128
			$file = $repo->newFile( $name );
129
		}
130
		if ( !$file->exists() || $file->isDeleted( File::DELETED_FILE ) ) {
131
			wfForbidden( 'img-auth-accessdenied', 'img-auth-nofile', $filename );
132
			return;
133
		}
134
	}
135
136
	$headers = []; // extra HTTP headers to send
137
138
	if ( !$publicWiki ) {
139
		// For private wikis, run extra auth checks and set cache control headers
140
		$headers[] = 'Cache-Control: private';
141
		$headers[] = 'Vary: Cookie';
142
143
		$title = Title::makeTitleSafe( NS_FILE, $name );
144
		if ( !$title instanceof Title ) { // files have valid titles
145
			wfForbidden( 'img-auth-accessdenied', 'img-auth-badtitle', $name );
146
			return;
147
		}
148
149
		// Run hook for extension authorization plugins
150
		/** @var $result array */
151
		$result = null;
152
		if ( !Hooks::run( 'ImgAuthBeforeStream', [ &$title, &$path, &$name, &$result ] ) ) {
153
			wfForbidden( $result[0], $result[1], array_slice( $result, 2 ) );
154
			return;
155
		}
156
157
		// Check user authorization for this title
158
		// Checks Whitelist too
159
		if ( !$title->userCan( 'read' ) ) {
160
			wfForbidden( 'img-auth-accessdenied', 'img-auth-noread', $name );
161
			return;
162
		}
163
	}
164
165
	$options = []; // HTTP header options
166
	if ( isset( $_SERVER['HTTP_RANGE'] ) ) {
167
		$options['range'] = $_SERVER['HTTP_RANGE'];
168
	}
169
	if ( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
170
		$options['if-modified-since'] = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
171
	}
172
173
	if ( $request->getCheck( 'download' ) ) {
174
		$headers[] = 'Content-Disposition: attachment';
175
	}
176
177
	// Stream the requested file
178
	wfDebugLog( 'img_auth', "Streaming `" . $filename . "`." );
179
	$repo->streamFile( $filename, $headers, $options );
180
}
181
182
/**
183
 * Issue a standard HTTP 403 Forbidden header ($msg1-a message index, not a message) and an
184
 * error message ($msg2, also a message index), (both required) then end the script
185
 * subsequent arguments to $msg2 will be passed as parameters only for replacing in $msg2
186
 * @param string $msg1
187
 * @param string $msg2
188
 */
189
function wfForbidden( $msg1, $msg2 ) {
190
	global $wgImgAuthDetails;
191
192
	$args = func_get_args();
193
	array_shift( $args );
194
	array_shift( $args );
195
	$args = ( isset( $args[0] ) && is_array( $args[0] ) ) ? $args[0] : $args;
196
197
	$msgHdr = wfMessage( $msg1 )->escaped();
198
	$detailMsgKey = $wgImgAuthDetails ? $msg2 : 'badaccess-group0';
199
	$detailMsg = wfMessage( $detailMsgKey, $args )->escaped();
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
	echo <<<ENDS
210
<!DOCTYPE html>
211
<html>
212
<head>
213
<meta charset="UTF-8" />
214
<title>$msgHdr</title>
215
</head>
216
<body>
217
<h1>$msgHdr</h1>
218
<p>$detailMsg</p>
219
</body>
220
</html>
221
ENDS;
222
}
223