|
- require 'net/http'
- require 'tempfile'
- require 'shellwords'
-
- module Cucumber
- module Formatter
- class HTTPIO
- class << self
- # Returns an IO that will write to a HTTP request's body
- # https_verify_mode can be set to OpenSSL::SSL::VERIFY_NONE
- # to ignore unsigned certificate - setting to nil will verify the certificate
- def open(url, https_verify_mode = nil, reporter = nil)
- @https_verify_mode = https_verify_mode
- uri, method, headers = CurlOptionParser.parse(url)
- IOHTTPBuffer.new(uri, method, headers, https_verify_mode, reporter)
- end
- end
- end
-
- class CurlOptionParser
- def self.parse(options)
- args = Shellwords.split(options)
-
- url = nil
- http_method = 'PUT'
- headers = {}
-
- until args.empty?
- arg = args.shift
- case arg
- when '-X', '--request'
- http_method = remove_arg_for(args, arg)
- when '-H'
- header_arg = remove_arg_for(args, arg)
- headers = headers.merge(parse_header(header_arg))
- else
- raise StandardError, "#{options} was not a valid curl command. Can't set url to #{arg} it is already set to #{url}" if url
-
- url = arg
- end
- end
- raise StandardError, "#{options} was not a valid curl command" unless url
-
- [
- url,
- http_method,
- headers
- ]
- end
-
- def self.remove_arg_for(args, arg)
- return args.shift unless args.empty?
-
- raise StandardError, "Missing argument for #{arg}"
- end
-
- def self.parse_header(header_arg)
- parts = header_arg.split(':', 2)
- raise StandardError, "#{header_arg} was not a valid header" unless parts.length == 2
-
- { parts[0].strip => parts[1].strip }
- end
- end
-
- class IOHTTPBuffer
- attr_reader :uri, :method, :headers
-
- def initialize(uri, method, headers = {}, https_verify_mode = nil, reporter = nil)
- @uri = URI(uri)
- @method = method
- @headers = headers
- @write_io = Tempfile.new('cucumber', encoding: 'UTF-8')
- @https_verify_mode = https_verify_mode
- @reporter = reporter || NoReporter.new
- end
-
- def close
- response = send_content(@uri, @method, @headers)
- @reporter.report(response.body)
- @write_io.close
- return if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPRedirection)
-
- raise StandardError, "request to #{uri} failed with status #{response.code}"
- end
-
- def write(data)
- @write_io.write(data)
- end
-
- def flush
- @write_io.flush
- end
-
- def closed?
- @write_io.closed?
- end
-
- private
-
- def send_content(uri, method, headers, attempt = 10)
- content = (method == 'GET' ? StringIO.new : @write_io)
- http = build_client(uri, @https_verify_mode)
-
- raise StandardError, "request to #{uri} failed (too many redirections)" if attempt <= 0
-
- req = build_request(
- uri,
- method,
- headers.merge(
- 'Content-Length' => content.size.to_s
- )
- )
-
- content.rewind
- req.body_stream = content
-
- begin
- response = http.request(req)
- rescue SystemCallError
- # We may get the redirect response before pushing the file.
- response = http.request(build_request(uri, method, headers))
- end
-
- case response
- when Net::HTTPAccepted
- send_content(URI(response['Location']), 'PUT', {}, attempt - 1) if response['Location']
- when Net::HTTPRedirection
- send_content(URI(response['Location']), method, headers, attempt - 1)
- end
- response
- end
-
- def build_request(uri, method, headers)
- method_class_name = "#{method[0].upcase}#{method[1..].downcase}"
- req = Net::HTTP.const_get(method_class_name).new(uri)
- headers.each do |header, value|
- req[header] = value
- end
- req
- end
-
- def build_client(uri, https_verify_mode)
- http = Net::HTTP.new(uri.hostname, uri.port)
- if uri.scheme == 'https'
- http.use_ssl = true
- http.verify_mode = https_verify_mode if https_verify_mode
- end
- http
- end
- end
- end
- end
|