mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-05-15 02:00:22 +02:00
Import SLIB 2d1.
This commit is contained in:
parent
92e7e03fae
commit
9ddacf866c
165 changed files with 61896 additions and 0 deletions
438
module/slib/http-cgi.scm
Normal file
438
module/slib/http-cgi.scm
Normal file
|
@ -0,0 +1,438 @@
|
|||
;;; "http-cgi.scm" service HTTP or CGI requests. -*-scheme-*-
|
||||
; Copyright 1997, 1998, 2000, 2001 Aubrey Jaffer
|
||||
;
|
||||
;Permission to copy this software, to redistribute it, and to use it
|
||||
;for any purpose is granted, subject to the following restrictions and
|
||||
;understandings.
|
||||
;
|
||||
;1. Any copy made of this software must include this copyright notice
|
||||
;in full.
|
||||
;
|
||||
;2. I have made no warrantee or representation that the operation of
|
||||
;this software will be error-free, and I am under no obligation to
|
||||
;provide any services, by way of maintenance, update, or otherwise.
|
||||
;
|
||||
;3. In conjunction with products arising from the use of this
|
||||
;material, there shall be no use of my name in any advertising,
|
||||
;promotional, or sales literature without prior written consent in
|
||||
;each case.
|
||||
|
||||
(require 'uri)
|
||||
(require 'scanf)
|
||||
(require 'printf)
|
||||
(require 'coerce)
|
||||
(require 'line-i/o)
|
||||
(require 'html-form)
|
||||
(require 'parameters)
|
||||
(require 'string-case)
|
||||
|
||||
;;@code{(require 'http)} or @code{(require 'cgi)}
|
||||
|
||||
(define http:crlf (string (integer->char 13) #\newline))
|
||||
(define (http:read-header port)
|
||||
(define alist '())
|
||||
(do ((line (read-line port) (read-line port)))
|
||||
((or (zero? (string-length line))
|
||||
(and (= 1 (string-length line))
|
||||
(char-whitespace? (string-ref line 0)))
|
||||
(eof-object? line))
|
||||
(if (and (= 1 (string-length line))
|
||||
(char-whitespace? (string-ref line 0)))
|
||||
(set! http:crlf (string (string-ref line 0) #\newline)))
|
||||
(if (eof-object? line) line alist))
|
||||
(let ((len (string-length line))
|
||||
(idx (string-index line #\:)))
|
||||
(if (char-whitespace? (string-ref line (+ -1 len)))
|
||||
(set! len (+ -1 len)))
|
||||
(and idx (do ((idx2 (+ idx 1) (+ idx2 1)))
|
||||
((or (>= idx2 len)
|
||||
(not (char-whitespace? (string-ref line idx2))))
|
||||
(set! alist
|
||||
(cons
|
||||
(cons (string-ci->symbol (substring line 0 idx))
|
||||
(substring line idx2 len))
|
||||
alist)))))
|
||||
;;Else -- ignore malformed line
|
||||
;;(else (slib:error 'http:read-header 'malformed-input line))
|
||||
)))
|
||||
|
||||
(define (http:read-query-string request-line header port)
|
||||
(case (car request-line)
|
||||
((get head)
|
||||
(let* ((request-uri (cadr request-line))
|
||||
(len (string-length request-uri)))
|
||||
(and (> len 3)
|
||||
(string-index request-uri #\?)
|
||||
(substring request-uri
|
||||
(+ 1 (string-index request-uri #\?))
|
||||
(if (eqv? #\/ (string-ref request-uri (+ -1 len)))
|
||||
(+ -1 len)
|
||||
len)))))
|
||||
((post put delete)
|
||||
(let ((content-length (assq 'content-length header)))
|
||||
(and content-length
|
||||
(set! content-length (string->number (cdr content-length))))
|
||||
(and content-length
|
||||
(let ((str (make-string content-length #\ )))
|
||||
(do ((idx 0 (+ idx 1)))
|
||||
((>= idx content-length)
|
||||
(if (>= idx (string-length str)) str (substring str 0 idx)))
|
||||
(let ((chr (read-char port)))
|
||||
(if (char? chr)
|
||||
(string-set! str idx chr)
|
||||
(set! content-length idx))))))))
|
||||
(else #f)))
|
||||
|
||||
(define (http:status-line status-code reason)
|
||||
(sprintf #f "HTTP/1.1 %d %s%s" status-code reason http:crlf))
|
||||
|
||||
;;@body Returns a string containing lines for each element of @1; the
|
||||
;;@code{car} of which is followed by @samp{: }, then the @code{cdr}.
|
||||
(define (http:header alist)
|
||||
(string-append
|
||||
(apply string-append
|
||||
(map (lambda (pair)
|
||||
(sprintf #f "%s: %s%s" (car pair) (cdr pair) http:crlf))
|
||||
alist))
|
||||
http:crlf))
|
||||
|
||||
;;@body Returns the concatenation of strings @2 with the
|
||||
;;@code{(http:header @1)} and the @samp{Content-Length} prepended.
|
||||
(define (http:content alist . body)
|
||||
(define hunk (apply string-append body))
|
||||
(string-append (http:header
|
||||
(cons (cons "Content-Length"
|
||||
(number->string (string-length hunk)))
|
||||
alist))
|
||||
hunk))
|
||||
|
||||
;;@body String appearing at the bottom of error pages.
|
||||
(define *http:byline* #f)
|
||||
|
||||
;;@body @1 and @2 should be an integer and string as specified in
|
||||
;;@cite{RFC 2068}. The returned page (string) will show the @1 and @2
|
||||
;;and any additional @3 @dots{}; with @var{*http:byline*} or SLIB's
|
||||
;;default at the bottom.
|
||||
(define (http:error-page status-code reason-phrase . html-strings)
|
||||
(define byline
|
||||
(or
|
||||
*http:byline*
|
||||
(sprintf
|
||||
#f
|
||||
"<A HREF=http://swissnet.ai.mit.edu/~jaffer/SLIB.html>SLIB</A> %s server"
|
||||
(if (getenv "SERVER_PROTOCOL") "CGI/1.1" "HTTP/1.1"))))
|
||||
(string-append (http:status-line status-code reason-phrase)
|
||||
(http:content
|
||||
'(("Content-Type" . "text/html"))
|
||||
(html:head (sprintf #f "%d %s" status-code reason-phrase))
|
||||
(apply html:body
|
||||
(append html-strings
|
||||
(list (sprintf #f "<HR>\\n%s\\n" byline)))))))
|
||||
|
||||
;;@body The string or symbol @1 is the page title. @2 is a non-negative
|
||||
;;integer. The @4 @dots{} are typically used to explain to the user why
|
||||
;;this page is being forwarded.
|
||||
;;
|
||||
;;@0 returns an HTML string for a page which automatically forwards to
|
||||
;;@3 after @2 seconds. The returned page (string) contains any @4
|
||||
;;@dots{} followed by a manual link to @3, in case the browser does not
|
||||
;;forward automatically.
|
||||
(define (http:forwarding-page title delay uri . html-strings)
|
||||
(string-append
|
||||
(html:head title #f (html:meta-refresh delay uri))
|
||||
(apply html:body
|
||||
(append html-strings
|
||||
(list (sprintf #f "\\n\\n<HR>\\nReturn to %s.\\n"
|
||||
(html:link uri title)))))))
|
||||
|
||||
;;@body reads the @dfn{URI} and @dfn{query-string} from @2. If the
|
||||
;;query is a valid @samp{"POST"} or @samp{"GET"} query, then @0 calls
|
||||
;;@1 with three arguments, the @var{request-line}, @var{query-string},
|
||||
;;and @var{header-alist}. Otherwise, @0 calls @1 with the
|
||||
;;@var{request-line}, #f, and @var{header-alist}.
|
||||
;;
|
||||
;;If @1 returns a string, it is sent to @3. If @1 returns a list,
|
||||
;;then an error page with number 525 and strings from the list. If @1
|
||||
;;returns #f, then a @samp{Bad Request} (400) page is sent to @3.
|
||||
;;
|
||||
;;Otherwise, @0 replies (to @3) with appropriate HTML describing the
|
||||
;;problem.
|
||||
(define (http:serve-query serve-proc input-port output-port)
|
||||
(let* ((request-line (http:read-request-line input-port))
|
||||
(header (and request-line (http:read-header input-port)))
|
||||
(query-string (and header (http:read-query-string
|
||||
request-line header input-port))))
|
||||
(display (http:service serve-proc request-line query-string header)
|
||||
output-port)))
|
||||
|
||||
(define (http:service serve-proc request-line query-string header)
|
||||
(cond ((not request-line) (http:error-page 400 "Bad Request."))
|
||||
((string? (car request-line))
|
||||
(http:error-page 501 "Not Implemented" (html:plain request-line)))
|
||||
((not (memq (car request-line) '(get post)))
|
||||
(http:error-page 405 "Method Not Allowed" (html:plain request-line)))
|
||||
((serve-proc request-line query-string header) =>
|
||||
(lambda (reply)
|
||||
(cond ((string? reply)
|
||||
(string-append (http:status-line 200 "OK")
|
||||
reply))
|
||||
((and (pair? reply) (list? reply))
|
||||
(if (number? (car reply))
|
||||
(apply http:error-page reply)
|
||||
(apply http:error-page 525 reply)))
|
||||
(else (http:error-page 500 "Internal Server Error")))))
|
||||
((not query-string)
|
||||
(http:error-page 400 "Bad Request" (html:plain request-line)))
|
||||
(else
|
||||
(http:error-page 500 "Internal Server Error" (html:plain header)))))
|
||||
|
||||
;;@
|
||||
;;
|
||||
;;This example services HTTP queries from @var{port-number}:
|
||||
;;@example
|
||||
;;
|
||||
;;(define socket (make-stream-socket AF_INET 0))
|
||||
;;(and (socket:bind socket port-number) ; AF_INET INADDR_ANY
|
||||
;; (socket:listen socket 10) ; Queue up to 10 requests.
|
||||
;; (dynamic-wind
|
||||
;; (lambda () #f)
|
||||
;; (lambda ()
|
||||
;; (do ((port (socket:accept socket) (socket:accept socket)))
|
||||
;; (#f)
|
||||
;; (let ((iport (duplicate-port port "r"))
|
||||
;; (oport (duplicate-port port "w")))
|
||||
;; (http:serve-query build:serve iport oport)
|
||||
;; (close-port iport)
|
||||
;; (close-port oport))
|
||||
;; (close-port port)))
|
||||
;; (lambda () (close-port socket))))
|
||||
;;@end example
|
||||
|
||||
(define (http:read-start-line port)
|
||||
(do ((line (read-line port) (read-line port)))
|
||||
((or (not (equal? "" line)) (eof-object? line)) line)))
|
||||
|
||||
;; @body
|
||||
;; Request lines are a list of three itmes:
|
||||
;;
|
||||
;; @enumerate 0
|
||||
;;
|
||||
;; @item Method
|
||||
;;
|
||||
;; A symbol (@code{options}, @code{get}, @code{head}, @code{post},
|
||||
;; @code{put}, @code{delete}, @code{trace} @dots{}).
|
||||
;;
|
||||
;; @item Request-URI
|
||||
;;
|
||||
;; A string. For direct HTTP, at the minimum it will be the string
|
||||
;; @samp{"/"}.
|
||||
;;
|
||||
;; @item HTTP-Version
|
||||
;;
|
||||
;; A string. For example, @samp{HTTP/1.0}.
|
||||
;; @end enumerate
|
||||
(define (http:read-request-line port)
|
||||
(let ((lst (scanf-read-list "%s %s %s %s" (http:read-start-line port))))
|
||||
(and (list? lst)
|
||||
(= 3 (length lst))
|
||||
(cons (string-ci->symbol (car lst)) (cdr lst)))))
|
||||
(define (cgi:request-line)
|
||||
(define method (getenv "REQUEST_METHOD"))
|
||||
(and method
|
||||
(list (string-ci->symbol method)
|
||||
(getenv "SCRIPT_NAME")
|
||||
(getenv "SERVER_PROTOCOL"))))
|
||||
|
||||
(define (cgi:query-header)
|
||||
(define assqs '())
|
||||
(cond ((and (getenv "SERVER_NAME") (getenv "SERVER_PORT"))
|
||||
(set! assqs (cons (cons 'host (string-append (getenv "SERVER_NAME")
|
||||
":"
|
||||
(getenv "SERVER_PORT")))
|
||||
assqs))))
|
||||
(for-each
|
||||
(lambda (envar)
|
||||
(define valstr (getenv envar))
|
||||
(if valstr (set! assqs
|
||||
(cons (cons (string-ci->symbol
|
||||
(string-subst envar "HTTP_" "" "_" "-"))
|
||||
valstr)
|
||||
assqs))))
|
||||
'(
|
||||
;;"AUTH_TYPE"
|
||||
"CONTENT_LENGTH"
|
||||
"CONTENT_TYPE"
|
||||
"DOCUMENT_ROOT"
|
||||
"GATEWAY_INTERFACE"
|
||||
"HTTP_ACCEPT"
|
||||
"HTTP_ACCEPT_CHARSET"
|
||||
"HTTP_ACCEPT_ENCODING"
|
||||
"HTTP_ACCEPT_LANGUAGE"
|
||||
"HTTP_CONNECTION"
|
||||
"HTTP_HOST"
|
||||
;;"HTTP_PRAGMA"
|
||||
"HTTP_REFERER"
|
||||
"HTTP_USER_AGENT"
|
||||
"PATH_INFO"
|
||||
"PATH_TRANSLATED"
|
||||
"QUERY_STRING"
|
||||
"REMOTE_ADDR"
|
||||
"REMOTE_HOST"
|
||||
;;"REMOTE_IDENT"
|
||||
;;"REMOTE_USER"
|
||||
"REQUEST_URI"
|
||||
"SCRIPT_FILENAME"
|
||||
"SCRIPT_NAME"
|
||||
;;"SERVER_SIGNATURE"
|
||||
;;"SERVER_SOFTWARE"
|
||||
))
|
||||
assqs)
|
||||
|
||||
;; @body Reads the @dfn{query-string} from @code{(current-input-port)}.
|
||||
;; @0 reads a @samp{"POST"} or @samp{"GET"} queries, depending on the
|
||||
;; value of @code{(getenv "REQUEST_METHOD")}.
|
||||
(define (cgi:read-query-string)
|
||||
(define request-method (getenv "REQUEST_METHOD"))
|
||||
(cond ((and request-method (string-ci=? "GET" request-method))
|
||||
(getenv "QUERY_STRING"))
|
||||
((and request-method (string-ci=? "POST" request-method))
|
||||
(let ((content-length (getenv "CONTENT_LENGTH")))
|
||||
(and content-length
|
||||
(set! content-length (string->number content-length)))
|
||||
(and content-length
|
||||
(let ((str (make-string content-length #\ )))
|
||||
(do ((idx 0 (+ idx 1)))
|
||||
((>= idx content-length)
|
||||
(if (>= idx (string-length str))
|
||||
str
|
||||
(substring str 0 idx)))
|
||||
(let ((chr (read-char)))
|
||||
(if (char? chr)
|
||||
(string-set! str idx chr)
|
||||
(set! content-length idx))))))))
|
||||
(else #f)))
|
||||
|
||||
;;@body reads the @dfn{URI} and @dfn{query-string} from
|
||||
;;@code{(current-input-port)}. If the query is a valid @samp{"POST"}
|
||||
;;or @samp{"GET"} query, then @0 calls @1 with three arguments, the
|
||||
;;@var{request-line}, @var{query-string}, and @var{header-alist}.
|
||||
;;Otherwise, @0 calls @1 with the @var{request-line}, #f, and
|
||||
;;@var{header-alist}.
|
||||
;;
|
||||
;;If @1 returns a string, it is sent to @code{(current-input-port)}.
|
||||
;;If @1 returns a list, then an error page with number 525 and strings
|
||||
;;from the list. If @1 returns #f, then a @samp{Bad Request} (400)
|
||||
;;page is sent to @code{(current-input-port)}.
|
||||
;;
|
||||
;;Otherwise, @0 replies (to @code{(current-input-port)}) with
|
||||
;;appropriate HTML describing the problem.
|
||||
(define (cgi:serve-query serve-proc)
|
||||
(let* ((script-name (getenv "SCRIPT_NAME"))
|
||||
(request-line (cgi:request-line))
|
||||
(header (and request-line (cgi:query-header)))
|
||||
(query-string (and header (cgi:read-query-string)))
|
||||
(reply (http:service serve-proc request-line query-string header)))
|
||||
(display (if (and script-name
|
||||
(not (eqv? 0 (substring? "nph-" script-name))))
|
||||
;; Eat http status line.
|
||||
(substring reply (+ 2 (substring? http:crlf reply))
|
||||
(string-length reply))
|
||||
reply))))
|
||||
|
||||
(define (coerce->list str type)
|
||||
(case type
|
||||
((expression)
|
||||
(slib:warn 'coerce->list 'unsafe 'read)
|
||||
(do ((tok (read port) (read port))
|
||||
(lst '() (cons tok lst)))
|
||||
((or (null? tok) (eof-object? tok)) lst)))
|
||||
((symbol)
|
||||
(call-with-input-string str
|
||||
(lambda (port)
|
||||
(do ((tok (scanf-read-list " %s" port)
|
||||
(scanf-read-list " %s" port))
|
||||
(lst '() (cons (string-ci->symbol (car tok)) lst)))
|
||||
((or (null? tok) (eof-object? tok)) lst)))))
|
||||
(else
|
||||
(call-with-input-string str
|
||||
(lambda (port)
|
||||
(do ((tok (scanf-read-list " %s" port)
|
||||
(scanf-read-list " %s" port))
|
||||
(lst '() (cons (coerce (car tok) type) lst)))
|
||||
((or (null? tok) (eof-object? tok)) lst)))))))
|
||||
|
||||
(define (query-alist->parameter-list alist optnames arities types)
|
||||
(let ((parameter-list (make-parameter-list optnames)))
|
||||
(for-each
|
||||
(lambda (lst)
|
||||
(let* ((value (cadr lst))
|
||||
(name (car lst))
|
||||
(opt-pos (position name optnames)))
|
||||
(cond ((not opt-pos)
|
||||
(slib:warn 'query-alist->parameter-list
|
||||
'unknown 'parameter name))
|
||||
((eq? (list-ref arities opt-pos) 'boolean)
|
||||
(adjoin-parameters! parameter-list (list name #t)))
|
||||
((and (equal? value "")
|
||||
(not (memq (list-ref types opt-pos) '(expression string))))
|
||||
(adjoin-parameters! parameter-list (list name #f)))
|
||||
(value
|
||||
(adjoin-parameters!
|
||||
parameter-list
|
||||
(cons name
|
||||
(case (list-ref arities opt-pos)
|
||||
((nary nary1)
|
||||
(coerce->list value (list-ref types opt-pos)))
|
||||
(else
|
||||
(list (coerce value (list-ref types opt-pos)))))))))))
|
||||
alist)
|
||||
parameter-list))
|
||||
|
||||
;;@args rdb command-table
|
||||
;;@args rdb command-table #t
|
||||
;;
|
||||
;;Returns a procedure of one argument. When that procedure is called
|
||||
;;with a @var{query-alist} (as returned by @code{uri:decode-query}, the
|
||||
;;value of the @samp{*command*} association will be the command invoked
|
||||
;;in @2. If @samp{*command*} is not in the @var{query-alist} then the
|
||||
;;value of @samp{*suggest*} is tried. If neither name is in the
|
||||
;;@var{query-alist}, then the literal value @samp{*default*} is tried in
|
||||
;;@2.
|
||||
;;
|
||||
;;If optional third argument is non-false, then the command is called
|
||||
;;with just the parameter-list; otherwise, command is called with the
|
||||
;;arguments described in its table.
|
||||
(define (make-query-alist-command-server rdb command-table . just-params?)
|
||||
(define comsrvcal (make-command-server rdb command-table))
|
||||
(set! just-params? (if (null? just-params?) #f (car just-params?)))
|
||||
(lambda (query-alist)
|
||||
(define comnam #f)
|
||||
(define find-command?
|
||||
(lambda (cname)
|
||||
(define tryp (parameter-list-ref query-alist cname))
|
||||
(cond ((not tryp) #f)
|
||||
(comnam
|
||||
(set! query-alist (remove-parameter cname query-alist)))
|
||||
(else
|
||||
(set! query-alist (remove-parameter cname query-alist))
|
||||
(set! comnam (string-ci->symbol (car tryp)))))))
|
||||
(find-command? '*command*)
|
||||
(find-command? '*suggest*)
|
||||
(find-command? '*button*)
|
||||
(cond ((not comnam) (set! comnam '*default*)))
|
||||
(cond
|
||||
(comnam
|
||||
(comsrvcal comnam
|
||||
(lambda (comname comval options positions
|
||||
arities types defaulters dirs aliases)
|
||||
(let* ((params (query-alist->parameter-list
|
||||
query-alist options arities types))
|
||||
(fparams (fill-empty-parameters defaulters params)))
|
||||
(and (list? fparams)
|
||||
(check-parameters dirs fparams)
|
||||
(if just-params?
|
||||
(comval fparams)
|
||||
(let ((arglist (parameter-list->arglist
|
||||
positions arities fparams)))
|
||||
(and arglist
|
||||
(apply comval arglist))))))))))))
|
Loading…
Add table
Add a link
Reference in a new issue