#!/usr/bin/newlisp ;; @module Web ;; @author Jeff Ober , Kanen Flowers ;; @version 0.3.3 ;; @location http://www.ScruffyThinking.com/artful ;; @package https://github.com/kanendosei/artful-newlisp/blob/master/web.qwerty ;; @description A collection of functions for writing web-based software. ;; Features: ;; ;; Known issues ;; ;; ;; Note: for JSON encoding and decoding, see the @link http://static.artfulcode.net/newlisp/json.lsp.html Json module. ;; ;;

To do

;; • add MIME decoding for multipart posts ;; ;;

Version history

;; 0.3.1 ;; • fixed ineffective usage of set/setf ;; ;; 0.3 ;; • made parse-query more tolerant and fixed parsing bug ;; • cookie now accepts an additional parameter that only permits access during HTTPS sessions ;; ;; 0.2 ;; • build-url now accepts query strings in addition to assoc lists ;; • session-id now accepts an optional parameter to set the session id ;; • fixed some typos with 'clean-sessions' ;; • fixed extra parameter in 'define-session-handlers' ;; ;; 0.1 ;; • initial release ;; (context 'Web) ;=============================================================================== ; !Constants and definitions ;=============================================================================== (constant 'POST_LIMIT 4096) (define GET) (define POST) (define COOKIE) (define SESSION_DIR "/tmp") (define SESSION_MAX_AGE (* 60 60 24 7)) ; seconds (define SESSION_KEY "NLWSESID") (define SESSION_PREFIX "NLWSES") (define SESSION_STARTED) (define SESSION_ID) ; stores the current session id ;=============================================================================== ; !Encoding and decoding ;=============================================================================== (define ENTITIES (list (list 34 {"}) (list 38 {&}) (list 39 {'}) (list 60 {<}) (list 62 {>}) (list 160 { }) (list 161 {¡}) (list 162 {¢}) (list 163 {£}) (list 164 {¤}) (list 165 {¥}) (list 166 {¦}) (list 167 {§}) (list 168 {¨}) (list 169 {©}) (list 170 {ª}) (list 171 {«}) (list 172 {¬}) (list 173 {­}) (list 174 {®}) (list 175 {¯}) (list 176 {°}) (list 177 {±}) (list 178 {²}) (list 179 {³}) (list 180 {´}) (list 181 {µ}) (list 182 {¶}) (list 183 {·}) (list 184 {¸}) (list 185 {¹}) (list 186 {º}) (list 187 {»}) (list 188 {¼}) (list 189 {½}) (list 190 {¾}) (list 191 {¿}) (list 192 {À}) (list 193 {Á}) (list 194 {Â}) (list 195 {Ã}) (list 196 {Ä}) (list 197 {Å}) (list 198 {Æ}) (list 199 {Ç}) (list 200 {È}) (list 201 {É}) (list 202 {Ê}) (list 203 {Ë}) (list 204 {Ì}) (list 205 {Í}) (list 206 {Î}) (list 207 {Ï}) (list 208 {Ð}) (list 209 {Ñ}) (list 210 {Ò}) (list 211 {Ó}) (list 212 {Ô}) (list 213 {Õ}) (list 214 {Ö}) (list 215 {×}) (list 216 {Ø}) (list 217 {Ù}) (list 218 {Ú}) (list 219 {Û}) (list 220 {Ü}) (list 221 {Ý}) (list 222 {Þ}) (list 223 {ß}) (list 224 {à}) (list 225 {á}) (list 226 {â}) (list 227 {ã}) (list 228 {ä}) (list 229 {å}) (list 230 {æ}) (list 231 {ç}) (list 232 {è}) (list 233 {é}) (list 234 {ê}) (list 235 {ë}) (list 236 {ì}) (list 237 {í}) (list 238 {î}) (list 239 {ï}) (list 240 {ð}) (list 241 {ñ}) (list 242 {ò}) (list 243 {ó}) (list 244 {ô}) (list 245 {õ}) (list 246 {ö}) (list 247 {÷}) (list 248 {ø}) (list 249 {ù}) (list 250 {ú}) (list 251 {û}) (list 252 {ü}) (list 253 {ý}) (list 254 {þ}) (list 255 {ÿ}) (list 338 {Œ}) (list 339 {œ}) (list 352 {Š}) (list 353 {š}) (list 376 {Ÿ}) (list 402 {ƒ}) (list 710 {ˆ}) (list 732 {˜}) (list 913 {Α}) (list 914 {Β}) (list 915 {Γ}) (list 916 {Δ}) (list 917 {Ε}) (list 918 {Ζ}) (list 919 {Η}) (list 920 {Θ}) (list 921 {Ι}) (list 922 {Κ}) (list 923 {Λ}) (list 924 {Μ}) (list 925 {Ν}) (list 926 {Ξ}) (list 927 {Ο}) (list 928 {Π}) (list 929 {Ρ}) (list 931 {Σ}) (list 932 {Τ}) (list 933 {Υ}) (list 934 {Φ}) (list 935 {Χ}) (list 936 {Ψ}) (list 937 {Ω}) (list 945 {α}) (list 946 {β}) (list 947 {γ}) (list 948 {δ}) (list 949 {ε}) (list 950 {ζ}) (list 951 {η}) (list 952 {θ}) (list 953 {ι}) (list 954 {κ}) (list 955 {λ}) (list 956 {μ}) (list 957 {ν}) (list 958 {ξ}) (list 959 {ο}) (list 960 {π}) (list 961 {ρ}) (list 962 {ς}) (list 963 {σ}) (list 964 {τ}) (list 965 {υ}) (list 966 {φ}) (list 967 {χ}) (list 968 {ψ}) (list 969 {ω}) (list 977 {ϑ}) (list 978 {ϒ}) (list 982 {ϖ}) (list 8194 { }) (list 8195 { }) (list 8201 { }) (list 8204 {‌}) (list 8204 {‍}) (list 8204 {‎}) (list 8204 {‏}) (list 8211 {–}) (list 8212 {—}) (list 8216 {‘}) (list 8217 {’}) (list 8218 {‚}) (list 8220 {“}) (list 8221 {”}) (list 8222 {„}) (list 8224 {†}) (list 8225 {‡}) (list 8226 {•}) (list 8230 {…}) (list 8240 {‰}) (list 8242 {′}) (list 8243 {″}) (list 8249 {‹}) (list 8250 {›}) (list 8254 {‾}) (list 8260 {⁄}) (list 8364 {€}) (list 8465 {ℑ}) (list 8472 {℘}) (list 8476 {ℜ}) (list 8482 {™}) (list 8501 {ℵ}) (list 8592 {←}) (list 8593 {↑}) (list 8594 {→}) (list 8595 {↓}) (list 8596 {↔}) (list 8629 {↵}) (list 8656 {⇐}) (list 8657 {⇑}) (list 8658 {⇒}) (list 8659 {⇓}) (list 8660 {⇔}) (list 8704 {∀}) (list 8706 {∂}) (list 8707 {∃}) (list 8709 {∅}) (list 8711 {∇}) (list 8712 {∈}) (list 8713 {∉}) (list 8715 {∋}) (list 8719 {∏}) (list 8721 {∑}) (list 8722 {−}) (list 8727 {∗}) (list 8730 {√}) (list 8733 {∝}) (list 8734 {∞}) (list 8736 {∠}) (list 8743 {∧}) (list 8744 {∨}) (list 8745 {∩}) (list 8746 {∪}) (list 8747 {∫}) (list 8756 {∴}) (list 8764 {∼}) (list 8773 {≅}) (list 8776 {≈}) (list 8800 {≠}) (list 8801 {≡}) (list 8804 {≤}) (list 8805 {≥}) (list 8834 {⊂}) (list 8835 {⊃}) (list 8836 {⊄}) (list 8838 {⊆}) (list 8839 {⊇}) (list 8853 {⊕}) (list 8855 {⊗}) (list 8869 {⊥}) (list 8901 {⋅}) (list 8968 {⌈}) (list 8969 {⌉}) (list 8970 {⌊}) (list 8971 {⌋}) (list 9001 {⟨}) (list 9002 {⟩}) (list 9674 {◊}) (list 9824 {♠}) (list 9827 {♣}) (list 9829 {♥}) (list 9830 {♦}))) (define UNENTITIES (map reverse ENTITIES)) (define JS_ESCAPE_CHARS (list (list {\} {\\}) (list {"} {\"}) (list {'} {\'}) (list "\n" {\n}) (list "\r" {\r}) (list ") ;; @param a string to escape ;;

Escapes a string for output in javascript. Does not encode entities; ;; just prevents control characters from causing syntax errors in javascript.

(define (escape-js str) (dolist (ch JS_ESCAPE_CHARS) (replace (first ch) str (last ch))) str) ;; @syntax (Web:escape ) ;; @param a string to escape ;; @return the escaped string ;;

Escapes characters that are part of the (X)HTML and XML syntax to prevent ;; characters from confusing browsers' parsing of markup. Escapes single and ;; double quotes, ampersands, and left and right angle brackets ;; ('"', ''', '&', '<', and '>').

(define (escape str) (replace {&} str {&}) (replace {"} str {"}) (replace {'} str {'}) (replace {<} str {<}) (replace {>} str {>}) str) ;; @syntax (Web:unescape ) ;; @param an entity-escaped string ;; @return the unescaped string ;;

Unescapes the basic (X)HTML and XML character entities in a string.

(define (unescape str) (replace {"} str {"}) (replace {'} str {'}) (replace {&} str {&}) (replace {<} str {<}) (replace {>} str {>}) str) ;; @syntax (Web:encode-entities ) ;; @param a string to escape ;; @return the escaped string ;;

Escapes characters with a much larger set of character entities than ;; 'escape' using a table derived from ;; @link http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references Wikipedia. (define (encode-entities str , ent (buf "")) (dostring (c str) (write-buffer buf (if (setf ent (lookup c ENTITIES)) ent (char c)))) buf) ;; @syntax (Web:decode-entities ) ;; @param an entity-encoded string ;; @return the decoded string ;;

Translates character entities to their character equivalents as well as ;; numeric entities.

(define (decode-entities str) (replace {&(\d+);} str (char (int $1)) 0) (replace {(&\S+?;)} str (char (lookup $1 UNENTITIES)) 0)) ; Translates a single character into a hex-encoded string suitable for a URL. (define (hex-encode-char ch) (if (= " " ch) "+" (format "%%%x" (char ch)))) ; Translates a URL-encoded hex into a string character. (define (hex-decode-char ch) (when (starts-with ch "%") (pop ch)) (char (int (append "0x" $1)))) ;; @syntax (Web:url-encode ) ;; @param a string token to encode for use in a URL ;; @return the URL-encoded string ;;

Encodes a string for use in a URL.

(constant 'REGEX_HTTP_SPECIAL_CHAR (regex-comp {([^-_.$+!*'()0-9a-z])} 1)) (define (url-encode str) (replace " " str "+") (replace REGEX_HTTP_SPECIAL_CHAR str (hex-encode-char $1) 0x10000)) ;; @syntax (Web:url-decode ) ;; @param a URL-encoded string ;; @return the decoded string ;;

