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,117 @@
1
+ module RaptorIO
2
+ module Protocol::HTTP
3
+
4
+ class Request
5
+
6
+ # Base manipulator class, all manipulator components should inherit from it.
7
+ #
8
+ # @author Tasos Laskos <tasos_laskos@rapid7.com>
9
+ # @abstract
10
+ class Manipulator
11
+
12
+ #
13
+ # {HTTP::Request::Manipulator} error namespace.
14
+ #
15
+ # All {HTTP::Request::Manipulator} errors inherit from and live under it.
16
+ #
17
+ # @author Tasos "Zapotek" Laskos
18
+ #
19
+ class Error < Request::Error
20
+
21
+ # Indicates invalid options for a manipulator.
22
+ #
23
+ # @author Tasos Laskos
24
+ class InvalidOptions < Error
25
+ end
26
+
27
+ end
28
+
29
+ # @return [HTTP::Client] Current HTTP client instance.
30
+ attr_reader :client
31
+
32
+ # @return [HTTP::Request] Request to manipulate.
33
+ attr_reader :request
34
+
35
+ # @return [Hash] Manipulator options.
36
+ attr_reader :options
37
+
38
+ # @param [HTTP::Client] client
39
+ # HTTP client which will handle the request.
40
+ # @param [HTTP::Request] request
41
+ # Request to process.
42
+ def initialize( client, request, options = {} )
43
+ @client = client
44
+ @request = request
45
+ @options = options
46
+ end
47
+
48
+ # Delivers the manipulator's payload.
49
+ # @abstract
50
+ def run
51
+ end
52
+
53
+ # Delegates the work to another manipulator.
54
+ #
55
+ # @param [Symbol] manipulator Manipulator to run.
56
+ # @param [Hash] opts Manipulator options.
57
+ def delegate( manipulator, opts = options )
58
+ Request::Manipulators.process( manipulator, client, request, opts )
59
+ end
60
+
61
+ # @return [Hash] Persistent storage -- per {HTTP::Client} instance.
62
+ def datastore
63
+ client.datastore[shortname]
64
+ end
65
+
66
+ # @return [String] Shortname of `self`.
67
+ def shortname
68
+ self.class.shortname
69
+ end
70
+
71
+ # @return [Hash{Symbol=>Array<String>}]
72
+ # Option names keys for and error messages for values.
73
+ def validate_options
74
+ self.class.validate_options!( options, client )
75
+ end
76
+
77
+ class <<self
78
+
79
+ # @return [Hash{Symbol=>Array<String>}]
80
+ # Option names keys for and error messages for values.
81
+ def validate_options( &block )
82
+ fail ArgumentError, 'Missing block.' if !block_given?
83
+ @validator = block
84
+ end
85
+
86
+ #
87
+ # @param [Hash] options Manipulator options.
88
+ # @param [HTTP::Client] client Applicable client.
89
+ #
90
+ # @return [Hash{Symbol=>Array<String>}]
91
+ # Option names keys for and error messages for values.
92
+ #
93
+ # @abstract
94
+ def validate_options!( options, client )
95
+ @validator ? @validator.call( options, client ) : {}
96
+ end
97
+
98
+ # @return [String] Shortname of `self`.
99
+ def shortname
100
+ @shortname ||= Request::Manipulators.class_to_name( self )
101
+ end
102
+
103
+ # Registers manipulators which inherit from this class.
104
+ #
105
+ # @see Request::Manipulators#register
106
+ def inherited( manipulator_klass )
107
+ Request::Manipulators.register(
108
+ Request::Manipulators.path_to_name( caller.first.split( ':' ).first ),
109
+ manipulator_klass
110
+ )
111
+ end
112
+ end
113
+
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,217 @@
1
+ module RaptorIO
2
+ module Protocol::HTTP
3
+
4
+ class Request
5
+
6
+ require_relative 'manipulator'
7
+
8
+ # Namespace holding all Request manipulators and providing some helper methods
9
+ # for management.
10
+ #
11
+ # @author Tasos Laskos <tasos_laskos@rapid7.com>
12
+ module Manipulators
13
+
14
+ class <<self
15
+ include Enumerable
16
+
17
+ # @return [String] Directory of the manipulators' repository.
18
+ attr_reader :library
19
+
20
+ # @param [String] manipulator
21
+ # Manipulator to run -- will be loaded if needed.
22
+ # @param [HTTP::Client] client
23
+ # Applicable client.
24
+ # @param [HTTP::Request] request
25
+ # Request to process.
26
+ # @param [Hash] options
27
+ # Manipulator options.
28
+ def process( manipulator, client, request, options = {} )
29
+ load( manipulator ).new( client, request, options ).run
30
+ end
31
+
32
+ # Performs batch validation of manipulator options.
33
+ #
34
+ # @param [Hash{String=>Hash}] manipulators
35
+ # Manipulators for keys and their options as values.
36
+ # @param [HTTP::Client] client
37
+ # Applicable client.
38
+ #
39
+ # @return [Hash{String=>Hash}]
40
+ # Manipulators for keys and error hashes as values.
41
+ #
42
+ def validate_batch_options( manipulators, client )
43
+ errors = {}
44
+ manipulators.each do |manipulator, options|
45
+ errors[manipulator] =
46
+ validate_options( manipulator, options, client )
47
+ end
48
+ errors.reject { |_, errs| errs.empty? }
49
+ end
50
+
51
+ # Same as {.validate_batch_options} but raises exception on errors.
52
+ def validate_batch_options!( manipulators, client )
53
+ errors = validate_batch_options( manipulators, client )
54
+ if errors.any?
55
+ fail Request::Manipulator::Error::InvalidOptions, errors.to_s
56
+ end
57
+ nil
58
+ end
59
+
60
+ # @param [String] manipulator
61
+ # @param [Hash] options Manipulator options.
62
+ # @param [HTTP::Client] client Applicable client.
63
+ #
64
+ # @return [Hash{Symbol=>Array<String>}]
65
+ # Option names keys for and error messages for values.
66
+ def validate_options( manipulator, options, client )
67
+ load( manipulator ).validate_options!( options, client )
68
+ end
69
+
70
+ # @param [String] directory Directory including manipulators.
71
+ def library=( directory )
72
+ @library = File.expand_path( directory ) + '/'
73
+ end
74
+
75
+ # @return [Array<String>] Paths of all manipulators.
76
+ def paths
77
+ Dir.glob( "#{library}**/*.rb" )
78
+ end
79
+
80
+ # @return [Array<Symbol>] Names of all manipulators.
81
+ def available
82
+ paths.map { |path| path_to_name path }
83
+ end
84
+
85
+ # @param [Symbol] manipulator
86
+ # Loads a manipulator by name.
87
+ #
88
+ # @return [Class] Loaded manipulator.
89
+ def load( manipulator )
90
+ manipulator = normalize_name( manipulator )
91
+ return @manipulators[manipulator] if @manipulators.include? manipulator
92
+
93
+ Kernel.load name_to_path( manipulator )
94
+ @manipulators[manipulator]
95
+ end
96
+
97
+ # Loads all manipulators.
98
+ #
99
+ # @return [Hash] All manipulators.
100
+ def load_all
101
+ paths.each { |path| load path_to_name( path ) }
102
+ loaded
103
+ end
104
+
105
+ # @param [Symbol] manipulator
106
+ # Unloads a manipulator by name.
107
+ #
108
+ # @return [Bool]
109
+ # `true` if the manipulator was unloaded successfully, `false` if no
110
+ # matching one was found.
111
+ def unload( manipulator )
112
+ klass = @manipulators.delete( normalize_name( manipulator ) )
113
+ return false if !klass
114
+
115
+ container = self
116
+ klass.to_s.gsub( "#{self}::", '' ).split( '::' )[0...-1].each do |c|
117
+ container = container.const_get( c.to_sym )
118
+ end
119
+
120
+ container.instance_eval do
121
+ remove_const klass.to_s.split( ':' ).last.to_sym
122
+ end
123
+
124
+ # Remove the container namespaces themselves if they're now empty.
125
+ container = self
126
+ klass.to_s.gsub( "#{self}::", '' ).split( '::' )[0...-1].each do |c|
127
+ container = container.const_get( c.to_sym )
128
+ if container != self && container.constants.empty?
129
+ remove_const container.to_s.split( ':' ).last.to_sym
130
+ end
131
+ end
132
+
133
+ true
134
+ end
135
+
136
+ # Unloads all manipulators.
137
+ def unload_all
138
+ @manipulators.keys.each { |manipulator| unload manipulator }
139
+ nil
140
+ end
141
+
142
+ # @param [Block] block
143
+ # Block to be passed each manipulator name=>class.
144
+ # @return [Enumerator, Manipulators]
145
+ # `Enumerator` if no `block` is given, `self` otherwise.
146
+ def each( &block )
147
+ return enum_for( __method__ ) if !block_given?
148
+ @manipulators.each( &block )
149
+ self
150
+ end
151
+
152
+ # @return [Hash] All manipulators as a frozen hash.
153
+ def loaded
154
+ @manipulators.dup.freeze
155
+ end
156
+
157
+ # Registers a manipulator.
158
+ #
159
+ # @param [Symbol] name
160
+ # @param [Base] klass
161
+ #
162
+ # @return [Manipulator] `self`
163
+ #
164
+ # @private
165
+ def register( name, klass )
166
+ @manipulators[normalize_name( name )] = klass
167
+ self
168
+ end
169
+
170
+ # Resets the manipulators by unloading all and settings the {#library} to
171
+ # its default setting.
172
+ def reset
173
+ unload_all if @manipulators
174
+
175
+ @library = File.expand_path( File.dirname( __FILE__ ) + '/manipulators' ) + '/'
176
+ @manipulators = {}
177
+ end
178
+
179
+ # @param [String] name Manipulator name.
180
+ # @return [Bool] `true` if the given manipulator exists, `false` otherwise.
181
+ def exist?( name )
182
+ File.exist? name_to_path( name )
183
+ end
184
+
185
+ # @param [String] path FS path to a manipulator.
186
+ # @return [String] Manipulator shortname.
187
+ def path_to_name( path )
188
+ normalize_name path.gsub( library, '' ).gsub( /(.+)\.rb$/, '\1' )
189
+ end
190
+
191
+ # @param [Class] klass Manipulator class.
192
+ # @return [String, nil]
193
+ # Manipulator shortname, `nil` if the manipulator isn't loaded.
194
+ def class_to_name( klass )
195
+ @manipulators.select { |name, k| return name if k == klass }
196
+ nil
197
+ end
198
+
199
+ # @param [String] name Manipulator shortname.
200
+ # @return [String] Manipulator FS path.
201
+ def name_to_path( name )
202
+ File.expand_path "#{library}/#{name}.rb"
203
+ end
204
+
205
+ # @param [String, Symbol] name Manipulator name.
206
+ # @return [String] Manipulator name.
207
+ def normalize_name( name )
208
+ name.to_s
209
+ end
210
+ end
211
+
212
+ reset
213
+
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,110 @@
1
+ module RaptorIO
2
+ module Protocol::HTTP
3
+ class Request
4
+
5
+ module Manipulators
6
+
7
+ # Namespace for all authenticator manipulators.
8
+ module Authenticators
9
+ end
10
+
11
+ #
12
+ # Implements automatic HTTP authentication.
13
+ #
14
+ # @author Tasos Laskos
15
+ #
16
+ class Authenticator < Manipulator
17
+
18
+ validate_options do |options, _|
19
+ errors = {}
20
+ next errors if options[:skip]
21
+
22
+ [:username, :password].each do |option|
23
+ errors[option] = [ "Can't be blank." ] if options[option].to_s.empty?
24
+ end
25
+
26
+ errors
27
+ end
28
+
29
+ def run
30
+ datastore[:tries] ||= 0
31
+ return if skip?
32
+
33
+ callbacks = request.callbacks.dup
34
+ request.clear_callbacks
35
+
36
+ # We need to block until authentication is complete, that's why we requeue
37
+ # and run.
38
+
39
+ requeue
40
+ request.on_complete do |response|
41
+ auth_type = type( response )
42
+
43
+ if !failed? && response.code == 401 && supported?( auth_type )
44
+ retry_with_auth( auth_type, response )
45
+ else
46
+ request.callbacks = callbacks
47
+ request.handle_response response
48
+ request.clear_callbacks
49
+ end
50
+ end
51
+ client.run
52
+ end
53
+
54
+ private
55
+
56
+ # @note Set by one of the authenticators, not `self`.
57
+ # @return [Bool] `true` if authentication failed, `false` otherwise.
58
+ def failed?
59
+ !!datastore[:failed]
60
+ end
61
+
62
+ # Retries the request with authentication.
63
+ #
64
+ # @param [Symbol] type Authenticator to use.
65
+ # @param [RaptorIO::Protocol::HTTP::Response] response
66
+ # Response signaling the need to authenticate.
67
+ def retry_with_auth( type, response )
68
+ datastore[:tries] += 1
69
+
70
+ remove_client_authenticators if ![:ntlm, :negotiate].include?( type )
71
+ client.manipulators.merge!({
72
+ "authenticators/#{type}" => options.merge( response: response )
73
+ })
74
+ requeue
75
+ end
76
+
77
+ # Requeues the request after the proper authenticator has been enabled.
78
+ def requeue
79
+ client.queue( request, shortname => { skip: true } )
80
+ end
81
+
82
+ # @param [RaptorIO::Protocol::HTTP::Response] response
83
+ # Response signaling the need to authenticate.
84
+ # @return [Symbol] Authentication type.
85
+ def type( response )
86
+ response.headers['www-authenticate'].to_s.split( ' ' ).first.to_s.downcase.to_s.to_sym
87
+ end
88
+
89
+ def skip?
90
+ failed? || !!options[:skip]
91
+ end
92
+
93
+ # @param [Symbol] type Authentication type to check.
94
+ # @return [Bool]
95
+ # `true` if the authentication `type` is supported, `false` otherwise.
96
+ def supported?( type )
97
+ Request::Manipulators.exist? "authenticators/#{type}"
98
+ end
99
+
100
+ # Removes all enabled authenticators.
101
+ def remove_client_authenticators
102
+ client.manipulators.reject!{ |k, _| k.start_with? 'authenticator' }
103
+ end
104
+
105
+ end
106
+
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,36 @@
1
+ module RaptorIO
2
+ module Protocol::HTTP
3
+ class Request
4
+
5
+ module Manipulators
6
+ module Authenticators
7
+
8
+ #
9
+ # Implements HTTP Basic authentication.
10
+ #
11
+ # @author Tasos Laskos
12
+ #
13
+ class Basic < Manipulator
14
+
15
+ def run
16
+ request.headers['Authorization'] =
17
+ "Basic #{Base64.encode64("#{username}:#{password}").chomp}"
18
+ end
19
+
20
+ private
21
+
22
+ def username
23
+ options[:username]
24
+ end
25
+
26
+ def password
27
+ options[:password]
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end