diff options
author | stuebinm | 2021-04-05 00:50:35 +0200 |
---|---|---|
committer | stuebinm | 2021-04-05 00:53:23 +0200 |
commit | 0f8b78c0b7e2aca94a14b36af020d0f7d8301cc5 (patch) | |
tree | eebc14b5dbe7e5af541ffaa3efa4fd397c5f671d | |
parent | ef1624f645b53cf60c89b2caabc3087883ad7dbd (diff) |
add simple server which takes POST requests
The idea is that it can accept surveys sent to it. Note that in an actual
deployment it should have at least some rate-limiting, since actual validation
of input is impossible if they are encrypted – i.e. this is bascially a pastbin.
It may, however, be possible to require signing answers for survey with access
restrictions, or do a challange/response type thing before allowing submission.
-rw-r--r-- | server/Readme.org | 14 | ||||
-rw-r--r-- | server/server.scm | 101 |
2 files changed, 115 insertions, 0 deletions
diff --git a/server/Readme.org b/server/Readme.org new file mode 100644 index 0000000..1242159 --- /dev/null +++ b/server/Readme.org @@ -0,0 +1,14 @@ +#+TITLE: Simple Server that accepts survey answers + +Run with: +#+BEGIN_SRC +guile server.scm +#+END_SRC + +Will listen to localhost:8080, and accept files sent via POST with +mimetypes of either ~text/plain~ (which will be appended to a single +file in the working directory) or ~text/age~ (which will be kept as a +single file). + +Expects a custom header ~X-Survey~ in these requests to know which survey +the answer belongs to. diff --git a/server/server.scm b/server/server.scm new file mode 100644 index 0000000..5159314 --- /dev/null +++ b/server/server.scm @@ -0,0 +1,101 @@ +(use-modules (web server) + (web request) + (web response) + (web http) + (web uri)) + +(use-modules (ice-9 format) + (ice-9 textual-ports) + (ice-9 iconv) + (ice-9 match)) + + +(define (get-path request) + (split-and-decode-uri-path (uri-path (request-uri request)))) + +(define (get-last list) + (list-ref list (- (length list) 1))) + +;; This was intended to also server the files. +;; However, it turns out that guile will attempt to "sanitise" anything +;; sent as response to anything, which includes attaching charsets to +;; all mimetypes it sees. Since the wasm specifications are also weird, +;; this results in the app not running at all since web assembly can't +;; be instantiated correctly on the client's side. +;; +;; So instead, just use another web server for serving files. +(define (fetch-file path) + (let ((ext (car (cdr (string-split (car (last-pair path)) (string->char-set ".")))))) + (let ((mimetype + (cond + ((equal? ext "html") 'text/html) + ((equal? ext "css") 'text/css) + ((equal? ext "js") 'text/javascript) + ((equal? ext "wasm") 'application/wasm) + (else "undefined")))) + (format #t "mimetype for .~a is ~a\n" ext mimetype) + (format #t "~a" (values `((content-type ,mimetype)) "test")) + (values (build-response + #:code 200 + #:headers `((content-type . (,mimetype (charset . "iso-8859-1")))) + #:validate-headers? #f) + (call-with-input-file (format #f "~a" (get-last path)) + (lambda (port) (get-string-all port))))))) + +(define (call-with-append-file filename proc) + (let ((port (open-file filename "a"))) + (proc port) + (newline port) + (close port))) + +;; extracts the (first) content type given in a set of headers +;; TODO: saveguards against multiple given content-types? +(define (get-header type request) + (cdr (car (filter + (lambda (header) + (equal? (car header) type)) + (request-headers request))))) + +;; accepts a POST request (assuming it contains a survey +;; response which should be saved somewhere) +(define (do-post request body) + (let ((survey (get-header 'x-survey request))) + (format #t "answer to survey ~a " survey) + ;; debug option to display ingoing answers before saving + ;(format #t "~a\n" (bytevector->string body "utf8")) + ;; content-types are wrapped into an extra () by guile + (case (car (get-header 'content-type request)) + ((text/age) (call-with-output-file + (string-append + survey "-answer" + ; save encrypted answers in their own files, using a hash + ; of the answer as name postfix (since age doesn't + ; produce the same ciphertext for the same input twice, + ; this ensures absence of collissions /and/ not counting + ; answers mistakenly sent twice double!) + (number->string (hash body 10000000000)) ".age") + (lambda (port) + (display "(encrypted)\n") + (display (bytevector->string body "ascii") port)))) + ((text/plain) (call-with-append-file + (string-append survey ".survey") + (lambda (port) + (display "(plaintext)\n") + (display (bytevector->string body "utf8") port))))) + (values '((content-type . (text/plain))) + "Hey, thanks for your answers!\n"))) + +;; main server function; delivers files on GET, and interprets +;; everything else as attempts at submissions. +(define (test-handler request body) + (let ((path (get-path request))) + (format #t "got request: ~a: ~:a\n" + (request-method request) + path) + (if (equal? (format #f "~a" (request-method request)) "POST") + (do-post request body) + (values '() "nothing here")))) + + + +(run-server test-handler) |