File: connect_patch.rb

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (167 lines) | stat: -rw-r--r-- 7,265 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# frozen_string_literal: true

# This patches Net::HTTP#connect to handle the hostname override patch,
# which is needed for Server Side Request Forgery (SSRF)
# protection. This stopped working in net-http v0.2.2 due to
# https://github.com/ruby/net-http/pull/36.
# https://github.com/ruby/net-http/issues/141 is outstanding to make
# this less hacky, but for now we restore the previous behavior by
# setting the SNI hostname with the hostname override, if available.
require 'net/http'

module Net
  class HTTP < Protocol
    # rubocop:disable Cop/LineBreakAroundConditionalBlock -- This is upstream code
    # rubocop:disable Layout/ArgumentAlignment -- This is upstream code
    # rubocop:disable Layout/AssignmentIndentation -- This is upstream code
    # rubocop:disable Layout/LineEndStringConcatenationIndentation -- This is upstream code
    # rubocop:disable Layout/MultilineOperationIndentation -- This is upstream code
    # rubocop:disable Layout/SpaceInsideBlockBraces -- This is upstream code
    # rubocop:disable Lint/UnusedBlockArgument -- This is upstream code
    # rubocop:disable Metrics/AbcSize -- This is upstream code
    # rubocop:disable Metrics/CyclomaticComplexity -- This is upstream code
    # rubocop:disable Metrics/PerceivedComplexity -- This is upstream code
    # rubocop:disable Naming/RescuedExceptionsVariableName -- This is upstream code
    # rubocop:disable Style/AndOr -- This is upstream code
    # rubocop:disable Style/BlockDelimiters -- This is upstream code
    # rubocop:disable Style/EmptyLiteral -- This is upstream code
    # rubocop:disable Style/IfUnlessModifier -- This is upstream code
    # rubocop:disable Style/LineEndConcatenation -- This is upstream code
    # rubocop:disable Style/MultilineIfThen -- This is upstream code
    # rubocop:disable Style/Next -- This is upstream code
    # rubocop:disable Style/RescueStandardError -- This is upstream code
    # rubocop:disable Style/StringConcatenation -- This is upstream code
    def connect
      if use_ssl?
        # reference early to load OpenSSL before connecting,
        # as OpenSSL may take time to load.
        @ssl_context = OpenSSL::SSL::SSLContext.new
      end

      if proxy? then
        conn_addr = proxy_address
        conn_port = proxy_port
      else
        conn_addr = conn_address
        conn_port = port
      end

      debug "opening connection to #{conn_addr}:#{conn_port}..."
      s = Timeout.timeout(@open_timeout, Net::OpenTimeout) {
        begin
          TCPSocket.open(conn_addr, conn_port, @local_host, @local_port)
        rescue => e
          raise e, "Failed to open TCP connection to " +
            "#{conn_addr}:#{conn_port} (#{e.message})"
        end
      }
      s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
      debug "opened"
      if use_ssl?
        if proxy?
          plain_sock = BufferedIO.new(s, read_timeout: @read_timeout,
                                      write_timeout: @write_timeout,
                                      continue_timeout: @continue_timeout,
                                      debug_output: @debug_output)
          buf = +"CONNECT #{conn_address}:#{@port} HTTP/#{HTTPVersion}\r\n" \
            "Host: #{@address}:#{@port}\r\n"
          if proxy_user
            credential = ["#{proxy_user}:#{proxy_pass}"].pack('m0')
            buf << "Proxy-Authorization: Basic #{credential}\r\n"
          end
          buf << "\r\n"
          plain_sock.write(buf)
          HTTPResponse.read_new(plain_sock).value
          # assuming nothing left in buffers after successful CONNECT response
        end

        ssl_parameters = Hash.new
        iv_list = instance_variables
        SSL_IVNAMES.each_with_index do |ivname, i|
          if iv_list.include?(ivname)
            value = instance_variable_get(ivname)
            unless value.nil?
              ssl_parameters[SSL_ATTRIBUTES[i]] = value
            end
          end
        end
        @ssl_context.set_params(ssl_parameters)
        unless @ssl_context.session_cache_mode.nil? # a dummy method on JRuby
          @ssl_context.session_cache_mode =
              OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT |
                  OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
        end
        if @ssl_context.respond_to?(:session_new_cb) # not implemented under JRuby
          @ssl_context.session_new_cb = proc {|sock, sess| @ssl_session = sess }
        end

        # Still do the post_connection_check below even if connecting
        # to IP address
        verify_hostname = @ssl_context.verify_hostname

        # This hack would not be needed with https://github.com/ruby/net-http/issues/141
        address_to_verify = hostname_override || @address

        # Server Name Indication (SNI) RFC 3546/6066
        case address_to_verify
        when Resolv::IPv4::Regex, Resolv::IPv6::Regex
          # don't set SNI, as IP addresses in SNI is not valid
          # per RFC 6066, section 3.

          # Avoid openssl warning
          @ssl_context.verify_hostname = false
        else
          ssl_host_address = address_to_verify
        end

        debug "starting SSL for #{conn_addr}:#{conn_port}..."
        s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
        s.sync_close = true
        s.hostname = ssl_host_address if s.respond_to?(:hostname=) && ssl_host_address

        if @ssl_session and
           Process.clock_gettime(Process::CLOCK_REALTIME) < @ssl_session.time.to_f + @ssl_session.timeout
          s.session = @ssl_session
        end
        ssl_socket_connect(s, @open_timeout)
        if (@ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE) && verify_hostname
          s.post_connection_check(@address)
        end
        debug "SSL established, protocol: #{s.ssl_version}, cipher: #{s.cipher[0]}"
      end
      @socket = BufferedIO.new(s, read_timeout: @read_timeout,
                               write_timeout: @write_timeout,
                               continue_timeout: @continue_timeout,
                               debug_output: @debug_output)
      @last_communicated = nil
      on_connect
    rescue => exception
      if s
        debug "Conn close because of connect error #{exception}"
        s.close
      end
      raise
    end
    private :connect
    # rubocop:enable Cop/LineBreakAroundConditionalBlock
    # rubocop:enable Layout/ArgumentAlignment
    # rubocop:enable Layout/AssignmentIndentation
    # rubocop:enable Layout/LineEndStringConcatenationIndentation
    # rubocop:enable Layout/MultilineOperationIndentation
    # rubocop:enable Layout/SpaceInsideBlockBraces
    # rubocop:enable Lint/UnusedBlockArgument
    # rubocop:enable Metrics/AbcSize
    # rubocop:enable Metrics/CyclomaticComplexity
    # rubocop:enable Metrics/PerceivedComplexity
    # rubocop:enable Naming/RescuedExceptionsVariableName
    # rubocop:enable Style/AndOr
    # rubocop:enable Style/BlockDelimiters
    # rubocop:enable Style/EmptyLiteral
    # rubocop:enable Style/IfUnlessModifier
    # rubocop:enable Style/LineEndConcatenation
    # rubocop:enable Style/MultilineIfThen
    # rubocop:enable Style/Next
    # rubocop:enable Style/RescueStandardError
    # rubocop:enable Style/StringConcatenation
  end
end