Decodes hexidecimals and spaces (represented as '+') in a URL-encoded string.

(constant 'REGEX_HEX_ENCODED_CHAR (regex-comp {%([0-9A-F][0-9A-F])} 1)) (define (url-decode str) (replace "+" str " ") (replace REGEX_HEX_ENCODED_CHAR str (hex-decode-char $1) 0x10000)) ;; @syntax (Web:parse-query ) ;; @param a URL-encoded query string ;; @return an association list of decoded key-value pairs ;;

Parses a URL-encoded query string and returns a list of key-values pairs.

(constant 'REGEX_QUERY (regex-comp {&([^&=]+?)=([^&=]+?)(?=&|$)} 1)) (define (parse-query query) (when (starts-with query "?") (pop query)) (push "&" query) (find-all REGEX_QUERY query (list (url-decode $1) (url-decode $2)) 0x10000)) ;; @syntax (Web:build-query ) ;; @param an association list ;; @return a URL-encoded query string ;;

Builds a URL-encoded query string using . Does not include the leading ;; question mark (so queries may be easily built of association list fragments.)

(define (build-query alist , query) (join (map (fn (pair) (join (map url-encode pair) "=")) alist) "&")) ;; @syntax (Web:parse-url ) ;; @param a URL ;; @return an association list with the decomposed URL's parts ;;

Parses a URL and returns an association list of its decomposed parts. The list's ;; keys (as strings) are: scheme, user, pass, host, port, path, query, and fragment. ;; Also handles IPV6 addresses. Modeled on the PHP function of the same name.

;; ;; Parsing based on code from @link http://us3.php.net/manual/en/function.parse-url.php#90365 this comment. (constant 'REGEX_URL (regex-comp [text] (?:([a-z0-9+-._]+)://)? (?: (?:((?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9a-f]{2})*)@)? (?:\[((?:[a-z0-9:])*)\])? ((?:[a-z0-9-._~!$&'()*+,;=]|%[0-9a-f]{2})*) (?::(\d*))? (/(?:[a-z0-9-._~!$&'()*+,;=:@/]|%[0-9a-f]{2})*)? | (/? (?:[a-z0-9-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+ (?:[a-z0-9-._~!$&'()*+,;=:@/]|%[0-9a-f]{2})* )? ) (?:\?((?:[a-z0-9-._~!$&'()*+,;=:/?@]|%[0-9a-f]{2})*))? (?:\#((?:[a-z0-9-._~!$&'()*+,;=:/?@]|%[0-9a-f]{2})*))? [/text] (| 1 8))) (define (parse-url url) ;; clear indices of previous matches (dolist (idx '($0 $1 $2 $3 $4 $5 $6 $7 $8 $9)) (set idx nil)) (when (regex REGEX_URL url 0x10000) (let ((user-pass (parse $2 ":"))) (list (list "scheme" (if (null? $1) "http" $1)) (list "user" (when user-pass (first user-pass))) (list "pass" (when (and user-pass (= (length user-pass) 2)) (last user-pass))) (list "host" (if-not (null? $3) $3 $4)) (list "port" (if (null? $5) nil $5)) (list "path" (if (and (null? $6) (null? $7)) "/" (string $6 $7))) (list "query" (if (null? $8) nil $8)) (list "fragment" (if (null? $9) nil $9)))))) ;; @syntax (Web:build-url [ ...]) ;; @param a string URL ;; @param one or more association lists of query parameters and their values ;; ;; @syntax (Web:build-url [ ...]) ;; @param an association list of URL components using the structure of 's return value ;; @param one or more association lists of query parameters and their values ;; @return a URL string composed of the initial URL data plus subsequently superseding query parameters ;;

In the first syntax, builds a URL from an existing URL string. ;; In the second syntax, builds a URL from an association list in the same ;; format as the return value of , with both keys and values being ;; strings. In both syntaxes, any number of additional association lists of ;; key/value pairs may be passed, which are serialized as query parameters, with ;; each list overriding the previous. If there are query parameters in the ;; initial URL, they are used as the initial list with the lowest priority.

(define (build-url url) (when (string? url) (setf url (parse-url url))) (local (params) ;; Build parameter list (setf params '()) (dolist (pairs (cons (lookup "query" url) (args))) (when (string? pairs) (setf pairs (parse-query pairs))) (dolist (pair pairs) (if (assoc (first pair) params) (setf (assoc (first pair) params) pair) (push pair params)))) (format "%s://%s%s%s%s%s%s" (or (lookup "scheme" url) "http") (cond ((and (lookup "user" url) (lookup "pass" url)) (string (lookup "user" url) ":" (lookup "pass" url) "@")) ((lookup "user" url) (string (lookup "user" url) "@")) (true "")) (lookup "host" url) (if (lookup "port" url) (string ":" (lookup "port" url)) "") (lookup "path" url) (if (null? params) "" (string "?" (build-query params))) (if (lookup "fragment" url) (string "#" (lookup "fragment" url)) "")))) ;=============================================================================== ; !Headers, COOKIES, GET, and POST ;=============================================================================== ;; @syntax (Web:header ) ;; @param the header name (e.g., "Content-type") ;; @param the header value (e.g., "text/html") ;;

Sets an HTTP output header. Headers are printed using 'Web:send-headers'.

(define headers '(("Content-type" "text/html"))) (define (header key value) (if (lookup key headers) (setf (assoc key headers) (list key value)) (push (list key value) headers -1))) ;; @syntax (Web:redir ) ;; @param a URL string ;;

Redirects the client to .

(define (redir url) (header "Location" url)) ;; @syntax (Web:send-headers) ;;

Writes the HTTP headers to stdout. This function should be called regardless ;; of whether any headers have been manually set to ensure that the minimum HTTP ;; headers are properly sent. Note: no check is made to verify that output has not ;; already begun.

(define (send-headers) (dolist (header headers) (print (format "%s: %s\n" (first header) (last header)))) (println)) ;; @syntax (Web:cookie ) ;; @param the cookie's name ;; ;; @syntax (Web:cookie [ [ [ [ []]]]) ;; @param the cookie's name ;; @param the cookie's value ;; @param (optional) the expiration date of the cookie as a unix timestamp; default is a session cookie ;; @param (optional) the cookie's path; default is the current path ;; @param (optional) the cookie's domain; default is the current host ;; @param (optional) whether the cookie may be read by client-side scripts ;; @param (optional) whether the cookie may be accessed/set outside of HTTPS ;;

In the first syntax, 'cookie' returns the value of the cookie named or 'nil'. If ;; is not provided, an association list of all cookie values is returned.

;;

In the second syntax, 'cookie' sets a new cookie or overwrites an existing cookie in the ;; client's browser. Note that defaults to true, but is not standard and ;; therefore is not necessarily implemented in all browsers. defaults to nil. ;; Cookies use the 'header' function and must be sent before calling 'send-headers'.

(define (cookie key value expires path domain http-only secure) (cond ((null? key) COOKIES) ((and (null? value) COOKIE) (lookup key COOKIE)) (true (when (or (not secure) (and secure (starts-with (lower-case (env "SERVER_PROTOCOL")) "https"))) (header "Set-Cookie" (format "%s=%s%s%s%s%s" (url-encode (string key)) (url-encode (string value)) (if expires (string "; expires=" (date expires 0 "%a, %d-%b-%Y %H:%M:%S %Z")) "") (if path (string "; path=" path) "") (if domain (string "; domain=" domain) "") (if-not http-only "; HttpOnly" ""))))))) ;; @syntax (Web:get ) ;;

Returns the value of in the query string or 'nil' if not present. ;; If is not provided, returns an association list of all GET values.

(define (get key) (when GET (if key (lookup key GET) GET))) ;; @syntax (Web:post ) ;;

Returns the value of in the client-submitted POST data or 'nil' if ;; not present. If is not provided, returns an association list of all ;; POST values.

(define (post key) (when POST (if key (lookup key POST) POST))) ;=============================================================================== ; !Session control ; notes: ; * sessions require cookies to function ; * close-session or MAIN:exit must be called to save session changes to disk ;=============================================================================== ;; @syntax (Web:define-session-handlers ) ;; @param function to begin a new session ;; @param function to close a session, saving changes ;; @param function to delete a session ;; @param function to prune old sessions ;;

Defines handler functions to be called when various session control ;; functions are used, making custom session storage a fairly simple matter.

;; The required handler functions are: ;;
    ;;
  • 'fn-open': called by 'open-session'; resumes or starts a new session storage instance, initializing the context tree
  • ;;
  • 'fn-close': called by 'close-session'; writes changes to a session to storage
  • ;;
  • 'fn-delete': called by 'delete-session'; deletes the entire session from storage
  • ;;
  • 'fn-clean': called by 'clean-sessions'; prunes old stored sessions
  • ;;
;; Some useful functions and variables for handler functions: ;;
    ;;
  • 'session-id': function that returns the current session id and sets the session cookie when necessary
  • ;;
  • 'session-context': function that returns the session context dictionary
  • ;;
  • 'SESSION_MAX_AGE': a variable storing the number of seconds after which an orphan session should be deleted
  • ;;
(define (define-session-handlers fn-open fn-close fn-delete fn-clean) (setf _open-session fn-open _close-session fn-close _delete-session fn-delete _clean-sessions fn-clean)) ;; @syntax (Web:session-id []) ;; @param (optional) the session ID to use ;; @return a unique session id for the client ;;

Creates or retrieves the client's session id. If this is a new session id, ;; a cookie is set in the client's browser to identify it on future loads.

;;

If is provided, it will be used as the new session ID.

(define (session-id sid) (setf SESSION_ID (or (when sid (cookie SESSION_KEY sid) sid) SESSION_ID (cookie SESSION_KEY) (begin (setf sid (string SESSION_PREFIX "-" (uuid))) (cookie SESSION_KEY sid) sid)))) ;; @syntax (Web:session-context) ;; @return a symbol pointing to the current session's context dictionary ;;

Run-time session data is stored in a context tree. 'session-context' ;; returns the current session tree or creates a new one when necessary. ;; This function is primarily intended for session handlers' use; it is ;; typically more useful to call 'session' on its own to retrieve an association ;; list of key/value pairs in an application.

(define (session-context , ctx) (setf ctx (sym (session-id) 'MAIN)) (unless (context? ctx) (context ctx)) ctx) ;; @syntax (Web:open-session) ;;

Initializes the client's session.

(define (open-session) (_open-session) (setf SESSION_STARTED true) (session-id)) ;; @syntax (close-session) ;;

Writes any changes to the session to file. This is automatically called ;; when the distribution function 'exit' is called.

(define (close-session) (when SESSION_STARTED (_close-session))) ;; @syntax (delete-session) ;;

Deletes the session. Sessions are then turned off and 'open-session' ;; must be called again to use sessions further.

(define (delete-session) (unless SESSION_STARTED (throw-error "session is not started")) (_delete-session) (delete (session-context)) (cookie SESSION_KEY "" 0) (setf SESSION_STARTED nil)) ;; @syntax (clear-session) ;;

Clears all session variables.

(define (clear-session) (when SESSION_STARTED (dotree (s (session-context)) (delete (sym s (session-context)))))) ;; @syntax (clean-sessions) ;;

Cleans old session files. This function is not currently called automatically; ;; note that there is the possibility of a race condition with this function and other ;; session handling functions.

(define (clean-sessions) (_clean-sessions)) ;; @syntax (session [ []]) ;; @param the session key ;; @param the new value ;; When called with both and , sets the session variable. When ;; called with only , returns the value of . Otherwise, returns ;; an association list of session variables. Returns nil if the session is not ;; opened. (define (session key value) (cond ((not SESSION_STARTED) nil) ((and key value) (context (session-context) key value)) ((true? key) (context (session-context) key)) (true (let ((alist '())) (dotree (s (session-context)) (push (list (name s) (context (session-context) (name s))) alist -1)) alist)))) ;=============================================================================== ; !Default session handlers ; ; The default session handlers use newLISP's 'save' and 'load' functions to ; easily serialize and import context data to and from file records. The files ; are stored unencrypted, so a custom handler should be used on a shared ; system. ;=============================================================================== ; Returns the name of the file in which the session data is stored. (define (default-session-file) (string SESSION_DIR "/" (session-id) ".lsp")) ; Loads/creates the session file; creates a new context tree when ; necessary. (define (default-open-session) (if (file? (default-session-file)) (load (default-session-file)) (save (default-session-file) (session-context)))) ; Saves the session context to the session file. (define (default-close-session) (save (default-session-file) (session-context))) ; Deletes the session file. (define (default-delete-session) (when (file? (default-session-file)) (delete-file (default-session-file)))) ; Deletes old session files. (define (default-clean-sessions , f) (dolist (tmp-file (directory SESSION_DIR)) (when (starts-with tmp-file SESSION_PREFIX) (setf f (string SESSION_DIR "/" tmp-file)) (when (> (- (date-value) (file-info f 5 nil)) SESSION_MAX_AGE) (delete-file f))))) ;=============================================================================== ; !Templating ;=============================================================================== ;; @syntax (Web:eval-template ) ;; @param a string containing the template syntax ;; @param the context in which to evaluate the template ;;

Translates a template using ASP-like tags, creating small islands of ;; newLISP code in an HTML (or other) document. This is similar to the ;; distribution CGI module's 'put-page' function, except that the short-cut ;; <%= foo %> is used to simply output the value of 'foo' and tags ;; may span multiple lines.

;;

Note that the opening and closing tags may be changed by setting the ;; values of 'Web:OPEN_TAG' and 'Web:CLOSE_TAG' if desired. The shortcut ;; print tag will be 'Web:OPEN_TAG' + '='.

;; @example ;; (Web:eval-template "<p><%= (* 3 3) %></p>") ;; => "<p>9</p>" ;; (Web:eval-template "<p><% (println (* 3 3)) %></p>") ;; => "<p>9</p>" (define OPEN_TAG "<%") (define CLOSE_TAG "%>") (define (eval-template str (ctx MAIN) , start end next-start next-end block (buf "")) (setf start (find OPEN_TAG str)) (setf end (find CLOSE_TAG str)) ;; Prevent use of code island tags inside code island from breaking parsing. (when (and start end) (while (and (setf next-end (find CLOSE_TAG (slice str (+ end 2)))) (setf next-start (find OPEN_TAG (slice str (+ end 2)))) (< next-end next-start)) (inc end (+ next-end 2))) (when (and start (not end)) (throw-error "Unbalanced tags."))) (while (and start end) (write-buffer buf (string "(print [text]" (slice str 0 start) "[/text])")) (setf block (slice str (+ start 2) (- end start 2))) (if (starts-with block "=") (write-buffer buf (string "(print " (rest block) ")")) (write-buffer buf (trim block))) (setf str (slice str (+ end 2))) (setf start (find OPEN_TAG str)) (setf end (find CLOSE_TAG str)) ;; Prevent use of code island tags inside code island from breaking parsing. (when (and start end) (while (and (setf next-end (find CLOSE_TAG (slice str (+ end 2)))) (setf next-start (find OPEN_TAG (slice str (+ end 2)))) (< next-end next-start)) (inc end (+ next-end 2))) (when (and start (not end)) (throw-error "Unbalanced tags.")))) (write-buffer buf (string "(print [text]" str "[/text])")) (eval-string buf ctx)) ;=============================================================================== ; !Module initialization ; ; Install default session handlers and create the GET, POST, and COOKIE data ; structures. ;=============================================================================== ; Content-Disposition: form-data; name="file"; filename="white-napkin.jpg"\r\nContent-Type: image/jpeg\r\n\r\n\253\152\191\160\128\144JFIF ; Content-Disposition: form-data; name="text"\r\n\r\nadsf\r\n (define (mime-decode str , content-type parts re decoded) (when (setf content-type (regex {^multipart/form-data; boundary=(.+?)$} (env "CONTENT_TYPE") 1)) (setf parts (find-all (string "--" (content-type 3) {\r\n(.+?)(?=--)}) str $1 (| 2 4))) (dolist (part parts) (cond ((regex {Content-Disposition: form-data; name="(.+?)"\r\n\r\n(.*?)\s+} part 1) (push (list $1 $2) decoded -1)) ((regex {Content-Disposition: form-data; name="(.+?)"; filename="(.+?)"\r\nContent-Type: (.+?)\r\n\r\n(.*)$} part (| 1 2 4)) (push (list $1 (list (list "filename" $2) (list "content-type" $3) (list "bytes" $4))) decoded -1)))) decoded)) ; Install default session handlers (define-session-handlers default-open-session default-close-session default-delete-session default-clean-sessions) ; Read GET data (setf GET (when (env "QUERY_STRING") (parse-query (env "QUERY_STRING")))) ; Read POST data (if-not (context? CGI) ;; CGI module not present; read and parse the POST data ourselves (let ((post "") (buffer "") (recvd 0) (conln 0)) (when (true? (set 'conln (int (env "CONTENT_LENGTH")))) (do-while (< recvd conln) (inc recvd (read (device) buffer (- conln recvd))) (write post buffer))) (setf POST (when post (parse-query post)))) ;This will replace the above line once mim-decode actually works. ;(setf POST ; (when post ; (if (env "CONTENT_TYPE") ; (mime-decode post) ; (parse-query post))))) ;; CGI module present; try to guess which values in CGI:params are ;; from GET and which are from POST. (begin (setf POST '()) (dolist (param CGI:params) (unless (lookup (first param) GET) (push param POST))))) ; Read COOKIE data (setf COOKIE (when (env "HTTP_COOKIE") (map (lambda (cookie , n) (setf n (find "=" cookie)) (list (url-decode (slice cookie 0 n)) (url-decode (slice cookie (+ 1 n))))) (parse (env "HTTP_COOKIE") "; *" 0)))) (context 'MAIN) ; This function wraps the distribution exit routine to ensure that sessions are ; written when the application exits. It is only called when the 'exit' function ; is explicitly called. The 'exit' function is renamed 'sys-exit'. The 'Web' ; function 'close-session' is only called on a normal exit (exit code 0.) (define (exit-with-session-close (n 0)) (when (zero? n) (Web:close-session)) (MAIN:sys-exit)) (constant 'sys-exit exit) (constant 'exit exit-with-session-close)