diff options
| -rw-r--r-- | tests/request.scm | 60 | ||||
| -rw-r--r-- | weeb/main.scm | 10 | ||||
| -rw-r--r-- | weeb/request.scm | 28 |
3 files changed, 95 insertions, 3 deletions
diff --git a/tests/request.scm b/tests/request.scm new file mode 100644 index 0000000..0b68ea1 --- /dev/null +++ b/tests/request.scm @@ -0,0 +1,60 @@ +;;; Weeb --- Web microframework for GNU Guile +;;; Copyright © 2026 Jakob Kreuze <zerodaysfordays@sdf.org> +;;; +;;; This file is part of Weeb. +;;; +;;; Weeb is free software: you can redistribute it and/or modify it +;;; under the terms of the GNU Affero General Public License as +;;; published by the Free Software Foundation, either version 3 of the +;;; License, or (at your option) any later version. +;;; +;;; Weeb is distributed in the hope that it will be useful, but WITHOUT +;;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +;;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General +;;; Public License for more details. +;;; +;;; You should have received a copy of the GNU Affero General Public +;;; License along with Weeb. If not, see <https://www.gnu.org/licenses/>. + +(use-modules (srfi srfi-64) + (weeb request)) + +(test-runner-current (test-runner-simple)) + +(test-begin "weeb-request") + +(test-group "urldecode" + (test-equal "hello" (urldecode "hello")) + (test-equal "hello world" (urldecode "hello+world")) + (test-equal "hello world" (urldecode "hello%20world")) + (test-equal "!@#$" (urldecode "%21%40%23%24")) + (test-equal "a b c" (urldecode "a+b+c")) + (test-equal "a b c" (urldecode "a%20b%20c")) + (test-equal "" (urldecode "")) + (test-equal "invalid%hex" (urldecode "invalid%hex")) + (test-equal "%" (urldecode "%")) + (test-equal "100%" (urldecode "100%25"))) + +(test-group "parse-form-urlencoded" + (test-equal '(("key" . "value")) + (parse-form-urlencoded "key=value")) + (test-equal '(("a" . "1") ("b" . "2")) + (parse-form-urlencoded "a=1&b=2")) + (test-equal '(("name" . "John Doe")) + (parse-form-urlencoded "name=John+Doe")) + (test-equal '(("query" . "foo bar")) + (parse-form-urlencoded "query=foo%20bar")) + (test-equal '(("user_id" . "42") ("role" . "admin")) + (parse-form-urlencoded "user_id=42&role=admin")) + (test-equal '(("empty" . "")) + (parse-form-urlencoded "empty=")) + (test-equal '(("foo-bar" . "baz")) + (parse-form-urlencoded "foo-bar=baz")) + (test-equal '(("a" . "1") ("b" . "2") ("c" . "3")) + (parse-form-urlencoded "a=1&b=2&c=3")) + (test-equal '(("foo" . "bar")) + (parse-form-urlencoded "foo=bar")) ; Verifies (string->symbol key) + (test-equal '() + (parse-form-urlencoded ""))) + +(test-end) diff --git a/weeb/main.scm b/weeb/main.scm index 7487f19..b6e47ec 100644 --- a/weeb/main.scm +++ b/weeb/main.scm @@ -110,6 +110,11 @@ (match-lambda ("GET" 'GET) ("POST" 'POST))) + (define (strip-query-string uri) + (let ((match-object (string-match "^(/.*)\\?" uri))) + (if match-object + (match:substring match-object 1) + uri))) ;; According to RFC 9110, header names must be treated as case-insensitive. (define headers (filter-map @@ -125,8 +130,9 @@ #f))) (environ))) (let* ((method (string->method (getenv "REQUEST_METHOD"))) - (uri (getenv "REQUEST_URI"))) - (make-struct <request> method uri '() headers (fcgi-read)))) + (query (parse-form-urlencoded (getenv "QUERY_STRING"))) + (uri (strip-query-string (getenv "REQUEST_URI")))) + (make-struct <request> method uri query headers (fcgi-read)))) (define (handle-errors thunk) (catch #t diff --git a/weeb/request.scm b/weeb/request.scm index 42cd933..3d18b0a 100644 --- a/weeb/request.scm +++ b/weeb/request.scm @@ -17,8 +17,34 @@ ;;; License along with Weeb. If not, see <https://www.gnu.org/licenses/>. (define-module (weeb request) + #:use-module (ice-9 match) #:use-module (weeb struct) - #:export (<request>)) + #:export (<request> + urldecode + parse-form-urlencoded)) (define-struct <request> method uri parameters headers body) + +(define (urldecode str) + (let loop ((i 0) (acc "")) + (cond + ((>= i (string-length str)) acc) + ((char=? (string-ref str i) #\+) + (loop (+ i 1) (string-append acc " "))) + ((and (char=? (string-ref str i) #\%) + (<= (+ i 2) (string-length str))) + (let* ((hex-part (substring str (+ i 1) (+ i 3))) + (code (string->number hex-part 16))) + (if code + (loop (+ i 3) (string-append acc (string (integer->char code)))) + (loop (+ i 1) (string-append acc (string (string-ref str i))))))) + (else + (loop (+ i 1) (string-append acc (string (string-ref str i)))))))) + +(define (parse-form-urlencoded encoded) + (map (lambda (encoded-pair) + (match (map urldecode (string-split encoded-pair #\=)) + ((key value) `(,key . ,value)))) + (filter (lambda (x) (not (zero? (string-length x)))) + (string-split encoded #\&)))) |