Sync with the WSI repository
This commit is contained in:
		
							parent
							
								
									01310403c1
								
							
						
					
					
						commit
						5862701455
					
				|  | @ -0,0 +1,42 @@ | |||
| SHELL = /bin/sh | ||||
| 
 | ||||
| version_id = 1.0 | ||||
| 
 | ||||
| TEMPDIR = /tmp | ||||
| 
 | ||||
| sunet_files = ChangeLog \
 | ||||
| 	Readme \
 | ||||
| 	cgi-script.scm \
 | ||||
| 	cgi-server.scm \
 | ||||
| 	conditionals.scm \
 | ||||
| 	crlf-io.scm \
 | ||||
| 	htmlout.scm \
 | ||||
| 	http-top.scm \
 | ||||
| 	httpd-access-control.scm \
 | ||||
| 	httpd-core.scm \
 | ||||
| 	httpd-error.scm \
 | ||||
| 	httpd-handlers.scm \
 | ||||
| 	info-gateway.scm \
 | ||||
| 	rman-gateway.scm \
 | ||||
| 	modules.scm \
 | ||||
| 	parse-forms.scm \
 | ||||
| 	program-modules.scm \
 | ||||
| 	rfc822.scm \
 | ||||
| 	scheme-program-server.scm \
 | ||||
| 	server.scm \
 | ||||
| 	seval.scm \
 | ||||
| 	smtp.scm \
 | ||||
| 	stringhax.scm \
 | ||||
| 	su-httpd.txt \
 | ||||
| 	toothless.scm \
 | ||||
| 	uri.scm \
 | ||||
| 	url.scm | ||||
| 
 | ||||
| sunet-$(version_id).tar.gz: $(sunet_files) | ||||
| 	sunet_root=`pwd`; \
 | ||||
| 	mkdir $(TEMPDIR)/sunet-$(version_id); \
 | ||||
| 	cp $(sunet_files) $(TEMPDIR)/sunet-$(version_id); \
 | ||||
| 	cd $(TEMPDIR); \
 | ||||
| 	tar czf sunet-$(version_id).tar.gz sunet-$(version_id); \
 | ||||
| 	mv sunet-$(version_id).tar.gz $$sunet_root; \
 | ||||
| 	rm -rf sunet-$(version_id) | ||||
|  | @ -0,0 +1,779 @@ | |||
| ; RFC 959 ftp daemon | ||||
| 
 | ||||
| ; Mike Sperber <sperber@informatik.uni-tuebingen.de> | ||||
| ; Copyright (c) 1998 Michael Sperber. | ||||
| 
 | ||||
| ; It doesn't support the following desirable things: | ||||
| ; | ||||
| ; - Login by user; this requires crypt which scsh doesn't have | ||||
| ; - RESTART support | ||||
| ; - Banners from files on CWD | ||||
| ; - Lots of fancy stuff like ProFTPD, http://www.proftpd.org/ | ||||
| 
 | ||||
| (define (ftpd . maybe-port) | ||||
|   (let ((port (optional maybe-port 21))) | ||||
|     (bind-listen-accept-loop | ||||
|      protocol-family/internet | ||||
|      (lambda (socket address) | ||||
|         | ||||
|        (set-ftp-socket-options! socket) | ||||
|         | ||||
|        (fork | ||||
| 	(lambda () | ||||
| 	  (handle-connection (socket:inport socket)  | ||||
| 			     (socket:outport socket)) | ||||
| 	  (reap-zombies) | ||||
| 	  (shutdown-socket socket shutdown/sends+receives)))) | ||||
|       | ||||
|      port))) | ||||
| 
 | ||||
| (define (ftpd-inetd) | ||||
|   (handle-connection (current-input-port) | ||||
| 		     (current-output-port))) | ||||
| 
 | ||||
