(bug 31514) Followup r99031: allow clicking of links in table headers in a different...
[lhc/web/wiklou.git] / thumb-handler.php
1 <?php
2
3 # Valid web server entry point
4 define( 'THUMB_HANDLER', true );
5
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" );
11 }
12 require( $configPath );
13
14 function wfHandleThumb404() {
15 global $thgThumbCallbacks, $thgThumb404File;
16
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'] ) );
23 } else {
24 $uri = $_SERVER['REQUEST_URI'];
25 }
26
27 # Extract thumb.php params from the URI...
28 if ( isset( $thgThumbCallbacks['extractParams'] )
29 && is_callable( $thgThumbCallbacks['extractParams'] ) )
30 {
31 # Overridden by configuration
32 $params = call_user_func_array( $thgThumbCallbacks['extractParams'], array( $uri ) );
33 } else {
34 $params = wfExtractThumbParams( $uri ); // basic wiki URL param extracting
35 }
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
39 return;
40 }
41
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." .
48 "</body></html>";
49 return;
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." .
56 "</body></html>";
57 return;
58 }
59
60 # Check any backend caches for the thumbnail...
61 if ( isset( $thgThumbCallbacks['checkCache'] )
62 && is_callable( $thgThumbCallbacks['checkCache'] ) )
63 {
64 if ( call_user_func_array( $thgThumbCallbacks['checkCache'], array( $uri, $params ) ) ) {
65 return; // file streamed from backend thumb cache
66 }
67 }
68
69 wfStreamThumbViaCurl( $params, $uri );
70 }
71
72 /**
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.
75 *
76 * @param $uri String Thumbnail request URI
77 * @return Array|null associative params array or null
78 */
79 function wfExtractThumbParams( $uri ) {
80 global $thgThumbServer, $thgThumbFragment, $thgThumbHashFragment;
81
82 $thumbRegex = '!^(?:' . preg_quote( $thgThumbServer ) . ')?/' .
83 preg_quote( $thgThumbFragment ) . '(/archive|/temp|)/' .
84 $thgThumbHashFragment . '([^/]*)/(page(\d*)-)*(\d*)px-[^/]*$!';
85
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 );
90 if ( $pagenum ) {
91 $params['page'] = $pagenum;
92 }
93 if ( $archOrTemp == '/archive' ) {
94 $params['archived'] = 1;
95 } elseif ( $archOrTemp == '/temp' ) {
96 $params['temp'] = 1;
97 }
98 } else {
99 $params = null;
100 }
101
102 return $params;
103 }
104
105 /**
106 * cURL to thumb.php and stream back the resulting file or give an error message.
107 *
108 * @param $params Array Parameters to thumb.php
109 * @param $uri String Thumbnail request URI
110 * @return void
111 */
112 function wfStreamThumbViaCurl( array $params, $uri ) {
113 global $thgThumbCallbacks, $thgThumbScriptPath, $thgThumbCurlProxy, $thgThumbCurlTimeout;
114
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." .
121 "</body></html>";
122 return;
123 }
124
125 # Build up the request URL to use with CURL...
126 $reqURL = "{$thgThumbScriptPath}?";
127 $first = true;
128 foreach ( $params as $name => $value ) {
129 if ( $first ) {
130 $first = false;
131 } else {
132 $reqURL .= '&';
133 }
134 $reqURL .= "$name=$value"; // Note: value is already urlencoded
135 }
136
137 # Set relevant HTTP headers...
138 $headers = array();
139 $headers[] = "X-Original-URI: " . str_replace( "\n", '', $uri );
140 if ( isset( $thgThumbCallbacks['curlHeaders'] )
141 && is_callable( $thgThumbCallbacks['curlHeaders'] ) )
142 {
143 # Add on any custom headers (like XFF)
144 call_user_func_array( $thgThumbCallbacks['curlHeaders'], array( &$headers ) );
145 }
146
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] );
154 }
155 }
156
157 $ch = curl_init( $reqURL );
158 if ( $thgThumbCurlProxy ) {
159 curl_setopt( $ch, CURLOPT_PROXY, $thgThumbCurlProxy );
160 }
161
162 curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
163 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
164 curl_setopt( $ch, CURLOPT_TIMEOUT, $thgThumbCurlTimeout );
165
166 # Actually make the request
167 $text = curl_exec( $ch );
168
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 );
173 if ( $errno ) {
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' );
179 $contentType = '';
180 $text = '';
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' )
191 {
192 # Error message, suppress cache
193 header( 'HTTP/1.1 500 Internal server error' );
194 header( 'Cache-Control: no-cache' );
195 } else {
196 # OK thumbnail; save to any backend caches...
197 if ( isset( $thgThumbCallbacks['fillCache'] )
198 && is_callable( $thgThumbCallbacks['fillCache'] ) )
199 {
200 call_user_func_array( $thgThumbCallbacks['fillCache'], array( $uri, $text ) );
201 }
202 }
203
204 if ( !$contentType ) {
205 header( 'Content-Type:' );
206 } else {
207 header( "Content-Type: $contentType" );
208 }
209
210 print $text; // thumb data or error text
211
212 curl_close( $ch );
213 }
214
215 /**
216 * Get error message and content type for when the cURL response is empty.
217 *
218 * @param $ch cURL handle
219 * @return Array (error html, content type)
220 */
221 function wfCurlErrorText( $ch ) {
222 $contentType = 'text/html';
223 $error = htmlspecialchars( curl_error( $ch ) );
224 $text = <<<EOT
225 <html>
226 <head><title>Thumbnail error</title></head>
227 <body>Error retrieving thumbnail from scaling server: $error</body>
228 </html>
229 EOT;
230 return array( $text, $contentType );
231 }
232
233 /**
234 * Get error message and content type for when the cURL response is an error.
235 *
236 * @param $ch cURL handle
237 * @return Array (error html, content type)
238 */
239 function wfCurlEmptyText( $ch ) {
240 $contentType = 'text/html';
241 $error = htmlspecialchars( curl_error( $ch ) );
242 $text = <<<EOT
243 <html>
244 <head><title>Thumbnail error</title></head>
245 <body>Error retrieving thumbnail from scaling server: empty response</body>
246 </html>
247 EOT;
248 return array( $text, $contentType );
249 }
250
251 # Entry point
252 wfHandleThumb404();