(defun reverse-alist (alist) (loop for (a . b) in alist collect `(,b . ,a))) (defvar +status-codes+ '((10 . :input) (11 . :sensitive-input) (20 . :success) (30 . :redirect) (40 . :temporary-failure) (41 . :server-unavailable) (42 . :gci-error) (43 . :proxy-error) (44 . :slow-down) (50 . :permanent-failure) (51 . :not-found) (52 . :gone) (59 . :bad-request) (60 . :client-certificate-required) (61 . :certificate-not-authorised) (62 . :certificate-not-valid))) (defun number->status (num) (cdr (assoc num +status-codes+))) (defun status->number (key) (cdr (assoc key (reverse-alist +status-codes+)))) (defvar +site-root+ (pathname (user-homedir-pathname))) (defun find-resource (url-path-string) ;; If the URL ends with a `.gmi` or `.gemini`, assume it points to a file and check if it exists. ;; Otherwise, assume it points to a directory, and check if `index.gmi` or `index.gemini` exists in that directory. ;; Returns the pathname of the file if it exists; nil otherwise. (let ((path-local (merge-pathnames (pathname url-path-string) +site-root+))) (cond ( (search ".." url-path-string) ; TODO: Replace this with some sort of (is-unsafe-URL) function. ; URL is unsafe. nil) ((or (string= "gmi" (pathname-type path-local)) (string= "gemini" (pathname-type path-local))) ; URL points to a file. (probe-file path-local)) (t ; URL points to a directory. ; Due to how pathnames work, we must ensure that the last character in the URL is a single "/" for it to be treated as a directory. (let ((path-local-dir (pathname (if (pathname-name path-local) (pathname (concatenate 'string (namestring path-local) "/")) path-local )))) (or (probe-file (merge-pathnames "index.gmi" path-local-dir)) (probe-file (merge-pathnames "index.gemini" path-local-dir))))))))