guile-pastebin/pastebin/httpserver.scm

httpserver.scm

1
(define-module (pastebin httpserver))
2
3
(use-modules (web request)
4
             (web response)
5
             (web uri)
6
             (sxml simple)
7
             (pastebin data)
8
             (rnrs bytevectors)
9
             (ice-9 textual-ports)
10
             (ice-9 binary-ports)
11
             (ice-9 regex)
12
             (ice-9 match)
13
             (srfi srfi-1))
14
15
(export make-pastebin-handler)
16
17
(define (read-parts reqbody boundary)
18
  (define b2 (string-append "(\r\n)?--" boundary))
19
  (let A ((start 0) (parts '()))
20
    (let ((sm (string-match b2 reqbody start)))
21
      (if sm
22
          ;; +2 => CRLF
23
          (A (+ 2 (match:end sm 0))
24
             (cons (substring reqbody start (match:start sm 0)) parts))
25
          (cdr (reverse parts))))))
26
27
(define (get-new-pin str pin)
28
  (let ((crlfi (string-contains str "\r\n" pin)))
29
    (if crlfi crlfi (string-length str))))
30
31
(define (parse-part partstr)
32
  (let A ((headers '()) (pin 0))
33
    (let* ((newpin (get-new-pin partstr pin))
34
           (line (substring partstr pin newpin)))
35
      (if (string-null? line)
36
          (cons (reverse headers) (substring partstr (+ 2 newpin)))
37
          (A (cons line headers) (+ 2 newpin))))))
38
39
(define (get-content-dispo-name-from-headers headers)
40
  (let ((fl (find (lambda (line) (string-prefix-ci? "content-disposition: " line)) headers)))
41
    (if fl
42
        (let ((sm (string-match "name=(.*)" fl)))
43
          (if sm (string-trim-both (match:substring sm 1) #\") ""))
44
        "")))
45
46
(define (read-multipart-form-data reqbody boundary)
47
  (define parts (read-parts reqbody boundary))
48
  (map
49
   (lambda (part)
50
     (let ((pp (parse-part part)))
51
       (cons (get-content-dispo-name-from-headers (car pp)) (cdr pp))))
52
   parts))
53
54
(define (templatize title body)
55
  `(html (head
56
          (title ,title)
57
          (meta (@ (name "viewport") (content "width=device-width, initial-scale=1"))))
58
         (body ,@body)))
59
60
(define (make-raw-link-pb-entry pb-id)
61
  `(a (@ (href ,(format #f "/raw/~a" pb-id))) "raw"))
62
63
(define (make-pastebin-handler data-path)
64
  (lambda (request request-body)
65
    (if (eq? (request-method request) 'POST)
66
        (let* ((headers (request-headers request))
67
               (content-type-all (assq-ref headers 'content-type))
68
               (content-type (if content-type-all (car content-type-all) #f))
69
               (boundary (if (eq? content-type 'multipart/form-data)
70
                             (assq-ref (cdr content-type-all) 'boundary) #f))
71
               (reqbody-string (utf8->string request-body)))
72
          (if boundary
73
              (let ((form-data (read-multipart-form-data reqbody-string boundary)))
74
                (call-with-dir-as-pb-data
75
                 data-path
76
                 (lambda (pb-data)
77
                   (pb-data-new-entry pb-data
78
                                      (assoc-ref form-data "text"))))))))
79
80
    ;; match raw file
81
    (match (split-and-decode-uri-path (uri-path (request-uri request)))
82
      (("raw" pb-id)
83
       (values (build-response
84
                #:code 200
85
                #:headers `((content-type . (text/plain))))
86
87
               (lambda (port)
88
                 (call-with-input-file
89
                     ;; the file name
90
                     (call-with-dir-as-pb-data data-path
91
                                               (lambda (p) (pb-get-file-path p pb-id)))
92
                   ;; the input port
93
                   (lambda (inport)
94
                     (let A ((inport' inport))
95
                       (let ((bv (get-bytevector-n inport' 4096)))
96
                         (if (not (eof-object? bv))
97
                             (begin
98
                               (put-bytevector port bv)
99
                               (A inport'))))))))))
100
101
      ;; match everything else
102
      (_
103
104
       (values (build-response
105
                #:code 200
106
                #:headers `((content-type . (text/html))))
107
108
               (lambda (port)
109
                 (let* ((top5 (call-with-dir-as-pb-data
110
                               data-path
111
                               (lambda (pb-data) (pb-data-get-top pb-data 5))))
112
                        (sxml (templatize
113
                               "pastebin"
114
                               `((form (@ (method "post") (enctype "multipart/form-data"))
115
                                       (textarea (@ (name "text")) "") (input (@ (type "submit"))))
116
                                 (table (@ (border 1)) (tr (th "id") (th "text") (th ""))
117
                                        ,(map (lambda (entry)
118
                                                `(tr (td ,(pb-entry-id entry))
119
                                                     (td ,(pb-entry-text entry))
120
                                                     (td ,(make-raw-link-pb-entry (pb-entry-id entry)))))
121
                                              top5))))))
122
                   (display "<!DOCTYPE html>\n" port)
123
                   (sxml->xml sxml port))))))))
124