raptor-io 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +30 -0
  3. data/README.md +51 -0
  4. data/lib/rack/handler/raptor-io.rb +130 -0
  5. data/lib/raptor-io.rb +11 -0
  6. data/lib/raptor-io/error.rb +19 -0
  7. data/lib/raptor-io/protocol.rb +6 -0
  8. data/lib/raptor-io/protocol/error.rb +10 -0
  9. data/lib/raptor-io/protocol/http.rb +34 -0
  10. data/lib/raptor-io/protocol/http/client.rb +685 -0
  11. data/lib/raptor-io/protocol/http/error.rb +16 -0
  12. data/lib/raptor-io/protocol/http/headers.rb +132 -0
  13. data/lib/raptor-io/protocol/http/message.rb +67 -0
  14. data/lib/raptor-io/protocol/http/request.rb +307 -0
  15. data/lib/raptor-io/protocol/http/request/manipulator.rb +117 -0
  16. data/lib/raptor-io/protocol/http/request/manipulators.rb +217 -0
  17. data/lib/raptor-io/protocol/http/request/manipulators/authenticator.rb +110 -0
  18. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/basic.rb +36 -0
  19. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/digest.rb +135 -0
  20. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/negotiate.rb +69 -0
  21. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/ntlm.rb +29 -0
  22. data/lib/raptor-io/protocol/http/request/manipulators/redirect_follower.rb +65 -0
  23. data/lib/raptor-io/protocol/http/response.rb +166 -0
  24. data/lib/raptor-io/protocol/http/server.rb +446 -0
  25. data/lib/raptor-io/ruby.rb +4 -0
  26. data/lib/raptor-io/ruby/hash.rb +24 -0
  27. data/lib/raptor-io/ruby/ipaddr.rb +15 -0
  28. data/lib/raptor-io/ruby/openssl.rb +23 -0
  29. data/lib/raptor-io/ruby/string.rb +27 -0
  30. data/lib/raptor-io/socket.rb +175 -0
  31. data/lib/raptor-io/socket/comm.rb +143 -0
  32. data/lib/raptor-io/socket/comm/local.rb +94 -0
  33. data/lib/raptor-io/socket/comm/sapni.rb +75 -0
  34. data/lib/raptor-io/socket/comm/socks.rb +237 -0
  35. data/lib/raptor-io/socket/comm_chain.rb +30 -0
  36. data/lib/raptor-io/socket/error.rb +45 -0
  37. data/lib/raptor-io/socket/switch_board.rb +183 -0
  38. data/lib/raptor-io/socket/switch_board/route.rb +42 -0
  39. data/lib/raptor-io/socket/tcp.rb +231 -0
  40. data/lib/raptor-io/socket/tcp/ssl.rb +77 -0
  41. data/lib/raptor-io/socket/tcp_server.rb +16 -0
  42. data/lib/raptor-io/socket/tcp_server/ssl.rb +52 -0
  43. data/lib/raptor-io/socket/udp.rb +0 -0
  44. data/lib/raptor-io/version.rb +6 -0
  45. data/lib/tasks/yard.rake +26 -0
  46. data/spec/rack/handler/raptor_spec.rb +140 -0
  47. data/spec/raptor-io/protocol/http/client_spec.rb +671 -0
  48. data/spec/raptor-io/protocol/http/headers_spec.rb +189 -0
  49. data/spec/raptor-io/protocol/http/message_spec.rb +5 -0
  50. data/spec/raptor-io/protocol/http/request/manipulators/authenticator_spec.rb +193 -0
  51. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/basic_spec.rb +32 -0
  52. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/digest_spec.rb +76 -0
  53. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/negotiate_spec.rb +52 -0
  54. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/ntlm_spec.rb +37 -0
  55. data/spec/raptor-io/protocol/http/request/manipulators/redirect_follower_spec.rb +51 -0
  56. data/spec/raptor-io/protocol/http/request/manipulators_spec.rb +202 -0
  57. data/spec/raptor-io/protocol/http/request_spec.rb +965 -0
  58. data/spec/raptor-io/protocol/http/response_spec.rb +236 -0
  59. data/spec/raptor-io/protocol/http/server_spec.rb +345 -0
  60. data/spec/raptor-io/ruby/hash_spec.rb +20 -0
  61. data/spec/raptor-io/ruby/string_spec.rb +20 -0
  62. data/spec/raptor-io/socket/comm/local_spec.rb +50 -0
  63. data/spec/raptor-io/socket/switch_board/route_spec.rb +49 -0
  64. data/spec/raptor-io/socket/switch_board_spec.rb +87 -0
  65. data/spec/raptor-io/socket/tcp/ssl_spec.rb +18 -0
  66. data/spec/raptor-io/socket/tcp_server/ssl_spec.rb +59 -0
  67. data/spec/raptor-io/socket/tcp_server_spec.rb +19 -0
  68. data/spec/raptor-io/socket/tcp_spec.rb +14 -0
  69. data/spec/raptor-io/socket_spec.rb +16 -0
  70. data/spec/raptor-io/version_spec.rb +10 -0
  71. data/spec/spec_helper.rb +56 -0
  72. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/manifoolators/fooer.rb +25 -0
  73. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/niccolo_machiavelli.rb +20 -0
  74. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/options_validator.rb +28 -0
  75. data/spec/support/fixtures/raptor/socket/ssl_server.crt +18 -0
  76. data/spec/support/fixtures/raptor/socket/ssl_server.key +15 -0
  77. data/spec/support/lib/path_helpers.rb +11 -0
  78. data/spec/support/lib/webserver_option_parser.rb +26 -0
  79. data/spec/support/lib/webservers.rb +120 -0
  80. data/spec/support/shared/contexts/with_ssl_server.rb +70 -0
  81. data/spec/support/shared/contexts/with_tcp_server.rb +58 -0
  82. data/spec/support/shared/examples/raptor/comm_examples.rb +26 -0
  83. data/spec/support/shared/examples/raptor/protocols/http/message.rb +106 -0
  84. data/spec/support/shared/examples/raptor/socket_examples.rb +135 -0
  85. data/spec/support/webservers/raptor/protocols/http/client.rb +100 -0
  86. data/spec/support/webservers/raptor/protocols/http/client_close_connection.rb +29 -0
  87. data/spec/support/webservers/raptor/protocols/http/client_https.rb +43 -0
  88. data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/basic.rb +9 -0
  89. data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/digest.rb +22 -0
  90. data/spec/support/webservers/raptor/protocols/http/request/manipulators/redirect_follower.rb +11 -0
  91. metadata +336 -0
