3 # Valid web server entry point
4 define( 'THUMB_HANDLER', true );
6 # Load thumb-handler configuration. We don't want to use
7 # WebStart.php or the like as it would kill performance.
8 $configPath = dirname( __FILE__
) . "/thumb.config.php";
9 if ( !file_exists( $configPath ) ) {
10 die( "Thumb-handler.php is not enabled for this wiki.\n" );
12 require( $configPath );
14 function wfHandleThumb404() {
15 global $thgThumbCallbacks, $thgThumb404File;
17 # lighttpd puts the original request in REQUEST_URI, while
18 # sjs sets that to the 404 handler, and puts the original
19 # request in REDIRECT_URL.
20 if ( isset( $_SERVER['REDIRECT_URL'] ) ) {
21 # The URL is un-encoded, so put it back how it was.
22 $uri = str_replace( "%2F", "/", urlencode( $_SERVER['REDIRECT_URL'] ) );
24 $uri = $_SERVER['REQUEST_URI'];
27 # Extract thumb.php params from the URI...
28 if ( isset( $thgThumbCallbacks['extractParams'] )
29 && is_callable( $thgThumbCallbacks['extractParams'] ) )
31 # Overridden by configuration
32 $params = call_user_func_array( $thgThumbCallbacks['extractParams'], array( $uri ) );
34 $params = wfExtractThumbParams( $uri ); // basic wiki URL param extracting
36 if ( $params === null ) { // not a valid thumb request
37 header( 'X-Debug: no regex match' ); // useful for debugging
38 require_once( $thgThumb404File ); // standard 404 message
42 # Do some basic checks on the filename...
43 if ( preg_match( '/[\x80-\xff]/', $uri ) ) {
44 header( 'HTTP/1.0 400 Bad request' );
45 header( 'Content-Type: text/html' );
46 print "<html><head><title>Bad request</title></head><body>" .
47 "The URI contained bytes with the high bit set, this is not allowed." .
50 } elseif ( strpos( $params['f'], '%20' ) !== false ) {
51 header( 'HTTP/1.0 404 Not found' );
52 header( 'Content-Type: text/html' );
53 header( 'X-Debug: filename contains a space' ); // useful for debugging
54 print "<html><head><title>Not found</title></head><body>" .
55 "The URL contained spaces, we don't have any thumbnail files with spaces." .
60 # Check any backend caches for the thumbnail...
61 if ( isset( $thgThumbCallbacks['checkCache'] )
62 && is_callable( $thgThumbCallbacks['checkCache'] ) )
64 if ( call_user_func_array( $thgThumbCallbacks['checkCache'], array( $uri, $params ) ) ) {
65 return; // file streamed from backend thumb cache
69 wfStreamThumbViaCurl( $params, $uri );
73 * Extract the required params for thumb.php from the thumbnail request URI.
74 * At least 'width' and 'f' should be set if the result is an array.
76 * @param $uri String Thumbnail request URI
77 * @return Array|null associative params array or null
79 function wfExtractThumbParams( $uri ) {
80 global $thgThumbServer, $thgThumbFragment, $thgThumbHashFragment;
82 $thumbRegex = '!^(?:' . preg_quote( $thgThumbServer ) . ')?/' .
83 preg_quote( $thgThumbFragment ) . '(/archive|/temp|)/' .
84 $thgThumbHashFragment . '([^/]*)/(page(\d*)-)*(\d*)px-[^/]*$!';
86 # Is this a thumbnail?
87 if ( preg_match( $thumbRegex, $uri, $matches ) ) {
88 list( $all, $archOrTemp, $filename, $pagefull, $pagenum, $size ) = $matches;
89 $params = array( 'f' => $filename, 'width' => $size );
91 $params['page'] = $pagenum;
93 if ( $archOrTemp == '/archive' ) {
94 $params['archived'] = 1;
95 } elseif ( $archOrTemp == '/temp' ) {
106 * cURL to thumb.php and stream back the resulting file or give an error message.
108 * @param $params Array Parameters to thumb.php
109 * @param $uri String Thumbnail request URI
112 function wfStreamThumbViaCurl( array $params, $uri ) {
113 global $thgThumbCallbacks, $thgThumbScriptPath, $thgThumbCurlProxy, $thgThumbCurlTimeout;
115 if ( !function_exists( 'curl_init' ) ) {
116 header( 'HTTP/1.0 404 Not found' );
117 header( 'Content-Type: text/html' );
118 header( 'X-Debug: cURL is not enabled' ); // useful for debugging
119 print "<html><head><title>Not found</title></head><body>" .
120 "cURL is not enabled for PHP on this wiki. Unable to send request thumb.php." .
125 # Build up the request URL to use with CURL...
126 $reqURL = "{$thgThumbScriptPath}?";
128 foreach ( $params as $name => $value ) {
134 $reqURL .= "$name=$value"; // Note: value is already urlencoded
137 # Set relevant HTTP headers...
139 $headers[] = "X-Original-URI: " . str_replace( "\n", '', $uri );
140 if ( isset( $thgThumbCallbacks['curlHeaders'] )
141 && is_callable( $thgThumbCallbacks['curlHeaders'] ) )
143 # Add on any custom headers (like XFF)
144 call_user_func_array( $thgThumbCallbacks['curlHeaders'], array( &$headers ) );
147 # Pass through some other headers...
148 $passThrough = array( 'If-Modified-Since', 'Referer', 'User-Agent' );
149 foreach ( $passThrough as $headerName ) {
150 $serverVarName = 'HTTP_' . str_replace( '-', '_', strtoupper( $headerName ) );
151 if ( !empty( $_SERVER[$serverVarName] ) ) {
152 $headers[] = $headerName . ': ' .
153 str_replace( "\n", '', $_SERVER[$serverVarName] );
157 $ch = curl_init( $reqURL );
158 if ( $thgThumbCurlProxy ) {
159 curl_setopt( $ch, CURLOPT_PROXY
, $thgThumbCurlProxy );
162 curl_setopt( $ch, CURLOPT_HTTPHEADER
, $headers );
163 curl_setopt( $ch, CURLOPT_RETURNTRANSFER
, true );
164 curl_setopt( $ch, CURLOPT_TIMEOUT
, $thgThumbCurlTimeout );
166 # Actually make the request
167 $text = curl_exec( $ch );
169 # Send it on to the client
170 $errno = curl_errno( $ch );
171 $contentType = curl_getinfo( $ch, CURLINFO_CONTENT_TYPE
);
172 $httpCode = curl_getinfo( $ch, CURLINFO_HTTP_CODE
);
174 header( 'HTTP/1.1 500 Internal server error' );
175 header( 'Cache-Control: no-cache' );
176 list( $text, $contentType ) = wfCurlErrorText( $ch );
177 } elseif ( $httpCode == 304 ) {
178 header( 'HTTP/1.1 304 Not modified' );
181 } elseif ( strval( $text ) == '' ) {
182 header( 'HTTP/1.1 500 Internal server error' );
183 header( 'Cache-Control: no-cache' );
184 list( $text, $contentType ) = wfCurlEmptyText( $ch );
185 } elseif ( $httpCode == 404 ) {
186 header( 'HTTP/1.1 404 Not found' );
187 header( 'Cache-Control: s-maxage=300, must-revalidate, max-age=0' );
188 } elseif ( $httpCode != 200
189 ||
substr( $contentType, 0, 9 ) == 'text/html'
190 ||
substr( $text, 0, 5 ) == '<html' )
192 # Error message, suppress cache
193 header( 'HTTP/1.1 500 Internal server error' );
194 header( 'Cache-Control: no-cache' );
196 # OK thumbnail; save to any backend caches...
197 if ( isset( $thgThumbCallbacks['fillCache'] )
198 && is_callable( $thgThumbCallbacks['fillCache'] ) )
200 call_user_func_array( $thgThumbCallbacks['fillCache'], array( $uri, $text ) );
204 if ( !$contentType ) {
205 header( 'Content-Type:' );
207 header( "Content-Type: $contentType" );
210 print $text; // thumb data or error text
216 * Get error message and content type for when the cURL response is empty.
218 * @param $ch cURL handle
219 * @return Array (error html, content type)
221 function wfCurlErrorText( $ch ) {
222 $contentType = 'text/html';
223 $error = htmlspecialchars( curl_error( $ch ) );
226 <head><title>Thumbnail error</title></head>
227 <body>Error retrieving thumbnail from scaling server: $error</body>
230 return array( $text, $contentType );
234 * Get error message and content type for when the cURL response is an error.
236 * @param $ch cURL handle
237 * @return Array (error html, content type)
239 function wfCurlEmptyText( $ch ) {
240 $contentType = 'text/html';
241 $error = htmlspecialchars( curl_error( $ch ) );
244 <head><title>Thumbnail error</title></head>
245 <body>Error retrieving thumbnail from scaling server: empty response</body>
248 return array( $text, $contentType );