aboutsummaryrefslogtreecommitdiffstats
path: root/lib/puke/server.rb
blob: 9808c5ad68ee3a198d60bffe460c1a517514ad96 (plain)
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
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 { |mid| Puke.metadata(mid) }
            .select { |m| m[:subject] } # Non-dummy messages
        }
      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%>">&lt;&lt; prev</a><%end%>
<%if threads.last&.dig(0, :tid)&.> 0%><a href="/?page=<%=page + 1%>">next &gt;&gt;</a><%end%>
</body>
</html>
EOF
end