Added missing termination periods to text and index output.
[scratchy.git] / burrower.scm
index 867a289..2886a07 100644 (file)
 
 ;;; Global constants
 
-(define gopher-index-file-name "index")
+(define gopher-index-filename "index")
 
 (define burrower-version "1.0.0")
 
 (define burrower-footer
   (conc "\n"
         "--------------------------------------------------\n"
-        "This gopher hole was dug using Burrower v" burrower-version "\n"
+        "This gopher hole was dug using Burrower v" burrower-version ".\n"
         "Powered by Chicken Scheme!"))
 
-
 ;;; Server loop
 
 ;; We don't yet use worker threads here to handle requests,
@@ -39,6 +38,7 @@
 (define-record config root-dir host port display-footer)
 
 (define (run-server config)
+  (set-buffering-mode! (current-output-port) #:line)
   (print "Gopher server listening on port " (config-port config) " ...")
   (let ((listener (tcp-listen (config-port config))))
     (let server-loop ()
@@ -55,7 +55,7 @@
                       (serve-selector (if (= (string-length selector) 0)
                                           "/"
                                           selector)
-                                      remote-ip config)))
+                                      config)))
                   (print "... served selector '" selector "'. Closing connection."))
               (o (exn)
                  (print-error-message o out-port)
 
 ;;; Selector type inference
 
-(define (has-suffix? selector . suffixes)
-  (if (null? suffixes)
+(define (true-for-one? predicate values)
+  (if (null? values)
       #f
-      (if (string-suffix? (car suffixes) selector)
+      (if (predicate (car values))
           #t
-          (apply has-suffix? selector (cdr suffixes)))))
+          (true-for-one? predicate (cdr values)))))
+
+(define (has-suffix? selector . suffixes)
+  (true-for-one? (lambda (suffix)
+                   (string-suffix? suffix selector))
+                 suffixes))
+
+(define (has-prefix? selector . prefixes)
+  (true-for-one? (lambda (prefix)
+                   (string-prefix? prefix selector))
+                 prefixes))
 
 (define (infer-selector-type selector)
   (let ((l (string-downcase selector)))
      ((or (= (string-length l) 0) (string-suffix? "/" l)) 1)
      ((has-suffix? l ".txt" ".org" ".md") 0)
      ((has-suffix? l ".png" ".jpg" ".gif" ".bmp" ".tif" ".tga") 'I)
-     ((has-suffix? l "?") 7)
+     ((has-suffix? l "?.scm" "%3f.scm") 7)
+     ((has-prefix? l "url:" "/url:") 'h)
      (else 9))))
 
 
 ;;; Selector retrieval
 
-(define (serve-selector raw-selector remote-ip config)
+(define (serve-selector raw-selector config)
   (let* ((selector-list (string-split raw-selector "\t"))
          (selector (car selector-list))
          (arguments (cdr selector-list)))
     (case (infer-selector-type selector)
       ((1) (serve-directory selector config))
       ((0) (serve-text-file selector config))
-      ((7) (serve-query selector arguments remote-ip config))
+      ((7) (serve-query selector arguments config))
+      ((h) (serve-url selector config))
       (else (serve-binary-file selector config)))))
 
+(define (legal-filename? filename config)
+  (and (string-prefix? (config-root-dir config)
+                       (normalize-pathname filename))
+       (regular-file? filename)))
+
 (define (serve-directory selector config)
-  (let ((file-name (make-pathname (list (config-root-dir config) selector)
-                                  gopher-index-file-name)))
-    (if (regular-file? file-name)
+  (let ((filename (make-pathname (list (config-root-dir config) selector)
+                                 gopher-index-filename)))
+    (if (legal-filename? filename config)
         (begin
-          (with-input-from-file file-name
+          (with-input-from-file filename
             (lambda ()
               (let loop ((c (peek-char)))
                 (if (eof-object? c)
                           (serve-info-records (read-line)))
                       (loop (peek-char)))))))
           (if (config-display-footer config)
-              (serve-info-records burrower-footer)))
+              (serve-info-records burrower-footer))
+          (print ".\r"))
         (error "Index file not found."))))
   
 (define (serve-text-file selector config)
-  (let ((file-name (make-pathname (config-root-dir config) selector)))
-    (if (regular-file? file-name)
-        (with-input-from-file file-name
-          (lambda ()
-            (for-each
-             (lambda (line)
-               (print line "\r"))
-             (read-lines))))
-        (error "File not found." file-name))))
+  (let ((filename (make-pathname (config-root-dir config) selector)))
+    (if (legal-filename? filename config)
+        (begin
+          (with-input-from-file filename
+            (lambda ()
+              (for-each
+               (lambda (line)
+                 (print line "\r"))
+               (read-lines))))
+          (print ".\r"))
+        (error "File not found." filename))))
 
 (define (serve-binary-file selector config)
-  (let ((file-name (make-pathname (config-root-dir config) selector)))
-    (if (regular-file? file-name)
-        (with-input-from-file file-name
+  (let ((filename (make-pathname (config-root-dir config) selector)))
+    (if (legal-filename? filename config)
+        (with-input-from-file filename
           (lambda ()
             (let loop ((b (read-byte)))
               (if (eof-object? b)
                   (begin
                     (write-byte b)
                     (loop (read-byte)))))))
-        (error "File not found." file-name))))
+        (error "File not found." filename))))
 
-(define (serve-query selector arguments remote-ip config)
-  (let ((file-name (make-pathname (config-root-dir config)
-                                  (conc (string-chomp selector "?") ".scm"))))
-    (if (and (regular-file? file-name)
+(define (serve-query selector arguments config)
+  (let ((filename (make-pathname (config-root-dir config)
+                                 (string-translate* selector '(("%3f" . "?"))))))
+    (if (and (legal-filename? filename config)
              (= (length arguments) 1))
-        (with-input-from-file file-name
+        (with-input-from-file filename
           (lambda ()
             (serve-info-records
              (with-selector-dir
               selector config
               (lambda ()
-                (apply (eval (read))
-                       (list (car arguments) remote-ip)))))))
-        (error "Invalid query."))))
+                (apply (eval (read)) arguments))))))
+        (error "Invalid query." selector arguments))))
+
+
+(define (serve-url selector config)
+  (let ((url (substring selector 4)))
+    (print
+     "<html><head><title>Redirection</title>"
+     "<meta http-equiv=\"refresh\" content=\"10; URL='" url "'\" />"
+     "</head><body>"
+     "<p>If you are seeing this page, your gopher browser does not "
+     "properly support URL directory entries or cannot follow such "
+     "links.</p>"
+     "<p>If you are viewing this page using a web browser, you should "
+     "be redirected shortly.  Otherwise, you can manually open the "
+     "the follwing url:\n"
+     "\n"
+     "<a href=\"" url "\">" url "</a>\n"
+     "</body></html>")))
 
 
 ;;; Index rendering
     (('shell command) (serve-shell-command command dir-selector config))
     (('eval expression) (serve-expression expression dir-selector config))
     (('url display-string url)
-     (print #\h display-string "\tURL:" url "\tfake\t80\r"))
+     (print #\h display-string "\tURL:" url
+            "\t" (config-host config)
+            "\t" (config-port config) "\r"))
     ((type display-string selector host port)
      (print type display-string "\t" selector "\t" host "\t" port "\r"))
     ((type display-string selector host)
    dir-selector config
    (lambda ()
      (let-values (((in-port out-port id) (process command)))
-       (serve-info-records (string-chomp (read-string #f in-port) "\n"))))))
+       (let ((string (read-string #f in-port)))
+         (if (and (not (eof-object? string))
+                  (> (string-length string) 0))
+             (serve-info-records (string-chomp string "\n"))))))))
 
 (define (serve-expression expression dir-selector config)
   (with-selector-dir
 (define (with-current-working-directory directory thunk)
   (let ((old-wd (current-directory))
         (result 'none))
-    (change-directory directory)
-    (set! result (thunk))
-    (change-directory old-wd)
-    result))
+    (condition-case
+        (begin
+          (change-directory directory)
+          (set! result (thunk))
+          (change-directory old-wd)
+          result)
+      (o (exn)
+         (change-directory old-wd)
+         (signal o)))))
 
 (define (with-selector-dir selector config thunk)
   (with-current-working-directory
                     (config-port-set! config (string->number (caddr args))))
                 (run-server config)))))))
 
-(main)
+;; (main)
 
-;; (define (test)
-;;   (run-server (make-config "gopher-root" "localhost" 70 #t)))
+(define (test)
+  (run-server (make-config "gopher-root" "localhost" 70 #t)))
 
 ;; (test)