PageRenderTime 51ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/tool/test/webrick/test_filehandler.rb

https://github.com/abdollar/ruby
Ruby | 403 lines | 363 code | 39 blank | 1 comment | 15 complexity | 1b8ee876ded24246fedf4b60cb2fec47 MD5 | raw file
  1. # frozen_string_literal: false
  2. require "test/unit"
  3. require_relative "utils.rb"
  4. require "webrick"
  5. require "stringio"
  6. require "tmpdir"
  7. class WEBrick::TestFileHandler < Test::Unit::TestCase
  8. def teardown
  9. WEBrick::Utils::TimeoutHandler.terminate
  10. super
  11. end
  12. def default_file_handler(filename)
  13. klass = WEBrick::HTTPServlet::DefaultFileHandler
  14. klass.new(WEBrick::Config::HTTP, filename)
  15. end
  16. def windows?
  17. File.directory?("\\")
  18. end
  19. def get_res_body(res)
  20. sio = StringIO.new
  21. sio.binmode
  22. res.send_body(sio)
  23. sio.string
  24. end
  25. def make_range_request(range_spec)
  26. msg = <<-END_OF_REQUEST
  27. GET / HTTP/1.0
  28. Range: #{range_spec}
  29. END_OF_REQUEST
  30. return StringIO.new(msg.gsub(/^ {6}/, ""))
  31. end
  32. def make_range_response(file, range_spec)
  33. req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
  34. req.parse(make_range_request(range_spec))
  35. res = WEBrick::HTTPResponse.new(WEBrick::Config::HTTP)
  36. size = File.size(file)
  37. handler = default_file_handler(file)
  38. handler.make_partial_content(req, res, file, size)
  39. return res
  40. end
  41. def test_make_partial_content
  42. filename = __FILE__
  43. filesize = File.size(filename)
  44. res = make_range_response(filename, "bytes=#{filesize-100}-")
  45. assert_match(%r{^text/plain}, res["content-type"])
  46. assert_equal(100, get_res_body(res).size)
  47. res = make_range_response(filename, "bytes=-100")
  48. assert_match(%r{^text/plain}, res["content-type"])
  49. assert_equal(100, get_res_body(res).size)
  50. res = make_range_response(filename, "bytes=0-99")
  51. assert_match(%r{^text/plain}, res["content-type"])
  52. assert_equal(100, get_res_body(res).size)
  53. res = make_range_response(filename, "bytes=100-199")
  54. assert_match(%r{^text/plain}, res["content-type"])
  55. assert_equal(100, get_res_body(res).size)
  56. res = make_range_response(filename, "bytes=0-0")
  57. assert_match(%r{^text/plain}, res["content-type"])
  58. assert_equal(1, get_res_body(res).size)
  59. res = make_range_response(filename, "bytes=-1")
  60. assert_match(%r{^text/plain}, res["content-type"])
  61. assert_equal(1, get_res_body(res).size)
  62. res = make_range_response(filename, "bytes=0-0, -2")
  63. assert_match(%r{^multipart/byteranges}, res["content-type"])
  64. body = get_res_body(res)
  65. boundary = /; boundary=(.+)/.match(res['content-type'])[1]
  66. off = filesize - 2
  67. last = filesize - 1
  68. exp = "--#{boundary}\r\n" \
  69. "Content-Type: text/plain\r\n" \
  70. "Content-Range: bytes 0-0/#{filesize}\r\n" \
  71. "\r\n" \
  72. "#{IO.read(__FILE__, 1)}\r\n" \
  73. "--#{boundary}\r\n" \
  74. "Content-Type: text/plain\r\n" \
  75. "Content-Range: bytes #{off}-#{last}/#{filesize}\r\n" \
  76. "\r\n" \
  77. "#{IO.read(__FILE__, 2, off)}\r\n" \
  78. "--#{boundary}--\r\n"
  79. assert_equal exp, body
  80. end
  81. def test_filehandler
  82. config = { :DocumentRoot => File.dirname(__FILE__), }
  83. this_file = File.basename(__FILE__)
  84. filesize = File.size(__FILE__)
  85. this_data = File.binread(__FILE__)
  86. range = nil
  87. bug2593 = '[ruby-dev:40030]'
  88. TestWEBrick.start_httpserver(config) do |server, addr, port, log|
  89. begin
  90. server[:DocumentRootOptions][:NondisclosureName] = []
  91. http = Net::HTTP.new(addr, port)
  92. req = Net::HTTP::Get.new("/")
  93. http.request(req){|res|
  94. assert_equal("200", res.code, log.call)
  95. assert_equal("text/html", res.content_type, log.call)
  96. assert_match(/HREF="#{this_file}"/, res.body, log.call)
  97. }
  98. req = Net::HTTP::Get.new("/#{this_file}")
  99. http.request(req){|res|
  100. assert_equal("200", res.code, log.call)
  101. assert_equal("text/plain", res.content_type, log.call)
  102. assert_equal(this_data, res.body, log.call)
  103. }
  104. req = Net::HTTP::Get.new("/#{this_file}", "range"=>"bytes=#{filesize-100}-")
  105. http.request(req){|res|
  106. assert_equal("206", res.code, log.call)
  107. assert_equal("text/plain", res.content_type, log.call)
  108. assert_nothing_raised(bug2593) {range = res.content_range}
  109. assert_equal((filesize-100)..(filesize-1), range, log.call)
  110. assert_equal(this_data[-100..-1], res.body, log.call)
  111. }
  112. req = Net::HTTP::Get.new("/#{this_file}", "range"=>"bytes=-100")
  113. http.request(req){|res|
  114. assert_equal("206", res.code, log.call)
  115. assert_equal("text/plain", res.content_type, log.call)
  116. assert_nothing_raised(bug2593) {range = res.content_range}
  117. assert_equal((filesize-100)..(filesize-1), range, log.call)
  118. assert_equal(this_data[-100..-1], res.body, log.call)
  119. }
  120. req = Net::HTTP::Get.new("/#{this_file}", "range"=>"bytes=0-99")
  121. http.request(req){|res|
  122. assert_equal("206", res.code, log.call)
  123. assert_equal("text/plain", res.content_type, log.call)
  124. assert_nothing_raised(bug2593) {range = res.content_range}
  125. assert_equal(0..99, range, log.call)
  126. assert_equal(this_data[0..99], res.body, log.call)
  127. }
  128. req = Net::HTTP::Get.new("/#{this_file}", "range"=>"bytes=100-199")
  129. http.request(req){|res|
  130. assert_equal("206", res.code, log.call)
  131. assert_equal("text/plain", res.content_type, log.call)
  132. assert_nothing_raised(bug2593) {range = res.content_range}
  133. assert_equal(100..199, range, log.call)
  134. assert_equal(this_data[100..199], res.body, log.call)
  135. }
  136. req = Net::HTTP::Get.new("/#{this_file}", "range"=>"bytes=0-0")
  137. http.request(req){|res|
  138. assert_equal("206", res.code, log.call)
  139. assert_equal("text/plain", res.content_type, log.call)
  140. assert_nothing_raised(bug2593) {range = res.content_range}
  141. assert_equal(0..0, range, log.call)
  142. assert_equal(this_data[0..0], res.body, log.call)
  143. }
  144. req = Net::HTTP::Get.new("/#{this_file}", "range"=>"bytes=-1")
  145. http.request(req){|res|
  146. assert_equal("206", res.code, log.call)
  147. assert_equal("text/plain", res.content_type, log.call)
  148. assert_nothing_raised(bug2593) {range = res.content_range}
  149. assert_equal((filesize-1)..(filesize-1), range, log.call)
  150. assert_equal(this_data[-1, 1], res.body, log.call)
  151. }
  152. req = Net::HTTP::Get.new("/#{this_file}", "range"=>"bytes=0-0, -2")
  153. http.request(req){|res|
  154. assert_equal("206", res.code, log.call)
  155. assert_equal("multipart/byteranges", res.content_type, log.call)
  156. }
  157. ensure
  158. server[:DocumentRootOptions].delete :NondisclosureName
  159. end
  160. end
  161. end
  162. def test_non_disclosure_name
  163. config = { :DocumentRoot => File.dirname(__FILE__), }
  164. log_tester = lambda {|log, access_log|
  165. log = log.reject {|s| /ERROR `.*\' not found\./ =~ s }
  166. log = log.reject {|s| /WARN the request refers nondisclosure name/ =~ s }
  167. assert_equal([], log)
  168. }
  169. this_file = File.basename(__FILE__)
  170. TestWEBrick.start_httpserver(config, log_tester) do |server, addr, port, log|
  171. http = Net::HTTP.new(addr, port)
  172. doc_root_opts = server[:DocumentRootOptions]
  173. doc_root_opts[:NondisclosureName] = %w(.ht* *~ test_*)
  174. req = Net::HTTP::Get.new("/")
  175. http.request(req){|res|
  176. assert_equal("200", res.code, log.call)
  177. assert_equal("text/html", res.content_type, log.call)
  178. assert_no_match(/HREF="#{File.basename(__FILE__)}"/, res.body)
  179. }
  180. req = Net::HTTP::Get.new("/#{this_file}")
  181. http.request(req){|res|
  182. assert_equal("404", res.code, log.call)
  183. }
  184. doc_root_opts[:NondisclosureName] = %w(.ht* *~ TEST_*)
  185. http.request(req){|res|
  186. assert_equal("404", res.code, log.call)
  187. }
  188. end
  189. end
  190. def test_directory_traversal
  191. return if File.executable?(__FILE__) # skip on strange file system
  192. config = { :DocumentRoot => File.dirname(__FILE__), }
  193. log_tester = lambda {|log, access_log|
  194. log = log.reject {|s| /ERROR bad URI/ =~ s }
  195. log = log.reject {|s| /ERROR `.*\' not found\./ =~ s }
  196. assert_equal([], log)
  197. }
  198. TestWEBrick.start_httpserver(config, log_tester) do |server, addr, port, log|
  199. http = Net::HTTP.new(addr, port)
  200. req = Net::HTTP::Get.new("/../../")
  201. http.request(req){|res| assert_equal("400", res.code, log.call) }
  202. req = Net::HTTP::Get.new("/..%5c../#{File.basename(__FILE__)}")
  203. http.request(req){|res| assert_equal(windows? ? "200" : "404", res.code, log.call) }
  204. req = Net::HTTP::Get.new("/..%5c..%5cruby.c")
  205. http.request(req){|res| assert_equal("404", res.code, log.call) }
  206. end
  207. end
  208. def test_unwise_in_path
  209. if windows?
  210. config = { :DocumentRoot => File.dirname(__FILE__), }
  211. TestWEBrick.start_httpserver(config) do |server, addr, port, log|
  212. http = Net::HTTP.new(addr, port)
  213. req = Net::HTTP::Get.new("/..%5c..")
  214. http.request(req){|res| assert_equal("301", res.code, log.call) }
  215. end
  216. end
  217. end
  218. def test_short_filename
  219. return if File.executable?(__FILE__) # skip on strange file system
  220. return if /mswin/ =~ RUBY_PLATFORM && ENV.key?('GITHUB_ACTIONS') # not working from the beginning
  221. config = {
  222. :CGIInterpreter => TestWEBrick::RubyBin,
  223. :DocumentRoot => File.dirname(__FILE__),
  224. :CGIPathEnv => ENV['PATH'],
  225. }
  226. log_tester = lambda {|log, access_log|
  227. log = log.reject {|s| /ERROR `.*\' not found\./ =~ s }
  228. log = log.reject {|s| /WARN the request refers nondisclosure name/ =~ s }
  229. assert_equal([], log)
  230. }
  231. TestWEBrick.start_httpserver(config, log_tester) do |server, addr, port, log|
  232. http = Net::HTTP.new(addr, port)
  233. if windows?
  234. root = config[:DocumentRoot].tr("/", "\\")
  235. fname = IO.popen(%W[dir /x #{root}\\webrick_long_filename.cgi], encoding: "binary", &:read)
  236. fname.sub!(/\A.*$^$.*$^$/m, '')
  237. if fname
  238. fname = fname[/\s(w.+?cgi)\s/i, 1]
  239. fname.downcase!
  240. end
  241. else
  242. fname = "webric~1.cgi"
  243. end
  244. req = Net::HTTP::Get.new("/#{fname}/test")
  245. http.request(req) do |res|
  246. if windows?
  247. assert_equal("200", res.code, log.call)
  248. assert_equal("/test", res.body, log.call)
  249. else
  250. assert_equal("404", res.code, log.call)
  251. end
  252. end
  253. req = Net::HTTP::Get.new("/.htaccess")
  254. http.request(req) {|res| assert_equal("404", res.code, log.call) }
  255. req = Net::HTTP::Get.new("/htacce~1")
  256. http.request(req) {|res| assert_equal("404", res.code, log.call) }
  257. req = Net::HTTP::Get.new("/HTACCE~1")
  258. http.request(req) {|res| assert_equal("404", res.code, log.call) }
  259. end
  260. end
  261. def test_multibyte_char_in_path
  262. if Encoding.default_external == Encoding.find('US-ASCII')
  263. reset_encoding = true
  264. verb = $VERBOSE
  265. $VERBOSE = false
  266. Encoding.default_external = Encoding.find('UTF-8')
  267. end
  268. c = "\u00a7"
  269. begin
  270. c = c.encode('filesystem')
  271. rescue EncodingError
  272. c = c.b
  273. end
  274. Dir.mktmpdir(c) do |dir|
  275. basename = "#{c}.txt"
  276. File.write("#{dir}/#{basename}", "test_multibyte_char_in_path")
  277. Dir.mkdir("#{dir}/#{c}")
  278. File.write("#{dir}/#{c}/#{basename}", "nested")
  279. config = {
  280. :DocumentRoot => dir,
  281. :DirectoryIndex => [basename],
  282. }
  283. TestWEBrick.start_httpserver(config) do |server, addr, port, log|
  284. http = Net::HTTP.new(addr, port)
  285. path = "/#{basename}"
  286. req = Net::HTTP::Get.new(WEBrick::HTTPUtils::escape(path))
  287. http.request(req){|res| assert_equal("200", res.code, log.call + "\nFilesystem encoding is #{Encoding.find('filesystem')}") }
  288. path = "/#{c}/#{basename}"
  289. req = Net::HTTP::Get.new(WEBrick::HTTPUtils::escape(path))
  290. http.request(req){|res| assert_equal("200", res.code, log.call) }
  291. req = Net::HTTP::Get.new('/')
  292. http.request(req){|res|
  293. assert_equal("test_multibyte_char_in_path", res.body, log.call)
  294. }
  295. end
  296. end
  297. ensure
  298. if reset_encoding
  299. Encoding.default_external = Encoding.find('US-ASCII')
  300. $VERBOSE = verb
  301. end
  302. end
  303. def test_script_disclosure
  304. return if File.executable?(__FILE__) # skip on strange file system
  305. config = {
  306. :CGIInterpreter => TestWEBrick::RubyBinArray,
  307. :DocumentRoot => File.dirname(__FILE__),
  308. :CGIPathEnv => ENV['PATH'],
  309. :RequestCallback => Proc.new{|req, res|
  310. def req.meta_vars
  311. meta = super
  312. meta["RUBYLIB"] = $:.join(File::PATH_SEPARATOR)
  313. meta[RbConfig::CONFIG['LIBPATHENV']] = ENV[RbConfig::CONFIG['LIBPATHENV']] if RbConfig::CONFIG['LIBPATHENV']
  314. return meta
  315. end
  316. },
  317. }
  318. log_tester = lambda {|log, access_log|
  319. log = log.reject {|s| /ERROR `.*\' not found\./ =~ s }
  320. assert_equal([], log)
  321. }
  322. TestWEBrick.start_httpserver(config, log_tester) do |server, addr, port, log|
  323. http = Net::HTTP.new(addr, port)
  324. http.read_timeout = EnvUtil.apply_timeout_scale(60)
  325. http.write_timeout = EnvUtil.apply_timeout_scale(60) if http.respond_to?(:write_timeout=)
  326. req = Net::HTTP::Get.new("/webrick.cgi/test")
  327. http.request(req) do |res|
  328. assert_equal("200", res.code, log.call)
  329. assert_equal("/test", res.body, log.call)
  330. end
  331. resok = windows?
  332. response_assertion = Proc.new do |res|
  333. if resok
  334. assert_equal("200", res.code, log.call)
  335. assert_equal("/test", res.body, log.call)
  336. else
  337. assert_equal("404", res.code, log.call)
  338. end
  339. end
  340. req = Net::HTTP::Get.new("/webrick.cgi%20/test")
  341. http.request(req, &response_assertion)
  342. req = Net::HTTP::Get.new("/webrick.cgi./test")
  343. http.request(req, &response_assertion)
  344. resok &&= File.exist?(__FILE__+"::$DATA")
  345. req = Net::HTTP::Get.new("/webrick.cgi::$DATA/test")
  346. http.request(req, &response_assertion)
  347. end
  348. end
  349. def test_erbhandler
  350. config = { :DocumentRoot => File.dirname(__FILE__) }
  351. log_tester = lambda {|log, access_log|
  352. log = log.reject {|s| /ERROR `.*\' not found\./ =~ s }
  353. assert_equal([], log)
  354. }
  355. TestWEBrick.start_httpserver(config, log_tester) do |server, addr, port, log|
  356. http = Net::HTTP.new(addr, port)
  357. req = Net::HTTP::Get.new("/webrick.rhtml")
  358. http.request(req) do |res|
  359. assert_equal("200", res.code, log.call)
  360. assert_match %r!\Areq to http://[^/]+/webrick\.rhtml {}\n!, res.body
  361. end
  362. end
  363. end
  364. end