From b2ace9bd9feda6fa16b13a0a067d9d786250213b Mon Sep 17 00:00:00 2001 From: Postmodern Date: Fri, 5 Jul 2024 18:28:12 -0700 Subject: [PATCH] Added `Request#remote_addr`, #remote_ip`, and `#remote_port` (closes #6). --- lib/ronin/listener/http/request.rb | 70 ++++-- lib/ronin/listener/http/server.rb | 11 +- spec/request_spec.rb | 89 ++++++-- spec/server_spec.rb | 347 +++++++++++++++++------------ 4 files changed, 334 insertions(+), 183 deletions(-) diff --git a/lib/ronin/listener/http/request.rb b/lib/ronin/listener/http/request.rb index 627e3fe..590ed2c 100644 --- a/lib/ronin/listener/http/request.rb +++ b/lib/ronin/listener/http/request.rb @@ -29,6 +29,11 @@ module HTTP # class Request + # The remote address that sent the request. + # + # @return [Addrinfo] + attr_reader :remote_addr + # The HTTP request method. # # @return [String] @@ -57,6 +62,9 @@ class Request # # Initializes the request. # + # @param [Addrinfo] remote_addr + # The remote address that sent the request. + # # @param [String] method # The HTTP request method. # @@ -72,12 +80,36 @@ class Request # @param [String, nil] body # The optional body sent with the request. # - def initialize(method: , path: , version: , headers:, body: nil) - @method = method - @path = path - @version = version - @headers = headers - @body = body + def initialize(remote_addr: , + method: , + path: , + version: , + headers:, + body: nil) + @remote_addr = remote_addr + @method = method + @path = path + @version = version + @headers = headers + @body = body + end + + # + # The remote IP address that sent the request. + # + # @return [String] + # + def remote_ip + @remote_addr.ip_address + end + + # + # The remote port that sent the request. + # + # @return [String] + # + def remote_port + @remote_addr.ip_port end # @@ -91,11 +123,13 @@ def initialize(method: , path: , version: , headers:, body: nil) # def ==(other) self.class == other.class && - @method == other.method && - @path == other.path && - @version == other.version && - @headers == other.headers && - @body == other.body + remote_ip == other.remote_ip && + remote_port == other.remote_port && + @method == other.method && + @path == other.path && + @version == other.version && + @headers == other.headers && + @body == other.body end # @@ -122,11 +156,13 @@ def to_s # def to_h { - method: @method, - path: @path, - version: @version, - headers: @headers, - body: @body + remote_ip: remote_ip, + remote_port: remote_port, + method: @method, + path: @path, + version: @version, + headers: @headers, + body: @body } end @@ -139,6 +175,8 @@ def to_h def to_csv CSV.generate_line( [ + remote_ip, + remote_port, @method, @path, @version, diff --git a/lib/ronin/listener/http/server.rb b/lib/ronin/listener/http/server.rb index 9891c2a..8e69c51 100644 --- a/lib/ronin/listener/http/server.rb +++ b/lib/ronin/listener/http/server.rb @@ -131,11 +131,12 @@ def process(request) if request.path == @root || request.path.start_with?(@root) @callback.call( Request.new( - method: request.method, - path: request.path, - version: request.version, - headers: request.headers, - body: request.body + remote_addr: request.remote_address, + method: request.method, + path: request.path, + version: request.version, + headers: request.headers, + body: request.body ) ) end diff --git a/spec/request_spec.rb b/spec/request_spec.rb index 5e91b3e..f23f91b 100644 --- a/spec/request_spec.rb +++ b/spec/request_spec.rb @@ -1,24 +1,34 @@ require 'spec_helper' require 'ronin/listener/http/request' +require 'socket' + describe Ronin::Listener::HTTP::Request do - let(:method) { 'GET' } - let(:path) { '/' } - let(:version) { '1.1' } - let(:headers) { {'Host' => 'host1.com'} } - let(:body) { "foo bar" } + let(:remote_ip) { '192.168.1.1' } + let(:remote_port) { 1234 } + let(:remote_addr) { Addrinfo.tcp(remote_ip,remote_port) } + let(:method) { 'GET' } + let(:path) { '/' } + let(:version) { '1.1' } + let(:headers) { {'Host' => 'host1.com'} } + let(:body) { "foo bar" } subject do described_class.new( - method: method, - path: path, - version: version, - headers: headers, - body: body + remote_addr: remote_addr, + method: method, + path: path, + version: version, + headers: headers, + body: body ) end describe "#initialize" do + it "must set #remote_addr" do + expect(subject.remote_addr).to eq(remote_addr) + end + it "must set #method" do expect(subject.method).to eq(method) end @@ -40,16 +50,29 @@ end end + describe "#remote_ip" do + it "must return the remote IP address String" do + expect(subject.remote_ip).to eq(remote_ip) + end + end + + describe "#remote_port" do + it "must return the remote port" do + expect(subject.remote_port).to eq(remote_port) + end + end + describe "#==" do context "when given a #{described_class}" do context "and all attributes are the same" do let(:other) do described_class.new( - method: method, - path: path, - version: version, - headers: headers, - body: body + remote_addr: remote_addr, + method: method, + path: path, + version: version, + headers: headers, + body: body ) end @@ -58,9 +81,27 @@ end end + context "but the #remote_addr is different" do + let(:other) do + described_class.new( + remote_addr: Addrinfo.tcp('192.168.1.42',2345), + method: method, + path: path, + version: version, + headers: headers, + body: body + ) + end + + it "must return false" do + expect(subject == other).to be(false) + end + end + context "but the #method is different" do let(:other) do described_class.new( + remote_addr: remote_addr, method: 'POST', path: path, version: version, @@ -77,6 +118,7 @@ context "but the #path is different" do let(:other) do described_class.new( + remote_addr: remote_addr, method: method, path: '/different', version: version, @@ -93,6 +135,7 @@ context "but the #path is different" do let(:other) do described_class.new( + remote_addr: remote_addr, method: method, path: path, version: '1.0', @@ -109,6 +152,7 @@ context "but the #path is different" do let(:other) do described_class.new( + remote_addr: remote_addr, method: method, path: path, version: version, @@ -125,6 +169,7 @@ context "but the #path is different" do let(:other) do described_class.new( + remote_addr: remote_addr, method: method, path: path, version: version, @@ -165,11 +210,13 @@ it "must return a Hash containing #method, #path, #version, #headers, and #body" do expect(subject.to_h).to eq( { - method: method, - path: path, - version: version, - headers: headers, - body: body + remote_ip: remote_ip, + remote_port: remote_port, + method: method, + path: path, + version: version, + headers: headers, + body: body } ) end @@ -180,6 +227,8 @@ expect(subject.to_csv).to eq( CSV.generate_line( [ + remote_ip, + remote_port, method, path, version, diff --git a/spec/server_spec.rb b/spec/server_spec.rb index e9f4bc3..2d6670a 100644 --- a/spec/server_spec.rb +++ b/spec/server_spec.rb @@ -81,41 +81,51 @@ end describe "#process" do + let(:remote_address) { Addrinfo.tcp('192.168.1.1',1234) } + context "when #vhost is nil" do context "and #root is '/'" do let(:http_request1) do - double('Async HTTP request1', method: 'GET', - path: '/', - version: '1.1', - authority: 'host1.com', - headers: {'Host' => 'host1.com'}, - body: nil) + double( + 'Async HTTP request1', remote_address: remote_address, + method: 'GET', + path: '/', + version: '1.1', + authority: 'host1.com', + headers: {'Host' => 'host1.com'}, + body: nil + ) end let(:http_request2) do - double('Async HTTP request2', method: 'GET', - path: '/foo', - version: '1.1', - authority: 'host2.com', - headers: {'Host' => 'host2.com'}, - body: nil) + double( + 'Async HTTP request2', remote_address: remote_address, + method: 'GET', + path: '/foo', + version: '1.1', + authority: 'host2.com', + headers: {'Host' => 'host2.com'}, + body: nil + ) end let(:yielded_request1) do Ronin::Listener::HTTP::Request.new( - method: http_request1.method, - version: http_request1.version, - headers: http_request1.headers, - path: http_request1.path + remote_addr: http_request1.remote_address, + method: http_request1.method, + version: http_request1.version, + headers: http_request1.headers, + path: http_request1.path ) end let(:yielded_request2) do Ronin::Listener::HTTP::Request.new( - method: http_request2.method, - version: http_request2.version, - headers: http_request2.headers, - path: http_request2.path + remote_addr: http_request2.remote_address, + method: http_request2.method, + version: http_request2.version, + headers: http_request2.headers, + path: http_request2.path ) end @@ -130,47 +140,58 @@ context "and #root is not '/'" do let(:http_request1) do - double('Async HTTP request1', method: 'GET', - path: '/', - version: '1.1', - authority: 'host1.com', - headers: {'Host' => 'host1.com'}, - body: nil) + double( + 'Async HTTP request1', remote_address: remote_address, + method: 'GET', + path: '/', + version: '1.1', + authority: 'host1.com', + headers: {'Host' => 'host1.com'}, + body: nil + ) end let(:http_request2) do - double('Async HTTP request2', method: 'GET', - path: '/dir/', - version: '1.1', - authority: 'host2.com', - headers: {'Host' => 'host2.com'}, - body: nil) + double( + 'Async HTTP request2', remote_address: remote_address, + method: 'GET', + path: '/dir/', + version: '1.1', + authority: 'host2.com', + headers: {'Host' => 'host2.com'}, + body: nil + ) end let(:http_request3) do - double('Async HTTP request3', method: 'GET', - path: '/dir/foo', - version: '1.1', - authority: 'host3.com', - headers: {'Host' => 'host3.com'}, - body: nil) + double( + 'Async HTTP request3', remote_address: remote_address, + method: 'GET', + path: '/dir/foo', + version: '1.1', + authority: 'host3.com', + headers: {'Host' => 'host3.com'}, + body: nil + ) end let(:yielded_request1) do Ronin::Listener::HTTP::Request.new( - method: http_request2.method, - version: http_request2.version, - headers: http_request2.headers, - path: http_request2.path + remote_addr: http_request2.remote_address, + method: http_request2.method, + version: http_request2.version, + headers: http_request2.headers, + path: http_request2.path ) end let(:yielded_request2) do Ronin::Listener::HTTP::Request.new( - method: http_request3.method, - version: http_request3.version, - headers: http_request3.headers, - path: http_request3.path + remote_addr: http_request2.remote_address, + method: http_request3.method, + version: http_request3.version, + headers: http_request3.headers, + path: http_request3.path ) end @@ -192,47 +213,58 @@ context "and #root is '/'" do let(:http_request1) do - double('Async HTTP request1', method: 'GET', - path: '/', - version: '1.1', - authority: 'other.com', - headers: {'Host' => 'other.com'}, - body: nil) + double( + 'Async HTTP request1', remote_address: remote_address, + method: 'GET', + path: '/', + version: '1.1', + authority: 'other.com', + headers: {'Host' => 'other.com'}, + body: nil + ) end let(:http_request2) do - double('Async HTTP request2', method: 'GET', - path: '/', - version: '1.1', - authority: 'example.com', - headers: {'Host' => 'example.com'}, - body: nil) + double( + 'Async HTTP request2', remote_address: remote_address, + method: 'GET', + path: '/', + version: '1.1', + authority: 'example.com', + headers: {'Host' => 'example.com'}, + body: nil + ) end let(:http_request3) do - double('Async HTTP request3', method: 'GET', - path: '/foo', - version: '1.1', - authority: 'example.com', - headers: {'Host' => 'example.com'}, - body: nil) + double( + 'Async HTTP request3', remote_address: remote_address, + method: 'GET', + path: '/foo', + version: '1.1', + authority: 'example.com', + headers: {'Host' => 'example.com'}, + body: nil + ) end let(:yielded_request1) do Ronin::Listener::HTTP::Request.new( - method: http_request2.method, - version: http_request2.version, - headers: http_request2.headers, - path: http_request2.path + remote_addr: http_request2.remote_address, + method: http_request2.method, + version: http_request2.version, + headers: http_request2.headers, + path: http_request2.path ) end let(:yielded_request2) do Ronin::Listener::HTTP::Request.new( - method: http_request3.method, - version: http_request3.version, - headers: http_request3.headers, - path: http_request3.path + remote_addr: http_request3.remote_address, + method: http_request3.method, + version: http_request3.version, + headers: http_request3.headers, + path: http_request3.path ) end @@ -248,38 +280,48 @@ context "and #root is not '/'" do let(:http_request1) do - double('Async HTTP request1', method: 'GET', - path: '/', - version: '1.1', - authority: 'other.com', - headers: {'Host' => 'other.com'}, - body: nil) + double( + 'Async HTTP request1', remote_address: remote_address, + method: 'GET', + path: '/', + version: '1.1', + authority: 'other.com', + headers: {'Host' => 'other.com'}, + body: nil + ) end let(:http_request2) do - double('Async HTTP request2', method: 'GET', - path: '/', - version: '1.1', - authority: 'example.com', - headers: {'Host' => 'example.com'}, - body: nil) + double( + 'Async HTTP request2', remote_address: remote_address, + method: 'GET', + path: '/', + version: '1.1', + authority: 'example.com', + headers: {'Host' => 'example.com'}, + body: nil + ) end let(:http_request3) do - double('Async HTTP request3', method: 'GET', - path: '/dir/foo', - version: '1.1', - authority: 'example.com', - headers: {'Host' => 'example.com'}, - body: nil) + double( + 'Async HTTP request3', remote_address: remote_address, + method: 'GET', + path: '/dir/foo', + version: '1.1', + authority: 'example.com', + headers: {'Host' => 'example.com'}, + body: nil + ) end let(:yielded_request) do Ronin::Listener::HTTP::Request.new( - method: http_request3.method, - version: http_request3.version, - headers: http_request3.headers, - path: http_request3.path + remote_addr: http_request3.remote_address, + method: http_request3.method, + version: http_request3.version, + headers: http_request3.headers, + path: http_request3.path ) end @@ -301,47 +343,58 @@ context "and #root is '/'" do let(:http_request1) do - double('Async HTTP request1', method: 'GET', - path: '/', - version: '1.1', - authority: 'other.com', - headers: {'Host' => 'other.com'}, - body: nil) + double( + 'Async HTTP request1', remote_address: remote_address, + method: 'GET', + path: '/', + version: '1.1', + authority: 'other.com', + headers: {'Host' => 'other.com'}, + body: nil + ) end let(:http_request2) do - double('Async HTTP request2', method: 'GET', - path: '/', - version: '1.1', - authority: 'foo.example.com', - headers: {'Host' => 'foo.example.com'}, - body: nil) + double( + 'Async HTTP request2', remote_address: remote_address, + method: 'GET', + path: '/', + version: '1.1', + authority: 'foo.example.com', + headers: {'Host' => 'foo.example.com'}, + body: nil + ) end let(:http_request3) do - double('Async HTTP request3', method: 'GET', - path: '/foo', - version: '1.1', - authority: 'bar.example.com', - headers: {'Host' => 'bar.example.com'}, - body: nil) + double( + 'Async HTTP request3', remote_address: remote_address, + method: 'GET', + path: '/foo', + version: '1.1', + authority: 'bar.example.com', + headers: {'Host' => 'bar.example.com'}, + body: nil + ) end let(:yielded_request1) do Ronin::Listener::HTTP::Request.new( - method: http_request2.method, - version: http_request2.version, - headers: http_request2.headers, - path: http_request2.path + remote_addr: http_request2.remote_address, + method: http_request2.method, + version: http_request2.version, + headers: http_request2.headers, + path: http_request2.path ) end let(:yielded_request2) do Ronin::Listener::HTTP::Request.new( - method: http_request3.method, - version: http_request3.version, - headers: http_request3.headers, - path: http_request3.path + remote_addr: http_request3.remote_address, + method: http_request3.method, + version: http_request3.version, + headers: http_request3.headers, + path: http_request3.path ) end @@ -357,38 +410,48 @@ context "and #root is not '/'" do let(:http_request1) do - double('Async HTTP request1', method: 'GET', - path: '/', - version: '1.1', - authority: 'other.com', - headers: {'Host' => 'bar.example.com'}, - body: nil) + double( + 'Async HTTP request1', remote_address: remote_address, + method: 'GET', + path: '/', + version: '1.1', + authority: 'other.com', + headers: {'Host' => 'bar.example.com'}, + body: nil + ) end let(:http_request2) do - double('Async HTTP request2', method: 'GET', - path: '/', - version: '1.1', - authority: 'foo.example.com', - headers: {'Host' => 'bar.example.com'}, - body: nil) + double( + 'Async HTTP request2', remote_address: remote_address, + method: 'GET', + path: '/', + version: '1.1', + authority: 'foo.example.com', + headers: {'Host' => 'bar.example.com'}, + body: nil + ) end let(:http_request3) do - double('Async HTTP request3', method: 'GET', - path: '/dir/foo', - version: '1.1', - authority: 'bar.example.com', - headers: {'Host' => 'bar.example.com'}, - body: nil) + double( + 'Async HTTP request3', remote_address: remote_address, + method: 'GET', + path: '/dir/foo', + version: '1.1', + authority: 'bar.example.com', + headers: {'Host' => 'bar.example.com'}, + body: nil + ) end let(:yielded_request) do Ronin::Listener::HTTP::Request.new( - method: http_request3.method, - version: http_request3.version, - headers: http_request3.headers, - path: http_request3.path + remote_addr: http_request3.remote_address, + method: http_request3.method, + version: http_request3.version, + headers: http_request3.headers, + path: http_request3.path ) end