| 1 | <?php |
|---|
| 2 | |
|---|
| 3 | /** |
|---|
| 4 | * Image authorisation script |
|---|
| 5 | * |
|---|
| 6 | * To use this, see http://www.mediawiki.org/wiki/Manual:Image_Authorization |
|---|
| 7 | * |
|---|
| 8 | * - Set $wgUploadDirectory to a non-public directory (not web accessible) |
|---|
| 9 | * - Set $wgUploadPath to point to this file |
|---|
| 10 | * |
|---|
| 11 | * Optional Parameters |
|---|
| 12 | * |
|---|
| 13 | * - Set $wgImgAuthDetails = true if you want the reason the access was denied messages to be displayed |
|---|
| 14 | * instead of just the 403 error (doesn't work on IE anyway), otherwise will only appear in error logs |
|---|
| 15 | * - Set $wgImgAuthPublicTest false if you don't want to just check and see if all are public |
|---|
| 16 | * must be set to false if using specific restrictions such as LockDown or NSFileRepo |
|---|
| 17 | * |
|---|
| 18 | * For security reasons, you usually don't want your user to know *why* access was denied, just that it was. |
|---|
| 19 | * If you want to change this, you can set $wgImgAuthDetails to 'true' in localsettings.php and it will give the user the reason |
|---|
| 20 | * why access was denied. |
|---|
| 21 | * |
|---|
| 22 | * Your server needs to support PATH_INFO; CGI-based configurations usually don't. |
|---|
| 23 | * |
|---|
| 24 | * @file |
|---|
| 25 | * |
|---|
| 26 | **/ |
|---|
| 27 | |
|---|
| 28 | define( 'MW_NO_OUTPUT_COMPRESSION', 1 ); |
|---|
| 29 | require_once( dirname( __FILE__ ) . '/includes/WebStart.php' ); |
|---|
| 30 | wfProfileIn( 'img_auth.php' ); |
|---|
| 31 | require_once( dirname( __FILE__ ) . '/includes/StreamFile.php' ); |
|---|
| 32 | |
|---|
| 33 | // See if this is a public Wiki (no protections) |
|---|
| 34 | if ( $wgImgAuthPublicTest |
|---|
| 35 | && in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) |
|---|
| 36 | { |
|---|
| 37 | wfForbidden('img-auth-accessdenied','img-auth-public'); |
|---|
| 38 | } |
|---|
| 39 | |
|---|
| 40 | // Check for bug 28235: QUERY_STRING overriding the correct extension |
|---|
| 41 | if ( isset( $_SERVER['QUERY_STRING'] ) |
|---|
| 42 | && preg_match( '/\.[^\\/:*?"<>|%]+(#|\?|$)/i', $_SERVER['QUERY_STRING'] ) ) |
|---|
| 43 | { |
|---|
| 44 | wfForbidden( 'img-auth-accessdenied', 'img-auth-bad-query-string' ); |
|---|
| 45 | } |
|---|
| 46 | |
|---|
| 47 | // Extract path and image information |
|---|
| 48 | if( !isset( $_SERVER['PATH_INFO'] ) ) |
|---|
| 49 | wfForbidden('img-auth-accessdenied','img-auth-nopathinfo'); |
|---|
| 50 | |
|---|
| 51 | $path = $_SERVER['PATH_INFO']; |
|---|
| 52 | $filename = realpath( $wgUploadDirectory . $_SERVER['PATH_INFO'] ); |
|---|
| 53 | $realUpload = realpath( $wgUploadDirectory ); |
|---|
| 54 | |
|---|
| 55 | // Basic directory traversal check |
|---|
| 56 | if( substr( $filename, 0, strlen( $realUpload ) ) != $realUpload ) |
|---|
| 57 | wfForbidden('img-auth-accessdenied','img-auth-notindir'); |
|---|
| 58 | |
|---|
| 59 | // Extract the file name and chop off the size specifier |
|---|
| 60 | // (e.g. 120px-Foo.png => Foo.png) |
|---|
| 61 | $name = wfBaseName( $path ); |
|---|
| 62 | if( preg_match( '!\d+px-(.*)!i', $name, $m ) ) |
|---|
| 63 | $name = $m[1]; |
|---|
| 64 | |
|---|
| 65 | // Check to see if the file exists |
|---|
| 66 | if( !file_exists( $filename ) ) |
|---|
| 67 | wfForbidden('img-auth-accessdenied','img-auth-nofile',$filename); |
|---|
| 68 | |
|---|
| 69 | // Check to see if tried to access a directory |
|---|
| 70 | if( is_dir( $filename ) ) |
|---|
| 71 | wfForbidden('img-auth-accessdenied','img-auth-isdir',$filename); |
|---|
| 72 | |
|---|
| 73 | |
|---|
| 74 | $title = Title::makeTitleSafe( NS_FILE, $name ); |
|---|
| 75 | |
|---|
| 76 | // See if could create the title object |
|---|
| 77 | if( !$title instanceof Title ) |
|---|
| 78 | wfForbidden('img-auth-accessdenied','img-auth-badtitle',$name); |
|---|
| 79 | |
|---|
| 80 | // Run hook |
|---|
| 81 | if (!wfRunHooks( 'ImgAuthBeforeStream', array( &$title, &$path, &$name, &$result ) ) ) |
|---|
| 82 | wfForbidden($result[0],$result[1],array_slice($result,2)); |
|---|
| 83 | |
|---|
| 84 | // Check user authorization for this title |
|---|
| 85 | // UserCanRead Checks Whitelist too |
|---|
| 86 | if( !$title->userCanRead() ) |
|---|
| 87 | wfForbidden('img-auth-accessdenied','img-auth-noread',$name); |
|---|
| 88 | |
|---|
| 89 | // Stream the requested file |
|---|
| 90 | wfDebugLog( 'img_auth', "Streaming `".$filename."`." ); |
|---|
| 91 | wfStreamFile( $filename, array( 'Cache-Control: private', 'Vary: Cookie' ) ); |
|---|
| 92 | wfLogProfilingData(); |
|---|
| 93 | |
|---|
| 94 | /** |
|---|
| 95 | * Issue a standard HTTP 403 Forbidden header ($msg1-a message index, not a message) and an |
|---|
| 96 | * error message ($msg2, also a message index), (both required) then end the script |
|---|
| 97 | * subsequent arguments to $msg2 will be passed as parameters only for replacing in $msg2 |
|---|
| 98 | */ |
|---|
| 99 | function wfForbidden($msg1,$msg2) { |
|---|
| 100 | global $wgImgAuthDetails; |
|---|
| 101 | $args = func_get_args(); |
|---|
| 102 | array_shift( $args ); |
|---|
| 103 | array_shift( $args ); |
|---|
| 104 | $MsgHdr = htmlspecialchars(wfMsg($msg1)); |
|---|
| 105 | $detailMsg = (htmlspecialchars(wfMsg(($wgImgAuthDetails ? $msg2 : 'badaccess-group0'),$args))); |
|---|
| 106 | wfDebugLog('img_auth', "wfForbidden Hdr:".wfMsgExt( $msg1, array('language' => 'en'))." Msg: ". |
|---|
| 107 | wfMsgExt($msg2,array('language' => 'en'),$args)); |
|---|
| 108 | header( 'HTTP/1.0 403 Forbidden' ); |
|---|
| 109 | header( 'Cache-Control: no-cache' ); |
|---|
| 110 | header( 'Content-Type: text/html; charset=utf-8' ); |
|---|
| 111 | echo <<<ENDS |
|---|
| 112 | <html> |
|---|
| 113 | <body> |
|---|
| 114 | <h1>$MsgHdr</h1> |
|---|
| 115 | <p>$detailMsg</p> |
|---|
| 116 | </body> |
|---|
| 117 | </html> |
|---|
| 118 | ENDS; |
|---|
| 119 | wfLogProfilingData(); |
|---|
| 120 | exit(); |
|---|
| 121 | } |
|---|