summaryrefslogtreecommitdiff
path: root/lib/webrick/httpservlet/filehandler.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/webrick/httpservlet/filehandler.rb')
-rw-r--r--lib/webrick/httpservlet/filehandler.rb95
1 files changed, 68 insertions, 27 deletions
diff --git a/lib/webrick/httpservlet/filehandler.rb b/lib/webrick/httpservlet/filehandler.rb
index 410cc6f9a9..daad8abd27 100644
--- a/lib/webrick/httpservlet/filehandler.rb
+++ b/lib/webrick/httpservlet/filehandler.rb
@@ -20,7 +20,7 @@ module WEBrick
class DefaultFileHandler < AbstractServlet
def initialize(server, local_path)
- super
+ super(server, local_path)
@local_path = local_path
end
@@ -32,7 +32,7 @@ module WEBrick
if not_modified?(req, res, mtime, res['etag'])
res.body = ''
raise HTTPStatus::NotModified
- elsif req['range']
+ elsif req['range']
make_partial_content(req, res, @local_path, st.size)
raise HTTPStatus::PartialContent
else
@@ -87,7 +87,7 @@ module WEBrick
content = io.read(last-first+1)
body << "--" << boundary << CRLF
body << "Content-Type: #{mtype}" << CRLF
- body << "Content-Range: #{first}-#{last}/#{filesize}" << CRLF
+ body << "Content-Range: bytes #{first}-#{last}/#{filesize}" << CRLF
body << CRLF
body << content
body << CRLF
@@ -107,7 +107,7 @@ module WEBrick
content = io.read(last-first+1)
end
res['content-type'] = mtype
- res['content-range'] = "#{first}-#{last}/#{filesize}"
+ res['content-range'] = "bytes #{first}-#{last}/#{filesize}"
res['content-length'] = last - first + 1
res.body = content
else
@@ -163,6 +163,7 @@ module WEBrick
end
end
end
+ prevent_directory_traversal(req, res)
super(req, res)
end
@@ -198,10 +199,42 @@ module WEBrick
private
+ def trailing_pathsep?(path)
+ # check for trailing path separator:
+ # File.dirname("/aaaa/bbbb/") #=> "/aaaa")
+ # File.dirname("/aaaa/bbbb/x") #=> "/aaaa/bbbb")
+ # File.dirname("/aaaa/bbbb") #=> "/aaaa")
+ # File.dirname("/aaaa/bbbbx") #=> "/aaaa")
+ return File.dirname(path) != File.dirname(path+"x")
+ end
+
+ def prevent_directory_traversal(req, res)
+ # Preventing directory traversal on Windows platforms;
+ # Backslashes (0x5c) in path_info are not interpreted as special
+ # character in URI notation. So the value of path_info should be
+ # normalize before accessing to the filesystem.
+
+ # dirty hack for filesystem encoding; in nature, File.expand_path
+ # should not be used for path normalization. [Bug #3345]
+ path = req.path_info.dup.force_encoding(Encoding.find("filesystem"))
+ if trailing_pathsep?(req.path_info)
+ # File.expand_path removes the trailing path separator.
+ # Adding a character is a workaround to save it.
+ # File.expand_path("/aaa/") #=> "/aaa"
+ # File.expand_path("/aaa/" + "x") #=> "/aaa/x"
+ expanded = File.expand_path(path + "x")
+ expanded.chop! # remove trailing "x"
+ else
+ expanded = File.expand_path(path)
+ end
+ expanded.force_encoding(req.path_info.encoding)
+ req.path_info = expanded
+ end
+
def exec_handler(req, res)
raise HTTPStatus::NotFound, "`#{req.path}' not found" unless @root
if set_filename(req, res)
- handler = get_handler(req)
+ handler = get_handler(req, res)
call_callback(:HandlerCallback, req, res)
h = handler.get_instance(@config, res.filename)
h.service(req, res)
@@ -211,9 +244,13 @@ module WEBrick
return false
end
- def get_handler(req)
- suffix1 = (/\.(\w+)$/ =~ req.script_name) && $1.downcase
- suffix2 = (/\.(\w+)\.[\w\-]+$/ =~ req.script_name) && $1.downcase
+ def get_handler(req, res)
+ suffix1 = (/\.(\w+)\z/ =~ res.filename) && $1.downcase
+ if /\.(\w+)\.([\w\-]+)\z/ =~ res.filename
+ if @options[:AcceptableLanguages].include?($2.downcase)
+ suffix2 = $1.downcase
+ end
+ end
handler_table = @options[:HandlerTable]
return handler_table[suffix1] || handler_table[suffix2] ||
HandlerTable[suffix1] || HandlerTable[suffix2] ||
@@ -226,15 +263,13 @@ module WEBrick
path_info.unshift("") # dummy for checking @root dir
while base = path_info.first
- check_filename(req, res, base)
break if base == "/"
- break unless File.directory?(res.filename + base)
+ break unless File.directory?(File.expand_path(res.filename + base))
shift_path_info(req, res, path_info)
call_callback(:DirectoryCallback, req, res)
end
if base = path_info.first
- check_filename(req, res, base)
if base == "/"
if file = search_index_file(req, res)
shift_path_info(req, res, path_info, file)
@@ -255,12 +290,10 @@ module WEBrick
end
def check_filename(req, res, name)
- @options[:NondisclosureName].each{|pattern|
- if File.fnmatch("/#{pattern}", name)
- @logger.warn("the request refers nondisclosure name `#{name}'.")
- raise HTTPStatus::NotFound, "`#{req.path}' not found."
- end
- }
+ if nondisclosure_name?(name) || windows_ambiguous_name?(name)
+ @logger.warn("the request refers nondisclosure name `#{name}'.")
+ raise HTTPStatus::NotFound, "`#{req.path}' not found."
+ end
end
def shift_path_info(req, res, path_info, base=nil)
@@ -268,7 +301,8 @@ module WEBrick
base = base || tmp
req.path_info = path_info.join
req.script_name << base
- res.filename << base
+ res.filename = File.expand_path(res.filename + base)
+ check_filename(req, res, File.basename(res.filename))
end
def search_index_file(req, res)
@@ -308,9 +342,15 @@ module WEBrick
end
end
+ def windows_ambiguous_name?(name)
+ return true if /[. ]+\z/ =~ name
+ return true if /::\$DATA\z/ =~ name
+ return false
+ end
+
def nondisclosure_name?(name)
@options[:NondisclosureName].each{|pattern|
- if File.fnmatch(pattern, name)
+ if File.fnmatch(pattern, name, File::FNM_CASEFOLD)
return true
end
}
@@ -326,7 +366,8 @@ module WEBrick
list = Dir::entries(local_path).collect{|name|
next if name == "." || name == ".."
next if nondisclosure_name?(name)
- st = (File::stat(local_path + name) rescue nil)
+ next if windows_ambiguous_name?(name)
+ st = (File::stat(File.join(local_path, name)) rescue nil)
if st.nil?
[ name, nil, -1 ]
elsif st.directory?
@@ -365,25 +406,25 @@ module WEBrick
res.body << "<A HREF=\"?M=#{d1}\">Last modified</A> "
res.body << "<A HREF=\"?S=#{d1}\">Size</A>\n"
res.body << "<HR>\n"
-
- list.unshift [ "..", File::mtime(local_path+".."), -1 ]
+
+ list.unshift [ "..", File::mtime(local_path+"/.."), -1 ]
list.each{ |name, time, size|
if name == ".."
dname = "Parent Directory"
- elsif name.size > 25
- dname = name.sub(/^(.{23})(.*)/){ $1 + ".." }
+ elsif name.bytesize > 25
+ dname = name.sub(/^(.{23})(?:.*)/, '\1..')
else
dname = name
end
- s = " <A HREF=\"#{HTTPUtils::escape(name)}\">#{dname}</A>"
- s << " " * (30 - dname.size)
+ s = " <A HREF=\"#{HTTPUtils::escape(name)}\">#{HTMLUtils::escape(dname)}</A>"
+ s << " " * (30 - dname.bytesize)
s << (time ? time.strftime("%Y/%m/%d %H:%M ") : " " * 22)
s << (size >= 0 ? size.to_s : "-") << "\n"
res.body << s
}
res.body << "</PRE><HR>"
- res.body << <<-_end_of_html_
+ res.body << <<-_end_of_html_
<ADDRESS>
#{HTMLUtils::escape(@config[:ServerSoftware])}<BR>
at #{req.host}:#{req.port}