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