-
Notifications
You must be signed in to change notification settings - Fork 2
/
chaton-poster
executable file
·154 lines (137 loc) · 5.73 KB
/
chaton-poster
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#!/usr/bin/env gosh
(add-load-path "@@server-data-dir@@")
(define-module chaton.poster
(use www.cgi)
(use srfi-13)
(use file.util)
(use util.list)
(use text.html-lite)
(use text.tree)
(use gauche.logger)
(use chaton)
(export poster-main))
(select-module chaton.poster)
(define *limit* 2048)
(define +sequence-old+ (build-path +docdir+ "var/seq"))
(define +logfile+ (build-path +logdir+ "post.log"))
(define (poster-main args)
(log-open +logfile+)
(cgi-main (^[params]
(let ([nick (cgi-get-parameter "nick" params :default #f)]
[text (cgi-get-parameter "text" params :default #f)]
[addr (cgi-get-metavariable "REMOTE_ADDR")])
(make-directory* +datadir+) ;ensure
(if (ip-blacklisted? addr)
(log-format "Post rejected from IP ~a: ~a: ~a"
addr nick text)
(chaton-with-exclusive-locking
(^[]
(call-with-output-file +current-file+
(cut update <>
(string-trim-both nick) (string-trim-both text))
:if-exists :append)
(update-rss))))
(emit-reply)))
:on-error
(and +show-stack-trace+
(^e
`(,(cgi-header)
,(html:pre
(html-escape-string
(call-with-output-string
(cut with-error-to-port <> (cut report-error e)))))))))
0)
(define (emit-reply) ;dummy
(write-tree `(,(cgi-header) ,(html:html (html:head) (html:body)))))
(define (update out nick text)
(unless (or (string-null? nick) (string-null? text))
(append-entry out nick text)
(increment-sequence)
(record-last-post-timestamp)))
(define (append-entry out nick text)
(port-seek out 0 SEEK_END)
(write (make-entry nick text) out)
(newline out))
(define (make-entry nick text)
;; Exclude control characters invalid for XML1.0
;; http://www.w3.org/TR/2008/REC-xml-20081126/#charsets
(define (filter-string s)
($ string-delete #[\u0000-\u0008\u000b\u000c\u000e-\u001f]
$ string-incomplete->complete s #\?))
(let ([text (filter-string text)]
[nick (filter-string nick)])
(receive (sec usec) (sys-gettimeofday)
`(,nick (,sec ,usec)
,(if (> (string-length text) *limit*)
(string-take text *limit*)
text)
,(cgi-get-metavariable "REMOTE_ADDR")))))
(define (increment-sequence)
;; NB: for the backward compatibility, we store the sequence number under
;; $DOCDIR (old location) as well as $(DATADIR). Don't forget to drop
;; the old stuff after some time.
(let1 cnt (x->integer (or (file->string +sequence-file+ :if-does-not-exist #f)
(file->string +sequence-old+ :if-does-not-exist #f)))
(make-directory* (sys-dirname +sequence-file+))
(with-output-to-file +sequence-file+ (cut write (+ cnt 1)) :atomic #t)
(make-directory* (sys-dirname +sequence-old+))
(with-output-to-file +sequence-old+ (cut write (+ cnt 1)) :atomic #t)))
(define (record-last-post-timestamp)
;; NB: Theoretically the value written may differ from the entry's timestamp,
;; but we don't need precision here.
(with-output-to-file +last-post-file+ (cut write (sys-time)) :atomic #t))
(define (update-rss)
(with-output-to-file +index.rdf+
(^[]
(write-tree
`("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
"<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n"
"<channel>\n"
"<title>" ,(html-escape-string "@@room-name@@") "</title>\n"
"<link>" ,(html-escape-string "@@httpd-url@@@@url-path@@") "</link>\n"
"<description>" ,(html-escape-string "@@room-description@@") "</description>\n"
,(chaton-render-from-file +current-file+ 0 #f
:renderer chaton-render-rss-1
:newest-first #t)
"</channel>\n"
"</rss>\n")))
:atomic #t))
;; IP blocking - if datadir contains blacklist.txt, read it.
;; The file lists one IP or IP range per line; IP range being xx.xx.xx.xx/bb
;; # to the end of line is a comment.
(define (ip-blacklisted? remote-addr)
(and-let* ([ (string? remote-addr) ]
[v (parse-ipaddr remote-addr)]
[addr (car v)]
[blacklist (load-ip-blacklist)])
(any (^p (<= (car p) addr (cdr p))) blacklist)))
;; Returns list of integer IP ranges (<start> . <end>), both inclusive
(define (load-ip-blacklist)
(if-let1 lines (file->string-list (build-path +datadir+ "blacklist.txt")
:if-does-not-exist #f)
(filter-map parse-ipaddr lines)
'()))
(define (parse-ipaddr addr-str)
(rxmatch-case addr-str
[#/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(?:\/(\d{1,2}))?/
(_ b3 b2 b1 b0 mask)
(let ([bs (map string->number (list b3 b2 b1 b0))]
[mm (if mask (string->number mask) 32)])
(if (or (any (cut <= 256 <>) bs)
(< 32 mm))
(begin (log-format "bad ip addr in blacklist: ~s. ignoring." addr-str)
#f)
(let* ([v (fold (^[b s] (+ (* s 256) b)) 0 bs)]
[m (logand (lognot (- (ash 1 (- 32 mm)) 1)) #xffffffff)])
(cons (logand v m)
(logior v (logand (lognot m) #xffffffff))))))]
[#/^#/ () #f]
[#/^\s*$/ () #f]
[else (log-format "bad ip addr in blacklist: ~s. ignoring." addr-str) #f]))
;;;===================================================================
(select-module user)
(import chaton.poster)
(define main poster-main)
;; Local variables:
;; mode: scheme
;; end: