hermeneutics 1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,321 @@
1
+ #
2
+ # hermeneutics/boxes.rb -- Mailboxes
3
+ #
4
+
5
+ =begin rdoc
6
+
7
+ :section: Classes definied here
8
+
9
+ Hermeneutics::Box is a general Mailbox.
10
+
11
+ Hermeneutics::MBox is the traditional mbox format (text file, separated by a
12
+ blank line).
13
+
14
+ Hermeneutics::Maildir is the maildir format.
15
+
16
+
17
+ =end
18
+
19
+
20
+ require "supplement"
21
+ require "supplement/locked"
22
+ require "date"
23
+
24
+
25
+ module Hermeneutics
26
+
27
+ # Mailboxes
28
+ class Box
29
+
30
+ @boxes = []
31
+
32
+ class <<self
33
+
34
+ # :call-seq:
35
+ # Box.find( path, default = nil) -> box
36
+ #
37
+ # Create a Box object (some subclass of Box), depending on
38
+ # what type the box is found at <code>path</code>.
39
+ #
40
+ def find path, default_format = nil
41
+ b = @boxes.find { |b| b.check path }
42
+ b ||= default_format
43
+ b ||= if File.directory? path then
44
+ Maildir
45
+ elsif File.file? path then
46
+ MBox
47
+ else
48
+ # If still nothing was found use Postfix convention:
49
+ path =~ /\/$/ ? Maildir : MBox
50
+ end
51
+ b.new path
52
+ end
53
+
54
+ # :call-seq:
55
+ # Box.check( path) -> nil
56
+ #
57
+ # By default, subclass mailboxes do not exist. You should overwrite
58
+ # this behaviour.
59
+ #
60
+ def check path
61
+ end
62
+
63
+ protected
64
+ attr_reader :boxes
65
+ def inherited cls
66
+ Box.boxes.push cls
67
+ end
68
+
69
+ end
70
+
71
+ # :call-seq:
72
+ # Box.new( path) -> box
73
+ #
74
+ # Instantiate a Box object, just store the <code>path</code>.
75
+ #
76
+ def initialize mailbox
77
+ @mailbox = mailbox
78
+ end
79
+
80
+ def to_s ; path ; end
81
+
82
+ def path ; @mailbox ; end
83
+
84
+ # :call-seq:
85
+ # box.exists? -> true or false
86
+ #
87
+ # Test whether the <code>Box</code> exists.
88
+ #
89
+ def exists?
90
+ self.class.check @mailbox
91
+ end
92
+
93
+ end
94
+
95
+ class MBox < Box
96
+
97
+ RE_F = /^From\s+/ # :nodoc:
98
+ RE_N = /^$/ # :nodoc:
99
+
100
+ class <<self
101
+
102
+ # :call-seq:
103
+ # MBox.check( path) -> true or false
104
+ #
105
+ # Check whether path is a <code>MBox</code>.
106
+ #
107
+ def check path
108
+ if File.file? path then
109
+ File.open path, :encoding => Encoding::ASCII_8BIT do |f|
110
+ f.size.zero? or f.readline =~ RE_F
111
+ end
112
+ end
113
+ end
114
+
115
+ end
116
+
117
+ # :stopdoc:
118
+ class Region
119
+ class <<self
120
+ private :new
121
+ def open file, start, stop
122
+ t = file.tell
123
+ begin
124
+ i = new file, start, stop
125
+ yield i
126
+ ensure
127
+ file.seek t
128
+ end
129
+ end
130
+ end
131
+ def initialize file, start, stop
132
+ @file, @start, @stop = file, start, stop
133
+ rewind
134
+ end
135
+ def rewind ; @file.seek @start ; end
136
+ def read n = nil
137
+ m = @stop - @file.tell
138
+ n = m if not n or n > m
139
+ @file.read n
140
+ end
141
+ def to_s
142
+ rewind
143
+ read
144
+ end
145
+ def each_line
146
+ @file.each_line { |l|
147
+ break if @file.tell > @stop
148
+ yield l
149
+ }
150
+ end
151
+ alias eat_lines each_line
152
+ end
153
+ # :startdoc:
154
+
155
+ # :call-seq:
156
+ # mbox.create -> self
157
+ #
158
+ # Create the <code>MBox</code>.
159
+ #
160
+ def create
161
+ d = File.dirname @mailbox
162
+ Dir.mkdir! d
163
+ File.open @mailbox, File::CREAT do |f| end
164
+ self
165
+ end
166
+
167
+ # :call-seq:
168
+ # mbox.deliver( msg) -> nil
169
+ #
170
+ # Store the mail into the local <code>MBox</code>.
171
+ #
172
+ def deliver msg
173
+ pos = nil
174
+ LockedFile.open @mailbox, "r+", :encoding => Encoding::ASCII_8BIT do |f|
175
+ f.seek [ f.size - 4, 0].max
176
+ last = ""
177
+ f.read.each_line { |l| last = l }
178
+ f.puts unless last =~ /^$/
179
+ pos = f.size
180
+ m = msg.to_s
181
+ i = 1
182
+ while (i = m.index RE_F, i rescue nil) do m.insert i, ">" end
183
+ f.write m
184
+ f.puts
185
+ end
186
+ pos
187
+ end
188
+
189
+ # :call-seq:
190
+ # mbox.each { |mail| ... } -> nil
191
+ #
192
+ # Iterate through <code>MBox</code>.
193
+ #
194
+ def each &block
195
+ File.open @mailbox, :encoding => Encoding::ASCII_8BIT do |f|
196
+ m, e = nil, true
197
+ s, t = t, f.tell
198
+ f.each_line { |l|
199
+ s, t = t, f.tell
200
+ if is_from_line? l and e then
201
+ begin
202
+ m and Region.open f, m, e, &block
203
+ ensure
204
+ m, e = s, nil
205
+ end
206
+ else
207
+ m or raise "#@mailbox does not seem to be a mailbox."
208
+ e = l =~ RE_N && s
209
+ end
210
+ }
211
+ # Treat it gracefully when there is no empty last line.
212
+ e ||= f.tell
213
+ m and Region.open f, m, e, &block
214
+ end
215
+ end
216
+ include Enumerable
217
+
218
+ private
219
+
220
+ def is_from_line? l
221
+ l =~ RE_F or return
222
+ addr, time = $'.split nil, 2
223
+ DateTime.parse time
224
+ addr =~ /@/
225
+ rescue ArgumentError, TypeError
226
+ end
227
+
228
+ end
229
+
230
+ class Maildir < Box
231
+
232
+ DIRS = %w(cur tmp new)
233
+ CUR, TMP, NEW = *DIRS
234
+
235
+ class <<self
236
+
237
+ # :call-seq:
238
+ # Maildir.check( path) -> true or false
239
+ #
240
+ # Check whether path is a <code>Maildir</code>.
241
+ #
242
+ def check mailbox
243
+ if File.directory? mailbox then
244
+ DIRS.each do |d|
245
+ s = File.join mailbox, d
246
+ File.directory? s or return false
247
+ end
248
+ true
249
+ end
250
+ end
251
+
252
+ end
253
+
254
+ # :call-seq:
255
+ # maildir.create -> self
256
+ #
257
+ # Create the <code>Maildir</code>.
258
+ #
259
+ def create
260
+ Dir.mkdir! @mailbox
261
+ DIRS.each do |d|
262
+ s = File.join @mailbox, d
263
+ Dir.mkdir s
264
+ end
265
+ self
266
+ end
267
+
268
+ # :call-seq:
269
+ # maildir.deliver( msg) -> nil
270
+ #
271
+ # Store the mail into the local <code>Maildir</code>.
272
+ #
273
+ def deliver msg
274
+ tmp = mkfilename TMP
275
+ File.open tmp, "w" do |f|
276
+ f.write msg
277
+ end
278
+ new = mkfilename NEW
279
+ File.rename tmp, new
280
+ new
281
+ end
282
+
283
+ # :call-seq:
284
+ # mbox.each { |mail| ... } -> nil
285
+ #
286
+ # Iterate through <code>MBox</code>.
287
+ #
288
+ def each
289
+ p = File.join @mailbox, CUR
290
+ d = Dir.new p
291
+ d.each { |f|
292
+ next if f.starts_with? "."
293
+ File.open f, :encoding => Encoding::ASCII_8BIT do |f|
294
+ yield f
295
+ end
296
+ }
297
+ end
298
+ include Enumerable
299
+
300
+ private
301
+
302
+ autoload :Socket, "socket"
303
+
304
+ def mkfilename d
305
+ dir = File.join @mailbox, d
306
+ c = 0
307
+ begin
308
+ n = "%.4f.%d_%d.%s" % [ Time.now.to_f, $$, c, Socket.gethostname]
309
+ path = File.join dir, n
310
+ File.open path, File::CREAT|File::EXCL do |f| end
311
+ path
312
+ rescue Errno::EEXIST
313
+ c += 1
314
+ retry
315
+ end
316
+ end
317
+
318
+ end
319
+
320
+ end
321
+
@@ -0,0 +1,253 @@
1
+ #
2
+ # hermeneutics/cgi.rb -- CGI responses
3
+ #
4
+
5
+ require "hermeneutics/escape"
6
+ require "hermeneutics/message"
7
+ require "hermeneutics/html"
8
+
9
+
10
+ module Hermeneutics
11
+
12
+ class Html
13
+
14
+ CONTENT_TYPE = "text/html"
15
+
16
+ attr_reader :cgi
17
+
18
+ def initialize cgi
19
+ @cgi = cgi
20
+ end
21
+
22
+ def form! **attrs, &block
23
+ attrs[ :action] = @cgi.fullpath attrs[ :action]
24
+ form **attrs, &block
25
+ end
26
+
27
+ def href dest, params = nil, anchor = nil
28
+ @utx ||= URLText.new
29
+ dest = @cgi.fullpath dest
30
+ @utx.mkurl dest, params, anchor
31
+ end
32
+
33
+ def href! params = nil, anchor = nil
34
+ href nil, params, anchor
35
+ end
36
+
37
+ end
38
+
39
+
40
+ # Example:
41
+ #
42
+ # class MyCgi < Cgi
43
+ # def run
44
+ # p = parameters
45
+ # if p.empty? then
46
+ # location "/sorry.rb"
47
+ # else
48
+ # document MyHtml
49
+ # end
50
+ # rescue
51
+ # document MyErrorPage
52
+ # end
53
+ # end
54
+ # Cgi.execute
55
+ #
56
+ class Cgi
57
+
58
+ class <<self
59
+ attr_accessor :main
60
+ def inherited cls
61
+ Cgi.main = cls
62
+ end
63
+ def execute out = nil
64
+ (@main||self).new.execute out
65
+ end
66
+ end
67
+
68
+ # Overwrite this.
69
+ def run
70
+ document Html
71
+ end
72
+
73
+ def parameters inp = nil, &block
74
+ if block_given? then
75
+ case request_method
76
+ when "GET", "HEAD" then parse_query query_string, &block
77
+ when "POST" then parse_posted inp||$stdin, &block
78
+ else parse_input &block
79
+ end
80
+ else
81
+ p = {}
82
+ parameters do |k,v|
83
+ p[ k] = v
84
+ end
85
+ p
86
+ end
87
+ end
88
+
89
+ CGIENV = %w(content document gateway http query
90
+ remote request script server unique)
91
+
92
+ def method_missing sym, *args
93
+ if args.empty? and CGIENV.include? sym[ /\A(\w+?)_\w+\z/, 1] then
94
+ ENV[ sym.to_s.upcase]
95
+ else
96
+ super
97
+ end
98
+ end
99
+
100
+ def https?
101
+ ENV[ "HTTPS"].notempty?
102
+ end
103
+
104
+ private
105
+
106
+ def parse_query data, &block
107
+ URLText.decode_hash data, &block
108
+ end
109
+
110
+ def parse_posted inp, &block
111
+ data = inp.read
112
+ data.bytesize == content_length.to_i or
113
+ @warn = "Content length #{content_length} is wrong (#{data.bytesize})."
114
+ ct = ContentType.parse content_type
115
+ case ct.fulltype
116
+ when "application/x-www-form-urlencoded" then
117
+ parse_query data, &block
118
+ when "multipart/form-data" then
119
+ mp = Multipart.parse data, ct.hash
120
+ parse_multipart mp, &block
121
+ when "text/plain" then
122
+ # Suppose this is for testing purposes only.
123
+ mk_params data.lines, &block
124
+ else
125
+ parse_query data, &block
126
+ end
127
+ end
128
+
129
+ def parse_multipart mp
130
+ mp.each { |part|
131
+ cd = part.headers.content_disposition
132
+ if cd.caption == "form-data" then
133
+ yield cd.name, part.body, **cd.hash
134
+ end
135
+ }
136
+ end
137
+
138
+ def mk_params l
139
+ l.each { |s|
140
+ k, v = s.split %r/=/
141
+ v ||= k
142
+ [k, v].each { |x| x.strip! }
143
+ yield k, v
144
+ }
145
+ end
146
+
147
+ def parse_input &block
148
+ if $*.any? then
149
+ l = $*
150
+ else
151
+ if $stdin.tty? then
152
+ $stderr.puts <<~EOT
153
+ Offline mode: Enter name=value pairs on standard input.
154
+ EOT
155
+ end
156
+ l = []
157
+ while (a = $stdin.gets) and a !~ /^$/ do
158
+ l.push a
159
+ end
160
+ end
161
+ ENV[ "SCRIPT_NAME"] = $0
162
+ mk_params l, &block
163
+ end
164
+
165
+
166
+ class Done < Exception
167
+ attr_reader :result
168
+ def initialize result
169
+ super nil
170
+ @result = result
171
+ end
172
+ end
173
+
174
+ def done ct = nil
175
+ res = Message.create
176
+ yield res
177
+ d = Done.new res
178
+ raise d
179
+ end
180
+
181
+ public
182
+
183
+ def execute out = nil
184
+ @out ||= $stdout
185
+ begin
186
+ run
187
+ rescue
188
+ done { |res|
189
+ res.body = "#$! (#{$!.class})#$/"
190
+ $@.each { |a| res.body << "\t" << a << $/ }
191
+ res.headers.add :content_type,
192
+ "text/plain", charset: res.body.encoding
193
+ }
194
+ end
195
+ rescue Done
196
+ @out << $!.result.to_s
197
+ ensure
198
+ @out = nil
199
+ end
200
+
201
+ def document cls = Html, *args, &block
202
+ done { |res|
203
+ doc = cls.new self
204
+ res.body = ""
205
+ doc.generate res.body do
206
+ doc.document *args, &block
207
+ end
208
+
209
+ ct = if doc.respond_to? :content_type then doc.content_type
210
+ elsif cls.const_defined? :CONTENT_TYPE then doc.class::CONTENT_TYPE
211
+ end
212
+ ct and res.headers.add :content_type, ct,
213
+ charset: res.body.encoding||Encoding.default_external
214
+ if doc.respond_to? :cookies then
215
+ doc.cookies do |c|
216
+ res.headers.add :set_cookie, c
217
+ end
218
+ end
219
+ }
220
+ end
221
+
222
+ def location dest = nil, params = nil, anchor = nil
223
+ if Hash === dest then
224
+ dest, params, anchor = anchor, dest, params
225
+ end
226
+ utx = URLText.new mask_space: true
227
+ unless dest =~ %r{\A\w+://} then
228
+ dest = %Q'#{https? ? "https" : "http"}://#{http_host}#{fullpath dest}'
229
+ end
230
+ url = utx.mkurl dest, params, anchor
231
+ done { |res| res.headers.add "Location", url }
232
+ end
233
+
234
+ def fullpath dest
235
+ if dest then
236
+ dest =~ %r{\A/} || dest =~ /\.\w+\z/ ? dest : dest + ".rb"
237
+ else
238
+ File.basename script_name
239
+ end
240
+ end
241
+
242
+
243
+ if defined? MOD_RUBY then
244
+ # This has not been tested.
245
+ def query_string
246
+ Apache::request.args
247
+ end
248
+ end
249
+
250
+ end
251
+
252
+ end
253
+