@@ -0,0 +1,42 @@
1
+ require 'ipaddr'
2
+ require 'raptor-io/ruby/ipaddr'
3
+
4
+ #
5
+ # A logical switch board route.
6
+ #
7
+ class RaptorIO::Socket::SwitchBoard::Route
8
+ include Comparable
9
+
10
+ # @param subnet [String,IPAddr] The network associated with this
11
+ # route. If specified as a String, must be parseable by IPAddr.new
12
+ # @param netmask [String,IPAddr] `subnet`'s netmask. If specified as
13
+ # a String, must be parseable by IPAddr.new
14
+ # @param comm [Comm] The endpoint where sockets for this route
15
+ # should be created.
16
+ def initialize(subnet, netmask, comm)
17
+ self.netmask = IPAddr.parse(netmask)
18
+ self.subnet = IPAddr.parse(subnet).mask netmask.to_s
19
+ self.comm = comm
20
+ end
21
+
22
+ #
23
+ # For direct equality, make sure all the attributes are the same
24
+ #
25
+ def ==(other)
26
+ return false unless other.kind_of? RaptorIO::Socket::SwitchBoard::Route
27
+ netmask == other.netmask && subnet == other.subnet && comm == other.comm
28
+ end
29
+
30
+ #
31
+ # For comparison, sort according to netmask.
32
+ #
33
+ # This allows {Route routes} to be ordered by specificity
34
+ #
35
+ def <=>(other)
36
+ netmask <=> other.netmask
37
+ end
38
+
39
+ attr_reader :subnet, :netmask, :comm
40
+ protected
41
+ attr_writer :subnet, :netmask, :comm
42
+ end
@@ -0,0 +1,231 @@
1
+ # TCP client socket
2
+ class RaptorIO::Socket::TCP < RaptorIO::Socket
3
+
4
+ # Default configuration options.
5
+ DEFAULT_OPTIONS = {
6
+ connect_timeout: 5,
7
+ }
8
+
9
+ # Default options for SSL streams connected through this socket.
10
+ #
11
+ # @see #to_ssl
12
+ # @see TCP::SSL
13
+ DEFAULT_SSL_OPTIONS = {
14
+ ssl_version: :TLSv1,
15
+ ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE,
16
+ }
17
+
18
+ # @!attribute socket
19
+ # The underlying IO for this socket. Usually this is the
20
+ # `socket` passed to {#initialize}
21
+ # @return [IO]
22
+ attr_accessor :socket
23
+
24
+ # @param (see Socket#initialize)
25
+ def initialize(socket, options = {})
26
+ options = DEFAULT_OPTIONS.merge(options)
27
+ super
28
+ @plaintext_socket = @socket = socket
29
+ end
30
+
31
+ # @!method getpeername(string)
32
+ # Return a Sockaddr struct for the *socket*. Note that this is the
33
+ # @return [String] Sockaddr data.
34
+ def_delegator :@plaintext_socket, :getpeername, :getpeername
35
+
36
+ # @!method ungetc
37
+ # Pushes back one character onto the {#socket}'s read buffer. Note
38
+ # that some streams will *lose data* if this is called with a
39
+ # `string` larger than one byte or called more than once between
40
+ # calls to {#read}!
41
+ #
42
+ # @param string [String] A single-byte string
43
+ # @return [nil]
44
+ def_delegator :@socket, :ungetc, :ungetc
45
+
46
+ def remote_address
47
+ ::Addrinfo.new([ "AF_INET", options[:peer_port], options[:peer_host], options[:peer_host] ])
48
+ end
49
+
50
+ # Write `data` to the {#socket}.
51
+ #
52
+ # @param data [String,#to_s]
53
+ # @return [Fixnum]
54
+ def write(data)
55
+ begin
56
+ write_nonblock(data)
57
+ rescue IO::WaitWritable
58
+ IO.select(nil, [@socket])
59
+ retry
60
+ end
61
+ end
62
+
63
+ # Try to write `data` to the {#socket}.
64
+ #
65
+ # @param maxlen [Fixnum]
66
+ # @return [String]
67
+ def write_nonblock(data)
68
+ translate_errors do
69
+ @socket.write_nonblock(data)
70
+ end
71
+ end
72
+
73
+ # Read exactly `maxlen` bytes from the {#socket}. If fewer than
74
+ # `maxlen` bytes are available for reading, wait until enough data
75
+ # is sent.
76
+ #
77
+ # @note May block
78
+ #
79
+ # @param (see #read_nonblock)
80
+ # @return (see #read_nonblock)
81
+ def read(maxlen)
82
+ buf = ""
83
+ until 0 == maxlen
84
+ prev_length = buf.length
85
+ buf << readpartial(maxlen)
86
+ maxlen -= buf.length - prev_length
87
+ end
88
+ buf
89
+ end
90
+
91
+ # Read at most `maxlen` bytes from the {#socket}.
92
+ #
93
+ # @note May block
94
+ #
95
+ # @param (see #read_nonblock)
96
+ # @return (see #read_nonblock)
97
+ def readpartial(maxlen = nil)
98
+ begin
99
+ read_nonblock(maxlen)
100
+ rescue IO::WaitReadable
101
+ IO.select([@socket])
102
+ retry
103
+ end
104
+ end
105
+
106
+ # Read at most `maxlen` bytes from the {#socket}.
107
+ #
108
+ # @param maxlen [Fixnum]
109
+ # @return [String]
110
+ def read_nonblock(maxlen = nil)
111
+ translate_errors do
112
+ @socket.read_nonblock(maxlen)
113
+ end
114
+ end
115
+
116
+ # Ruby `Socket#gets` accepts:
117
+ #
118
+ # * `gets( sep = $/ )`
119
+ # * `gets( limit = nil )`
120
+ # * `gets( sep = $/, limit = nil )`
121
+ #
122
+ # `OpenSSL::SSL::SSLSocket#gets` however only supports `gets(sep=$/, limit=nil)`.
123
+ # This hack allows SSLSocket to behave the same as Ruby Socket.
124
+ #
125
+ # @note May block
126
+ def gets(*args)
127
+ translate_errors do
128
+ if args.size == 1
129
+ arg = args.first
130
+ if arg.is_a?(Numeric)
131
+ @socket.gets($/, arg)
132
+ else
133
+ @socket.gets(arg)
134
+ end
135
+ else
136
+ @socket.gets(*args)
137
+ end
138
+ end
139
+ end
140
+
141
+ # Close this socket. If this socket is an SSL stream, closes both the
142
+ # SSL stream and the underlying socket
143
+ #
144
+ # @return [void]
145
+ def close
146
+ begin
147
+ super
148
+ ensure
149
+ if (!@plaintext_socket.closed?)
150
+ @plaintext_socket.close
151
+ end
152
+ end
153
+ end
154
+
155
+ # Attempt to turn this into something usable by `IO.select`.
156
+ #
157
+ # @return [IO]
158
+ def to_io
159
+ IO.try_convert(@socket) || @socket
160
+ end
161
+
162
+ # Whether this socket is encrypted with SSL
163
+ def ssl?
164
+ !!(@socket.respond_to?(:context) && @socket.context)
165
+ end
166
+
167
+ # The version of SSL/TLS that was negotiated with the server.
168
+ #
169
+ # @return [String] See OpenSSL::SSL::SSLSocket#ssl_version for
170
+ # possible values
171
+ # @return [nil] If this socket is not SSL
172
+ def ssl_version
173
+ return nil unless ssl?
174
+ @socket.ssl_version
175
+ end
176
+
177
+ # @return [OpenSSL::SSL::SSLContext]
178
+ # @return [nil] If this socket is not SSL (see {#ssl?})
179
+ def ssl_context
180
+ return nil unless ssl?
181
+ @socket.context
182
+ end
183
+
184
+ # @note The original socket is replaced by the newly connected
185
+ # {TCP::SSL} socket
186
+ #
187
+ # Starts an SSL/TLS stream over this connection.
188
+ #
189
+ # Using this as opposed to directly instantiating {TCP::SSL} allows
190
+ # you to start a TLS connection after data has already been exchanged
191
+ # to enable things like `STARTTLS`.
192
+ #
193
+ # @note May block
194
+ #
195
+ # @param ssl_options [Hash] Options
196
+ # @option ssl_options :ssl_version [Symbol] (:TLSv1)
197
+ # @option ssl_options :ssl_verify_mode [Constant] (OpenSSL::SSL::VERIFY_PEER)
198
+ # Peer verification mode.
199
+ # @option ssl_config :ssl_context [OpenSSL::SSL::SSLContext] (nil)
200
+ # SSL context to use.
201
+ #
202
+ # @return [RaptorIO::Socket::TCP::SSL] A new Socket with an established
203
+ # SSL connection
204
+ def to_ssl(ssl_options = {})
205
+ if ssl_options[:ssl_context]
206
+ options[:ssl_context] = ssl_options[:ssl_context]
207
+ else
208
+ ssl_options = DEFAULT_SSL_OPTIONS.merge(ssl_options)
209
+ options[:ssl_context] = OpenSSL::SSL::SSLContext.new.tap do |ctx|
210
+ ctx.ssl_version = ssl_options[:ssl_version]
211
+ ctx.verify_mode = ssl_options[:ssl_verify_mode]
212
+ end
213
+ end
214
+
215
+ s = RaptorIO::Socket::TCP::SSL.new(@plaintext_socket, options)
216
+ @socket = s
217
+ s
218
+ end
219
+
220
+ private
221
+ attr_accessor :plaintext_socket
222
+
223
+ def translate_errors(&block)
224
+ yield
225
+ rescue Errno::ECONNRESET, Errno::EPIPE
226
+ raise RaptorIO::Socket::Error::BrokenPipe
227
+ rescue Errno::ECONNREFUSED
228
+ raise RaptorIO::Socket::Error::ConnectionRefused
229
+ end
230
+
231
+ end
@@ -0,0 +1,77 @@
1
+
2
+ # TCP client with SSL encryption.
3
+ #
4
+ # @author Tasos Laskos <tasos_laskos@rapid7.com>
5
+ class RaptorIO::Socket::TCP::SSL < RaptorIO::Socket::TCP
6
+
7
+ # Create a new {SSL} from an already-connected
8
+ # `OpenSSL::SSL::SSLSocket`.
9
+ #
10
+ # @example
11
+ # tcp_server = ::TCPServer.new()
12
+ # ssl_server = OpenSSL::SSL::SSLServer.new(tcp_server)
13
+ # RaptorIO::Socket::TCP::SSL.from_openssl(ssl_server.accept)
14
+ #
15
+ # @see TCPServer::SSL
16
+ # @param openssl_socket [OpenSSL::SSL::SSLSocket]
17
+ # @return [SSL]
18
+ def self.from_openssl(openssl_socket)
19
+ raptor = self.allocate
20
+ raptor.__send__(:socket=, openssl_socket)
21
+ raptor.__send__(:plaintext_socket=, openssl_socket.to_io)
22
+ raptor.options = {}
23
+ raptor.options[:ssl_context] = openssl_socket.context
24
+
25
+ raptor
26
+ end
27
+
28
+ # @!method ssl_context
29
+ # The SSL context for this encrypted stream.
30
+ #
31
+ # @return [OpenSSL::SSL::Context]
32
+ def_delegator :@socket, :ssl_context, :context
33
+
34
+ # @!method verify_mode
35
+ # @return [Fixnum] One of the `OpenSSL::SSL::VERIFY_*` constants
36
+ def_delegator :@socket, :ssl_verify_mode, :verify_mode
37
+
38
+ # @!method version
39
+ # @return [Symbol] SSL version.
40
+ def_delegator :@socket, :ssl_version, :version
41
+
42
+ # @param socket [RaptorIO::Socket]
43
+ # @param options [Hash] Options
44
+ # @option (see TCP#to_ssl)
45
+ def initialize( socket, options = {} )
46
+ options = DEFAULT_SSL_OPTIONS.merge( options )
47
+ super
48
+
49
+ @context = options[:context] || options[:ssl_context]
50
+
51
+ if @context.nil?
52
+ @context = OpenSSL::SSL::SSLContext.new( options[:ssl_version] )
53
+ @context.verify_mode = options[:ssl_verify_mode]
54
+ end
55
+
56
+ @socket = OpenSSL::SSL::SSLSocket.new(socket.to_io, @context)
57
+ begin
58
+ #$stderr.puts("#{self.class}#initialize connecting")
59
+ @socket.connect_nonblock
60
+ rescue IO::WaitReadable, IO::WaitWritable => e
61
+ #$stderr.puts("Wait*able #{e}, #{options[:connect_timeout].inspect}")
62
+ if e.kind_of? IO::WaitReadable
63
+ r,w,_ = IO.select([@socket], nil, nil, options[:connect_timeout])
64
+ else
65
+ r,w,_ = IO.select(nil, [@socket], nil, options[:connect_timeout])
66
+ end
67
+
68
+ if r.nil? && w.nil?
69
+ #$stderr.puts("timeout")
70
+ raise RaptorIO::Socket::Error::ConnectionTimeout.new(e.to_s)
71
+ end
72
+
73
+ retry
74
+ end
75
+ end
76
+
77
+ end
@@ -0,0 +1,16 @@
1
+ # A listening TCP socket
2
+ class RaptorIO::Socket::TCPServer < RaptorIO::Socket
3
+
4
+ # @!method accept
5
+ def_delegator :@socket, :accept, :accept
6
+
7
+ # @!method accept_nonblock
8
+ def_delegator :@socket, :accept_nonblock, :accept_nonblock
9
+
10
+ # @!method bind
11
+ def_delegator :@socket, :bind, :bind
12
+
13
+ # @!method listen
14
+ def_delegator :@socket, :listen, :listen
15
+
16
+ end
@@ -0,0 +1,52 @@
1
+ # TCP server with SSL encryption.
2
+ #
3
+ # @author Tasos Laskos <tasos_laskos@rapid7.com>
4
+ class RaptorIO::Socket::TCPServer::SSL < RaptorIO::Socket::TCPServer
5
+
6
+ def initialize( socket, options = {} )
7
+ #p options[:context].frozen?
8
+ super
9
+ #p options[:context].frozen?
10
+
11
+ @context = options[:context]
12
+ if @context.nil?
13
+ @context = OpenSSL::SSL::SSLContext.new( options[:ssl_version] )
14
+ @context.verify_mode = options[:verify_mode]
15
+ end
16
+
17
+ @plaintext_socket = socket
18
+ @socket = OpenSSL::SSL::SSLServer.new( socket, @context )
19
+ end
20
+
21
+ # Accepts a client connection.
22
+ #
23
+ # @see Socket::TCP::SSL.from_openssl
24
+ # @return [RaptorIO::Socket::TCP::SSL]
25
+ def accept
26
+ RaptorIO::Socket::TCP::SSL.from_openssl(@socket.accept)
27
+ end
28
+
29
+ # Accepts a client connection without blocking.
30
+ #
31
+ # @see Socket::TCP::SSL.from_openssl
32
+ # @return [RaptorIO::Socket::TCP::SSL]
33
+ # @raise [IO::WaitWritable]
34
+ def accept_nonblock
35
+ RaptorIO::Socket::TCP::SSL.from_openssl(@socket.accept_nonblock)
36
+ end
37
+
38
+ # Close this SSL stream and the underlying socket
39
+ #
40
+ # @return [void]
41
+ def close
42
+ begin
43
+ @socket.close
44
+ ensure
45
+ if (!@plaintext_socket.closed?)
46
+ @plaintext_socket.close
47
+ end
48
+ end
49
+ end
50
+
51
+
52
+ end
File without changes
@@ -0,0 +1,6 @@
1
+ module RaptorIO
2
+
3
+ # Version number.
4
+ VERSION = '0.0.1'
5
+
6
+ end
@@ -0,0 +1,26 @@
1
+ # @note All options not specific to any given rake task should go in the .yardopts file so they are available to both
2
+ # the below rake tasks and when invoking `yard` from the command line
3
+
4
+ if defined? YARD
5
+ namespace :yard do
6
+ YARD::Rake::YardocTask.new(:doc) do |t|
7
+ # --no-stats here as 'stats' task called after will print fuller stats
8
+ t.options = ['--no-stats']
9
+
10
+ t.after = Proc.new {
11
+ Rake::Task['yard:stats'].execute
12
+ }
13
+ end
14
+
15
+ desc "Shows stats for YARD Documentation including listing undocumented modules, classes, constants, and methods"
16
+ task :stats => :environment do
17
+ stats = YARD::CLI::Stats.new
18
+ stats.run('--compact', '--list-undoc')
19
+ end
20
+ end
21
+
22
+ # @todo Figure out how to just clone description from yard:doc
23
+ desc "Generate YARD documentation"
24
+ # allow calling namespace to as a task that goes to default task for namespace
25
+ task :yard => ['yard:doc']
26
+ end