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,189 @@
1
+ require 'spec_helper'
2
+
3
+ describe RaptorIO::Protocol::HTTP::Headers do
4
+
5
+ describe '#to_s' do
6
+ it 'supports multiple headers' do
7
+ headers = described_class.new( 'X-Stuff' => %w(1 2 3) )
8
+ headers.to_s.should == "X-Stuff: 1\r\nX-Stuff: 2\r\nX-Stuff: 3"
9
+ end
10
+
11
+ it 'formats headers for HTTP transmission' do
12
+ options = {
13
+ 'x-morE-stUfF' => 'blah'
14
+ }
15
+ described_class.new( options ).to_s.should ==
16
+ "X-More-Stuff: blah"
17
+ end
18
+ end
19
+
20
+ describe '#delete' do
21
+ it 'deleted a header field' do
22
+ h = described_class.new( 'x-my-field' => 'stuff' )
23
+ h.delete( 'X-My-Field' ).should == 'stuff'
24
+ end
25
+ end
26
+
27
+ describe '#include?' do
28
+ context 'when the field is included' do
29
+ it 'returns true' do
30
+ h = described_class.new( 'X-My-Field' => 'stuff' )
31
+ h.include?( 'x-my-field' ).should be_true
32
+ end
33
+ end
34
+ context 'when the field is not included' do
35
+ it 'returns false' do
36
+ described_class.new.include?( 'x-my-field' ).should be_false
37
+ end
38
+ end
39
+ end
40
+
41
+ describe '#set_cookie' do
42
+ context 'when there are no set-cookie fields' do
43
+ it 'returns an empty array' do
44
+ described_class.new.cookies.should == []
45
+ end
46
+ end
47
+
48
+ it 'returns an array of set-cookie strings' do
49
+ set_coookies = [
50
+ 'name=value; Expires=Wed, 09 Jun 2020 10:18:14 GMT',
51
+ 'name2=value2; Expires=Wed, 09 Jun 2021 10:18:14 GMT'
52
+ ]
53
+
54
+ described_class.new( 'Set-Cookie' => set_coookies ).set_cookie.should == set_coookies
55
+ end
56
+ end
57
+
58
+ describe '#parsed_set_cookie' do
59
+ context 'when there are no Set-cookie fields' do
60
+ it 'returns an empty array' do
61
+ described_class.new.parsed_set_cookie.should == []
62
+ end
63
+ end
64
+
65
+ it 'returns an array of cookies as hashes' do
66
+ described_class.new(
67
+ 'Set-Cookie' => [
68
+ 'name=value; Expires=Wed, 09 Jun 2020 10:18:14 GMT',
69
+ 'name2=value2; Expires=Wed, 09 Jun 2021 10:18:14 GMT'
70
+ ]
71
+ ).parsed_set_cookie.should == [
72
+ {
73
+ name: 'name',
74
+ value: 'value',
75
+ version: 0,
76
+ port: nil,
77
+ discard: nil,
78
+ comment_url: nil,
79
+ expires: Time.parse( '2020-06-09 13:18:14 +0300' ),
80
+ max_age: nil,
81
+ comment: nil,
82
+ secure: nil,
83
+ path: nil,
84
+ domain: nil
85
+ },
86
+ {
87
+ name: 'name2',
88
+ value: 'value2',
89
+ version: 0,
90
+ port: nil,
91
+ discard: nil,
92
+ comment_url: nil,
93
+ expires: Time.parse( '2021-06-09 13:18:14 +0300' ),
94
+ max_age: nil,
95
+ comment: nil,
96
+ secure: nil,
97
+ path: nil,
98
+ domain: nil
99
+ }
100
+ ]
101
+ end
102
+ end
103
+
104
+ describe '#cookies' do
105
+ context 'when there is no Cookie fied' do
106
+ it 'returns an empty array' do
107
+ described_class.new.cookies.should == []
108
+ end
109
+ end
110
+
111
+ it 'returns an array of cookies as hashes' do
112
+ described_class.new(
113
+ 'Cookie' => 'cname=cvalue; c2name=c2value'
114
+ ).cookies.should == [
115
+ {
116
+ name: 'cname',
117
+ value: 'cvalue',
118
+ version: 0,
119
+ port: nil,
120
+ discard: nil,
121
+ comment_url: nil,
122
+ expires: nil,
123
+ max_age: nil,
124
+ comment: nil,
125
+ secure: nil,
126
+ path: nil,
127
+ domain: nil
128
+ },
129
+ {
130
+ name: 'c2name',
131
+ value: 'c2value',
132
+ version: 0,
133
+ port: nil,
134
+ discard: nil,
135
+ comment_url: nil,
136
+ expires: nil,
137
+ max_age: nil,
138
+ comment: nil,
139
+ secure: nil,
140
+ path: nil,
141
+ domain: nil
142
+ }
143
+ ]
144
+ end
145
+ end
146
+
147
+ describe '.parse' do
148
+ context 'when passed an empty string' do
149
+ it 'returns empty Headers' do
150
+ described_class.parse( '' ).should be_empty
151
+ end
152
+ end
153
+
154
+ it 'supports multiple headers' do
155
+ headers_string = "X-Stuff: 1\r\nX-Stuff: 2\r\n\r\n"
156
+
157
+ headers = described_class.parse( headers_string )
158
+ headers['x-stuff'].should == %w(1 2)
159
+ headers.class.should == described_class
160
+ end
161
+
162
+ it 'supports CRLF terminators' do
163
+ headers_string = "content-Type: text/html;charset=utf-8\r\n" +
164
+ "Content-length: 431\r\n\r\n"
165
+
166
+ headers = described_class.parse( headers_string )
167
+ headers.should ==
168
+ {
169
+ 'Content-Type' => 'text/html;charset=utf-8',
170
+ 'Content-Length' => '431'
171
+ }
172
+ headers.class.should == described_class
173
+ end
174
+
175
+ it 'supports CR terminators' do
176
+ headers_string = "content-Type: text/html;charset=utf-8\n" +
177
+ "Content-length: 431\n\n"
178
+
179
+ headers = described_class.parse( headers_string )
180
+ headers.should ==
181
+ {
182
+ 'Content-Type' => 'text/html;charset=utf-8',
183
+ 'Content-Length' => '431'
184
+ }
185
+ headers.class.should == described_class
186
+ end
187
+ end
188
+
189
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe RaptorIO::Protocol::HTTP::Message do
4
+ it_should_behave_like 'RaptorIO::Protocol::HTTP::Message'
5
+ end
@@ -0,0 +1,193 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'RaptorIO::Protocol::HTTP::Request::Manipulators::Authenticator' do
4
+ before :all do
5
+ WebServers.start :basic
6
+ @basic_url = WebServers.url_for( :basic )
7
+
8
+ WebServers.start :digest
9
+ @digest_url = WebServers.url_for( :digest )
10
+
11
+ @iis_address = ENV['IIS']
12
+ @ntlm_url = "http://#{@iis_address}/ntlm/"
13
+ @negotiate_url = "http://#{@iis_address}/negotiate/"
14
+ end
15
+
16
+ before( :each ) do
17
+ RaptorIO::Protocol::HTTP::Request::Manipulators.reset
18
+ RaptorIO::Protocol::HTTP::Client.reset
19
+ end
20
+
21
+
22
+ let(:client) do
23
+ RaptorIO::Protocol::HTTP::Client.new(
24
+ switch_board: RaptorIO::Socket::SwitchBoard.new,
25
+ manipulators: {
26
+ 'authenticator' =>
27
+ {
28
+ username: 'admin',
29
+ password: 'secret'
30
+ }
31
+ }
32
+ )
33
+ end
34
+
35
+ context 'when authentication is of type' do
36
+ context 'Basic' do
37
+ it 'provides Basic authentication' do
38
+ opts = { mode: :sync }
39
+ 2.times do
40
+ client.get( @basic_url, opts ).code.should == 200
41
+ end
42
+ end
43
+ end
44
+
45
+ context 'Digest' do
46
+ it 'provides Digest authentication' do
47
+ opts = { mode: :sync }
48
+
49
+ 2.times do
50
+ client.get( @digest_url, opts ).code.should == 200
51
+ end
52
+ end
53
+
54
+ it 'doesn\'t run any queued request until the auth finishes' do
55
+ cnt = 0
56
+
57
+ 50.times do |i|
58
+ client.get( @digest_url ) do |response|
59
+ response.code.should == 200
60
+ cnt += 1
61
+ end
62
+ end
63
+
64
+ client.run
65
+ client.datastore['authenticator'][:tries].should == 1
66
+ cnt.should == 50
67
+ end
68
+ end
69
+
70
+ context 'Negotiate' do
71
+ let(:client) do
72
+ RaptorIO::Protocol::HTTP::Client.new(
73
+ manipulators: {
74
+ 'authenticator' =>
75
+ {
76
+ username: 'msfadmin',
77
+ password: 'msfadmin'
78
+ }
79
+ }
80
+ )
81
+ end
82
+
83
+ it 'provides Negotiate authentication' do
84
+ pending if !ENV['IIS']
85
+
86
+ opts = { mode: :sync }
87
+
88
+ 2.times do
89
+ client.get( @negotiate_url, opts ).code.should == 200
90
+ end
91
+ end
92
+
93
+ it 'doesn\'t run any queued request until the auth finishes' do
94
+ pending if !ENV['IIS']
95
+
96
+ cnt = 0
97
+
98
+ 50.times do |i|
99
+ client.get( @negotiate_url ) do |response|
100
+ response.code.should == 200
101
+ cnt += 1
102
+ end
103
+ end
104
+
105
+ client.run
106
+ cnt.should == 50
107
+ end
108
+
109
+ context 'on wrong credentials' do
110
+ it 'returns a 401' do
111
+ pending if !ENV['IIS']
112
+
113
+ opts = {
114
+ mode: :sync, manipulators: {
115
+ 'authenticator' =>
116
+ {
117
+ username: 'blah',
118
+ password: 'blah'
119
+ }
120
+ }
121
+ }
122
+
123
+ 2.times do
124
+ client.get( @negotiate_url, opts ).code.should == 401
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ context 'NTLM' do
131
+ let(:client) do
132
+ RaptorIO::Protocol::HTTP::Client.new(
133
+ manipulators: {
134
+ 'authenticator' =>
135
+ {
136
+ username: 'msfadmin',
137
+ password: 'msfadmin'
138
+ }
139
+ }
140
+ )
141
+ end
142
+
143
+ it 'provides NTLM authentication' do
144
+ pending if !ENV['IIS']
145
+
146
+ opts = { mode: :sync }
147
+
148
+ 2.times do
149
+ client.get( @ntlm_url, opts ).code.should == 200
150
+ end
151
+ end
152
+
153
+ it 'doesn\'t run any queued request until the auth finishes' do
154
+ pending if !ENV['IIS']
155
+
156
+ cnt = 0
157
+
158
+ 50.times do |i|
159
+ client.get( @ntlm_url ) do |response|
160
+ response.code.should == 200
161
+ cnt += 1
162
+ end
163
+ end
164
+
165
+ client.run
166
+ cnt.should == 50
167
+ end
168
+
169
+ context 'on wrong credentials' do
170
+ it 'returns a 401' do
171
+ pending if !ENV['IIS']
172
+
173
+ opts = {
174
+ mode: :sync, manipulators: {
175
+ 'authenticator' =>
176
+ {
177
+ username: 'blah',
178
+ password: 'blah'
179
+ }
180
+ }
181
+ }
182
+
183
+ 2.times do
184
+ client.get( @ntlm_url, opts ).code.should == 401
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ end
191
+
192
+ end
193
+
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+ require 'raptor-io/socket'
3
+
4
+ describe 'RaptorIO::Protocol::HTTP::Request::Manipulators::Authenticators::Basic' do
5
+ before :all do
6
+ WebServers.start :basic
7
+ @url = WebServers.url_for( :basic )
8
+ end
9
+
10
+ before( :each ) do
11
+ RaptorIO::Protocol::HTTP::Request::Manipulators.reset
12
+ end
13
+
14
+ let(:client) { RaptorIO::Protocol::HTTP::Client.new(switch_board:RaptorIO::Socket::SwitchBoard.new) }
15
+
16
+ it 'provides Basic authentication' do
17
+ opts = {
18
+ mode: :sync, manipulators: {
19
+ 'authenticators/basic' =>
20
+ {
21
+ username: 'admin',
22
+ password: 'secret'
23
+ }
24
+ }
25
+ }
26
+
27
+ 2.times do
28
+ client.get( @url, opts ).code.should == 200
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+ require 'raptor-io/socket'
3
+
4
+ describe 'RaptorIO::Protocol::HTTP::Request::Manipulators::Authenticators::Digest' do
5
+ before :all do
6
+ WebServers.start :digest
7
+ @url = WebServers.url_for( :digest )
8
+ end
9
+
10
+ let( :manipulators ) { RaptorIO::Protocol::HTTP::Request::Manipulators }
11
+ before( :each ) do
12
+ RaptorIO::Protocol::HTTP::Request::Manipulators.reset
13
+ end
14
+
15
+ let(:client) do
16
+ sb = RaptorIO::Socket::SwitchBoard.new
17
+ RaptorIO::Protocol::HTTP::Client.new(switch_board: sb)
18
+ end
19
+
20
+ def response( algo )
21
+ RaptorIO::Protocol::HTTP::Response.parse "HTTP/1.1 401 Unauthorized
22
+ Content-Type: text/plain
23
+ Content-Length: 0
24
+ Www-Authenticate: Digest realm=\"Protected Area\", algorithm=\"#{algo}\", nonce=\"MTM3MzI5OTYxNiAxYzk2ZDM4OWY1MTY2ZGM3ODllNGQ2N2RjZDIyYzk1ZA==\", opaque=\"610a2ee688cda9e724885e23cd2cfdee\", qop=\"auth\"
25
+ Connection: keep-alive
26
+ Server: thin 1.5.1 codename Straight Razor\r\n\r\n"
27
+ end
28
+
29
+ it 'provides Digest authentication' do
30
+ opts = {
31
+ mode: :sync, manipulators: {
32
+ 'authenticators/digest' =>
33
+ {
34
+ response: client.get( @url, mode: :sync ),
35
+ username: 'admin',
36
+ password: 'secret'
37
+ }
38
+ }
39
+ }
40
+
41
+ 2.times do
42
+ client.get( @url, opts ).code.should == 200
43
+ end
44
+ end
45
+
46
+ %w(MD5 SHA1 SHA2 SHA256 SHA384 SHA512 RMD160).each do |algo|
47
+ it "supports #{algo}" do
48
+ manipulators.process(
49
+ 'authenticators/digest',
50
+ client,
51
+ RaptorIO::Protocol::HTTP::Request.new( url: @url ),
52
+ {
53
+ response: response( algo ),
54
+ username: 'admin',
55
+ password: 'secret'
56
+ }
57
+ )
58
+ end
59
+ end
60
+
61
+ it 'raises error on unknown algorithm' do
62
+ expect do
63
+ manipulators.process(
64
+ 'authenticators/digest',
65
+ client,
66
+ RaptorIO::Protocol::HTTP::Request.new( url: @url ),
67
+ {
68
+ response: response( 'stuff' ),
69
+ username: 'admin',
70
+ password: 'secret'
71
+ }
72
+ )
73
+ end.to raise_error RaptorIO::Protocol::HTTP::Error
74
+ end
75
+ end
76
+