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
|
require_relative "core"
require_relative "concatenated_io"
require "webrick"
require "erb"
class Puke::Server
THREADS_PER_PAGE = 20
def initialize
@access_log = File.open(File.join(Puke.datadir, "access.log"), "a")
@access_log.sync = true
al = [[@access_log, WEBrick::AccessLog::COMMON_LOG_FORMAT]]
@server = WEBrick::HTTPServer.new(:BindAddress => Puke.listen_address,
:Port => Puke.listen_port,
:AccessLog => al)
@server.mount_proc("/", method(:root))
@server.mount_proc("/mid", method(:mid))
@server.mount_proc("/new", method(:new))
@root_template = ERB.new(ROOT_TEMPLATE)
end
def root(req, res)
case req.path_info
when "/"
page = req.query.fetch("page", "1").to_i
last_tid = Puke.last_tid
if last_tid >= 0
b = (last_tid-page*THREADS_PER_PAGE+1).clamp(0, last_tid)
e = (last_tid-(page-1)*THREADS_PER_PAGE).clamp(0, last_tid)
threads = e.downto(b)
.map { |tid| Puke.thread(tid) }
.map { |mids| mids.map { |mid| Puke.metadata(mid) } }
else
threads = []
end
res.content_type = "text/html; charset=UTF-8"
res.body = @root_template.result(binding)
when "/robots.txt"
# I don't want search engines to index puke
res.content_type = "text/plain"
res.body = <<EOF
User-agent: *
Disallow: /
EOF
else
res.status = 404
end
end
def mid(req, res)
/\A\/(?<mid>.+@.+?)(?:\/(?<unya>\w+)\.mbox)?\z/ =~ req.path_info and
tid = Puke.tid_for(mid) or (res.status = 404 and return)
# NB: Puke::ConcatenatedIO is not really an IO object and
# 'res.body.is_a?(IO)' returns false, leading the non-existent 'bytesize'
# method to be called by WEBrick, unless chunked response is enabled.
req.http_version >= "1.1" or (res.status = 505 and return)
res.chunked = true
case unya
when "thread"
files = Puke.thread(tid).map { |mid| Puke.open(mid) }
res.body = Puke::ConcatenatedIO.new(files)
when nil, "raw"
res.body = Puke.open(mid)
else
res.status = 400
return
end
res.content_type = "text/plain"
end
# FIXME: How about large messages?
def new(req, res)
Puke.secret and
req.path_info == "/#{Puke.secret}" or (res.status = 404 and return)
# Parse query string ourselves because req.query does not work for POST
# requests as for GET requests.
query = WEBrick::HTTPUtils.parse_query(req.query_string)
if format = query["format"]
is_raw = format == "raw"
else
is_raw = req.body[0, 5] != "From "
end
mids = Puke.create(StringIO.new(req.body), is_raw)
res.content_type = "text/plain"
res.body = "#{mids}\n"
end
def start
@server.start
end
def shutdown
@server.shutdown
@access_log.close
end
private
include ERB::Util
def fdate(time)
time.strftime("%Y-%m-%d %H:%M:%S %:z")
end
ROOT_TEMPLATE = <<'EOF'
<html>
<head>
<title>random texts (page: <%=page%>)</title>
<style>
pre { whitespace: pre-wrap; }
</style>
</head>
<body><h1>random texts (page: <%=page%>)</h1>
<hr>
<%threads.each do |ms| root, *child = ms%><pre>
--<%=h fdate root[:date]%> <a href="/mid/<%=h root[:mid]%>"><%=h root[:subject]%></a> <a href="/mid/<%=h root[:mid]%>/thread.mbox">[thread.mbox]</a><%child.each do |m|%>
`<%=h fdate m[:date]%> <a href="/mid/<%=h m[:mid]%>"><%=h m[:subject]%></a><%end%></pre>
<%end%>
<hr>
page: <%if page > 1%><a href="/?page=<%=page - 1%>"><< prev</a><%end%>
<%if threads.last&.dig(0, :tid)&.> 0%><a href="/?page=<%=page + 1%>">next >></a><%end%>
</body>
</html>
EOF
end
|