raptor-io 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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