| (define (set-ftp-socket-options! socket) | ||||
|   ;; If the client closes the connection, we won't lose when we try to | ||||
|   ;; close the socket by trying to flush the output buffer. | ||||
|   (set-port-buffering (socket:outport socket) bufpol/none) | ||||
| 
 | ||||
|   (set-socket-option socket level/socket socket/oob-inline #t)) | ||||
| 
 | ||||
| ; We're stateful anyway, so what the hell ... | ||||
| 
 | ||||
| (define *control-input-port* #f) | ||||
| (define *control-output-port* #f) | ||||
| 
 | ||||
| (define (handle-connection input-port output-port) | ||||
|   (call-with-current-continuation | ||||
|    (lambda (escape) | ||||
|      (with-handler | ||||
|       (lambda (condition more) | ||||
| 	(escape 'fick-dich-ins-knie)) | ||||
|       (lambda () | ||||
| 	(set! *control-input-port* input-port) | ||||
| 	(set! *control-output-port* output-port) | ||||
| 	(display-banner) | ||||
| 	(handle-commands)))))) | ||||
| 
 | ||||
| (define (display-banner) | ||||
|   (register-reply! 220 | ||||
| 		   (string-append | ||||
| 		    "Scheme Untergrund ftp server (" | ||||
| 		    *ftpd-version* | ||||
| 		    ") ready."))) | ||||
| 
 | ||||
| (define-condition-type 'ftpd-quit '()) | ||||
| (define ftpd-quit? (condition-predicate 'ftpd-quit)) | ||||
| 
 | ||||
| (define-condition-type 'ftpd-error '()) | ||||
| (define ftpd-error? (condition-predicate 'ftpd-error)) | ||||
| 
 | ||||
| (define (handle-commands) | ||||
|   (with-handler | ||||
|    (lambda (condition more) | ||||
|      ;; this in really only for ftpd-quit | ||||
|      (write-replies) | ||||
|      (more)) | ||||
|    (lambda () | ||||
|      (let loop () | ||||
|        (write-replies) | ||||
|        (accept-command) | ||||
|        (loop))))) | ||||
| 
 | ||||
| (define (accept-command) | ||||
|   (let ((command-line (read-crlf-line *control-input-port*))) | ||||
|     ;; (format #t "Command line: ~A~%" command-line) | ||||
|     (call-with-values | ||||
|      (lambda () (parse-command-line command-line)) | ||||
|      (lambda (command arg) | ||||
|        (handle-command command arg))))) | ||||
| 
 | ||||
| (define (handle-command command arg) | ||||
|   (call-with-current-continuation | ||||
|    (lambda (escape) | ||||
|      (with-handler | ||||
|       (lambda (condition more) | ||||
| 	(cond | ||||
| 	 ((error? condition) | ||||
| 	  (register-reply! 451 | ||||
| 			   (format #f "Internal error: ~S" | ||||
| 				   (condition-stuff condition))) | ||||
| 	  (escape 'fick-dich-ins-knie)) | ||||
| 	 ((ftpd-error? condition) | ||||
| 	  (escape 'fick-dich-ins-knie)) | ||||
| 	 (else | ||||
| 	  (more)))) | ||||
|       (lambda () | ||||
| 	(with-errno-handler* | ||||
| 	 (lambda (errno packet) | ||||
| 	   (register-reply! 451 | ||||
| 			    (format #f "Unix error: ~A." (car packet))) | ||||
| 	   (escape 'fick-dich-ins-knie)) | ||||
| 	 (lambda () | ||||
| 	   (dispatch-command command arg)))))))) | ||||
| 
 | ||||
| (define (dispatch-command command arg) | ||||
|   (cond | ||||
|    ((assoc command *command-alist*) | ||||
|     => (lambda (pair) | ||||
| 	 ((cdr pair) arg))) | ||||
|    (else | ||||
|     (register-reply! 500 | ||||
| 		     (string-append | ||||
| 		      (format #f "Unknown command: \"~A\"" command) | ||||
| 		      (if (string=? "" arg) | ||||
| 			  "." | ||||
| 			  (format #f " (argument(s) \"~A\")." arg))))))) | ||||
| 
 | ||||
| (define *logged-in?* #f) | ||||
| (define *authenticated?* #f) | ||||
| (define *anonymous?* #f) | ||||
| (define *root-directory* #f) | ||||
| (define *current-directory* "") | ||||
| 
 | ||||
| (define (handle-user name) | ||||
|   (cond | ||||
|    (*logged-in?* | ||||
|     (register-reply! 230 | ||||
| 		     "You are already logged in.")) | ||||
|    ((or (string=? "anonymous" name) | ||||
| 	(string=? "ftp" name)) | ||||
|     (handle-user-anonymous)) | ||||
|    (else | ||||
|     (register-reply! 530 | ||||
| 		     "Only anonymous logins allowed.")))) | ||||
| 
 | ||||
| (define (handle-user-anonymous) | ||||
|   (let ((ftp-info (user-info "ftp"))) | ||||
| 
 | ||||
|     (set-gid (user-info:gid ftp-info)) | ||||
|     (set-uid (user-info:uid ftp-info)) | ||||
| 
 | ||||
|     (set! *logged-in?* #t) | ||||
|     (set! *authenticated?* #t) | ||||
|     (set! *anonymous?* #t) | ||||
|     (set! *root-directory* (file-name-as-directory (user-info:home-dir ftp-info))) | ||||
|     (set! *current-directory* "") | ||||
| 
 | ||||
|     (register-reply! 230 "Anonymous user logged in."))) | ||||
| 
 | ||||
| (define (handle-pass password) | ||||
|   (cond | ||||
|    ((not *logged-in?*) | ||||
|     (register-reply! 530 "You have not logged in yet.")) | ||||
|    (*anonymous?* | ||||
|     (register-reply! 200 "Thank you.")) | ||||
|    (else | ||||
|     (register-reply! 502 "This can't happen.")))) | ||||
| 
 | ||||
| (define (handle-quit foo) | ||||
|   (register-reply! 221 "Goodbye!  Au revoir!  Auf Wiedersehen!") | ||||
|   (signal 'ftpd-quit)) | ||||
| 
 | ||||
| (define (handle-syst foo) | ||||
|   (register-reply! 215 "UNIX Type: L8")) | ||||
| 
 | ||||
| (define (handle-cwd path) | ||||
|   (ensure-authenticated-login) | ||||
|   (let ((current-directory (assemble-path path))) | ||||
|     (with-errno-handler* | ||||
|      (lambda (errno packet) | ||||
|        (signal-error! 550 | ||||
| 			(format #f "Can't change directory to \"~A\": ~A." | ||||
| 				path | ||||
| 				(car packet)))) | ||||
|      (lambda () | ||||
|        (with-cwd* | ||||
| 	(file-name-as-directory | ||||
| 	 (string-append *root-directory* current-directory)) | ||||
| 	(lambda ()			; I hate gratuitous syntax | ||||
| 	  (set! *current-directory* current-directory) | ||||
| 	  (register-reply! 250 | ||||
| 			   (format #f "Current directory changed to \"/~A\"." | ||||
| 				   current-directory)))))))) | ||||
| 
 | ||||
| (define (handle-cdup foo) | ||||
|   (handle-cwd "..")) | ||||
| 
 | ||||
| (define (handle-pwd foo) | ||||
|   (ensure-authenticated-login) | ||||
|   (register-reply! 257 | ||||
| 		   (format #f "Current directory is \"/~A\"." | ||||
| 			   *current-directory*))) | ||||
| 
 | ||||
| 
 | ||||
| (define (make-file-action-handler error-format-string action) | ||||
|   (lambda (path) | ||||
|     (ensure-authenticated-login) | ||||
|     (if (string=? "" path) | ||||
| 	(signal-error! 500 "No argument.")) | ||||
|     (let ((full-path (string-append *root-directory* | ||||
| 				    (assemble-path path)))) | ||||
|       (with-errno-handler* | ||||
|        (lambda (errno packet) | ||||
| 	 (signal-error! 550 | ||||
| 			(format #f error-format-string | ||||
| 				path (car packet)))) | ||||
|        (lambda () | ||||
| 	 (action path full-path)))))) | ||||
| 
 | ||||
| (define handle-dele | ||||
|   (make-file-action-handler | ||||
|    "Could not delete \"~A\": ~A." | ||||
|    (lambda (path full-path) | ||||
|      (delete-file full-path) | ||||
|      (register-reply! 250 (format #f "Deleted \"~A\"." path))))) | ||||
| 
 | ||||
| (define handle-mdtm | ||||
|   (make-file-action-handler | ||||
|    "Could not get info on \"~A\": ~A." | ||||
|    (lambda (path full-path) | ||||
|      (let* ((info (file-info full-path)) | ||||
| 	    (the-date (date (file-info:mtime info) 0))) | ||||
|        (register-reply! 213 | ||||
| 			(format-date "~Y~m~d~H~M~S" the-date)))))) | ||||
| 
 | ||||
| (define handle-mkd | ||||
|   (make-file-action-handler | ||||
|    "Could not make directory \"~A\": ~A." | ||||
|    (lambda (path full-path) | ||||
|      (create-directory full-path #o755) | ||||
|      (register-reply! 257 | ||||
| 		      (format #f "Created directory \"~A\"." path))))) | ||||
| 
 | ||||
| (define handle-rmd | ||||
|   (make-file-action-handler | ||||
|    "Could not remove directory \"~A\": ~A." | ||||
|    (lambda (path full-path) | ||||
|      (delete-directory full-path) | ||||
|      (register-reply! 250 | ||||
| 		      (format #f "Deleted directory \"~A\"." path))))) | ||||
| 
 | ||||
| (define *to-be-renamed* #f) | ||||
| 
 | ||||
| (define handle-rnfr | ||||
|   (make-file-action-handler | ||||
|    "Could not get info on file \"~A\": ~A." | ||||
|    (lambda (path full-path) | ||||
|      (file-info full-path) | ||||
|      (register-reply! 350 "RNFR accepted.  Gimme a RNTO next.") | ||||
|      (set! *to-be-renamed* full-path)))) | ||||
| 
 | ||||
| (define (handle-rnto path) | ||||
|   (ensure-authenticated-login) | ||||
|   (if (not *to-be-renamed*) | ||||
|       (signal-error! 503 "Need RNFR before RNTO.")) | ||||
|   (if (string=? "" path) | ||||
|       (signal-error! 500 "No argument.")) | ||||
|   (let ((full-path (string-append *root-directory* | ||||
| 				  (assemble-path path)))) | ||||
| 
 | ||||
|     (if (file-exists? full-path) | ||||
| 	(signal-error! | ||||
| 	 550 | ||||
| 	 (format #f "Rename failed---\"~A\" already exists or is protected." | ||||
| 		 path))) | ||||
| 
 | ||||
|     (with-errno-handler* | ||||
|      (lambda (errno packet) | ||||
|        (signal-error! 550 | ||||
| 		      (format #f "Could not rename: ~A." path))) | ||||
|      (lambda () | ||||
|        (rename-file *to-be-renamed* full-path) | ||||
|        (register-reply! 250 "File renamed.") | ||||
|        (set! *to-be-renamed* #f))))) | ||||
|    | ||||
| (define handle-size | ||||
|   (make-file-action-handler | ||||
|    "Could not get info on file \"~A\": ~A." | ||||
|    (lambda (path full-path) | ||||
|      (let ((info (file-info full-path))) | ||||
|        (if (not (eq? 'regular (file-info:type info))) | ||||
| 	   (signal-error! 550 | ||||
| 			  (format #f "\"~A\" is not a regular file." | ||||
| 				  path))) | ||||
|        (register-reply! 213 (number->string (file-info:size info))))))) | ||||
| 
 | ||||
| (define *type* 'ascii) | ||||
| 
 | ||||
| (define (handle-type arg) | ||||
|   (cond | ||||
|    ((string-ci=? "A" arg) | ||||
|     (set! *type* 'ascii)) | ||||
|    ((string-ci=? "I" arg) | ||||
|     (set! *type* 'image)) | ||||
|    ((string-ci=? "L8" arg) | ||||
|     (set! *type* 'image)) | ||||
|    (else | ||||
|     (signal-error! 504 | ||||
| 		   (format #f "Unknown TYPE: ~A." arg)))) | ||||
| 
 | ||||
|   (register-reply! 200 | ||||
| 		   (format #f "TYPE is now ~A." | ||||
| 			   (case *type* | ||||
| 			     ((ascii) "ASCII") | ||||
| 			     ((image) "8-bit binary") | ||||
| 			     (else "somethin' weird, man"))))) | ||||
| 
 | ||||
| (define (handle-mode arg) | ||||
|   (cond | ||||
|    ((string=? "" arg) | ||||
|     (register-reply! 500 | ||||
| 		     "No arguments.  Not to worry---I'd ignore them anyway.")) | ||||
|    ((string-ci=? "S" arg) | ||||
|     (register-reply! 200 "Using stream mode to transfer files.")) | ||||
|    (else | ||||
|     (register-reply! 504 (format #f "Mode \"~A\" is not supported." | ||||
| 				 arg))))) | ||||
| 
 | ||||
| (define (handle-stru arg) | ||||
|   (cond | ||||
|    ((string=? "" arg) | ||||
|     (register-reply! 500 | ||||
| 		     "No arguments.  Not to worry---I'd ignore them anyway.")) | ||||
|    ((string-ci=? "F" arg) | ||||
|     (register-reply! 200 "Using file structure to transfer files.")) | ||||
|    (else | ||||
|     (register-reply! 504 | ||||
| 		     (format #f "File structure \"~A\" is not supported." | ||||
| 			     arg))))) | ||||
| 
 | ||||
| (define (handle-noop arg) | ||||
|   (register-reply! 200 "Done nothing, but successfully.")) | ||||
| 
 | ||||
| (define *port-arg-regexp* | ||||
|   (make-regexp "^([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+)$")) | ||||
| 
 | ||||
| (define (parse-port-arg string) | ||||
|   (cond | ||||
|    ((regexp-exec *port-arg-regexp* string) | ||||
|     => (lambda (match) | ||||
| 	 (let ((components | ||||
| 		(map (lambda (match-index) | ||||
| 		       (string->number | ||||
| 			(match:substring match match-index))) | ||||
| 		     '(1 2 3 4 5 6)))) | ||||
| 	   (if (any? (lambda (component) | ||||
| 		       (> component 255)) | ||||
| 		     components) | ||||
| 	       (signal-error! 501 | ||||
| 			      "Invalid arguments to PORT.")) | ||||
| 	   (apply | ||||
| 	    (lambda (a1 a2 a3 a4 p1 p2) | ||||
| 	      (values (+ (arithmetic-shift a1 24) | ||||
| 			 (arithmetic-shift a2 16) | ||||
| 			 (arithmetic-shift a3 8) | ||||
| 			 a4) | ||||
| 		      (+ (arithmetic-shift p1 8) | ||||
| 			 p2))) | ||||
| 	    components)))) | ||||
|    (else | ||||
|     (signal-error! 500 | ||||
| 		   "Syntax error in argument to PORT.")))) | ||||
| 
 | ||||
| (define *data-socket* #f) | ||||
| 
 | ||||
| (define (handle-port stuff) | ||||
|   (ensure-authenticated-login) | ||||
|   (maybe-close-data-connection) | ||||
|   (call-with-values | ||||
|    (lambda () (parse-port-arg stuff)) | ||||
|    (lambda (address port) | ||||
|      (let ((socket (create-socket protocol-family/internet | ||||
| 				  socket-type/stream))) | ||||
| 
 | ||||
|        (set-socket-option socket level/socket socket/reuse-address #t) | ||||
| 
 | ||||
|        (connect-socket socket | ||||
| 		       (internet-address->socket-address | ||||
| 			address port)) | ||||
|         | ||||
|        (set! *data-socket* socket) | ||||
| 
 | ||||
|        (register-reply! 200 | ||||
| 			(format #f "Connected to ~A, port ~A." | ||||
| 				(format-internet-host-address address) | ||||
| 				port)))))) | ||||
| 
 | ||||
| (define *passive-socket* #f) | ||||
| 
 | ||||
| (define (handle-pasv stuff) | ||||
|   (ensure-authenticated-login) | ||||
|   (maybe-close-data-connection) | ||||
|   (let ((socket (create-socket protocol-family/internet | ||||
| 			       socket-type/stream))) | ||||
|      | ||||
|     (set-socket-option socket level/socket socket/reuse-address #t) | ||||
| 
 | ||||
|     ;; kludge | ||||
|     (bind-socket socket | ||||
| 		 (internet-address->socket-address (this-host-address) | ||||
| 						   0)) | ||||
|     (listen-socket socket 1) | ||||
| 
 | ||||
|     (let ((address (socket-local-address socket))) | ||||
| 
 | ||||
|       (call-with-values | ||||
|        (lambda () (socket-address->internet-address address)) | ||||
|        (lambda (host-address port) | ||||
| 
 | ||||
| 	 (set! *passive-socket* socket) | ||||
| 
 | ||||
| 	 (register-reply! 227 | ||||
| 			  (format #f "Passive mode OK (~A,~A)" | ||||
| 				  (format-internet-host-address host-address ",") | ||||
| 				  (format-port port)))))))) | ||||
| 
 | ||||
| ; This doesn't look right.  But I can't look into the socket of the | ||||
| ; control connection if we're running under inetd---there's no way to | ||||
| ; coerce a port to a socket as there is in C. | ||||
| 
 | ||||
| (define (this-host-address) | ||||
|   (car (host-info:addresses (host-info (system-name))))) | ||||
| 
 | ||||
| (define (format-internet-host-address address . maybe-separator) | ||||
| 
 | ||||
|   (define (extract shift) | ||||
|     (number->string | ||||
|      (bitwise-and (arithmetic-shift address (- shift)) | ||||
| 		  255))) | ||||
| 
 | ||||
|   (let ((separator (optional maybe-separator "."))) | ||||
|     (string-append | ||||
|      (extract 24) separator (extract 16) separator | ||||
|      (extract 8) separator (extract 0)))) | ||||
| 
 | ||||
| (define (format-port port) | ||||
|   (string-append | ||||
|    (number->string (bitwise-and (arithmetic-shift port -8) 255)) | ||||
|    "," | ||||
|    (number->string (bitwise-and port 255)))) | ||||
| 
 | ||||
| (define (handle-nlst arg) | ||||
|   (handle-listing arg '())) | ||||
| 
 | ||||
| (define (handle-list arg) | ||||
|   (handle-listing arg '(long))) | ||||
|    | ||||
| (define (handle-listing arg preset-flags) | ||||
|   (ensure-authenticated-login) | ||||
|   (with-data-connection | ||||
|    (lambda () | ||||
|      (let ((args (split-arguments arg))) | ||||
|        (call-with-values | ||||
| 	(lambda () | ||||
| 	  (partition-list | ||||
| 	   (lambda (arg) | ||||
| 	     (and (not (string=? "" arg)) | ||||
| 		  (char=? #\- (string-ref arg 0)))) | ||||
| 	   args)) | ||||
| 	(lambda (flag-args rest-args) | ||||
| 
 | ||||
| 	  (if (and (not (null? rest-args)) | ||||
| 		   (not (null? (cdr rest-args)))) | ||||
| 	      (signal-error! 501 "More than one path argument.")) | ||||
| 
 | ||||
| 	  (let ((path (if (null? rest-args) | ||||
| 			  "" | ||||
| 			  (car rest-args))) | ||||
| 		(flags (arguments->ls-flags flag-args))) | ||||
| 
 | ||||
| 	    (if (not flags) | ||||
| 		(signal-error! 501 "Invalid flag(s).")) | ||||
| 
 | ||||
| 	    (generate-listing path (append preset-flags flags))))))))) | ||||
| 
 | ||||
| ; Note this doesn't call ENSURE-AUTHENTICATED-LOGIN or | ||||
| ; ENSURE-DATA-CONNECTION. | ||||
| 
 | ||||
| (define (generate-listing path flags) | ||||
|   (let ((full-path (string-append *root-directory* | ||||
| 				  (assemble-path path)))) | ||||
|     (with-errno-handler* | ||||
|      (lambda (errno packet) | ||||
|        (signal-error! 451 | ||||
| 		      (format #f "Can't access directory at ~A: ~A." | ||||
| 			      path | ||||
| 			      (car packet)))) | ||||
|      (lambda () | ||||
|        (ls flags (list full-path) (socket:outport *data-socket*)))))) | ||||
| 
 | ||||
| (define (handle-abor foo) | ||||
|   (maybe-close-data-connection) | ||||
|   (register-reply! 226 "Closing data connection.")) | ||||
| 
 | ||||
| (define (handle-retr path) | ||||
|   (ensure-authenticated-login) | ||||
|   (let ((full-path (string-append *root-directory* | ||||
| 				  (assemble-path path)))) | ||||
|     (with-fatal-error-handler*		; CALL-WITH-INPUT-FILE doesn't go through ERRNO | ||||
|      (lambda (condition more) | ||||
|        (signal-error! 550 | ||||
| 		      (format #f "Can't open \"~A\" for reading." | ||||
| 			      path))) | ||||
|      (lambda () | ||||
|        (let ((info (file-info full-path))) | ||||
| 	 (if (not (eq? 'regular (file-info:type info))) | ||||
| 	     (signal-error! 450 | ||||
| 			    (format #f "\"~A\" is not a regular file." | ||||
| 				    path))) | ||||
| 	 (call-with-input-file full-path | ||||
| 	   (lambda (file-port) | ||||
| 	     (with-data-connection | ||||
| 	      (lambda () | ||||
| 		(case *type* | ||||
| 		  ((image) | ||||
| 		   (copy-port->port-binary | ||||
| 		    file-port | ||||
| 		    (socket:outport *data-socket*))) | ||||
| 		  ((ascii) | ||||
| 		   (copy-port->port-ascii | ||||
| 		    file-port | ||||
| 		    (socket:outport *data-socket*))))))))))))) | ||||
| 
 | ||||
| (define (handle-stor path) | ||||
|   (ensure-authenticated-login) | ||||
|   (let ((full-path (string-append *root-directory* | ||||
| 				  (assemble-path path)))) | ||||
|     (with-fatal-error-handler* | ||||
|      (lambda (condition more) | ||||
|        (signal-error! 550 | ||||
| 		      (format #f "Can't open \"~A\" for writing." | ||||
| 			      path))) | ||||
|      (lambda () | ||||
|        (call-with-output-file full-path | ||||
| 	 (lambda (file-port) | ||||
| 	   (with-data-connection | ||||
| 	    (lambda () | ||||
| 	      (case *type* | ||||
| 		((image) | ||||
| 		 (copy-port->port-binary | ||||
| 		  (socket:inport *data-socket*) | ||||
| 		  file-port)) | ||||
| 		((ascii) | ||||
| 		 (copy-ascii-port->port | ||||
| 		  (socket:inport *data-socket*) | ||||
| 		  file-port))))))))))) | ||||
|    | ||||
| (define (assemble-path path) | ||||
|   (let* ((interim-path | ||||
| 	  (if (not (file-name-rooted? path)) | ||||
| 	      (string-append (file-name-as-directory *current-directory*) | ||||
| 			     path) | ||||
| 	      path)) | ||||
| 	 (complete-path (if (file-name-rooted? interim-path) | ||||
| 			    (file-name-sans-rooted interim-path) | ||||
| 			    interim-path))) | ||||
|     (cond | ||||
|      ((normalize-path complete-path) | ||||
|       => (lambda (assembled-path) assembled-path)) | ||||
|      (else | ||||
|       (signal-error! 501 "Invalid pathname"))))) | ||||
| 
 | ||||
| (define (ensure-authenticated-login) | ||||
|   (if (or (not *logged-in?*) | ||||
| 	  (not *authenticated?*)) | ||||
|       (signal-error! 530 "You're not logged in yet."))) | ||||
| 
 | ||||
| (define (with-data-connection thunk) | ||||
|   (dynamic-wind ensure-data-connection | ||||
| 		thunk | ||||
| 		maybe-close-data-connection)) | ||||
|    | ||||
| (define *window-size* 51200) | ||||
| 
 | ||||
| (define (ensure-data-connection) | ||||
|   (if (and (not *data-socket*) (not *passive-socket*)) | ||||
|       (signal-error! 425 "No data connection.")) | ||||
| 
 | ||||
|   (if *passive-socket* | ||||
|       (call-with-values | ||||
|        (lambda () (accept-connection *passive-socket*)) | ||||
|        (lambda (socket socket-address) | ||||
| 	 (set! *data-socket* socket)))) | ||||
| 
 | ||||
|   (register-reply! 150 "Opening data connection.") | ||||
|   (write-replies) | ||||
| 
 | ||||
|   (set-socket-option *data-socket* level/socket | ||||
| 		     socket/send-buffer *window-size*) | ||||
|   (set-socket-option *data-socket* level/socket | ||||
| 		     socket/receive-buffer *window-size*)) | ||||
| 
 | ||||
| (define (maybe-close-data-connection) | ||||
|   (if (or *data-socket* *passive-socket*) | ||||
|       (close-data-connection))) | ||||
| 
 | ||||
| (define (close-data-connection) | ||||
|   (if *data-socket* | ||||
|       (close-socket *data-socket*)) | ||||
|   (if *passive-socket* | ||||
|       (close-socket *passive-socket*)) | ||||
|   (register-reply! 226 "Closing data connection.") | ||||
|   (set! *data-socket* #f) | ||||
|   (set! *passive-socket* #f)) | ||||
| 
 | ||||
| (define *command-alist* | ||||
|   (list | ||||
|    (cons "NOOP" handle-noop) | ||||
|    (cons "USER" handle-user) | ||||
|    (cons "PASS" handle-pass) | ||||
|    (cons "QUIT" handle-quit) | ||||
|    (cons "SYST" handle-syst) | ||||
|    (cons "CWD" handle-cwd) | ||||
|    (cons "PWD" handle-pwd) | ||||
|    (cons "CDUP" handle-cdup) | ||||
|    (cons "DELE" handle-dele) | ||||
|    (cons "MDTM" handle-mdtm) | ||||
|    (cons "MKD" handle-mkd) | ||||
|    (cons "RMD" handle-rmd) | ||||
|    (cons "RNFR" handle-rnfr) | ||||
|    (cons "RNTO" handle-rnto) | ||||
|    (cons "SIZE" handle-size) | ||||
|    (cons "TYPE" handle-type) | ||||
|    (cons "MODE" handle-mode) | ||||
|    (cons "STRU" handle-stru) | ||||
|    (cons "PORT" handle-port) | ||||
|    (cons "PASV" handle-pasv) | ||||
|    (cons "NLST" handle-nlst) | ||||
|    (cons "LIST" handle-list) | ||||
|    (cons "RETR" handle-retr) | ||||
|    (cons "STOR" handle-stor) | ||||
|    (cons "ABOR" handle-abor))) | ||||
| 
 | ||||
| (define (parse-command-line line) | ||||
|   (if (eof-object? line) ; Netscape does this | ||||
|       (values "QUIT" "") | ||||
|       (let* ((line (trim-spaces line)) | ||||
| 	     (split-position (index line #\space))) | ||||
| 	(if split-position | ||||
| 	    (values (upcase-string (substring line 0 split-position)) | ||||
| 		    (trim-spaces (substring line | ||||
| 					    (+ 1 split-position) | ||||
| 					    (string-length line)))) | ||||
| 	    (values (upcase-string line) ""))))) | ||||
| 
 | ||||
| ; Path names | ||||
| 
 | ||||
| ; This removes all internal ..'s from a path. | ||||
| ; NORMALIZE-PATH returns #f if PATH points to a parent directory. | ||||
| 
 | ||||
| (define (normalize-path path) | ||||
|   (let loop ((components (split-file-name (simplify-file-name path))) | ||||
| 	     (reverse-result '())) | ||||
|     (cond | ||||
|      ((null? components) | ||||
|       (path-list->file-name (reverse reverse-result))) | ||||
|      ((null? (cdr components)) | ||||
|       (if (string=? ".." (car components)) | ||||
| 	  #f | ||||
| 	  (path-list->file-name | ||||
| 	   (reverse (cons (car components) reverse-result))))) | ||||
|      ((string=? ".." (cadr components)) | ||||
|       (loop (cddr components) reverse-result)) | ||||
|      (else | ||||
|       (loop (cdr components) (cons (car components) reverse-result)))))) | ||||
| 
 | ||||
| (define (file-name-rooted? file-name) | ||||
|   (and (not (string=? "" file-name)) | ||||
|        (char=? #\/ (string-ref file-name 0)))) | ||||
| 
 | ||||
| (define (file-name-sans-rooted file-name) | ||||
|   (substring file-name 1 (string-length file-name))) | ||||
| 
 | ||||
| (define split-arguments | ||||
|   (infix-splitter " +")) | ||||
| 
 | ||||
| ; Reply handling | ||||
| 
 | ||||
| ; Replies must be synchronous with requests and actions.  Therefore, | ||||
| ; they are queued on generation via REGISTER-REPLY!.  The messages are | ||||
| ; printed via WRITE-REPLIES.  For the nature of the replies, see RFC | ||||
| ; 959. | ||||
| 
 | ||||
| (define *reverse-replies* '()) | ||||
| (define *reply-code* #f) ; the last one wins | ||||
| 
 | ||||
| (define (write-replies) | ||||
|   (if (not (null? *reverse-replies*)) | ||||
|       (let loop ((messages (reverse *reverse-replies*))) | ||||
| 	(if (null? (cdr messages)) | ||||
| 	    (write-final-reply (car messages)) | ||||
| 	    (begin | ||||
| 	      (write-nonfinal-reply (car messages)) | ||||
| 	      (loop (cdr messages)))))) | ||||
|   (set! *reverse-replies* '())) | ||||
| 
 | ||||
| (define (write-final-reply line) | ||||
|   (format *control-output-port* "~D ~A" *reply-code* line) | ||||
|   ;; (format #t "Reply: ~D ~A~%" *reply-code* line) | ||||
|   (write-crlf *control-output-port*)) | ||||
| 
 | ||||
| (define (write-nonfinal-reply line) | ||||
|   (format *control-output-port* "~D-~A" *reply-code* line) | ||||
|   ;; (format #t "Reply: ~D-~A~%" *reply-code* line) | ||||
|   (write-crlf *control-output-port*)) | ||||
| 
 | ||||
| (define (signal-error! code message) | ||||
|   (register-reply! code message) | ||||
|   (signal 'ftpd-error)) | ||||
| 
 | ||||
| (define (register-reply! code message) | ||||
|   (set! *reverse-replies*  | ||||
| 	(cons message *reverse-replies*)) | ||||
|   (set! *reply-code* code)) | ||||
| 
 | ||||
| ; Version | ||||
| 
 | ||||
| (define *ftpd-version* "$Revision: 1.1 $") | ||||
| 
 | ||||
| (define (copy-port->port-binary input-port output-port) | ||||
|   (let ((buffer (make-string *window-size*))) | ||||
|     (let loop () | ||||
|       (cond | ||||
|        ((read-string! buffer input-port) | ||||
| 	=> (lambda (length) | ||||
| 	     (write-string buffer output-port 0 length) | ||||
| 	     (loop)))))) | ||||
|   (force-output output-port)) | ||||
| 
 | ||||
| (define (copy-port->port-ascii input-port output-port) | ||||
|   (let loop () | ||||
|     (let ((line (read-line input-port 'concat))) | ||||
|       (if (not (eof-object? line)) | ||||
| 	  (let ((length (string-length line))) | ||||
| 	    (cond | ||||
| 	     ((zero? length) | ||||
| 	      'fick-dich-ins-knie) | ||||
| 	     ((char=? #\newline (string-ref line (- length 1))) | ||||
| 	      (write-string line output-port 0 (- length 1)) | ||||
| 	      (write-crlf output-port)) | ||||
| 	     (else | ||||
| 	      (write-string line output-port))) | ||||
| 	    (loop))))) | ||||
|   (force-output output-port)) | ||||
| 
 | ||||
| (define (copy-ascii-port->port input-port output-port) | ||||
|   (let loop () | ||||
|     (let* ((line (read-crlf-line input-port)) | ||||
| 	   (length (string-length line))) | ||||
|       (if (not (eof-object? line)) | ||||
| 	  (begin | ||||
| 	    (write-string line output-port 0 length) | ||||
| 	    (newline output-port) | ||||
| 	    (loop))))) | ||||
|   (force-output output-port)) | ||||
| 
 | ||||
| ; Utilities | ||||
| 
 | ||||
| (define (optional maybe-arg default-exp) | ||||
|   (cond | ||||
|    ((null? maybe-arg) default-exp) | ||||
|    ((null? (cdr maybe-arg)) (car maybe-arg)) | ||||
|    (else (error "too many optional arguments" maybe-arg)))) | ||||
| 
 | ||||
| ; Stuff from Big Scheme | ||||
| ; We can't open BIG-SCHEME because we use virgin SIGNALS.  Sigh. | ||||
| 
 | ||||
| (define any? (structure-ref big-scheme any?)) | ||||
| (define partition-list (structure-ref big-scheme partition-list)) | ||||
|  | @ -0,0 +1,92 @@ | |||
| ;;; Handle fatal errors in a sensible way. -*- Scheme -*- | ||||
| ;;; Copyright (c) 1995 by Olin Shivers. | ||||
| 
 | ||||
| ;;; (with-fatal-error-handler* handler thunk) | ||||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
| ;;; Call THUNK, and return whatever it returns. If THUNK signals a condition, | ||||
| ;;; and that condition is an error condition (or a subtype of error), then | ||||
| ;;; HANDLER gets a chance to handle it. | ||||
| ;;; The HANDLER proc is applied to two values:  | ||||
| ;;;     (HANDLER condition decline) | ||||
| ;;; HANDLER's continuation is WITH-FATAL-ERROR-HANDLER*'s; whatever HANDLER | ||||
| ;;; returns is returned from WITH-FATAL-ERROR-HANDLER. HANDLER declines to | ||||
| ;;; handle the error by throwing to DECLINE, a nullary continuation. | ||||
| ;;; | ||||
| ;;; Why is it called with-FATAL-error-handler*? Because returning to the | ||||
| ;;; guy that signalled the error is not an option. | ||||
| ;;; | ||||
| ;;; Why the nested outer pair of CALL/CC's? Well, what happens if the user's | ||||
| ;;; error handler *itself* raises an error? This could potentially give | ||||
| ;;; rise to an infinite loop, because WITH-HANDLER runs its handler in | ||||
| ;;; the original condition-signaller's context, so you'd search back for a | ||||
| ;;; handler, and find yourself again. For example, here is an infinite loop: | ||||
| ;;; | ||||
| ;;;   (with-handler (lambda (condition more) | ||||
| ;;;                   (display "Loop!") | ||||
| ;;;                   (error "ouch"))	; Get back, Loretta. | ||||
| ;;;     (lambda () (error "start me up"))) | ||||
| ;;; | ||||
| ;;; I could require W-F-E-H* users to code carefully, but instead I make sure | ||||
| ;;; the user's fatal-error handler runs in w-f-e-h*'s handler context, so | ||||
| ;;; if it signals a condition, we'll start the search from there. That's the | ||||
| ;;; point of continuation K. When the original thunk completes successfully, | ||||
| ;;; we dodge the K hackery by using ACCEPT to make a normal return. | ||||
| 
 | ||||
| (define (with-fatal-error-handler* handler thunk) | ||||
|   (call-with-current-continuation | ||||
|     (lambda (accept) | ||||
|       ((call-with-current-continuation | ||||
|          (lambda (k) | ||||
| 	   (with-handler (lambda (condition more) | ||||
| 			   (if (error? condition) | ||||
| 			       (call-with-current-continuation | ||||
| 				 (lambda (decline) | ||||
| 				   (k (lambda () (handler condition decline)))))) | ||||
| 			   (more))	; Keep looking for a handler. | ||||
| 	      (lambda () (call-with-values thunk accept))))))))) | ||||
| 		   | ||||
| (define-syntax with-fatal-error-handler  | ||||
|   (syntax-rules () | ||||
|     ((with-fatal-error-handler handler body ...) | ||||
|      (with-fatal-error-handler* handler | ||||
|        (lambda () body ...))))) | ||||
| 
 | ||||
| ;This one ran HANDLER in the signaller's condition-handler context. | ||||
| ;It was therefore susceptible to infinite loops if you didn't code  | ||||
| ;your handler's carefully. | ||||
| ; | ||||
| ;(define (with-fatal-error-handler* handler thunk) | ||||
| ;  (call-with-current-continuation | ||||
| ;    (lambda (accept) | ||||
| ;      (with-handler (lambda (condition more) | ||||
| ;		      (if (error? condition) | ||||
| ;			  (call-with-current-continuation | ||||
| ;		            (lambda (decline) | ||||
| ;			      (accept (handler condition decline))))) | ||||
| ;		      (more))	; Keep looking for a handler. | ||||
| ;        thunk)))) | ||||
| 
 | ||||
| ;;; (%error-handler-cond kont eh-clauses cond-clauses) | ||||
| ;;; Transform error-handler clauses into COND clauses by wrapping continuation | ||||
| ;;; KONT around the body of each e-h clause, so that if it fires, the result | ||||
| ;;; is thrown to KONT, but if no clause fires, the cond returns to the default | ||||
| ;;; continuation. | ||||
| 
 | ||||
| ;(define-syntax %error-handler-cond | ||||
| ;  (syntax-rules (=> else) | ||||
| ; | ||||
| ;   ((%error-handler-cond kont ((test => proc) clause ...) (ans ...)) | ||||
| ;    (%error-handler-cond kont | ||||
| ;			 (clause ...) | ||||
| ;			 ((test => (lambda (v) (kont (proc v)))) ans ...))) | ||||
| ; | ||||
| ;   ((%error-handler-cond kont ((test body ...) clause ...) (ans ...)) | ||||
| ;    (%error-handler-cond kont | ||||
| ;			 (clause ...) | ||||
| ;			 ((test (kont (begin body ...))) ans ...))) | ||||
| ; | ||||
| ;   ((%error-handler-cond kont ((else body ...)) (ans-clause ...)) | ||||
| ;    (cond (else body ...) ans-clause ...)) | ||||
| ; | ||||
| ;   ((%error-handler-cond kont () (ans-clause ...)) | ||||
| ;    (cond ans-clause ...)))) | ||||
|  | @ -26,11 +26,9 @@ | |||
| (define (access-controller . controls) | ||||
|   (lambda (info) | ||||
|     (let loop ((controls controls)) | ||||
|       (if (null? controls) | ||||
| 	  #f | ||||
| 	  (cond | ||||
| 	   (((car controls) info) => identity) | ||||
| 	   (else (loop (cdr controls)))))))) | ||||
|       (and (pair? controls) | ||||
| 	   (or ((car controls) info) | ||||
| 	       (loop (cdr controls))))))) | ||||
| 
 | ||||
| (define (access-controlled-handler control ph) | ||||
|   (lambda (path req) | ||||
|  |  | |||
|  | @ -6,10 +6,6 @@ | |||
| ;;; You recognise one with HTTP-ERROR?, and retrieve the pieces with | ||||
| ;;; CONDITION-STUFF. | ||||
| ;;; | ||||
| ;;; You can find out more about the Scheme 48 condition system by consulting | ||||
| ;;; s48-error.txt, where I scribbled some notes as I was browsing the source | ||||
| ;;; code when I wrote this file. | ||||
| 
 | ||||
| ;;; ,open conditions signals handle | ||||
| 
 | ||||
| ;;; HTTP error condition | ||||
|  | @ -39,93 +35,3 @@ | |||
| (define (fatal-syntax-error msg . irritants) | ||||
|   (apply signal 'fatal-syntax-error msg irritants)) | ||||
| 
 | ||||
| 
 | ||||
| ;;; (with-fatal-error-handler* handler thunk) | ||||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
| ;;; Call THUNK, and return whatever it returns. If THUNK signals a condition, | ||||
| ;;; and that condition is an error condition (or a subtype of error), then | ||||
| ;;; HANDLER gets a chance to handle it. | ||||
| ;;; The HANDLER proc is applied to two values:  | ||||
| ;;;     (HANDLER condition decline) | ||||
| ;;; HANDLER's continuation is WITH-FATAL-ERROR-HANDLER*'s; whatever HANDLER | ||||
| ;;; returns is returned from WITH-FATAL-ERROR-HANDLER. HANDLER declines to | ||||
| ;;; handle the error by throwing to DECLINE, a nullary continuation. | ||||
| ;;; | ||||
| ;;; Why is it called with-FATAL-error-handler*? Because returning to the | ||||
| ;;; guy that signalled the error is not an option. | ||||
| ;;; | ||||
| ;;; Why the nested outer pair of CALL/CC's? Well, what happens if the user's | ||||
| ;;; error handler *itself* raises an error? This could potentially give | ||||
| ;;; rise to an infinite loop, because WITH-HANDLER runs its handler in | ||||
| ;;; the original condition-signaller's context, so you'd search back for a | ||||
| ;;; handler, and find yourself again. For example, here is an infinite loop: | ||||
| ;;; | ||||
| ;;;   (with-handler (lambda (condition more) | ||||
| ;;;                   (display "Loop!") | ||||
| ;;;                   (error "ouch"))	; Get back, Loretta. | ||||
| ;;;     (lambda () (error "start me up"))) | ||||
| ;;; | ||||
| ;;; I could require W-F-E-H* users to code carefully, but instead I make sure | ||||
| ;;; the user's fatal-error handler runs in w-f-e-h*'s handler context, so | ||||
| ;;; if it signals a condition, we'll start the search from there. That's the | ||||
| ;;; point of continuation K. When the original thunk completes successfully, | ||||
| ;;; we dodge the K hackery by using ACCEPT to make a normal return. | ||||
| 
 | ||||
| (define (with-fatal-error-handler* handler thunk) | ||||
|   (call-with-current-continuation | ||||
|     (lambda (accept) | ||||
|       ((call-with-current-continuation | ||||
|          (lambda (k) | ||||
| 	   (with-handler (lambda (condition more) | ||||
| 			   (if (error? condition) | ||||
| 			       (call-with-current-continuation | ||||
| 				 (lambda (decline) | ||||
| 				   (k (lambda () (handler condition decline)))))) | ||||
| 			   (more))	; Keep looking for a handler. | ||||
| 	      (lambda () (call-with-values thunk accept))))))))) | ||||
| 		   | ||||
| (define-syntax with-fatal-error-handler  | ||||
|   (syntax-rules () | ||||
|     ((with-fatal-error-handler handler body ...) | ||||
|      (with-fatal-error-handler* handler | ||||
|        (lambda () body ...))))) | ||||
| 
 | ||||
| ;This one ran HANDLER in the signaller's condition-handler context. | ||||
| ;It was therefore susceptible to infinite loops if you didn't code  | ||||
| ;your handler's carefully. | ||||
| ; | ||||
| ;(define (with-fatal-error-handler* handler thunk) | ||||
| ;  (call-with-current-continuation | ||||
| ;    (lambda (accept) | ||||
| ;      (with-handler (lambda (condition more) | ||||
| ;		      (if (error? condition) | ||||
| ;			  (call-with-current-continuation | ||||
| ;		            (lambda (decline) | ||||
| ;			      (accept (handler condition decline))))) | ||||
| ;		      (more))	; Keep looking for a handler. | ||||
| ;        thunk)))) | ||||
| 
 | ||||
| ;;; (%error-handler-cond kont eh-clauses cond-clauses) | ||||
| ;;; Transform error-handler clauses into COND clauses by wrapping continuation | ||||
| ;;; KONT around the body of each e-h clause, so that if it fires, the result | ||||
| ;;; is thrown to KONT, but if no clause fires, the cond returns to the default | ||||
| ;;; continuation. | ||||
| 
 | ||||
| ;(define-syntax %error-handler-cond | ||||
| ;  (syntax-rules (=> else) | ||||
| ; | ||||
| ;   ((%error-handler-cond kont ((test => proc) clause ...) (ans ...)) | ||||
| ;    (%error-handler-cond kont | ||||
| ;			 (clause ...) | ||||
| ;			 ((test => (lambda (v) (kont (proc v)))) ans ...))) | ||||
| ; | ||||
| ;   ((%error-handler-cond kont ((test body ...) clause ...) (ans ...)) | ||||
| ;    (%error-handler-cond kont | ||||
| ;			 (clause ...) | ||||
| ;			 ((test (kont (begin body ...))) ans ...))) | ||||
| ; | ||||
| ;   ((%error-handler-cond kont ((else body ...)) (ans-clause ...)) | ||||
| ;    (cond (else body ...) ans-clause ...)) | ||||
| ; | ||||
| ;   ((%error-handler-cond kont () (ans-clause ...)) | ||||
| ;    (cond ans-clause ...)))) | ||||
|  |  | |||
|  | @ -499,7 +499,9 @@ | |||
| (define (file-extension->content-type fname) | ||||
|   (switch string-ci=? (file-name-extension fname) | ||||
|     ((".html")		"text/html") | ||||
|     ((".txt")           "text/plain") | ||||
|     ((".gif")		"image/gif") | ||||
|     ((".png")		"image/png") | ||||
|     ((".jpg" ".jpeg")	"image/jpeg") | ||||
|     ((".tiff" ".tif")	"image/tif") | ||||
|     ((".rtf")		"text/rtf") | ||||
|  | @ -511,7 +513,8 @@ | |||
|     ((".zip")		"application/zip") | ||||
|     ((".tar")		"application/tar") | ||||
|     ((".ps") 		"application/postscript") | ||||
|     (else #f))) | ||||
|     ((".pdf") 		"application/pdf") | ||||
|     (else               "application/octet-stream"))) | ||||
| 
 | ||||
| (define (file-extension->content-encoding fname) | ||||
|   (cond | ||||
|  |  | |||
|  | @ -0,0 +1,290 @@ | |||
| ; ls clone in scsh | ||||
| 
 | ||||
| ; Mike Sperber <sperber@informatik.uni-tuebingen.de> | ||||
| ; Copyright (c) 1998 Michael Sperber. | ||||
| 
 | ||||
| ; This currently does a whole bunch of stats on every file in some | ||||
| ; cases.  In a decent OS implementation, this stuff is cached, so | ||||
| ; there isn't any problem, at least not in theory :-) | ||||
| 
 | ||||
| ; FLAGS is a list of symbols from: | ||||
| ; | ||||
| ; all        - include stuff starting with "." | ||||
| ; recursive  - guess what | ||||
| ; long       - output interesting information per file | ||||
| ; directory  - display only the information for the directory named | ||||
| ; flag       - flag files as per their types | ||||
| ; columns    - sorts output vertically in a multicolumn format | ||||
| 
 | ||||
| (define (ls flags paths . maybe-port) | ||||
|   (let* ((port (optional maybe-port (current-output-port))) | ||||
| 	 (paths (if (null? paths) | ||||
| 		    (list (cwd)) | ||||
| 		    paths)) | ||||
| 	 (only-one? (null? (cdr paths)))) | ||||
|     (call-with-values | ||||
|      (lambda () (parse-flags flags)) | ||||
|      (lambda (all? recursive? long? directory? flag? columns?) | ||||
|        (real-ls paths | ||||
| 		(if only-one? #f "") | ||||
| 		all? recursive? long? directory? flag? columns? | ||||
| 		port))))) | ||||
| 
 | ||||
| (define (parse-flags flags) | ||||
|   (let ((all? (memq 'all flags)) | ||||
| 	(recursive? (memq 'recursive flags)) | ||||
| 	(long? (memq 'long flags)) | ||||
| 	(directory? (memq 'directory flags)) | ||||
| 	(flag? (memq 'flag flags)) | ||||
| 	(columns? (memq 'columns flags))) | ||||
|     (values all? recursive? long? directory? flag? columns?))) | ||||
| 
 | ||||
| (define (real-ls paths prefix | ||||
| 		 all? recursive? long? directory? flag? columns? | ||||
| 		 port) | ||||
|   (let ((first #t)) | ||||
|     (for-each | ||||
|      (lambda (path) | ||||
|        (if first | ||||
| 	   (set! first #f) | ||||
| 	   (newline port)) | ||||
|        (if prefix | ||||
| 	   (format port "~A~A:~%" prefix path)) | ||||
|        (ls-path path all? recursive? long? directory? flag? columns? port)) | ||||
|      paths))) | ||||
| 
 | ||||
| (define (ls-path path all? recursive? long? directory? flag? columns? port) | ||||
|   (cond | ||||
|    ((and (file-directory? path #f) | ||||
| 	 (not directory?)) | ||||
|     (ls-directory path all? recursive? long? directory? flag? columns? port)) | ||||
|    (else | ||||
|     (ls-file path long? flag? port)))) | ||||
| 
 | ||||
| (define (ls-directory directory all? recursive? long? directory? flag? columns? port) | ||||
|   (let* ((directory (file-name-as-directory directory)) | ||||
| 	 (substantial-directory (string-append directory ".")) | ||||
| 	 (files (directory-files substantial-directory all?))) | ||||
|     (with-cwd* | ||||
|      substantial-directory | ||||
|      (lambda () | ||||
|        (if (and (not long?) | ||||
| 		columns?) | ||||
| 	   (ls-files-columns files flag? port) | ||||
| 	   (ls-files-column files long? flag? port)) | ||||
|         | ||||
|        (if recursive? | ||||
| 	   (let ((directories | ||||
| 		  (map (lambda (file-name) | ||||
| 			 (string-append directory file-name)) | ||||
| 		       (filter (lambda (file) | ||||
| 				 (file-directory? file #f)) | ||||
| 			       files)))) | ||||
| 	     (if (not (null? directories)) | ||||
| 		 (begin | ||||
| 		   (newline port) | ||||
| 		   (real-ls directories directory | ||||
| 			    all? recursive? long? directory? flag? columns? | ||||
| 			    port))))))))) | ||||
| 
 | ||||
| (define *width* 79) | ||||
| 
 | ||||
| (define (ls-files-columns files flag? port) | ||||
|   (let* ((max-file-name-width | ||||
| 	  (if (null? files) | ||||
| 	      0 | ||||
| 	      (apply max (map string-length files)))) | ||||
| 	 (max-file-name-width | ||||
| 	  (if flag? | ||||
| 	      (+ 1 max-file-name-width) | ||||
| 	      max-file-name-width)) | ||||
| 
 | ||||
| 	 (column-width (+ 2 max-file-name-width)) | ||||
| 
 | ||||
| 	 (columns (quotient *width* | ||||
| 			    column-width)) | ||||
| 	 (columns (if (zero? columns) | ||||
| 		      1 | ||||
| 		      columns)) | ||||
| 
 | ||||
| 	 (number-of-files (length files)) | ||||
| 	 (rows (quotient (+ number-of-files (- columns 1)) | ||||
| 			 columns)) | ||||
| 
 | ||||
| 	 (tails | ||||
| 	  (do ((column 0 (+ 1 column)) | ||||
| 	       (tails (make-vector columns))) | ||||
| 	      ((= column columns) | ||||
| 	       tails) | ||||
| 	    (vector-set! tails column | ||||
| 			 (list-tail-or-null files (* rows column)))))) | ||||
| 
 | ||||
|     (do ((row 0 (+ 1 row))) | ||||
| 	((= row rows)) | ||||
|       (do ((column 0 (+ 1 column))) | ||||
| 	  ((= column columns)) | ||||
| 	(let ((tail (vector-ref tails column))) | ||||
| 	  (if (not (null? tail)) | ||||
| 	      (let ((width (display-file (car tail) flag? port))) | ||||
| 		(display-spaces (- column-width width) port) | ||||
| 		(vector-set! tails column (cdr tail)))))) | ||||
|       (newline port)))) | ||||
| 
 | ||||
| (define (list-tail-or-null list index) | ||||
|   (let loop ((list list) (index index)) | ||||
|     (cond | ||||
|      ((null? list) list) | ||||
|      ((zero? index) list) | ||||
|      (else (loop (cdr list) (- index 1)))))) | ||||
| 
 | ||||
| (define (ls-files-column files long? flag? port) | ||||
|   (for-each | ||||
|    (lambda (file) | ||||
|      (ls-file file long? flag? port)) | ||||
|    files)) | ||||
| 
 | ||||
| (define (ls-file file-name long? flag? port) | ||||
|   (if long? | ||||
|       (ls-file-long file-name flag? port) | ||||
|       (ls-file-short file-name flag? port))) | ||||
| 
 | ||||
| (define (ls-file-short file-name flag? port) | ||||
|   (display-file file-name flag? port) | ||||
|   (newline port)) | ||||
| 
 | ||||
| (define (ls-file-long file-name flag? port) | ||||
|   (let ((info (file-info file-name #f))) | ||||
|     (display-permissions info port) | ||||
|     (display-decimal-justified (file-info:nlinks info) 4 port) | ||||
|     (write-char #\space port) | ||||
|     (let ((user-name (user-info:name (user-info (file-info:uid info))))) | ||||
|       (display-padded user-name 9 port)) | ||||
|     (let ((group-name (group-info:name (group-info (file-info:gid info))))) | ||||
|       (display-padded group-name 9 port)) | ||||
|     (display-decimal-justified (file-info:size info) 7 port) | ||||
|     (write-char #\space port) | ||||
|     (display-time  (file-info:mtime info) port) | ||||
|     (write-char #\space port) | ||||
|     (display-file file-name flag? port) | ||||
|     (if (file-symlink? file-name) | ||||
| 	(begin | ||||
| 	  (display " -> " port) | ||||
| 	  (display (read-symlink file-name) port))) | ||||
|     (newline port))) | ||||
| 
 | ||||
| 
 | ||||
| (define *year-seconds* (* 365 24 60 60)) | ||||
| 
 | ||||
| (define (display-time the-time port) | ||||
|   (let ((time-difference (abs (- (time) the-time))) | ||||
| 	(date (date the-time 0))) | ||||
|     (if (< time-difference *year-seconds*) | ||||
| 	(display (format-date "~b ~d ~H:~M" date) port) | ||||
| 	(display (format-date "~b ~d ~Y " date) port)))) | ||||
| 
 | ||||
| (define (display-file file-name flag? port) | ||||
|   (display file-name port) | ||||
|   (if (maybe-display-flag file-name flag? port) | ||||
|       (+ 1 (string-length file-name)) | ||||
|       (string-length file-name))) | ||||
| 
 | ||||
| (define (maybe-display-flag file-name flag? port) | ||||
|   (if (not (and flag? | ||||
| 		(not (file-regular? file-name)))) | ||||
|       #f | ||||
|       (begin | ||||
| 	(cond | ||||
| 	 ((file-directory? file-name) | ||||
| 	  (write-char #\/ port)) | ||||
| 	 ((file-symlink? file-name) | ||||
| 	  (write-char #\@ port)) | ||||
| 	 ((file-executable? file-name) | ||||
| 	  (write-char #\* port)) | ||||
| 	 ((file-socket? file-name) | ||||
| 	  (write-char #\= port)) | ||||
| 	 ((file-fifo? file-name) | ||||
| 	  (write-char #\| port))) | ||||
| 	#t))) | ||||
| 
 | ||||
| (define (display-permissions info port) | ||||
|   (case (file-info:type info) | ||||
|     ((directory) | ||||
|      (write-char #\d port)) | ||||
|     ((symlink) | ||||
|      (write-char #\l port)) | ||||
|     (else | ||||
|      (write-char #\- port))) | ||||
|   (let ((mode (file-info:mode info)) | ||||
| 	(bit 8)) | ||||
|     (for-each | ||||
|      (lambda (id) | ||||
|        (if (not (zero? (bitwise-and (arithmetic-shift 1 bit) | ||||
| 				    mode))) | ||||
| 	   (write-char id port) | ||||
| 	   (write-char #\- port)) | ||||
|        (set! bit (- bit 1))) | ||||
|      '(#\r #\w #\x #\r #\w #\x #\r #\w #\x)))) | ||||
| 
 | ||||
| (define (display-decimal-justified number width port) | ||||
|   (display-justified (number->string number) width port)) | ||||
| 
 | ||||
| (define (display-justified string width port) | ||||
|   (let ((length (string-length string))) | ||||
|     (if (< length width) | ||||
| 	(display-spaces (- width length) port)) | ||||
|     (display string port))) | ||||
| 
 | ||||
| (define (display-padded string width port) | ||||
|   (let ((length (string-length string))) | ||||
|     (display string port) | ||||
|     (if (< length width) | ||||
| 	(display-spaces (- width length) port)))) | ||||
| 
 | ||||
| (define (display-spaces number port) | ||||
|   (do ((i 0 (+ 1 i))) | ||||
|       ((= i number)) | ||||
|     (write-char #\space port))) | ||||
| 
 | ||||
| ;; Convert Unix-style arguments to flags suitable for LS. | ||||
| 
 | ||||
| (define (arguments->ls-flags args) | ||||
|   (let loop ((args args) (flags '())) | ||||
|     (if (null? args) | ||||
| 	flags | ||||
| 	(cond | ||||
| 	 ((argument->ls-flags (car args)) | ||||
| 	  => (lambda (new-flags) | ||||
| 	       (loop (cdr args) (append new-flags flags)))) | ||||
| 	 (else #f))))) | ||||
| 
 | ||||
| (define (argument->ls-flags arg) | ||||
|   (let ((arg (if (symbol? arg) | ||||
| 		 (symbol->string arg) | ||||
| 		 arg))) | ||||
|     (if (or (string=? "" arg) | ||||
| 	    (not (char=? #\- (string-ref arg 0)))) | ||||
| 	#f | ||||
| 	(let loop ((chars (cdr (string->list arg))) (flags '())) | ||||
| 	  (cond | ||||
| 	   ((null? chars) | ||||
| 	    flags) | ||||
| 	   ((char->flag (car chars)) | ||||
| 	    => (lambda (flag) | ||||
| 		 (loop (cdr chars) (cons flag flags)))) | ||||
| 	   (else #f)))))) | ||||
| 
 | ||||
| (define (char->flag char) | ||||
|   (case char | ||||
|     ((#\a) 'all) | ||||
|     ((#\R) 'recursive) | ||||
|     ((#\l) 'long) | ||||
|     ((#\d) 'directory) | ||||
|     ((#\F) 'flag) | ||||
|     ((#\C) 'columns) | ||||
|     (else #f))) | ||||
| 
 | ||||
| (define (optional maybe-arg default-exp) | ||||
|   (cond | ||||
|    ((null? maybe-arg) default-exp) | ||||
|    ((null? (cdr maybe-arg)) (car maybe-arg)) | ||||
|    (else (error "too many optional arguments" maybe-arg)))) | ||||
							
								
								
									
										55
									
								
								modules.scm
								
								
								
								
							
							
						
						
									
										55
									
								
								modules.scm
								
								
								
								
							|  | @ -82,7 +82,8 @@ | |||
| 				  string-reduce | ||||
| 				  skip-whitespace | ||||
| 				  string-prefix? | ||||
| 				  string-suffix?) | ||||
| 				  string-suffix? | ||||
| 				  trim-spaces) | ||||
|   (open char-set-package let-opt scheme)  | ||||
|   (files stringhax)) | ||||
| 
 | ||||
|  | @ -152,12 +153,14 @@ | |||
| (define-structure httpd-error (export http-error? | ||||
| 				      http-error | ||||
| 				      fatal-syntax-error? | ||||
| 				      fatal-syntax-error | ||||
| 				      with-fatal-error-handler* | ||||
| 				      (with-fatal-error-handler :syntax)) | ||||
| 				      fatal-syntax-error) | ||||
|   (open conditions signals handle scheme) | ||||
|   (files httpd-error)) | ||||
| 
 | ||||
| (define-structure handle-fatal-error (export with-fatal-error-handler* | ||||
| 					     (with-fatal-error-handler :syntax)) | ||||
|   (open scheme conditions handle) | ||||
|   (files handle-fatal-error)) | ||||
| 
 | ||||
| (define-structure httpd-core (export server/version | ||||
| 				     server/protocol | ||||
|  | @ -238,6 +241,7 @@ | |||
| 	conditions	; condition-stuff | ||||
| 	defenum-package | ||||
| 	httpd-error | ||||
| 	handle-fatal-error | ||||
| 	uri-package | ||||
| 	url-package | ||||
| 	formats | ||||
|  | @ -321,6 +325,7 @@ | |||
| 	htmlout-package | ||||
| 	conditions	; CONDITION-STUFF | ||||
| 	url-package	; HTTP-URL record type | ||||
| 	handle-fatal-error | ||||
| 	scheme) | ||||
|   (files httpd-handlers)) | ||||
| 
 | ||||
|  | @ -369,6 +374,48 @@ | |||
| 	httpd-error | ||||
| 	url-package | ||||
| 	uri-package | ||||
| 	handle-fatal-error | ||||
| 	scsh | ||||
| 	scheme) | ||||
|   (files info-gateway)) | ||||
| 
 | ||||
| (define-structure rman-gateway (export rman-handler | ||||
| 				       man | ||||
| 				       parse-man-entry | ||||
| 				       cat-man-page | ||||
| 				       find-man-file | ||||
| 				       file->man-directory | ||||
| 				       cat-n-decode | ||||
| 				       nroff-n-decode) | ||||
|   (open httpd-core | ||||
| 	httpd-error | ||||
| 	conditions | ||||
| 	url-package | ||||
| 	uri-package | ||||
| 	htmlout-package | ||||
| 	httpd-basic-handlers | ||||
| 	switch-syntax | ||||
| 	condhax | ||||
| 	handle-fatal-error | ||||
| 	scsh | ||||
| 	let-opt | ||||
| 	scheme) | ||||
|   (files rman-gateway)) | ||||
| 
 | ||||
| (define-structure ls (export ls | ||||
| 			     arguments->ls-flags) | ||||
|   (open scheme | ||||
| 	big-scheme bitwise | ||||
| 	scsh) | ||||
|   (files ls)) | ||||
| 	 | ||||
| (define-structure ftpd (export ftpd | ||||
| 			       ftpd-inetd) | ||||
|   (open scheme | ||||
| 	conditions handle signals | ||||
| 	structure-refs | ||||
| 	handle-fatal-error | ||||
| 	scsh | ||||
| 	crlf-io strings ls) | ||||
|   (access big-scheme) | ||||
|   (files ftpd)) | ||||
|  |  | |||
|  | @ -0,0 +1,167 @@ | |||
| ;;; man page -> HTML gateway for the SU web server. -*- Scheme -*- | ||||
| ;;; Copyright (c) 1996 by Mike Sperber. | ||||
| ;;; This uses RosettaMan and is currently based on version 2.5a6 | ||||
| ;;; (RosettaMan is based at | ||||
| ;;;   ftp.cs.berkeley.edu:/ucb/people/phelps/tcltk/rman.tar.Z) | ||||
| 
 | ||||
| (define rman/rman '(rman -fHTML)) | ||||
| (define rman/man '(man)) | ||||
| (define rman/nroff '(nroff -man)) | ||||
| (define rman/gzcat '(zcat)) | ||||
| (define rman/zcat '(zcat)) | ||||
| 
 | ||||
| (define (rman-handler finder referencer address . maybe-man) | ||||
|   (let ((parse-man-url | ||||
| 	 (cond | ||||
| 	  ((procedure? finder) finder) | ||||
| 	  ((list? finder) | ||||
| 	   (lambda (url) | ||||
| 	     (values finder | ||||
| 		     (unescape-uri (http-url:search url)) | ||||
| 		     '()))) | ||||
| 	  (else | ||||
| 	   (let ((man-path ((infix-splitter ":") (getenv "MANPATH")))) | ||||
| 	     (lambda (url) | ||||
| 	       (values man-path | ||||
| 		       (unescape-uri (http-url:search url)) | ||||
| 		       '())))))) | ||||
| 	(reference-template | ||||
| 	 (cond | ||||
| 	  ((procedure? referencer) referencer) | ||||
| 	  ((string? referencer) (lambda (entry section) referencer)) | ||||
| 	  (else (lambda (entry section) "man?%s(%s)")))) | ||||
| 	(man (:optional maybe-man man))) | ||||
| 
 | ||||
|     (lambda (path req) | ||||
|       (switch string=? (request:method req) | ||||
| 	(("GET") | ||||
| 	 (with-fatal-error-handler | ||||
| 	  (lambda (c decline) | ||||
| 	    (cond | ||||
| 	     ((http-error? c) | ||||
| 	      (apply http-error (car (condition-stuff c)) req | ||||
| 		     (cddr (condition-stuff c)))) | ||||
| 	     (else | ||||
| 	      (decline)))) | ||||
| 
 | ||||
| 	  (if (not (v0.9-request? req)) | ||||
| 	      (begin | ||||
| 		(begin-http-header #t http-reply/ok) | ||||
| 		(write-string "Content-type: text/html\r\n") | ||||
| 		(write-string "\r\n"))) | ||||
| 
 | ||||
| 	  (receive (man-path entry and-then) (parse-man-url (request:url req)) | ||||
| 	    (emit-man-page entry man man-path and-then reference-template)) | ||||
| 
 | ||||
| 	  (with-tag #t address () | ||||
| 	    (display address)))) | ||||
| 	(else (http-error http-reply/method-not-allowed req)))))) | ||||
| 
 | ||||
| (define (cat-man-page key section) | ||||
|   (let ((title (if section | ||||
| 		   (format #f "~a(~a) manual page" key section) | ||||
| 		   (format #f "~a manual page" key)))) | ||||
|     (emit-title #t title) | ||||
|     (emit-header #t 1 title) | ||||
|     (newline) | ||||
|     (with-tag #t body () | ||||
|       (with-tag #t pre () | ||||
| 	(copy-inport->outport (current-input-port) | ||||
| 			      (current-output-port)))))) | ||||
| 
 | ||||
| (define (emit-man-page entry man man-path and-then reference-template) | ||||
|   (receive (key section) (parse-man-entry entry) | ||||
|     (let ((status | ||||
| 	   (cond | ||||
| 	    ((procedure? and-then) | ||||
| 	     (run (| (begin (man section key man-path)) | ||||
| 		     (begin (and-then key section))))) | ||||
| 	    (else | ||||
| 	     (run (| (begin (man section key man-path)) | ||||
| 		     (,@rman/rman ,@and-then | ||||
| 				  -r ,(reference-template entry section)))))))) | ||||
| 
 | ||||
|       (if (not (zero? status)) | ||||
| 	  (http-error http-reply/internal-error #f | ||||
| 		      "internal error emitting man page"))))) | ||||
|        | ||||
| (define parse-man-entry | ||||
|   (let ((entry-regexp (make-regexp "(.*)\\((.)\\)"))) | ||||
|     (lambda (s) | ||||
|       (cond | ||||
|        ((regexp-exec entry-regexp s) | ||||
| 	=> (lambda (match) | ||||
| 	     (values (match:substring match 1) | ||||
| 		     (match:substring match 2)))) | ||||
|        (else (values s #f)))))) | ||||
| 
 | ||||
| (define (man section key man-path) | ||||
|   (cond | ||||
|    ((procedure? man-path) (man-path)) | ||||
|    ((find-man-file key section "cat" man-path) => cat-n-decode) | ||||
|    ((find-man-file key section "man" man-path) => nroff-n-decode) | ||||
|    (else | ||||
|     (if (not (zero? | ||||
| 	      (with-env (("MANPATH" . ,(join-strings man-path ":"))) | ||||
| 	        (run (,@rman/man ,@(if section `(,section) '()) ,key) | ||||
| 		     (< /dev/null) | ||||
| 		     (> 2 /dev/null))))) | ||||
| 	(http-error http-reply/not-found #f "man page not found"))))) | ||||
| 
 | ||||
| (define man-default-sections | ||||
|   '("1" "2" "3" "4" "5" "6" "7" "8" "9" "o" "l" "n" "p")) | ||||
| 
 | ||||
| (define (find-man-file name section cat-man man-path . maybe-sections) | ||||
| 
 | ||||
|   (define (section-dir section) | ||||
|     (lambda (dir) | ||||
|       (file-name-as-directory | ||||
|        (string-append (file-name-as-directory dir) | ||||
| 		      cat-man | ||||
| 		      section)))) | ||||
| 
 | ||||
|   (let* ((prefix (if section | ||||
| 		     (string-append name "." section) | ||||
| 		     (string-append name "."))) | ||||
| 	 (pattern (string-append (glob-quote prefix) "*")) | ||||
| 	 (sections (:optional maybe-sections man-default-sections)) | ||||
| 	 (path (if section | ||||
| 		   (map (section-dir section) man-path) | ||||
| 		   (apply append | ||||
| 			  (map (lambda (dir) | ||||
| 				 (map (lambda (section) | ||||
| 					((section-dir section) dir)) | ||||
| 				      sections)) | ||||
| 			       man-path))))) | ||||
| 
 | ||||
|     (let loop ((path path)) | ||||
|       (and (not (null? path)) | ||||
| 	   (let ((matches (glob (string-append (car path) pattern)))) | ||||
| 	     (if (not (null? matches)) | ||||
| 		 (car matches) | ||||
| 		 (loop (cdr path)))))))) | ||||
| 
 | ||||
| (define (file->man-directory file) | ||||
|   (path-list->file-name | ||||
|    (reverse | ||||
|     (cdr | ||||
|      (reverse | ||||
|       (split-file-name | ||||
|        (file-name-directory file))))))) | ||||
| 
 | ||||
| (define (cat-n-decode file) | ||||
|   (let ((ext (file-name-extension file))) | ||||
|     (cond | ||||
|      ((string=? ".gz" ext) (run (,@rman/gzcat ,file))) | ||||
|      ((string=? ".Z" ext) (run (,@rman/zcat ,file))) | ||||
|      (else (call-with-input-file | ||||
| 	       file | ||||
| 	     (lambda (port) | ||||
| 	       (copy-inport->outport port (current-output-port)))))))) | ||||
| 
 | ||||
| (define (nroff-n-decode file) | ||||
|   (if (not (zero? (run (| (begin (cat-n-decode file)) | ||||
| 			  (begin | ||||
| 			    (with-cwd (file->man-directory file) | ||||
| 				      (exec-epf (,@rman/nroff)))))))) | ||||
|       (http-error http-reply/not-found #f "man page not found"))) | ||||
|  | @ -1,5 +1,6 @@ | |||
| ;;; Random string-hacking procs -*- Scheme -*- | ||||
| ;;; Copyright (c) 1995 by Olin Shivers. | ||||
| ;;; Copyright (c) 1997 by Mike Sperber | ||||
| 
 | ||||
| (define (string-map f s) | ||||
|   (let* ((slen (string-length s)) | ||||
|  | @ -61,3 +62,28 @@ | |||
| (define skip-whitespace | ||||
|   (let ((non-whitespace (char-set-invert char-set:whitespace))) | ||||
|     (lambda (s) (char-set-index s non-whitespace)))) | ||||
| 
 | ||||
| ; Why is this so complicated? | ||||
| 
 | ||||
| (define (trim-spaces string) | ||||
|   (if (string=? "" string) | ||||
|       string | ||||
|       (let* ((length (string-length string)) | ||||
| 	     (start | ||||
| 	      (if (not (char=? #\space (string-ref string 0))) | ||||
| 		  0 | ||||
| 		  (do ((index 0 (+ 1 index))) | ||||
| 		      ((or (= index length) | ||||
| 			   (not (char=? #\space (string-ref string index)))) | ||||
| 		       index)))) | ||||
| 	     (end | ||||
| 	      (if (not (char=? #\space (string-ref string (- length 1)))) | ||||
| 		  length | ||||
| 		  (do ((index (- length 1) (- index 1))) | ||||
| 		      ((or (= index 0) | ||||
| 			   (not (char=? #\space (string-ref string index)))) | ||||
| 		       (+ 1 index)))))) | ||||
| 	(if (and (= 0 start) | ||||
| 		 (= length end)) | ||||
| 	    string | ||||
| 	    (substring string start end))))) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 mainzelm
						mainzelm