From 76386fe9f3bf89c273b280366d706a5dba45cee6 Mon Sep 17 00:00:00 2001
From: Stan Hu <stanhu@gmail.com>
Date: Fri, 24 May 2024 17:20:18 -0700
Subject: [PATCH] Add a test for Net::HTTP#connect

This will ensure that SNI is used with the hostname override patch.
---
 gems/gitlab-http/Gemfile.lock                 |   2 +
 gems/gitlab-http/gitlab-http.gemspec          |   1 +
 .../http_v2/net_http_connect_patch_spec.rb    | 102 ++++++++++++++++++
 3 files changed, 105 insertions(+)
 create mode 100644 gems/gitlab-http/spec/gitlab/http_v2/net_http_connect_patch_spec.rb

diff --git a/gems/gitlab-http/Gemfile.lock b/gems/gitlab-http/Gemfile.lock
index da9a1398e1cfa..afdf6f3dce282 100644
--- a/gems/gitlab-http/Gemfile.lock
+++ b/gems/gitlab-http/Gemfile.lock
@@ -173,6 +173,7 @@ GEM
       addressable (>= 2.8.0)
       crack (>= 0.3.2)
       hashdiff (>= 0.4.0, < 2.0.0)
+    webrick (1.8.1)
     zeitwerk (2.6.8)
 
 PLATFORMS
@@ -187,6 +188,7 @@ DEPENDENCIES
   rubocop (~> 1.50.2)
   rubocop-rspec (~> 2.22)
   webmock (~> 3.18.1)
+  webrick (~> 1.8)
 
 BUNDLED WITH
    2.4.14
diff --git a/gems/gitlab-http/gitlab-http.gemspec b/gems/gitlab-http/gitlab-http.gemspec
index ada9d672acd41..2378945cbcc35 100644
--- a/gems/gitlab-http/gitlab-http.gemspec
+++ b/gems/gitlab-http/gitlab-http.gemspec
@@ -32,4 +32,5 @@ Gem::Specification.new do |spec|
   spec.add_development_dependency "rubocop", "~> 1.50.2"
   spec.add_development_dependency "rubocop-rspec", "~> 2.22"
   spec.add_development_dependency 'webmock', '~> 3.18.1'
+  spec.add_development_dependency 'webrick', '~> 1.8'
 end
diff --git a/gems/gitlab-http/spec/gitlab/http_v2/net_http_connect_patch_spec.rb b/gems/gitlab-http/spec/gitlab/http_v2/net_http_connect_patch_spec.rb
new file mode 100644
index 0000000000000..548c33058bed0
--- /dev/null
+++ b/gems/gitlab-http/spec/gitlab/http_v2/net_http_connect_patch_spec.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'webrick'
+require 'webrick/https'
+
+RSpec.describe 'Net::HTTP#connect DNS rebinding tests', feature_category: :shared do
+  let(:host) { 'localhost' }
+  let(:host_ip) { '127.0.0.1' }
+  let(:rack_app) do
+    proc do |_env|
+      ['200', { 'Content-Type' => 'text/plain' }, ['Hello, world!']]
+    end
+  end
+
+  let!(:http_server) do
+    Class.new do
+      attr_accessor :sni_hostname
+
+      def initialize
+        @server = WEBrick::HTTPServer.new(
+          Port: 0,
+          SSLEnable: true,
+          SSLCertName: [%w[CN localhost]],
+          SSLServerNameCallback: proc { |args| sni_callback(*args) },
+          Logger: WEBrick::Log.new('/dev/null'),
+          AccessLog: []
+        )
+
+        @server.mount_proc '/' do |_req, res|
+          res.body = 'Hello, world!'
+        end
+
+        Thread.new { @server.start }
+      end
+
+      def port
+        @server.config[:Port]
+      end
+
+      def shutdown
+        @server.shutdown
+      end
+
+      def sni_callback(sslsocket, hostname = nil)
+        @sni_hostname = hostname
+        @server.ssl_servername_callback(sslsocket, hostname)
+      end
+    end.new
+  end
+
+  describe '#connect' do
+    before do
+      WebMock.allow_net_connect!
+    end
+
+    after do
+      WebMock.disable_net_connect! # rubocop:disable RSpec/WebMockEnable -- method not available in gem
+      http_server.shutdown
+    end
+
+    shared_examples 'GET request' do
+      it 'makes a successful HTTPS connection' do
+        http = Net::HTTP.new(http_host, http_server.port)
+        http.use_ssl = true
+        http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+        http.hostname_override = hostname_override if hostname_override
+
+        request = Net::HTTP::Get.new('/')
+
+        response = http.start { http.request(request) }
+        expect(response.code).to eq('200')
+        expect(response.body).to include('Hello, world!')
+        expect(http_server.sni_hostname).to eq(expected_sni)
+      end
+    end
+
+    context 'with hostname' do
+      let(:http_host) { host }
+      let(:expected_sni) { host }
+      let(:hostname_override) { nil }
+
+      it_behaves_like 'GET request'
+    end
+
+    context 'with IP address' do
+      let(:http_host) { host_ip }
+      let(:expected_sni) { nil }
+      let(:hostname_override) { nil }
+
+      it_behaves_like 'GET request'
+    end
+
+    context 'with hostname override' do
+      let(:http_host) { host_ip }
+      let(:hostname_override) { host }
+      let(:expected_sni) { host }
+
+      it_behaves_like 'GET request'
+    end
+  end
+end
-- 
GitLab