source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/webrick/httpservlet/filehandler.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

File size: 12.2 KB
Line 
1#
2# filehandler.rb -- FileHandler Module
3#
4# Author: IPR -- Internet Programming with Ruby -- writers
5# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
6# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
7# reserved.
8#
9# $IPR: filehandler.rb,v 1.44 2003/06/07 01:34:51 gotoyuzo Exp $
10
11require 'thread'
12require 'time'
13
14require 'webrick/htmlutils'
15require 'webrick/httputils'
16require 'webrick/httpstatus'
17
18module WEBrick
19 module HTTPServlet
20
21 class DefaultFileHandler < AbstractServlet
22 def initialize(server, local_path)
23 super
24 @local_path = local_path
25 end
26
27 def do_GET(req, res)
28 st = File::stat(@local_path)
29 mtime = st.mtime
30 res['etag'] = sprintf("%x-%x-%x", st.ino, st.size, st.mtime.to_i)
31
32 if not_modified?(req, res, mtime, res['etag'])
33 res.body = ''
34 raise HTTPStatus::NotModified
35 elsif req['range']
36 make_partial_content(req, res, @local_path, st.size)
37 raise HTTPStatus::PartialContent
38 else
39 mtype = HTTPUtils::mime_type(@local_path, @config[:MimeTypes])
40 res['content-type'] = mtype
41 res['content-length'] = st.size
42 res['last-modified'] = mtime.httpdate
43 res.body = open(@local_path, "rb")
44 end
45 end
46
47 def not_modified?(req, res, mtime, etag)
48 if ir = req['if-range']
49 begin
50 if Time.httpdate(ir) >= mtime
51 return true
52 end
53 rescue
54 if HTTPUtils::split_header_value(ir).member?(res['etag'])
55 return true
56 end
57 end
58 end
59
60 if (ims = req['if-modified-since']) && Time.parse(ims) >= mtime
61 return true
62 end
63
64 if (inm = req['if-none-match']) &&
65 HTTPUtils::split_header_value(inm).member?(res['etag'])
66 return true
67 end
68
69 return false
70 end
71
72 def make_partial_content(req, res, filename, filesize)
73 mtype = HTTPUtils::mime_type(filename, @config[:MimeTypes])
74 unless ranges = HTTPUtils::parse_range_header(req['range'])
75 raise HTTPStatus::BadRequest,
76 "Unrecognized range-spec: \"#{req['range']}\""
77 end
78 open(filename, "rb"){|io|
79 if ranges.size > 1
80 time = Time.now
81 boundary = "#{time.sec}_#{time.usec}_#{Process::pid}"
82 body = ''
83 ranges.each{|range|
84 first, last = prepare_range(range, filesize)
85 next if first < 0
86 io.pos = first
87 content = io.read(last-first+1)
88 body << "--" << boundary << CRLF
89 body << "Content-Type: #{mtype}" << CRLF
90 body << "Content-Range: #{first}-#{last}/#{filesize}" << CRLF
91 body << CRLF
92 body << content
93 body << CRLF
94 }
95 raise HTTPStatus::RequestRangeNotSatisfiable if body.empty?
96 body << "--" << boundary << "--" << CRLF
97 res["content-type"] = "multipart/byteranges; boundary=#{boundary}"
98 res.body = body
99 elsif range = ranges[0]
100 first, last = prepare_range(range, filesize)
101 raise HTTPStatus::RequestRangeNotSatisfiable if first < 0
102 if last == filesize - 1
103 content = io.dup
104 content.pos = first
105 else
106 io.pos = first
107 content = io.read(last-first+1)
108 end
109 res['content-type'] = mtype
110 res['content-range'] = "#{first}-#{last}/#{filesize}"
111 res['content-length'] = last - first + 1
112 res.body = content
113 else
114 raise HTTPStatus::BadRequest
115 end
116 }
117 end
118
119 def prepare_range(range, filesize)
120 first = range.first < 0 ? filesize + range.first : range.first
121 return -1, -1 if first < 0 || first >= filesize
122 last = range.last < 0 ? filesize + range.last : range.last
123 last = filesize - 1 if last >= filesize
124 return first, last
125 end
126 end
127
128 class FileHandler < AbstractServlet
129 HandlerTable = Hash.new
130
131 def self.add_handler(suffix, handler)
132 HandlerTable[suffix] = handler
133 end
134
135 def self.remove_handler(suffix)
136 HandlerTable.delete(suffix)
137 end
138
139 def initialize(server, root, options={}, default=Config::FileHandler)
140 @config = server.config
141 @logger = @config[:Logger]
142 @root = File.expand_path(root)
143 if options == true || options == false
144 options = { :FancyIndexing => options }
145 end
146 @options = default.dup.update(options)
147 end
148
149 def service(req, res)
150 # if this class is mounted on "/" and /~username is requested.
151 # we're going to override path informations before invoking service.
152 if defined?(Etc) && @options[:UserDir] && req.script_name.empty?
153 if %r|^(/~([^/]+))| =~ req.path_info
154 script_name, user = $1, $2
155 path_info = $'
156 begin
157 passwd = Etc::getpwnam(user)
158 @root = File::join(passwd.dir, @options[:UserDir])
159 req.script_name = script_name
160 req.path_info = path_info
161 rescue
162 @logger.debug "#{self.class}#do_GET: getpwnam(#{user}) failed"
163 end
164 end
165 end
166 super(req, res)
167 end
168
169 def do_GET(req, res)
170 unless exec_handler(req, res)
171 set_dir_list(req, res)
172 end
173 end
174
175 def do_POST(req, res)
176 unless exec_handler(req, res)
177 raise HTTPStatus::NotFound, "`#{req.path}' not found."
178 end
179 end
180
181 def do_OPTIONS(req, res)
182 unless exec_handler(req, res)
183 super(req, res)
184 end
185 end
186
187 # ToDo
188 # RFC2518: HTTP Extensions for Distributed Authoring -- WEBDAV
189 #
190 # PROPFIND PROPPATCH MKCOL DELETE PUT COPY MOVE
191 # LOCK UNLOCK
192
193 # RFC3253: Versioning Extensions to WebDAV
194 # (Web Distributed Authoring and Versioning)
195 #
196 # VERSION-CONTROL REPORT CHECKOUT CHECK_IN UNCHECKOUT
197 # MKWORKSPACE UPDATE LABEL MERGE ACTIVITY
198
199 private
200
201 def exec_handler(req, res)
202 raise HTTPStatus::NotFound, "`#{req.path}' not found" unless @root
203 if set_filename(req, res)
204 handler = get_handler(req)
205 call_callback(:HandlerCallback, req, res)
206 h = handler.get_instance(@config, res.filename)
207 h.service(req, res)
208 return true
209 end
210 call_callback(:HandlerCallback, req, res)
211 return false
212 end
213
214 def get_handler(req)
215 suffix1 = (/\.(\w+)$/ =~ req.script_name) && $1.downcase
216 suffix2 = (/\.(\w+)\.[\w\-]+$/ =~ req.script_name) && $1.downcase
217 handler_table = @options[:HandlerTable]
218 return handler_table[suffix1] || handler_table[suffix2] ||
219 HandlerTable[suffix1] || HandlerTable[suffix2] ||
220 DefaultFileHandler
221 end
222
223 def set_filename(req, res)
224 res.filename = @root.dup
225 path_info = req.path_info.scan(%r|/[^/]*|)
226
227 path_info.unshift("") # dummy for checking @root dir
228 while base = path_info.first
229 check_filename(req, res, base)
230 break if base == "/"
231 break unless File.directory?(res.filename + base)
232 shift_path_info(req, res, path_info)
233 call_callback(:DirectoryCallback, req, res)
234 end
235
236 if base = path_info.first
237 check_filename(req, res, base)
238 if base == "/"
239 if file = search_index_file(req, res)
240 shift_path_info(req, res, path_info, file)
241 call_callback(:FileCallback, req, res)
242 return true
243 end
244 shift_path_info(req, res, path_info)
245 elsif file = search_file(req, res, base)
246 shift_path_info(req, res, path_info, file)
247 call_callback(:FileCallback, req, res)
248 return true
249 else
250 raise HTTPStatus::NotFound, "`#{req.path}' not found."
251 end
252 end
253
254 return false
255 end
256
257 def check_filename(req, res, name)
258 @options[:NondisclosureName].each{|pattern|
259 if File.fnmatch("/#{pattern}", name)
260 @logger.warn("the request refers nondisclosure name `#{name}'.")
261 raise HTTPStatus::NotFound, "`#{req.path}' not found."
262 end
263 }
264 end
265
266 def shift_path_info(req, res, path_info, base=nil)
267 tmp = path_info.shift
268 base = base || tmp
269 req.path_info = path_info.join
270 req.script_name << base
271 res.filename << base
272 end
273
274 def search_index_file(req, res)
275 @config[:DirectoryIndex].each{|index|
276 if file = search_file(req, res, "/"+index)
277 return file
278 end
279 }
280 return nil
281 end
282
283 def search_file(req, res, basename)
284 langs = @options[:AcceptableLanguages]
285 path = res.filename + basename
286 if File.file?(path)
287 return basename
288 elsif langs.size > 0
289 req.accept_language.each{|lang|
290 path_with_lang = path + ".#{lang}"
291 if langs.member?(lang) && File.file?(path_with_lang)
292 return basename + ".#{lang}"
293 end
294 }
295 (langs - req.accept_language).each{|lang|
296 path_with_lang = path + ".#{lang}"
297 if File.file?(path_with_lang)
298 return basename + ".#{lang}"
299 end
300 }
301 end
302 return nil
303 end
304
305 def call_callback(callback_name, req, res)
306 if cb = @options[callback_name]
307 cb.call(req, res)
308 end
309 end
310
311 def nondisclosure_name?(name)
312 @options[:NondisclosureName].each{|pattern|
313 if File.fnmatch(pattern, name)
314 return true
315 end
316 }
317 return false
318 end
319
320 def set_dir_list(req, res)
321 redirect_to_directory_uri(req, res)
322 unless @options[:FancyIndexing]
323 raise HTTPStatus::Forbidden, "no access permission to `#{req.path}'"
324 end
325 local_path = res.filename
326 list = Dir::entries(local_path).collect{|name|
327 next if name == "." || name == ".."
328 next if nondisclosure_name?(name)
329 st = (File::stat(local_path + name) rescue nil)
330 if st.nil?
331 [ name, nil, -1 ]
332 elsif st.directory?
333 [ name + "/", st.mtime, -1 ]
334 else
335 [ name, st.mtime, st.size ]
336 end
337 }
338 list.compact!
339
340 if d0 = req.query["N"]; idx = 0
341 elsif d0 = req.query["M"]; idx = 1
342 elsif d0 = req.query["S"]; idx = 2
343 else d0 = "A" ; idx = 0
344 end
345 d1 = (d0 == "A") ? "D" : "A"
346
347 if d0 == "A"
348 list.sort!{|a,b| a[idx] <=> b[idx] }
349 else
350 list.sort!{|a,b| b[idx] <=> a[idx] }
351 end
352
353 res['content-type'] = "text/html"
354
355 res.body = <<-_end_of_html_
356<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
357<HTML>
358 <HEAD><TITLE>Index of #{HTMLUtils::escape(req.path)}</TITLE></HEAD>
359 <BODY>
360 <H1>Index of #{HTMLUtils::escape(req.path)}</H1>
361 _end_of_html_
362
363 res.body << "<PRE>\n"
364 res.body << " <A HREF=\"?N=#{d1}\">Name</A> "
365 res.body << "<A HREF=\"?M=#{d1}\">Last modified</A> "
366 res.body << "<A HREF=\"?S=#{d1}\">Size</A>\n"
367 res.body << "<HR>\n"
368
369 list.unshift [ "..", File::mtime(local_path+".."), -1 ]
370 list.each{ |name, time, size|
371 if name == ".."
372 dname = "Parent Directory"
373 elsif name.size > 25
374 dname = name.sub(/^(.{23})(.*)/){ $1 + ".." }
375 else
376 dname = name
377 end
378 s = " <A HREF=\"#{HTTPUtils::escape(name)}\">#{dname}</A>"
379 s << " " * (30 - dname.size)
380 s << (time ? time.strftime("%Y/%m/%d %H:%M ") : " " * 22)
381 s << (size >= 0 ? size.to_s : "-") << "\n"
382 res.body << s
383 }
384 res.body << "</PRE><HR>"
385
386 res.body << <<-_end_of_html_
387 <ADDRESS>
388 #{HTMLUtils::escape(@config[:ServerSoftware])}<BR>
389 at #{req.host}:#{req.port}
390 </ADDRESS>
391 </BODY>
392</HTML>
393 _end_of_html_
394 end
395
396 end
397 end
398end
Note: See TracBrowser for help on using the repository browser.