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)
|
(define (access-controller . controls)
|
||||||
(lambda (info)
|
(lambda (info)
|
||||||
(let loop ((controls controls))
|
(let loop ((controls controls))
|
||||||
(if (null? controls)
|
(and (pair? controls)
|
||||||
#f
|
(or ((car controls) info)
|
||||||
(cond
|
(loop (cdr controls)))))))
|
||||||
(((car controls) info) => identity)
|
|
||||||
(else (loop (cdr controls))))))))
|
|
||||||
|
|
||||||
(define (access-controlled-handler control ph)
|
(define (access-controlled-handler control ph)
|
||||||
(lambda (path req)
|
(lambda (path req)
|
||||||
|
|
|
@ -6,10 +6,6 @@
|
||||||
;;; You recognise one with HTTP-ERROR?, and retrieve the pieces with
|
;;; You recognise one with HTTP-ERROR?, and retrieve the pieces with
|
||||||
;;; CONDITION-STUFF.
|
;;; 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
|
;;; ,open conditions signals handle
|
||||||
|
|
||||||
;;; HTTP error condition
|
;;; HTTP error condition
|
||||||
|
@ -39,93 +35,3 @@
|
||||||
(define (fatal-syntax-error msg . irritants)
|
(define (fatal-syntax-error msg . irritants)
|
||||||
(apply signal '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)
|
(define (file-extension->content-type fname)
|
||||||
(switch string-ci=? (file-name-extension fname)
|
(switch string-ci=? (file-name-extension fname)
|
||||||
((".html") "text/html")
|
((".html") "text/html")
|
||||||
|
((".txt") "text/plain")
|
||||||
((".gif") "image/gif")
|
((".gif") "image/gif")
|
||||||
|
((".png") "image/png")
|
||||||
((".jpg" ".jpeg") "image/jpeg")
|
((".jpg" ".jpeg") "image/jpeg")
|
||||||
((".tiff" ".tif") "image/tif")
|
((".tiff" ".tif") "image/tif")
|
||||||
((".rtf") "text/rtf")
|
((".rtf") "text/rtf")
|
||||||
|
@ -511,7 +513,8 @@
|
||||||
((".zip") "application/zip")
|
((".zip") "application/zip")
|
||||||
((".tar") "application/tar")
|
((".tar") "application/tar")
|
||||||
((".ps") "application/postscript")
|
((".ps") "application/postscript")
|
||||||
(else #f)))
|
((".pdf") "application/pdf")
|
||||||
|
(else "application/octet-stream")))
|
||||||
|
|
||||||
(define (file-extension->content-encoding fname)
|
(define (file-extension->content-encoding fname)
|
||||||
(cond
|
(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
|
string-reduce
|
||||||
skip-whitespace
|
skip-whitespace
|
||||||
string-prefix?
|
string-prefix?
|
||||||
string-suffix?)
|
string-suffix?
|
||||||
|
trim-spaces)
|
||||||
(open char-set-package let-opt scheme)
|
(open char-set-package let-opt scheme)
|
||||||
(files stringhax))
|
(files stringhax))
|
||||||
|
|
||||||
|
@ -152,12 +153,14 @@
|
||||||
(define-structure httpd-error (export http-error?
|
(define-structure httpd-error (export http-error?
|
||||||
http-error
|
http-error
|
||||||
fatal-syntax-error?
|
fatal-syntax-error?
|
||||||
fatal-syntax-error
|
fatal-syntax-error)
|
||||||
with-fatal-error-handler*
|
|
||||||
(with-fatal-error-handler :syntax))
|
|
||||||
(open conditions signals handle scheme)
|
(open conditions signals handle scheme)
|
||||||
(files httpd-error))
|
(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
|
(define-structure httpd-core (export server/version
|
||||||
server/protocol
|
server/protocol
|
||||||
|
@ -238,6 +241,7 @@
|
||||||
conditions ; condition-stuff
|
conditions ; condition-stuff
|
||||||
defenum-package
|
defenum-package
|
||||||
httpd-error
|
httpd-error
|
||||||
|
handle-fatal-error
|
||||||
uri-package
|
uri-package
|
||||||
url-package
|
url-package
|
||||||
formats
|
formats
|
||||||
|
@ -321,6 +325,7 @@
|
||||||
htmlout-package
|
htmlout-package
|
||||||
conditions ; CONDITION-STUFF
|
conditions ; CONDITION-STUFF
|
||||||
url-package ; HTTP-URL record type
|
url-package ; HTTP-URL record type
|
||||||
|
handle-fatal-error
|
||||||
scheme)
|
scheme)
|
||||||
(files httpd-handlers))
|
(files httpd-handlers))
|
||||||
|
|
||||||
|
@ -369,6 +374,48 @@
|
||||||
httpd-error
|
httpd-error
|
||||||
url-package
|
url-package
|
||||||
uri-package
|
uri-package
|
||||||
|
handle-fatal-error
|
||||||
scsh
|
scsh
|
||||||
scheme)
|
scheme)
|
||||||
(files info-gateway))
|
(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 -*-
|
;;; Random string-hacking procs -*- Scheme -*-
|
||||||
;;; Copyright (c) 1995 by Olin Shivers.
|
;;; Copyright (c) 1995 by Olin Shivers.
|
||||||
|
;;; Copyright (c) 1997 by Mike Sperber
|
||||||
|
|
||||||
(define (string-map f s)
|
(define (string-map f s)
|
||||||
(let* ((slen (string-length s))
|
(let* ((slen (string-length s))
|
||||||
|
@ -61,3 +62,28 @@
|
||||||
(define skip-whitespace
|
(define skip-whitespace
|
||||||
(let ((non-whitespace (char-set-invert char-set:whitespace)))
|
(let ((non-whitespace (char-set-invert char-set:whitespace)))
|
||||||
(lambda (s) (char-set-index s non-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