108 */ |
123 */ |
109 |
124 |
110 var $in_keepalive = false; |
125 var $in_keepalive = false; |
111 |
126 |
112 /** |
127 /** |
|
128 * UUID for this server instance |
|
129 * @var string |
|
130 */ |
|
131 |
|
132 var $uuid = '00000000-0000-0000-0000-000000000000'; |
|
133 |
|
134 /** |
|
135 * Switch to track whether a scriptlet is running. If it is, send_http_error() does more than normal. |
|
136 * @var bool |
|
137 */ |
|
138 |
|
139 var $in_scriptlet = false; |
|
140 |
|
141 /** |
|
142 * Switch to track whether headers have been sent or not. |
|
143 * @var bool |
|
144 */ |
|
145 |
|
146 var $headers_sent = false; |
|
147 |
|
148 /** |
|
149 * Switch to track if the socket is bound and thus needs to be freed or not |
|
150 * @var bool |
|
151 */ |
|
152 |
|
153 var $socket_initted = false; |
|
154 |
|
155 /** |
113 * Constructor. |
156 * Constructor. |
114 * @param string IPv4 address to bind to |
157 * @param string IPv4 address to bind to |
115 * @param int Port number |
158 * @param int Port number |
116 */ |
159 * @param int If port is under 1024, specify a user ID/name to switch to here |
117 |
160 * @param int If port is under 1024, specify a group ID/name to switch to here |
118 function __construct($address = '127.0.0.1', $port = 8080) |
161 */ |
|
162 |
|
163 function __construct($address = '127.0.0.1', $port = 8080, $targetuser = null, $targetgroup = null) |
119 { |
164 { |
120 @set_time_limit(0); |
165 @set_time_limit(0); |
121 @ini_set('memory_limit', '256M'); |
166 @ini_set('memory_limit', '256M'); |
122 |
167 |
123 // do we have socket functions? |
168 // do we have socket functions? |
124 if ( !function_exists('socket_create') ) |
169 if ( !function_exists('socket_create') ) |
125 { |
170 { |
126 burnout('System does not support socket functions. Please rebuild your PHP or install an appropriate extension.'); |
171 burnout('System does not support socket functions. Please rebuild your PHP or install an appropriate extension.'); |
127 } |
172 } |
128 |
173 |
129 $this->sock = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp')); |
174 // make sure we're not running as root |
|
175 // note that if allow_root is true, you must specify a UID/GID (or user/group) to switch to once the socket is bound |
|
176 $allow_root = ( $port < 1024 ) ? true : false; |
|
177 if ( function_exists('posix_geteuid') ) |
|
178 { |
|
179 $euid = posix_geteuid(); |
|
180 $egid = posix_getegid(); |
|
181 $username = posix_getpwuid($euid); |
|
182 $username = $username['name']; |
|
183 $group = posix_getgrgid($egid); |
|
184 $group = $group['name']; |
|
185 if ( $euid == 0 && !$allow_root ) |
|
186 { |
|
187 // running as root but not on a privileged port - die for security |
|
188 burnout("Running as superuser (user \"$username\" and group \"$group\"). This is not allowed for security reasons."); |
|
189 } |
|
190 else if ( $euid == 0 && $allow_root ) |
|
191 { |
|
192 // running as root and port below 1024, so notify of the switch and verify that a target UID and GID were passed |
|
193 if ( $targetuser === null || $targetgroup === null ) |
|
194 { |
|
195 // no target user/group specified |
|
196 burnout("Must specify a target user and group when running server as root"); |
|
197 } |
|
198 // get info about target user/group |
|
199 if ( is_string($targetuser) ) |
|
200 { |
|
201 $targetuser = posix_getpwnam($targetuser); |
|
202 $targetuser = $targetuser['uid']; |
|
203 } |
|
204 if ( is_string($targetgroup) ) |
|
205 { |
|
206 $targetgroup = posix_getgrnam($targetgroup); |
|
207 $targetgroup = $targetgroup['uid']; |
|
208 } |
|
209 // make sure all info is valid |
|
210 if ( !is_int($targetuser) || !is_int($targetgroup) ) |
|
211 { |
|
212 burnout('Invalid user or group specified'); |
|
213 } |
|
214 $userinfo = posix_getpwuid($targetuser); |
|
215 $groupinfo = posix_getgrgid($targetgroup); |
|
216 if ( function_exists('status') ) |
|
217 status("Will switch to user \"{$userinfo['name']}\" and group \"{$groupinfo['name']}\" shortly after binding to socket"); |
|
218 } |
|
219 else if ( $allow_root && $euid > 0 ) |
|
220 { |
|
221 burnout("Must be superuser to bind to ports below 1024"); |
|
222 } |
|
223 } |
|
224 $socket_do_root = ( $allow_root ) ? function_exists('posix_geteuid') : false; |
|
225 |
|
226 $this->sock = @socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp')); |
130 if ( !$this->sock ) |
227 if ( !$this->sock ) |
131 throw new Exception('Could not create socket'); |
228 throw new Exception('Could not create socket'); |
132 $result = socket_bind($this->sock, $address, $port); |
229 $result = @socket_bind($this->sock, $address, $port); |
133 if ( !$result ) |
230 if ( !$result ) |
134 throw new Exception("Could not bind to $address:$port"); |
231 throw new Exception("Could not bind to $address:$port"); |
135 $result = socket_listen($this->sock, SOMAXCONN); |
232 $this->socket_initted = true; |
|
233 $result = @socket_listen($this->sock, SOMAXCONN); |
136 if ( !$result ) |
234 if ( !$result ) |
137 throw new Exception("Could not listen for connections $address:$port"); |
235 throw new Exception("Could not listen for connections $address:$port"); |
|
236 |
|
237 // if running as root and we made it here, switch credentials |
|
238 if ( $socket_do_root ) |
|
239 { |
|
240 posix_setuid($targetuser); |
|
241 posix_setgid($targetgroup); |
|
242 posix_setegid($targetgroup); |
|
243 posix_seteuid($targetuser); |
|
244 if ( function_exists('status') ) |
|
245 status('Successfully switched user ID'); |
|
246 } |
|
247 |
138 $this->bind_address = $address; |
248 $this->bind_address = $address; |
139 $this->server_string = "PhpHttpd/" . HTTPD_VERSION . " PHP/" . PHP_VERSION . "\r\n"; |
249 $this->server_string = "PhpHttpd/" . HTTPD_VERSION . " PHP/" . PHP_VERSION . "\r\n"; |
|
250 |
|
251 // create a UUID |
|
252 $uuid_base = md5(microtime() . ( function_exists('mt_rand') ? mt_rand() : rand() )); |
|
253 $this->uuid = substr($uuid_base, 0, 8) . '-' . |
|
254 substr($uuid_base, 8, 4) . '-' . |
|
255 substr($uuid_base, 12, 4) . '-' . |
|
256 substr($uuid_base, 16, 4) . '-' . |
|
257 substr($uuid_base, 20, 20); |
140 } |
258 } |
141 |
259 |
142 /** |
260 /** |
143 * Destructor. |
261 * Destructor. |
144 */ |
262 */ |
145 |
263 |
146 function __destruct() |
264 function __destruct() |
147 { |
265 { |
148 if ( !defined('HTTPD_WS_CHILD') ) |
266 if ( !defined('HTTPD_WS_CHILD') && $this->socket_initted ) |
149 { |
267 { |
150 status('WebServer: destroying socket'); |
268 if ( function_exists('status') ) |
|
269 status('WebServer: destroying socket'); |
|
270 // http://us3.php.net/manual/en/function.socket-bind.php |
|
271 if ( !@socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1) ) |
|
272 { |
|
273 echo socket_strerror(socket_last_error($sock)) . "\n"; |
|
274 } |
151 @socket_shutdown($this->sock, 2); |
275 @socket_shutdown($this->sock, 2); |
152 @socket_close($this->sock); |
276 @socket_close($this->sock); |
153 } |
277 } |
154 } |
278 } |
155 |
279 |
259 if ( isset($_SERVER['HTTP_CONNECTION']) && defined('HTTPD_WS_CHILD') ) |
387 if ( isset($_SERVER['HTTP_CONNECTION']) && defined('HTTPD_WS_CHILD') ) |
260 { |
388 { |
261 $this->in_keepalive = ( strtolower($_SERVER['HTTP_CONNECTION']) === 'keep-alive' ); |
389 $this->in_keepalive = ( strtolower($_SERVER['HTTP_CONNECTION']) === 'keep-alive' ); |
262 } |
390 } |
263 |
391 |
|
392 // parse authorization, if any |
|
393 if ( isset($_SERVER['PHP_AUTH_USER']) ) |
|
394 { |
|
395 unset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']); |
|
396 } |
264 if ( isset($_SERVER['HTTP_AUTHORIZATION']) ) |
397 if ( isset($_SERVER['HTTP_AUTHORIZATION']) ) |
265 { |
398 { |
266 $data = $_SERVER['HTTP_AUTHORIZATION']; |
399 $data = $_SERVER['HTTP_AUTHORIZATION']; |
267 $data = substr(strstr($data, ' '), 1); |
400 $data = substr(strstr($data, ' '), 1); |
268 $data = base64_decode($data); |
401 $data = base64_decode($data); |
269 $_SERVER['PHP_AUTH_USER'] = substr($data, 0, strpos($data, ':')); |
402 $_SERVER['PHP_AUTH_USER'] = substr($data, 0, strpos($data, ':')); |
270 $_SERVER['PHP_AUTH_PW'] = substr(strstr($data, ':'), 1); |
403 $_SERVER['PHP_AUTH_PW'] = substr(strstr($data, ':'), 1); |
271 } |
404 } |
272 |
405 |
|
406 // anything on POST? |
273 $postdata = ''; |
407 $postdata = ''; |
274 $_POST = array(); |
408 $_POST = array(); |
275 if ( $method == 'POST' ) |
409 if ( $method == 'POST' ) |
276 { |
410 { |
277 // read POST data |
411 // read POST data |
317 $_GET[$matches[2][$i]] = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true; |
454 $_GET[$matches[2][$i]] = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true; |
318 } |
455 } |
319 } |
456 } |
320 } |
457 } |
321 |
458 |
|
459 // init handler |
|
460 $handler = false; |
|
461 |
322 if ( $uri == '' ) |
462 if ( $uri == '' ) |
323 { |
463 { |
|
464 // user requested the root (/). If there's a default document, use that; else, see if we can do a directory listing |
324 $uri = strval($this->default_document); |
465 $uri = strval($this->default_document); |
|
466 if ( !$this->default_document && $this->allow_dir_list ) |
|
467 { |
|
468 // we can list directories and this was requested by the user, so list it out |
|
469 $handler = array('type' => 'rootdir'); |
|
470 } |
325 } |
471 } |
326 |
472 |
327 $uri_parts = explode('/', $uri); |
473 $uri_parts = explode('/', $uri); |
328 |
474 |
|
475 // hook for the special UUID handler |
|
476 if ( $uri_parts[0] === $this->uuid && !$handler ) |
|
477 { |
|
478 $handler = array('type' => 'sysuuid'); |
|
479 } |
|
480 |
329 // loop through URI parts, see if a handler is set |
481 // loop through URI parts, see if a handler is set |
330 $handler = false; |
|
331 for ( $i = count($uri_parts) - 1; $i >= 0; $i-- ) |
|
332 { |
|
333 $handler_test = implode('/', $uri_parts); |
|
334 if ( isset($this->handlers[$handler_test]) ) |
|
335 { |
|
336 $handler = $this->handlers[$handler_test]; |
|
337 $handler['id'] = $handler_test; |
|
338 break; |
|
339 } |
|
340 unset($uri_parts[$i]); |
|
341 } |
|
342 |
|
343 if ( !$handler ) |
482 if ( !$handler ) |
344 { |
483 { |
345 $this->send_http_error($remote, 404, "The requested URL /$uri was not found on this server."); |
484 for ( $i = count($uri_parts) - 1; $i >= 0; $i-- ) |
346 continue; |
485 { |
|
486 $handler_test = implode('/', $uri_parts); |
|
487 if ( isset($this->handlers[$handler_test]) ) |
|
488 { |
|
489 $handler = $this->handlers[$handler_test]; |
|
490 $handler['id'] = $handler_test; |
|
491 break; |
|
492 } |
|
493 unset($uri_parts[$i]); |
|
494 } |
|
495 } |
|
496 |
|
497 if ( !$handler ) |
|
498 { |
|
499 // try to make a fakie |
|
500 if ( $this->check_for_handler_children($uri) ) |
|
501 { |
|
502 $handler = array( |
|
503 'type' => 'folder', |
|
504 'dir' => "/{$this->uuid}/__fakie", |
|
505 'id' => $uri |
|
506 ); |
|
507 } |
|
508 if ( !$handler ) |
|
509 { |
|
510 $this->send_http_error($remote, 404, "The requested URL /$uri was not found on this server."); |
|
511 continue; |
|
512 } |
347 } |
513 } |
348 |
514 |
349 $this->send_standard_response($remote, $handler, $uri, $params); |
515 $this->send_standard_response($remote, $handler, $uri, $params); |
350 |
516 |
351 if ( !$this->in_keepalive && defined('HTTPD_WS_CHILD') ) |
517 if ( !$this->in_keepalive && defined('HTTPD_WS_CHILD') ) |
352 { |
518 { |
353 // if ( defined('HTTPD_WS_CHILD') ) |
519 // if ( defined('HTTPD_WS_CHILD') ) |
354 // status('Closing connection'); |
520 // status('Closing connection'); |
355 @socket_shutdown($remote); |
521 @socket_shutdown($remote); |
356 @socket_close($remote); |
522 @socket_close($remote); |
357 status('Exiting child process'); |
523 if ( function_exists('status') ) |
|
524 status('Exiting child process'); |
358 exit(0); |
525 exit(0); |
359 } |
526 } |
360 else if ( defined('HTTPD_WS_CHILD') ) |
527 else if ( defined('HTTPD_WS_CHILD') ) |
361 { |
528 { |
362 // if ( defined('HTTPD_WS_CHILD') ) |
529 // if ( defined('HTTPD_WS_CHILD') ) |
363 // status('Continuing connection'); |
530 // status('Continuing connection'); |
364 // @socket_write($remote, "\r\n\r\n"); |
531 // @socket_write($remote, "\r\n\r\n"); |
|
532 } |
|
533 else |
|
534 { |
|
535 @socket_shutdown($remote); |
|
536 @socket_close($remote); |
365 } |
537 } |
366 } |
538 } |
367 } |
539 } |
368 |
540 |
369 /** |
541 /** |
375 */ |
547 */ |
376 |
548 |
377 function send_client_headers($socket, $http_code = 200, $contenttype = 'text/html', $headers = '') |
549 function send_client_headers($socket, $http_code = 200, $contenttype = 'text/html', $headers = '') |
378 { |
550 { |
379 global $http_responses; |
551 global $http_responses; |
|
552 if ( $this->headers_sent ) |
|
553 return false; |
|
554 |
|
555 // this is reset after the request is completed (hopefully) |
|
556 $this->headers_sent = true; |
|
557 |
380 $reason_code = ( isset($http_responses[$http_code]) ) ? $http_responses[$http_code] : 'Unknown'; |
558 $reason_code = ( isset($http_responses[$http_code]) ) ? $http_responses[$http_code] : 'Unknown'; |
381 |
559 |
382 $_SERVER['HTTP_USER_AGENT'] = ( isset($_SERVER['HTTP_USER_AGENT']) ) ? $_SERVER['HTTP_USER_AGENT'] : '(no user agent)'; |
560 $_SERVER['HTTP_USER_AGENT'] = ( isset($_SERVER['HTTP_USER_AGENT']) ) ? $_SERVER['HTTP_USER_AGENT'] : '(no user agent)'; |
383 status("{$_SERVER['REMOTE_ADDR']} {$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']} $http_code {$_SERVER['HTTP_USER_AGENT']}"); |
561 |
|
562 if ( function_exists('status') ) |
|
563 status("{$_SERVER['REMOTE_ADDR']} {$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']} $http_code {$_SERVER['HTTP_USER_AGENT']}"); |
384 |
564 |
385 $headers = str_replace("\r\n", "\n", $headers); |
565 $headers = str_replace("\r\n", "\n", $headers); |
386 $headers = str_replace("\n", "\r\n", $headers); |
566 $headers = str_replace("\n", "\r\n", $headers); |
387 $headers = preg_replace("#[\r\n]+$#", '', $headers); |
567 $headers = preg_replace("#[\r\n]+$#", '', $headers); |
388 $connection = ( $this->in_keepalive ) ? 'keep-alive' : 'close'; |
568 $connection = ( $this->in_keepalive ) ? 'keep-alive' : 'close'; |
418 |
598 |
419 // import mimetypes |
599 // import mimetypes |
420 global $mime_types; |
600 global $mime_types; |
421 |
601 |
422 // trim handler id from uri |
602 // trim handler id from uri |
|
603 $uri_full = rtrim($uri, '/'); |
423 $uri = substr($uri, strlen($handler['id']) + 1); |
604 $uri = substr($uri, strlen($handler['id']) + 1); |
424 |
605 |
425 // get file path |
606 // get file path |
426 $file_path = rtrim($handler['dir'], '/') . $uri; |
607 $file_path = rtrim($handler['dir'], '/') . $uri; |
427 if ( file_exists($file_path) ) |
608 if ( file_exists($file_path) || $this->check_for_handler_children($uri_full) ) |
428 { |
609 { |
429 // found it :-D |
610 // found it :-D |
430 |
611 |
431 // is this a directory? |
612 // is this a directory? |
432 if ( is_dir($file_path) ) |
613 if ( is_dir($file_path) || $this->check_for_handler_children($uri_full) ) |
433 { |
614 { |
|
615 // allowed to list? |
434 if ( !$this->allow_dir_list ) |
616 if ( !$this->allow_dir_list ) |
435 { |
617 { |
436 $this->send_http_error($socket, 403, "Directory listing is not allowed."); |
618 $this->send_http_error($socket, 403, "Directory listing is not allowed."); |
437 return true; |
619 return true; |
438 } |
620 } |
439 // yes, list contents |
621 // yes, list contents |
440 $root = '/' . $handler['id'] . rtrim($uri, '/'); |
622 try |
441 $parent = substr($root, 0, strrpos($root, '/')) . '/'; |
623 { |
442 |
624 $dir_list = $this->list_directory($uri_full, true); |
|
625 } |
|
626 catch ( Exception $e ) |
|
627 { |
|
628 $this->send_http_error($socket, 500, "Directory listing failed due to an error in the listing core method. This may indicate that the webserver process does not have filesystem access to the specified directory.<br /><br />Debugging details:<pre>$e</pre>"); |
|
629 return true; |
|
630 } |
|
631 |
|
632 $root = rtrim($uri_full, '/') . '/'; |
|
633 $parent = rtrim(dirname(rtrim($uri_full, '/')), '/') . '/'; |
|
634 |
443 $contents = <<<EOF |
635 $contents = <<<EOF |
444 <html> |
636 <html> |
445 <head> |
637 <head> |
446 <title>Index of: $root</title> |
638 <title>Index of: $root</title> |
|
639 <link rel="stylesheet" type="text/css" href="/{$this->uuid}/dirlist.css" /> |
447 </head> |
640 </head> |
448 <body> |
641 <body> |
449 <h1>Index of $root</h1> |
642 <h1>Index of $root</h1> |
450 <ul> |
643 <ul> |
451 <li><a href="$parent">Parent directory</a></li> |
644 <li><tt><a href="$parent">Parent directory</a></tt></li> |
452 |
645 |
453 EOF; |
646 EOF; |
454 $dirs = array(); |
647 |
455 $files = array(); |
648 foreach ( $dir_list as $filename => $info ) |
456 $d = @opendir($file_path); |
|
457 while ( $dh = readdir($d) ) |
|
458 { |
649 { |
459 if ( $dh == '.' || $dh == '..' ) |
650 $ts = ( $info['type'] == 'folder' ) ? '/' : ''; |
460 continue; |
651 $contents .= ' <li><tt><a href="' . htmlspecialchars($root . basename($filename) . $ts) . '"><img alt="[ ]" src="/' . $this->uuid . '/' . $info['type'] . '.png" /> ' . htmlspecialchars($filename) . $ts . '</a></tt></li>' . "\n "; |
461 if ( is_dir("$file_path/$dh") ) |
|
462 $dirs[] = $dh; |
|
463 else |
|
464 $files[] = $dh; |
|
465 } |
|
466 asort($dirs); |
|
467 asort($files); |
|
468 foreach ( $dirs as $dh ) |
|
469 { |
|
470 $contents .= ' <li><a href="' . $root . '/' . $dh . '">' . $dh . '/</a></li>' . "\n "; |
|
471 } |
|
472 foreach ( $files as $dh ) |
|
473 { |
|
474 $contents .= ' <li><a href="' . $root . '/' . $dh . '">' . $dh . '</a></li>' . "\n "; |
|
475 } |
652 } |
476 $contents .= "\n </ul>\n <address>Served by {$this->server_string}</address>\n</body>\n</html>\n\n"; |
653 $contents .= "\n </ul>\n <address>Served by {$this->server_string}</address>\n</body>\n</html>\n\n"; |
477 |
654 |
478 $sz = strlen($contents); |
655 $sz = strlen($contents); |
479 $this->send_client_headers($socket, 200, 'text/html', "Content-length: $sz\r\n"); |
656 $this->send_client_headers($socket, 200, 'text/html', "Content-length: $sz\r\n"); |
565 { |
742 { |
566 $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server."); |
743 $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server."); |
567 } |
744 } |
568 |
745 |
569 break; |
746 break; |
570 case 'function': |
747 case 'script': |
571 // init vars |
748 // init vars |
572 $this->content_type = 'text/html'; |
749 $this->content_type = 'text/html'; |
573 $this->response_code = 200; |
750 $this->response_code = 200; |
574 $this->response_headers = array(); |
751 $this->response_headers = array(); |
575 |
752 |
576 // error handling |
753 // error handling |
577 @set_error_handler(array($this, 'function_error_handler'), E_ALL); |
754 @set_error_handler(array($this, 'function_error_handler'), E_ALL); |
578 try |
755 try |
579 { |
756 { |
580 ob_start(); |
757 ob_start(); |
581 $result = @call_user_func($handler['function'], $this); |
758 $this->in_scriptlet = true; |
|
759 $result = @call_user_func($handler['function'], $this, $socket); |
|
760 $this->in_scriptlet = false; |
582 $output = ob_get_contents(); |
761 $output = ob_get_contents(); |
583 ob_end_clean(); |
762 ob_end_clean(); |
584 } |
763 } |
585 catch ( Exception $e ) |
764 catch ( Exception $e ) |
586 { |
765 { |
587 restore_error_handler(); |
766 restore_error_handler(); |
588 $this->send_http_error($socket, 500, "A handler crashed with an exception; see the command line for details."); |
767 $this->send_http_error($socket, 500, "A handler crashed with an exception; see the command line for details."); |
589 status("caught exception in handler {$handler['id']}:\n$e"); |
768 if ( function_exists('status') ) |
|
769 status("caught exception in handler {$handler['id']}:\n$e"); |
590 return true; |
770 return true; |
591 } |
771 } |
592 restore_error_handler(); |
772 restore_error_handler(); |
593 |
773 |
594 // the handler function should return this magic string if it writes its own headers and socket data |
774 // the handler function should return this magic string if it writes its own headers and socket data |
608 // $output = dechex(strlen($output)) . "\r\n$output"; |
788 // $output = dechex(strlen($output)) . "\r\n$output"; |
609 |
789 |
610 // write body |
790 // write body |
611 @socket_write($socket, $output); |
791 @socket_write($socket, $output); |
612 |
792 |
|
793 $this->headers_sent = false; |
|
794 |
|
795 break; |
|
796 case 'sysuuid': |
|
797 // requested one of the system's icon images |
|
798 $uri_parts = explode('/', $_SERVER['REQUEST_URI']); |
|
799 if ( count($uri_parts) != 3 ) |
|
800 { |
|
801 $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server."); |
|
802 } |
|
803 |
|
804 // load image data |
|
805 $filename =& $uri_parts[2]; |
|
806 switch ( $filename ) |
|
807 { |
|
808 case 'script.png': |
|
809 ( !isset($image_data) ) ? $image_data = HTTPD_ICON_SCRIPT : null; |
|
810 case 'folder.png': |
|
811 ( !isset($image_data) ) ? $image_data = HTTPD_ICON_FOLDER : null; |
|
812 case 'file.png': |
|
813 ( !isset($image_data) ) ? $image_data = HTTPD_ICON_FILE : null; |
|
814 |
|
815 $image_data = base64_decode($image_data); |
|
816 $found = true; |
|
817 $type = 'image/png'; |
|
818 break; |
|
819 case 'dirlist.css': |
|
820 $type = 'text/css'; |
|
821 $found = true; |
|
822 $image_data = <<<EOF |
|
823 /** |
|
824 * PhpHttpd directory list visual style |
|
825 */ |
|
826 |
|
827 html { |
|
828 background-color: #c9c9c9; |
|
829 margin: 0; |
|
830 padding: 0; |
|
831 } |
|
832 |
|
833 body { |
|
834 background-color: #ffffff; |
|
835 margin: 20px; |
|
836 padding: 10px; |
|
837 border: 1px solid #aaaaaa; |
|
838 } |
|
839 |
|
840 a { |
|
841 text-decoration: none; |
|
842 } |
|
843 |
|
844 a img { |
|
845 border-width: 0; |
|
846 } |
|
847 |
|
848 ul { |
|
849 list-style-type: none; |
|
850 } |
|
851 |
|
852 EOF; |
|
853 break; |
|
854 default: |
|
855 $found = false; |
|
856 } |
|
857 |
|
858 // ship it out |
|
859 if ( $found ) |
|
860 { |
|
861 $lm_date = date('r', filemtime(__FILE__)); |
|
862 $size = strlen($image_data); |
|
863 $this->send_client_headers($socket, 200, $type, "Last-Modified: $lm_date\r\nContent-Length: $size"); |
|
864 @socket_write($socket, $image_data); |
|
865 } |
|
866 else |
|
867 { |
|
868 $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server."); |
|
869 } |
|
870 |
|
871 return true; |
|
872 |
|
873 break; |
|
874 case 'rootdir': |
|
875 // |
|
876 // list the contents of the document root |
|
877 // |
|
878 |
|
879 $handlers = $this->list_directory('/', true); |
|
880 |
|
881 $contents = <<<EOF |
|
882 <html> |
|
883 <head> |
|
884 <title>Index of: /</title> |
|
885 <link rel="stylesheet" type="text/css" href="/{$this->uuid}/dirlist.css" /> |
|
886 </head> |
|
887 <body> |
|
888 <h1>Index of /</h1> |
|
889 <ul> |
|
890 |
|
891 EOF; |
|
892 |
|
893 $html = ''; |
|
894 // generate content |
|
895 foreach ( $handlers as $uri => $handler ) |
|
896 { |
|
897 switch($handler['type']) |
|
898 { |
|
899 case 'folder': |
|
900 $image = 'folder.png'; |
|
901 $abbr = 'DIR'; |
|
902 $add = '/'; |
|
903 break; |
|
904 case 'file': |
|
905 default: |
|
906 $image = 'file.png'; |
|
907 $abbr = ' '; |
|
908 $add = ''; |
|
909 break; |
|
910 case 'script': |
|
911 $image = 'script.png'; |
|
912 $abbr = 'CGI'; |
|
913 $add = ''; |
|
914 break; |
|
915 } |
|
916 $html .= " <li><tt><a href=\"/$uri\"><img alt=\"[{$abbr}]\" src=\"/{$this->uuid}/{$image}\" /> {$uri}{$add}</a></tt></li>\n "; |
|
917 } |
|
918 |
|
919 $contents .= $html; |
|
920 $contents .= <<<EOF |
|
921 </ul> |
|
922 <address>Served by {$this->server_string}</address> |
|
923 </body> |
|
924 </html> |
|
925 EOF; |
|
926 |
|
927 // get length |
|
928 $len = strlen($contents); |
|
929 |
|
930 // send headers |
|
931 $this->send_client_headers($socket, 200, 'text/html', "Content-Length: $len"); |
|
932 |
|
933 // write to the socket |
|
934 @socket_write($socket, $contents); |
|
935 |
|
936 return true; |
613 break; |
937 break; |
614 } |
938 } |
615 } |
939 } |
616 |
940 |
617 /** |
941 /** |
705 { |
1039 { |
706 echo '<div style="border: 1px solid #AA0000; background-color: #FFF0F0; padding: 10px;">'; |
1040 echo '<div style="border: 1px solid #AA0000; background-color: #FFF0F0; padding: 10px;">'; |
707 echo "<b>PHP warning/error:</b> type $errno ($errstr) caught in <b>$errfile</b> on <b>$errline</b><br />"; |
1041 echo "<b>PHP warning/error:</b> type $errno ($errstr) caught in <b>$errfile</b> on <b>$errline</b><br />"; |
708 // echo "Error context:<pre>" . htmlspecialchars(print_r($errcontext, true)) . "</pre>"; |
1042 // echo "Error context:<pre>" . htmlspecialchars(print_r($errcontext, true)) . "</pre>"; |
709 echo '</div>'; |
1043 echo '</div>'; |
|
1044 } |
|
1045 |
|
1046 /** |
|
1047 * Lists out the contents of a directory, including virtual handlers. |
|
1048 * @example |
|
1049 * Example return data: (will be ksorted) |
|
1050 <code> |
|
1051 array( |
|
1052 'bar' => 'folder', |
|
1053 'baz' => 'script', |
|
1054 'foo' => 'file' |
|
1055 ); |
|
1056 </code> |
|
1057 * @param string Directory name, relative to the server's document root |
|
1058 * @param bool If true, sorts folders first (default: false) |
|
1059 * @return array Exception thrown on failure |
|
1060 */ |
|
1061 |
|
1062 function list_directory($dir, $folders_first = false) |
|
1063 { |
|
1064 // clean slashes from the directory name |
|
1065 $dir = trim($dir, '/'); |
|
1066 |
|
1067 if ( $dir == '' ) |
|
1068 { |
|
1069 // |
|
1070 // list the root, which can consist only of handlers |
|
1071 // |
|
1072 |
|
1073 // copy the handlers array, which we need to ksort |
|
1074 $handlers = $this->handlers; |
|
1075 |
|
1076 // get rid of multi-depth handlers |
|
1077 foreach ( $handlers as $uri => $handler ) |
|
1078 { |
|
1079 if ( strpos($uri, '/') ) |
|
1080 { |
|
1081 unset($handlers[$uri]); |
|
1082 $newuri = explode('/', $uri); |
|
1083 if ( !isset($handlers[$newuri[0]]) ) |
|
1084 { |
|
1085 $handlers[$newuri[0]] = array( |
|
1086 'type' => 'folder' |
|
1087 ); |
|
1088 } |
|
1089 } |
|
1090 } |
|
1091 |
|
1092 ksort($handlers); |
|
1093 |
|
1094 if ( $folders_first ) |
|
1095 { |
|
1096 // sort folders first |
|
1097 $handlers_sorted = array(); |
|
1098 foreach ( $handlers as $uri => $handler ) |
|
1099 { |
|
1100 if ( $handler['type'] == 'folder' ) |
|
1101 $handlers_sorted[$uri] = $handler; |
|
1102 } |
|
1103 foreach ( $handlers as $uri => $handler ) |
|
1104 { |
|
1105 if ( $handler['type'] != 'folder' ) |
|
1106 $handlers_sorted[$uri] = $handler; |
|
1107 } |
|
1108 $handlers = $handlers_sorted; |
|
1109 unset($handlers_sorted); |
|
1110 } |
|
1111 |
|
1112 // done |
|
1113 return $handlers; |
|
1114 } |
|
1115 else |
|
1116 { |
|
1117 // list something within the root |
|
1118 $dir_stack = explode('/', $dir); |
|
1119 |
|
1120 // lookup handler |
|
1121 $handler_search = $dir; |
|
1122 $found_handler = false; |
|
1123 $fake_handler = false; |
|
1124 $i = 1; |
|
1125 while ( $i > 0 ) |
|
1126 { |
|
1127 if ( isset($this->handlers[$handler_search]) ) |
|
1128 { |
|
1129 $found_handler = true; |
|
1130 break; |
|
1131 } |
|
1132 $i = strrpos($handler_search, '/'); |
|
1133 $handler_search = substr($handler_search, 0, strrpos($handler_search, '/')); |
|
1134 } |
|
1135 if ( $this->check_for_handler_children($dir) ) |
|
1136 { |
|
1137 $fake_handler = true; |
|
1138 } |
|
1139 else if ( !$found_handler ) |
|
1140 { |
|
1141 // nope. not there. |
|
1142 throw new Exception("ERR_NO_SUCH_FILE_OR_DIRECTORY"); |
|
1143 } |
|
1144 |
|
1145 // make sure this is a directory |
|
1146 if ( !$fake_handler ) |
|
1147 { |
|
1148 $handler =& $handler_search; |
|
1149 if ( $this->handlers[$handler]['type'] != 'folder' ) |
|
1150 { |
|
1151 throw new Exception("ERR_NOT_A_DIRECTORY"); |
|
1152 } |
|
1153 |
|
1154 // determine real path |
|
1155 $real_path = realpath($this->handlers[$handler]['dir'] . substr($dir, strlen($handler))); |
|
1156 |
|
1157 // directory is resolved; list contents |
|
1158 $dir_contents = array(); |
|
1159 |
|
1160 if ( $dr = opendir($real_path) ) |
|
1161 { |
|
1162 while ( $dh = readdir($dr) ) |
|
1163 { |
|
1164 if ( $dh == '.' || $dh == '..' ) |
|
1165 { |
|
1166 continue; |
|
1167 } |
|
1168 $dir_contents[$dh] = array( |
|
1169 'type' => ( is_dir("$real_path/$dh") ) ? 'folder' : 'file', |
|
1170 'size' => filesize("$real_path/$dh"), |
|
1171 'time' => filemtime("$real_path/$dh") |
|
1172 ); |
|
1173 } |
|
1174 } |
|
1175 else |
|
1176 { |
|
1177 // only if directory open failed |
|
1178 throw new Exception("ERR_PERMISSION_DENIED"); |
|
1179 } |
|
1180 |
|
1181 closedir($dr); |
|
1182 |
|
1183 // some cleanup |
|
1184 unset($handler, $handler_search); |
|
1185 } |
|
1186 |
|
1187 // list any additional handlers in there |
|
1188 foreach ( $this->handlers as $handler => $info ) |
|
1189 { |
|
1190 // parse handler name |
|
1191 $handler_name = explode('/', trim($handler, '/')); |
|
1192 // is this handler in this directory? |
|
1193 if ( count($handler_name) != count($dir_stack) + 1 ) |
|
1194 { |
|
1195 continue; |
|
1196 } |
|
1197 foreach ( $dir_stack as $i => $_ ) |
|
1198 { |
|
1199 if ( $dir_stack[$i] != $handler_name[$i] ) |
|
1200 { |
|
1201 continue 2; |
|
1202 } |
|
1203 } |
|
1204 // it's in here! |
|
1205 $dir_contents[ basename($handler) ] = array( |
|
1206 'type' => $info['type'] |
|
1207 ); |
|
1208 } |
|
1209 |
|
1210 // list "fake" handlers |
|
1211 foreach ( $this->handlers as $handler => $info ) |
|
1212 { |
|
1213 // parse handler name |
|
1214 $handler_name = explode('/', trim($handler, '/')); |
|
1215 // is this handler somewhere underneath this directory? |
|
1216 if ( count($handler_name) < count($dir_stack) + 2 ) |
|
1217 { |
|
1218 continue; |
|
1219 } |
|
1220 // path check |
|
1221 foreach ( $dir_stack as $i => $_ ) |
|
1222 { |
|
1223 if ( $dir_stack[$i] != $handler_name[$i] ) |
|
1224 { |
|
1225 continue 2; |
|
1226 } |
|
1227 } |
|
1228 // create a "fake" directory |
|
1229 $fakie_name = $handler_name[ count($dir_stack) ]; |
|
1230 $dir_contents[$fakie_name] = array( |
|
1231 'type' => 'folder' |
|
1232 ); |
|
1233 } |
|
1234 |
|
1235 if ( $folders_first ) |
|
1236 { |
|
1237 // perform folder sorting |
|
1238 $unsorted = $dir_contents; |
|
1239 ksort($unsorted); |
|
1240 $dir_contents = array(); |
|
1241 foreach ( $unsorted as $name => $info ) |
|
1242 { |
|
1243 if ( $info['type'] == 'folder' ) |
|
1244 $dir_contents[$name] = $info; |
|
1245 } |
|
1246 foreach ( $unsorted as $name => $info ) |
|
1247 { |
|
1248 if ( $info['type'] != 'folder' ) |
|
1249 $dir_contents[$name] = $info; |
|
1250 } |
|
1251 } |
|
1252 else |
|
1253 { |
|
1254 // not sorting with folders first, so just alphabetize |
|
1255 ksort($dir_contents); |
|
1256 } |
|
1257 |
|
1258 // done |
|
1259 |
|
1260 return $dir_contents; |
|
1261 } |
|
1262 } |
|
1263 |
|
1264 /** |
|
1265 * Searches deeper to see if there are sub-handlers within a path to see if a fake handler can be created |
|
1266 * @param string URI |
|
1267 * @return bool |
|
1268 */ |
|
1269 |
|
1270 function check_for_handler_children($file_path) |
|
1271 { |
|
1272 $file_path = trim($file_path, '/'); |
|
1273 $dir_stack = explode('/', $file_path); |
|
1274 |
|
1275 // make sure this isn't a "real" handler |
|
1276 if ( isset($this->handlers[$file_path]) ) |
|
1277 { |
|
1278 return false; |
|
1279 } |
|
1280 |
|
1281 // list any additional handlers in there |
|
1282 foreach ( $this->handlers as $handler => $info ) |
|
1283 { |
|
1284 // parse handler name |
|
1285 $handler_name = explode('/', trim($handler, '/')); |
|
1286 // is this handler in this directory? |
|
1287 if ( count($handler_name) != count($dir_stack) + 1 ) |
|
1288 { |
|
1289 continue; |
|
1290 } |
|
1291 foreach ( $dir_stack as $i => $_ ) |
|
1292 { |
|
1293 if ( $dir_stack[$i] != $handler_name[$i] ) |
|
1294 { |
|
1295 continue 2; |
|
1296 } |
|
1297 } |
|
1298 // it's in here! |
|
1299 return true; |
|
1300 } |
|
1301 |
|
1302 return false; |
710 } |
1303 } |
711 |
1304 |
712 } |
1305 } |
713 |
1306 |
714 /** |
1307 /** |