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,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