diff --git a/Makefile b/Makefile
index df1db7b..65f099c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
.SILENT: build install test test-docker clean ${TMPDIR}
SCHEME=chibi
LIBRARY=cgi
-AUTHOR=Retropikzel
+AUTHOR=retropikzel
LIBRARY_FILE=retropikzel/${LIBRARY}.sld
VERSION=$(shell cat retropikzel/${LIBRARY}/VERSION)
diff --git a/retropikzel/cgi.scm b/retropikzel/cgi.scm
index eb31164..09fa398 100644
--- a/retropikzel/cgi.scm
+++ b/retropikzel/cgi.scm
@@ -192,7 +192,7 @@
(define breaker (char->integer #\-))
-(define request
+#;(define request
(list (cons 'headers headers)
(cons 'parameters parameters)
(cons 'cookies cookies)
@@ -222,8 +222,6 @@
(close-port input)
(close-port output)))
-(define (cgi) request)
-
(define cgi-exit
(lambda args
(for-each (lambda (file)
@@ -235,82 +233,89 @@
(exit 0)
(exit (car args)))))
-(cond ((and content-type-pair (string=? content-type "multipart/form-data"))
- (letrec* ((boundary (string->utf8 (string-append (list-ref (string-split
- (list-ref content-type-data 1) #\=) 1))))
- (boundary-length (bytevector-length boundary))
- (content (letrec ((looper (lambda (bytes result)
- (if (eof-object? bytes)
- result
- (looper (read-bytevector buffer-size stdin)
- (bytevector-append result bytes))))))
- (looper (read-bytevector buffer-size stdin)
- (bytevector))))
- (header-content-length (string->number (cdr (assoc 'CONTENT_LENGTH headers))))
- (content-length (bytevector-length content))
- (content-mark 0)
- (looper (lambda (index)
- (cond ((< index (- content-length 4))
- (if (and (= breaker (bytevector-u8-ref content index))
- (= breaker (bytevector-u8-ref content (+ index 1)))
- (equal? boundary (bytevector-copy content (+ index 2) (+ index 2 boundary-length))))
- (let* ((part (bytevector-copy content content-mark index))
- (part-length (bytevector-length part))
- (part-port (open-input-bytevector part))
- (part-headers-length 0)
- (part-headers (letrec ((loop (lambda (line result)
- (if (or (eof-object? line) (string=? line ""))
- (map (lambda (p) (string-split p #\:)) result)
- (begin
- (set! part-headers-length (+ part-headers-length
- (string-length line)
- 2))
- (loop (read-bytevector-line part-port)
- (append result (list line))))))))
- (loop (read-bytevector-line part-port) (list)))))
- (if (and (not (null? part-headers))
- (assoc "Content-Disposition" part-headers))
- (let* ((content-disposition
- (map
- (lambda (str)
- (let ((split (string-split str #\=)))
- (cons (string-filter (list-ref split 0) (lambda (c) (not (char=? c #\space))))
- (if (= (length split) 2)
- (string-filter (list-ref split 1) (lambda (c) (not (char=? c #\"))))
- ""))))
- (string-split (car (cdr (assoc "Content-Disposition" part-headers))) #\;)))
- (filename (assoc "filename" content-disposition)))
- (if (not filename)
- (set! parameters
- (append parameters
- (list
- (cons (cdr (assoc "name" content-disposition))
- (utf8->string (bytevector-copy content
- (+ (+ content-mark part-headers-length) 2)
- (- index 2)))))))
- (let* ((tmp-file-path (string-append temporary-directory
- "/"
- (make-temp-filename (cdr filename))))
- (tmp-file-port (begin (when (file-exists? tmp-file-path)
- (delete-file tmp-file-path))
- (open-binary-output-file tmp-file-path))))
- (write-bytevector (bytevector-copy content
- (+ (+ content-mark part-headers-length) 2)
- (- index 2))
- tmp-file-port)
- (close-port tmp-file-port)
- (set! files (append files (list
- (cons (cdr (assoc "name" content-disposition))
- tmp-file-path))))))
- (set! content-mark index)))
- (looper (+ index boundary-length)))
- (looper (+ index 1))))))))
- (looper 0)))
- (else (let ((raw-body (if (string=? request-method "POST")
- (read-until-eof stdin (bytevector))
- "")))
- (set! parameters (split-http-parameters (if (string=? request-method "POST")
- raw-body
- query-string)))
- (when (string=? request-method "POST")
- (set! body raw-body)))))
+(define (cgi)
+ (cond ((and content-type-pair (string=? content-type "multipart/form-data"))
+ (letrec* ((boundary (string->utf8 (string-append (list-ref (string-split
+ (list-ref content-type-data 1) #\=) 1))))
+ (boundary-length (bytevector-length boundary))
+ (content (letrec ((looper (lambda (bytes result)
+ (if (eof-object? bytes)
+ result
+ (looper (read-bytevector buffer-size stdin)
+ (bytevector-append result bytes))))))
+ (looper (read-bytevector buffer-size stdin)
+ (bytevector))))
+ (header-content-length (string->number (cdr (assoc 'CONTENT_LENGTH headers))))
+ (content-length (bytevector-length content))
+ (content-mark 0)
+ (looper (lambda (index)
+ (cond ((< index (- content-length 4))
+ (if (and (= breaker (bytevector-u8-ref content index))
+ (= breaker (bytevector-u8-ref content (+ index 1)))
+ (equal? boundary (bytevector-copy content (+ index 2) (+ index 2 boundary-length))))
+ (let* ((part (bytevector-copy content content-mark index))
+ (part-length (bytevector-length part))
+ (part-port (open-input-bytevector part))
+ (part-headers-length 0)
+ (part-headers (letrec ((loop (lambda (line result)
+ (if (or (eof-object? line) (string=? line ""))
+ (map (lambda (p) (string-split p #\:)) result)
+ (begin
+ (set! part-headers-length (+ part-headers-length
+ (string-length line)
+ 2))
+ (loop (read-bytevector-line part-port)
+ (append result (list line))))))))
+ (loop (read-bytevector-line part-port) (list)))))
+ (if (and (not (null? part-headers))
+ (assoc "Content-Disposition" part-headers))
+ (let* ((content-disposition
+ (map
+ (lambda (str)
+ (let ((split (string-split str #\=)))
+ (cons (string-filter (list-ref split 0) (lambda (c) (not (char=? c #\space))))
+ (if (= (length split) 2)
+ (string-filter (list-ref split 1) (lambda (c) (not (char=? c #\"))))
+ ""))))
+ (string-split (car (cdr (assoc "Content-Disposition" part-headers))) #\;)))
+ (filename (assoc "filename" content-disposition)))
+ (if (not filename)
+ (set! parameters
+ (append parameters
+ (list
+ (cons (cdr (assoc "name" content-disposition))
+ (utf8->string (bytevector-copy content
+ (+ (+ content-mark part-headers-length) 2)
+ (- index 2)))))))
+ (let* ((tmp-file-path (string-append temporary-directory
+ "/"
+ (make-temp-filename (cdr filename))))
+ (tmp-file-port (begin (when (file-exists? tmp-file-path)
+ (delete-file tmp-file-path))
+ (open-binary-output-file tmp-file-path))))
+ (write-bytevector (bytevector-copy content
+ (+ (+ content-mark part-headers-length) 2)
+ (- index 2))
+ tmp-file-port)
+ (close-port tmp-file-port)
+ (set! files (append files (list
+ (cons (cdr (assoc "name" content-disposition))
+ tmp-file-path))))))
+ (set! content-mark index)))
+ (looper (+ index boundary-length)))
+ (looper (+ index 1))))))))
+ (looper 0)))
+ (else (let ((raw-body (if (string=? request-method "POST")
+ (read-until-eof stdin (bytevector))
+ "")))
+ (set! parameters (split-http-parameters (if (string=? request-method "POST")
+ raw-body
+ query-string)))
+ (when (string=? request-method "POST")
+ (set! body raw-body)))))
+
+ (list (cons 'headers headers)
+ (cons 'parameters parameters)
+ (cons 'cookies cookies)
+ (cons 'body body)
+ (cons 'files files)))
diff --git a/retropikzel/cgi/LICENSE b/retropikzel/cgi/LICENSE
new file mode 100644
index 0000000..0a04128
--- /dev/null
+++ b/retropikzel/cgi/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
R7RS Scheme library for Common Gateway Interface
+
+If you dont know what CGI is, in short server runs your Scheme script and
+displays it's output as a webpage. Also checkout
+[https://git.sr.ht/~retropikzel/scheme-php](https://git.sr.ht/~retropikzel/scheme-php).
+
+[Project](https://sr.ht/~retropikzel/scheme-cgi/)
+
+[Repository](https://git.sr.ht/~retropikzel/scheme-cgi)
+
+[Issue tracker](https://todo.sr.ht/~retropikzel/scheme-cgi)
+
+## Caveats
+
+- Works only on unix as it reads from /dev/fd/0 and /dev/random.
+
+## Buggy on implementations
+
+- Does not work with mit-scheme
+ - For some reason mit-scheme exits when it reads eof-object from standard
+ input
+- STklos
+ - No output for some reason
+
+## How to use
+
+Example using Gauche in Docker.
+
+lighttpd.conf:
+
+ server.document-root = "/workdir"
+ server.port = 3000
+ server.modules += ("mod_cgi", "mod_dirlisting")
+ cgi.assign = (".scm" => "/usr/bin/scheme-script")
+ dir-listing.activate = "enable"
+
+Dockerfile:
+
+ FROM schemers/gauche
+ RUN apt-get update && apt-get install -y --no-install-recommends lighttpd
+ WORKDIR /workdir
+ EXPOSE 3000
+ COPY lighttpd.conf /lighttpd.conf
+ RUN echo "#!/bin/sh" > /usr/bin/scheme-script
+ RUN echo "exec gosh -r7 -I ./snow \$@" >> /usr/bin/scheme-script
+ RUN chmod +x /usr/bin/scheme-script
+ ENTRYPOINT ["/usr/sbin/lighttpd", "-D", "-f", "/lighttpd.conf"]
+
+hello.scm:
+
+ (import (scheme base)
+ (scheme write)
+ (retropikzel cgi))
+
+ (display "Content-type: text/html")
+ (display "
+")
+ (display "
+")
+ (display "Hello")
+ (display "")
+
+ (display "Request: ")
+ (write (get-request))
+ (display "")
+ (cgi-exit)
+
+Run:
+
+ docker build . --tag=scheme-cgi
+ docker run -it -v ${PWD}:/workdir -p 3000:3000 scheme-cgi
+
+Then navigate with your browser to http://127.0.0.1:3000
+
+## Documentation
+
+### Reference
+
+**get-request**
+
+Returns the whole request as association list.
+
+**get-header** _name_
+
+Name can be symbol or a string. Returns the value of given header or #f.
+
+**get-headers**
+
+Returns association list of all headers.
+
+**get-parameter** _name_
+
+Name can be symbol or a string. Returns the value of given parameter or #f.
+
+**get-parameters**
+
+Returns association list of all parameters.
+
+**get-cookie** _name_
+
+Returns the value of given cookie or #f if.
+
+**get-cookies**
+
+Returns association list of all cookies.
+
+**get-file** _filename_
+
+Filename is a symbol or a string. Returns the path of given file from files or #f.
+
+Uploaded files are stored in /tmp, with randomly generated prefix on their
+name. They are not deleted unless cgi-exit is called. Use **move-file** to move
+them into preferred location.
+
+**get-files**
+
+Returns association list of all files.
+
+**move-file** _from_ _to_
+
+Moves a file from _from_ path to _to_ path.
+
+**get-body**
+
+Returns the request body.
+
+**cgi-exit**
+**cgi-exit** _code_
+
+Does necessary cleanup and exits the script. Code is a number, if it is given
+then that is used as exit code.
+
+### Environment variables
+
+**SCHEME\_CGI\_TMP\_PATH**
+
+Path to where uploaded files are stored. Default is /tmp.