aboutsummaryrefslogtreecommitdiffstats
path: root/lib/webrick/httpresponse.rb
diff options
context:
space:
mode:
authorHiroshi SHIBATA <hsbt@ruby-lang.org>2020-11-02 13:44:28 +0900
committerHiroshi SHIBATA <hsbt@ruby-lang.org>2020-12-10 18:06:25 +0900
commit5dc786bf86bb6e0da2639f88659598ec8b9db30d (patch)
tree87cc4d68088fc7c2616c7c1bef44c36ca10fa01f /lib/webrick/httpresponse.rb
parent46d3ea2c2569e2e5a9ee3e7e206f07f0f8b693f5 (diff)
downloadruby-5dc786bf86bb6e0da2639f88659598ec8b9db30d.tar.gz
Move webrick library into internal test toolchain
Diffstat (limited to 'lib/webrick/httpresponse.rb')
-rw-r--r--lib/webrick/httpresponse.rb564
1 files changed, 0 insertions, 564 deletions
diff --git a/lib/webrick/httpresponse.rb b/lib/webrick/httpresponse.rb
deleted file mode 100644
index ba4494ab74..0000000000
--- a/lib/webrick/httpresponse.rb
+++ /dev/null
@@ -1,564 +0,0 @@
-# frozen_string_literal: false
-#
-# httpresponse.rb -- HTTPResponse Class
-#
-# Author: IPR -- Internet Programming with Ruby -- writers
-# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
-# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
-# reserved.
-#
-# $IPR: httpresponse.rb,v 1.45 2003/07/11 11:02:25 gotoyuzo Exp $
-
-require 'time'
-require 'uri'
-require_relative 'httpversion'
-require_relative 'htmlutils'
-require_relative 'httputils'
-require_relative 'httpstatus'
-
-module WEBrick
- ##
- # An HTTP response. This is filled in by the service or do_* methods of a
- # WEBrick HTTP Servlet.
-
- class HTTPResponse
- class InvalidHeader < StandardError
- end
-
- ##
- # HTTP Response version
-
- attr_reader :http_version
-
- ##
- # Response status code (200)
-
- attr_reader :status
-
- ##
- # Response header
-
- attr_reader :header
-
- ##
- # Response cookies
-
- attr_reader :cookies
-
- ##
- # Response reason phrase ("OK")
-
- attr_accessor :reason_phrase
-
- ##
- # Body may be:
- # * a String;
- # * an IO-like object that responds to +#read+ and +#readpartial+;
- # * a Proc-like object that responds to +#call+.
- #
- # In the latter case, either #chunked= should be set to +true+,
- # or <code>header['content-length']</code> explicitly provided.
- # Example:
- #
- # server.mount_proc '/' do |req, res|
- # res.chunked = true
- # # or
- # # res.header['content-length'] = 10
- # res.body = proc { |out| out.write(Time.now.to_s) }
- # end
-
- attr_accessor :body
-
- ##
- # Request method for this response
-
- attr_accessor :request_method
-
- ##
- # Request URI for this response
-
- attr_accessor :request_uri
-
- ##
- # Request HTTP version for this response
-
- attr_accessor :request_http_version
-
- ##
- # Filename of the static file in this response. Only used by the
- # FileHandler servlet.
-
- attr_accessor :filename
-
- ##
- # Is this a keep-alive response?
-
- attr_accessor :keep_alive
-
- ##
- # Configuration for this response
-
- attr_reader :config
-
- ##
- # Bytes sent in this response
-
- attr_reader :sent_size
-
- ##
- # Creates a new HTTP response object. WEBrick::Config::HTTP is the
- # default configuration.
-
- def initialize(config)
- @config = config
- @buffer_size = config[:OutputBufferSize]
- @logger = config[:Logger]
- @header = Hash.new
- @status = HTTPStatus::RC_OK
- @reason_phrase = nil
- @http_version = HTTPVersion::convert(@config[:HTTPVersion])
- @body = ''
- @keep_alive = true
- @cookies = []
- @request_method = nil
- @request_uri = nil
- @request_http_version = @http_version # temporary
- @chunked = false
- @filename = nil
- @sent_size = 0
- @bodytempfile = nil
- end
-
- ##
- # The response's HTTP status line
-
- def status_line
- "HTTP/#@http_version #@status #@reason_phrase".rstrip << CRLF
- end
-
- ##
- # Sets the response's status to the +status+ code
-
- def status=(status)
- @status = status
- @reason_phrase = HTTPStatus::reason_phrase(status)
- end
-
- ##
- # Retrieves the response header +field+
-
- def [](field)
- @header[field.downcase]
- end
-
- ##
- # Sets the response header +field+ to +value+
-
- def []=(field, value)
- @chunked = value.to_s.downcase == 'chunked' if field.downcase == 'transfer-encoding'
- @header[field.downcase] = value.to_s
- end
-
- ##
- # The content-length header
-
- def content_length
- if len = self['content-length']
- return Integer(len)
- end
- end
-
- ##
- # Sets the content-length header to +len+
-
- def content_length=(len)
- self['content-length'] = len.to_s
- end
-
- ##
- # The content-type header
-
- def content_type
- self['content-type']
- end
-
- ##
- # Sets the content-type header to +type+
-
- def content_type=(type)
- self['content-type'] = type
- end
-
- ##
- # Iterates over each header in the response
-
- def each
- @header.each{|field, value| yield(field, value) }
- end
-
- ##
- # Will this response body be returned using chunked transfer-encoding?
-
- def chunked?
- @chunked
- end
-
- ##
- # Enables chunked transfer encoding.
-
- def chunked=(val)
- @chunked = val ? true : false
- end
-
- ##
- # Will this response's connection be kept alive?
-
- def keep_alive?
- @keep_alive
- end
-
- ##
- # Sends the response on +socket+
-
- def send_response(socket) # :nodoc:
- begin
- setup_header()
- send_header(socket)
- send_body(socket)
- rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN => ex
- @logger.debug(ex)
- @keep_alive = false
- rescue Exception => ex
- @logger.error(ex)
- @keep_alive = false
- end
- end
-
- ##
- # Sets up the headers for sending
-
- def setup_header() # :nodoc:
- @reason_phrase ||= HTTPStatus::reason_phrase(@status)
- @header['server'] ||= @config[:ServerSoftware]
- @header['date'] ||= Time.now.httpdate
-
- # HTTP/0.9 features
- if @request_http_version < "1.0"
- @http_version = HTTPVersion.new("0.9")
- @keep_alive = false
- end
-
- # HTTP/1.0 features
- if @request_http_version < "1.1"
- if chunked?
- @chunked = false
- ver = @request_http_version.to_s
- msg = "chunked is set for an HTTP/#{ver} request. (ignored)"
- @logger.warn(msg)
- end
- end
-
- # Determine the message length (RFC2616 -- 4.4 Message Length)
- if @status == 304 || @status == 204 || HTTPStatus::info?(@status)
- @header.delete('content-length')
- @body = ""
- elsif chunked?
- @header["transfer-encoding"] = "chunked"
- @header.delete('content-length')
- elsif %r{^multipart/byteranges} =~ @header['content-type']
- @header.delete('content-length')
- elsif @header['content-length'].nil?
- if @body.respond_to? :readpartial
- elsif @body.respond_to? :call
- make_body_tempfile
- else
- @header['content-length'] = (@body ? @body.bytesize : 0).to_s
- end
- end
-
- # Keep-Alive connection.
- if @header['connection'] == "close"
- @keep_alive = false
- elsif keep_alive?
- if chunked? || @header['content-length'] || @status == 304 || @status == 204 || HTTPStatus.info?(@status)
- @header['connection'] = "Keep-Alive"
- else
- msg = "Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true"
- @logger.warn(msg)
- @header['connection'] = "close"
- @keep_alive = false
- end
- else
- @header['connection'] = "close"
- end
-
- # Location is a single absoluteURI.
- if location = @header['location']
- if @request_uri
- @header['location'] = @request_uri.merge(location).to_s
- end
- end
- end
-
- def make_body_tempfile # :nodoc:
- return if @bodytempfile
- bodytempfile = Tempfile.create("webrick")
- if @body.nil?
- # nothing
- elsif @body.respond_to? :readpartial
- IO.copy_stream(@body, bodytempfile)
- @body.close
- elsif @body.respond_to? :call
- @body.call(bodytempfile)
- else
- bodytempfile.write @body
- end
- bodytempfile.rewind
- @body = @bodytempfile = bodytempfile
- @header['content-length'] = bodytempfile.stat.size.to_s
- end
-
- def remove_body_tempfile # :nodoc:
- if @bodytempfile
- @bodytempfile.close
- File.unlink @bodytempfile.path
- @bodytempfile = nil
- end
- end
-
-
- ##
- # Sends the headers on +socket+
-
- def send_header(socket) # :nodoc:
- if @http_version.major > 0
- data = status_line()
- @header.each{|key, value|
- tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }
- data << "#{tmp}: #{check_header(value)}" << CRLF
- }
- @cookies.each{|cookie|
- data << "Set-Cookie: " << check_header(cookie.to_s) << CRLF
- }
- data << CRLF
- socket.write(data)
- end
- rescue InvalidHeader => e
- @header.clear
- @cookies.clear
- set_error e
- retry
- end
-
- ##
- # Sends the body on +socket+
-
- def send_body(socket) # :nodoc:
- if @body.respond_to? :readpartial then
- send_body_io(socket)
- elsif @body.respond_to?(:call) then
- send_body_proc(socket)
- else
- send_body_string(socket)
- end
- end
-
- ##
- # Redirects to +url+ with a WEBrick::HTTPStatus::Redirect +status+.
- #
- # Example:
- #
- # res.set_redirect WEBrick::HTTPStatus::TemporaryRedirect
-
- def set_redirect(status, url)
- url = URI(url).to_s
- @body = "<HTML><A HREF=\"#{url}\">#{url}</A>.</HTML>\n"
- @header['location'] = url
- raise status
- end
-
- ##
- # Creates an error page for exception +ex+ with an optional +backtrace+
-
- def set_error(ex, backtrace=false)
- case ex
- when HTTPStatus::Status
- @keep_alive = false if HTTPStatus::error?(ex.code)
- self.status = ex.code
- else
- @keep_alive = false
- self.status = HTTPStatus::RC_INTERNAL_SERVER_ERROR
- end
- @header['content-type'] = "text/html; charset=ISO-8859-1"
-
- if respond_to?(:create_error_page)
- create_error_page()
- return
- end
-
- if @request_uri
- host, port = @request_uri.host, @request_uri.port
- else
- host, port = @config[:ServerName], @config[:Port]
- end
-
- error_body(backtrace, ex, host, port)
- end
-
- private
-
- def check_header(header_value)
- header_value = header_value.to_s
- if /[\r\n]/ =~ header_value
- raise InvalidHeader
- else
- header_value
- end
- end
-
- # :stopdoc:
-
- def error_body(backtrace, ex, host, port)
- @body = ''
- @body << <<-_end_of_html_
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
-<HTML>
- <HEAD><TITLE>#{HTMLUtils::escape(@reason_phrase)}</TITLE></HEAD>
- <BODY>
- <H1>#{HTMLUtils::escape(@reason_phrase)}</H1>
- #{HTMLUtils::escape(ex.message)}
- <HR>
- _end_of_html_
-
- if backtrace && $DEBUG
- @body << "backtrace of `#{HTMLUtils::escape(ex.class.to_s)}' "
- @body << "#{HTMLUtils::escape(ex.message)}"
- @body << "<PRE>"
- ex.backtrace.each{|line| @body << "\t#{line}\n"}
- @body << "</PRE><HR>"
- end
-
- @body << <<-_end_of_html_
- <ADDRESS>
- #{HTMLUtils::escape(@config[:ServerSoftware])} at
- #{host}:#{port}
- </ADDRESS>
- </BODY>
-</HTML>
- _end_of_html_
- end
-
- def send_body_io(socket)
- begin
- if @request_method == "HEAD"
- # do nothing
- elsif chunked?
- buf = ''
- begin
- @body.readpartial(@buffer_size, buf)
- size = buf.bytesize
- data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
- socket.write(data)
- data.clear
- @sent_size += size
- rescue EOFError
- break
- end while true
- buf.clear
- socket.write("0#{CRLF}#{CRLF}")
- else
- if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ @header['content-range']
- offset = $1.to_i
- size = $2.to_i - offset + 1
- else
- offset = nil
- size = @header['content-length']
- size = size.to_i if size
- end
- begin
- @sent_size = IO.copy_stream(@body, socket, size, offset)
- rescue NotImplementedError
- @body.seek(offset, IO::SEEK_SET)
- @sent_size = IO.copy_stream(@body, socket, size)
- end
- end
- ensure
- @body.close
- end
- remove_body_tempfile
- end
-
- def send_body_string(socket)
- if @request_method == "HEAD"
- # do nothing
- elsif chunked?
- body ? @body.bytesize : 0
- while buf = @body[@sent_size, @buffer_size]
- break if buf.empty?
- size = buf.bytesize
- data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
- buf.clear
- socket.write(data)
- @sent_size += size
- end
- socket.write("0#{CRLF}#{CRLF}")
- else
- if @body && @body.bytesize > 0
- socket.write(@body)
- @sent_size = @body.bytesize
- end
- end
- end
-
- def send_body_proc(socket)
- if @request_method == "HEAD"
- # do nothing
- elsif chunked?
- @body.call(ChunkedWrapper.new(socket, self))
- socket.write("0#{CRLF}#{CRLF}")
- else
- size = @header['content-length'].to_i
- if @bodytempfile
- @bodytempfile.rewind
- IO.copy_stream(@bodytempfile, socket)
- else
- @body.call(socket)
- end
- @sent_size = size
- end
- end
-
- class ChunkedWrapper
- def initialize(socket, resp)
- @socket = socket
- @resp = resp
- end
-
- def write(buf)
- return 0 if buf.empty?
- socket = @socket
- @resp.instance_eval {
- size = buf.bytesize
- data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
- socket.write(data)
- data.clear
- @sent_size += size
- size
- }
- end
-
- def <<(*buf)
- write(buf)
- self
- end
- end
-
- # preserved for compatibility with some 3rd-party handlers
- def _write_data(socket, data)
- socket << data
- end
-
- # :startdoc:
- end
-
-end