(define encode-vector
(list->vector
'("%00" "%01" "%02" "%03" "%04" "%05" "%06" "%07" "%08" "%09" "%0a" "%0b" "%0c" "%0d" "%0e" "%0f" "%10" "%11" "%12" "%13" "%14" "%15" "%16" "%17" "%18" "%19" "%1a" "%1b" "%1c" "%1d" "%1e" "%1f" "%20" "%21" "%22" "%23" "%24" "%25" "%26" "%27" "%28" "%29" "%2a" "%2b" "%2c" "%2d" "%2e" "%2f" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "%3a" "%3b" "%3c" "%3d" "%3e" "%3f" "%40" "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "%5b" "%5c" "%5d" "%5e" "%5f" "%60" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" "%7b" "%7c" "%7d" "%7e" "%7f")))
(define (encode-char char)
(let* ((n (char->integer char)))
(if (and (>= n 0) (<= n 128))
(vector-ref encode-vector n)
(string-append "%" (number-in-base n 16)))))
(define (encode-string str)
(encode-string-help str 0 "") )
(define (encode-string-help str i res)
(if (= i (string-length str))
res
(encode-string-help str (+ i 1)
(string-append res (encode-char (string-ref str i))))))
(define (encode-a-list a-list)
(let ((res (encode-a-list-1 a-list)))
(if (not (null? a-list))
(substring res 0 (- (string-length res) 1))
res)))
(define (encode-a-list-1 a-list)
(if (null? a-list)
""
(let ((key (car (car a-list)))
(val (cdr (car a-list))))
(string-append (encode-string (as-string key))
"="
(encode-string (as-string val))
"&"
(encode-a-list-1 (cdr a-list))))))
(define decode-out-string "")
(define (string-decode str-a-list)
(set! decode-out-string (make-string (string-length str-a-list) #\space))
(let ((res (decode-string-alist-1 str-a-list 0 (string-length str-a-list) decode-out-string 0 '() "" 'in-key-or-value)))
(cond ((and (= 1 (length res)) (empty-string? (car res))) '())
(else (propertylist-to-alist (reverse res))))
))
(define extract-attributes string-decode)
(define (decode-string-alist-1 instr inptr inlength outstr outptr prop-list collected current-state)
(if (= inptr inlength)
(cond
((eq? current-state 'in-key-or-value) (cons (substring outstr 0 outptr) prop-list))
((eq? current-state 'hex3) (cons (substring outstr 0 outptr) prop-list))
((eq? current-state 'equal-accepted) (cons "" prop-list))
((eq? current-state 'ampersand-accepted) prop-list)
(else (error "decode-string-a-list-1: Strange end of string input")))
(let* ((inch (string-ref instr inptr))
(trans-res (decode-string-transition current-state inch collected))
(next-state (car trans-res))
(next-collected (cdr trans-res))
)
(cond
((and (eq? next-state 'in-key-or-value) (eq? inch #\+)) (string-set! outstr outptr #\space))
((eq? next-state 'in-key-or-value) (string-set! outstr outptr inch))
((eq? next-state 'hex3) (string-set! outstr outptr next-collected))
)
(decode-string-alist-1 instr (+ 1 inptr) inlength
outstr
(cond ((eq? next-state 'equal-accepted) 0)
((eq? next-state 'ampersand-accepted) 0)
((eq? next-state 'hex1) outptr)
((eq? next-state 'hex2) outptr)
(else (+ outptr 1)))
(if (or (eq? next-state 'equal-accepted) (eq? next-state 'ampersand-accepted))
(cons (substring outstr 0 outptr) prop-list)
prop-list)
next-collected
next-state)
)))
(define hex1-state (cons 'hex1 ""))
(define equal-accepted-state (cons 'equal-accepted ""))
(define ampersand-accepted-state (cons 'ampersand-accepted ""))
(define in-key-or-value-state (cons 'in-key-or-value ""))
(define (decode-string-transition in-state ch hex-collect)
(let ((char (as-string ch)))
(cond
((eq? in-state 'in-key-or-value)
(cond
((eqv? ch #\%) hex1-state)
((eqv? ch #\=) equal-accepted-state)
((eqv? ch #\&) ampersand-accepted-state)
(else in-key-or-value-state)
))
((eq? in-state 'hex1)
(cons 'hex2 (as-string ch)))
((eq? in-state 'hex2)
(cons 'hex3
(two-digit-hex-to-char
(string-append hex-collect (as-string ch)))))
((eq? in-state 'hex3)
(cond
((eqv? ch #\&) ampersand-accepted-state)
((eqv? ch #\=) equal-accepted-state)
((eqv? ch #\%) hex1-state)
(else in-key-or-value-state)
))
((eq? in-state 'ampersand-accepted)
(cond
((eqv? ch #\%) hex1-state)
((eqv? ch #\=) equal-accepted-state)
((eqv? ch #\&) ampersand-accepted-state)
(else in-key-or-value-state)
))
((eq? in-state 'equal-accepted)
(cond
((eqv? ch #\%) hex1-state)
((eqv? ch #\=) equal-accepted-state)
((eqv? ch #\&) ampersand-accepted-state)
(else in-key-or-value-state)
))
(else (error (string-append
"decode-string-transition: Unknown state: "
(as-string in-state))))
)))
(define (two-digit-hex-to-char two-char-string)
(if (= 2 (string-length two-char-string))
(let ((c1 (hex-ciffer->decimal-ciffer (string-ref two-char-string 0)))
(c2 (hex-ciffer->decimal-ciffer (string-ref two-char-string 1)))
)
(integer->char (+ (* c1 16) c2)))
(error "two-digit-hex-to-char: First parameter must be a string of length two")))
(define (multipart-decode cur-time)
(let* ((c-and-b (content_type-and-boundary))
(content_type (car c-and-b))
(boundary (string-append "--" (cdr c-and-b))))
(display-mes-if-debugging boundary)
(if (equal? content_type "multipart/form-data")
(begin
(pass-next-boundary! boundary)
(multipart-decode-1! cur-time boundary)
)
(error (string-append "multipart-decode: content_type must be 'multipart-decode'")))))
(define debugging-multiform-decode #f)
(define debug-port
(if debugging-multiform-decode
(open-output-file "/user/aabudd/.public_html/cgi-bin/debug-info")
#f))
(define (display-mes-if-debugging mes)
(if debugging-multiform-decode
(display (string-append (as-string mes) (as-string #\newline)) debug-port)))
(define collected-form-alist '())
(define multiform-file-path #f)
(define multiform-dir-url #f)
(define (multipart-decode-1! cur-time boundary)
(if (not (at-end-of-form-input))
(let ((content-disposition (read-content-disposition))
(name (read-name))
(file-name (read-possible-filename))
)
(cond ((and file-name (not (blank-string? file-name)))
(let* ((possible-content-type (read-possible-content-type))
(extension (file-name-extension file-name))
(proper-filename (file-name-proper file-name))
(target-file-path (string-append multiform-file-path (upload-target-filename proper-filename extension cur-time)))
(op (open-output-file target-file-path))
)
(set! collected-form-alist (cons (cons name (list file-name target-file-path possible-content-type multiform-dir-url)) collected-form-alist))
(read-a-string 4)
(pass-uploaded-file! op boundary)
(close-output-port op)
(multipart-decode-1! cur-time boundary)
))
((and file-name (blank-string? file-name))
(skip-until-string boundary #t)
(multipart-decode-1! cur-time boundary)
)
(else
(let ((value (read-value boundary)))
(begin
(set! collected-form-alist (cons (cons name value) collected-form-alist))
(catch-possible-file-path! name value)
(catch-possible-directory-url! name value)
(pass-next-boundary! boundary)
(multipart-decode-1! cur-time boundary)
)
)
)
)
)
(reverse collected-form-alist)
)
)
(define (pass-uploaded-file! op boundary)
(pass-uploaded-file-1! op boundary 0 (string-length boundary))
)
(define (pass-uploaded-file-1! op boundary match-pos boundary-lgt)
(if (= boundary-lgt match-pos)
'done
(let ((ch (read-a-char))
(match-ch (string-ref boundary match-pos))
)
(cond ((eqv? ch match-ch)
(display-mes-if-debugging (string-append "Matches " (as-string ch) " match-pos: " (as-string (+ match-pos 1))))
(pass-uploaded-file-1! op boundary (+ match-pos 1) boundary-lgt))
((and (not (eqv? ch match-ch)) (> match-pos 0))
(display-mes-if-debugging (string-append "Writing " (substring boundary 0 match-pos) "to op"))
(write-string-to-port (substring boundary 0 match-pos) op)
(write-char ch op)
(pass-uploaded-file-1! op boundary 0 boundary-lgt))
((not (eqv? ch match-ch))
(display-mes-if-debugging (string-append "Passing " (as-string ch) " through"))
(write-char ch op)
(pass-uploaded-file-1! op boundary 0 boundary-lgt))
))))
(define (pass-next-boundary! boundary)
(display-mes-if-debugging "pass-next-boundary")
(skip-string boundary "Boundary expected"))
(define (read-value boundary)
(display-mes-if-debugging "read-value")
(read-a-string 4)
(let* ((val (collect-until-string boundary))
(lgt (string-length val)))
(if (>= lgt 2)
(substring val 0 (- lgt 2))
val)))
(define (read-possible-content-type)
(display-mes-if-debugging "read-possible-content-type")
(ensure-look-ahead 16)
(if (substring-index (look-ahead-prefix 16) 0 "Content-Type")
(begin
(skip-while is-white-space?)
(skip-string "Content-Type" "Content-Type expected")
(skip-while is-white-space?)
(skip-while (char-predicate #\:))
(skip-while is-white-space?)
(let ((contenttype (collect-until is-white-space?)))
contenttype)
)
#f))
(define (is-white-space-or-semicolon? ch)
(or (is-white-space? ch) (eqv? #\; ch)))
(define (read-possible-filename)
(display-mes-if-debugging "read-possible-filename")
(skip-while (char-predicate #\;))
(ensure-look-ahead 9)
(if (equal? (look-ahead-prefix 9) " filename") (begin
(skip-while is-white-space?)
(skip-string "filename" "filename expected")
(skip-while is-white-space?)
(skip-while (char-predicate #\=))
(skip-while is-white-space?)
(skip-string (as-string #\") "String quote expected after 'filename='")
(let ((filename (collect-until (char-predicate #\"))))
(skip-string (as-string #\") "String quote expected after 'name=...'")
filename)
)
#f))
(define (at-end-of-form-input)
(display-mes-if-debugging "at-end-of-form-input")
(ensure-look-ahead 2)
(equal? (look-ahead-prefix 2) "--"))
(define (read-content-disposition)
(display-mes-if-debugging "read-content-disposition")
(skip-until-string "Content-Disposition:" #t)
(skip-while is-white-space?)
(let ((val (collect-until (char-predicate #\;))))
(skip-string ";" "Semicolon expected after content-Disposition form data")
val))
(define (read-name)
(display-mes-if-debugging "read-name")
(skip-until-string "name=" #t)
(skip-while is-white-space?)
(skip-string (as-string #\") "String quote expected after 'name='")
(let ((val (collect-until (char-predicate #\"))))
(skip-string (as-string #\") "String quote expected after 'name=...'")
val))
(define (upload-target-filename proper-name extension cur-time)
(string-append proper-name "-" (as-string cur-time)
(if (not (empty-string? extension)) "." "") extension))
(define (content_type-and-boundary)
(let* ((content-type (getenv "CONTENT_TYPE"))
(pos (find-in-string content-type #\; 0)))
(if pos
(let* ((real-content-type (substring content-type 0 pos))
(pos1 (skip-chars-in-string content-type white-space-char-list (+ pos 1)))
(pos2 (find-in-string content-type #\= pos1)))
(if pos2
(let ((boundary (substring content-type (+ 1 pos2) (string-length content-type))))
(cons real-content-type boundary))
(error (string-append "multipart-decode: unexpected content type - case 1: " content-type)))
)
(error (string-append "multipart-decode: unexpected content type - case 2: " content-type))
)))
(define (catch-possible-file-path! name value)
(display-mes-if-debugging "catch-possible-file-path!")
(if (equal? name "target-directory!!!")
(set! multiform-file-path value)))
(define (catch-possible-directory-url! name value)
(display-mes-if-debugging "catch-possible-directory-url!")
(if (equal? name "target-directory-url!!!")
(set! multiform-dir-url value)))