Initial release.

Li Ian-Xue (b4283)Fri Sep 10 09:15:06+0300 2021

6202431

Initial release.

.gitignore unknown status 1

1+
data

main.scm unknown status 1

1+
(use-modules (pastebin httpserver)
2+
             (web server))
3+
4+
(define (run-pastebin args)
5+
  (let ((data-dir (cadr args)))
6+
    (if (not (file-exists? data-dir))
7+
        (mkdir data-dir))
8+
    (run-server (make-pastebin-handler data-dir) 'http '(#:addr 0))))

pastebin/data.scm unknown status 1

1+
(define-module (pastebin data))
2+
3+
(use-modules (srfi srfi-9)
4+
             (srfi srfi-1)
5+
             (ice-9 ftw)
6+
             (ice-9 textual-ports))
7+
8+
(export <pb-data>
9+
        <pb-entry>
10+
        pb-entry-id
11+
        pb-entry-text
12+
        pb-data-open
13+
        pb-data-close
14+
        call-with-dir-as-pb-data
15+
        pb-data-get-top
16+
        pb-data-new-entry)
17+
18+
(define-record-type <pb-data>
19+
  (make-pb-data dir)
20+
  pb-data?
21+
  (dir pb-data-dir set-pb-data-dir!))
22+
23+
(define-record-type <pb-entry>
24+
  (make-pb-entry id text)
25+
  pb-entry?
26+
  (id pb-entry-id set-pb-entry-id!)
27+
  (text pb-entry-text set-pb-entry-text!))
28+
29+
;; input: dir: string
30+
;; output: <pb-data>
31+
(define (pb-data-open dir)
32+
  (make-pb-data dir))
33+
34+
(define (pb-data-close pb-data) #t)
35+
36+
(define (call-with-dir-as-pb-data dir p)
37+
  (let ((pb-data (pb-data-open dir)))
38+
    (let ((R (p pb-data)))
39+
      (pb-data-close pb-data)
40+
      R)))
41+
42+
(define (get-file-path pb-data filename)
43+
  (string-append (pb-data-dir pb-data) "/" filename))
44+
45+
;; input: <pb-data>, integer
46+
;; output: list of <pb-entry>
47+
(define (pb-data-get-top pb-data n)
48+
  (map
49+
   (lambda (filename)
50+
     (make-pb-entry
51+
      filename
52+
      (call-with-input-file (get-file-path pb-data filename)
53+
        (lambda (port)
54+
          (get-string-all port)))))
55+
   (let ((file-ls (list-files pb-data)))
56+
     (reverse (take-right file-ls (min n (length file-ls)))))))
57+
58+
;; input: <pb-data>, text
59+
;; output: <pb-entry>
60+
(define (pb-data-new-entry pb-data text)
61+
  (let ((next-filename (get-next-filename pb-data)))
62+
    (call-with-output-file (get-file-path pb-data next-filename)
63+
      (lambda (port)
64+
        (put-string port text)))
65+
    (make-pb-entry next-filename text)))
66+
67+
(define (list-files pb-data)
68+
  (scandir (pb-data-dir pb-data) (lambda (filename) (= (string-length filename) 5))))
69+
70+
(define (get-next-filename pb-data)
71+
  (let ((entries (list-files pb-data)))
72+
    (if (null? entries)
73+
        "00000"
74+
        (get-next-5digit (last entries)))))
75+
76+
(define (get-next-5digit str)
77+
  (let A ((add #t)
78+
          (char-int-ls (reverse (map char->integer (string->list str))))
79+
          (result '()))
80+
    (if (null? char-int-ls)
81+
        (list->string (map integer->char result))
82+
        (if add
83+
            (let* ((hd (car char-int-ls)) (tl (cdr char-int-ls)))
84+
              (if (= hd #x7A)
85+
                  (A #t tl (cons #x30 result))
86+
                  (A #f tl (cons (case hd
87+
                                   ((#x39) #x41)
88+
                                   ((#x5A) #x61)
89+
                                   (else (1+ hd))) result))))
90+
            (A #f '() (append (reverse char-int-ls) result))))))

pastebin/httpserver.scm unknown status 1

1+
(define-module (pastebin httpserver))
2+
3+
(use-modules (web request)
4+
             (web response)
5+
             (sxml simple)
6+
             (pastebin data)
7+
             (rnrs bytevectors)
8+
             (ice-9 textual-ports)
9+
             (ice-9 regex)
10+
             (srfi srfi-1))
11+
12+
(export make-pastebin-handler)
13+
14+
(define (read-parts reqbody boundary)
15+
  (define b2 (string-append "(\r\n)?--" boundary))
16+
  (let A ((start 0) (parts '()))
17+
    (let ((sm (string-match b2 reqbody start)))
18+
      (if sm
19+
          ;; +2 => CRLF
20+
          (A (+ 2 (match:end sm 0))
21+
             (cons (substring reqbody start (match:start sm 0)) parts))
22+
          (cdr (reverse parts))))))
23+
24+
(define (get-new-pin str pin)
25+
  (let ((crlfi (string-contains str "\r\n" pin)))
26+
    (if crlfi crlfi (string-length str))))
27+
28+
(define (parse-part partstr)
29+
  (let A ((headers '()) (pin 0))
30+
    (let* ((newpin (get-new-pin partstr pin))
31+
           (line (substring partstr pin newpin)))
32+
      (if (string-null? line)
33+
          (cons (reverse headers) (substring partstr (+ 2 newpin)))
34+
          (A (cons line headers) (+ 2 newpin))))))
35+
36+
(define (get-content-dispo-name-from-headers headers)
37+
  (let ((fl (find (lambda (line) (string-prefix-ci? "content-disposition: " line)) headers)))
38+
    (if fl
39+
        (let ((sm (string-match "name=(.*)" fl)))
40+
          (if sm (string-trim-both (match:substring sm 1) #\") ""))
41+
        "")))
42+
43+
(define (read-multipart-form-data reqbody boundary)
44+
  (define parts (read-parts reqbody boundary))
45+
  (map
46+
   (lambda (part)
47+
     (let ((pp (parse-part part)))
48+
       (cons (get-content-dispo-name-from-headers (car pp)) (cdr pp))))
49+
   parts))
50+
51+
(define (templatize title body)
52+
  `(html (head
53+
          (title ,title)
54+
          (meta (@ (name "viewport") (content "width=device-width, initial-scale=1"))))
55+
         (body ,@body)))
56+
57+
(define (make-pastebin-handler data-path)
58+
  (lambda (request request-body)
59+
    (if (eq? (request-method request) 'POST)
60+
        (let* ((headers (request-headers request))
61+
               (content-type-all (assq-ref headers 'content-type))
62+
               (content-type (if content-type-all (car content-type-all) #f))
63+
               (boundary (if (eq? content-type 'multipart/form-data)
64+
                             (assq-ref (cdr content-type-all) 'boundary) #f))
65+
               (reqbody-string (utf8->string request-body)))
66+
          (if boundary
67+
              (let ((form-data (read-multipart-form-data reqbody-string boundary)))
68+
                (call-with-dir-as-pb-data
69+
                 data-path
70+
                 (lambda (pb-data)
71+
                   (pb-data-new-entry pb-data
72+
                                      (assoc-ref form-data "text"))))))))
73+
74+
    (values (build-response
75+
             #:code 200
76+
             #:headers `((content-type . (text/html))))
77+
78+
            (lambda (port)
79+
              (let* ((top5 (call-with-dir-as-pb-data
80+
                            data-path
81+
                            (lambda (pb-data) (pb-data-get-top pb-data 5))))
82+
                     (sxml (templatize
83+
                            "pastebin"
84+
                            `((form (@ (method "post") (enctype "multipart/form-data"))
85+
                                    (textarea (@ (name "text")) "") (input (@ (type "submit"))))
86+
                              (table (@ (border 1)) (tr (th "id") (th "text"))
87+
                                     ,(map (lambda (entry) `(tr (td ,(pb-entry-id entry))
88+
                                                                (td ,(pb-entry-text entry))))
89+
                                           top5))))))
90+
                (display "<!DOCTYPE html>\n" port)
91+
                (sxml->xml sxml port))))))