dap 0.1.3 → 0.1.4
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.
- checksums.yaml +4 -4
- data/lib/dap/filter/http.rb +75 -44
- data/lib/dap/version.rb +1 -1
- data/spec/dap/filter/http_filter_spec.rb +13 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1cc592fe131d06b96989baa2072f92ac1c513465
|
4
|
+
data.tar.gz: 2550d569f533d19d43c05f7a1e778e111f4c96e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a162fc9fb0ae6f9caa0ac9fe1b4c6a3437eccb083d4c6127b334da28906b88584f06ef49ddd3f3a760c844cefe030c90141c6933359e5f44b18b21ba37c1f15
|
7
|
+
data.tar.gz: bbecce1d8ae4a2b1edcbc0e89c282fc04066431954a99520a80a910980dab9b552d4346aaf0016039885a5582d0c7c78c93a092be892a09cbd7f12e93d49532d
|
data/lib/dap/filter/http.rb
CHANGED
@@ -134,18 +134,77 @@ end
|
|
134
134
|
class FilterDecodeHTTPReply
|
135
135
|
include BaseDecoder
|
136
136
|
|
137
|
-
# TODO: Decode transfer-chunked responses
|
138
137
|
def decode(data)
|
139
138
|
lines = data.split(/\r?\n/)
|
140
|
-
resp
|
139
|
+
resp = lines.shift
|
141
140
|
save = {}
|
142
141
|
return save if resp !~ /^HTTP\/\d+\.\d+\s+(\d+)(?:\s+(.*))?/
|
143
142
|
|
144
143
|
save["http_code"] = $1.to_i
|
145
144
|
save["http_message"] = ($2 ? $2.strip : '')
|
146
145
|
save["http_raw_headers"] = {}
|
146
|
+
save.merge!(parse_headers(lines))
|
147
147
|
|
148
|
-
|
148
|
+
head, raw_body = data.split(/\r?\n\r?\n/, 2)
|
149
|
+
|
150
|
+
# Some buggy systems exclude the header entirely
|
151
|
+
raw_body ||= head
|
152
|
+
|
153
|
+
save["http_raw_body"] = [raw_body].pack("m*").gsub(/\s+/n, "")
|
154
|
+
body = raw_body
|
155
|
+
|
156
|
+
transfer_encoding = save["http_raw_headers"]["transfer-encoding"]
|
157
|
+
if transfer_encoding && transfer_encoding.include?("chunked")
|
158
|
+
offset = 0
|
159
|
+
body = ''
|
160
|
+
while (true)
|
161
|
+
# read the chunk size from where we currently are. The chunk size will
|
162
|
+
# be specified in hex, at the beginning, and is followed by \r\n.
|
163
|
+
if /^(?<chunk_size_str>[a-z0-9]+)\r\n/i =~ raw_body.slice(offset, raw_body.size)
|
164
|
+
# convert chunk size
|
165
|
+
chunk_size = chunk_size_str.to_i(16)
|
166
|
+
# advance past this chunk marker and its trailing \r\n
|
167
|
+
offset += chunk_size_str.size + 2
|
168
|
+
# read this chunk, starting from just past the chunk marker and
|
169
|
+
# stopping at the supposed end of the chunk
|
170
|
+
body << raw_body.slice(offset, chunk_size)
|
171
|
+
# advance the offset to past the end of the chunk and its trailing \r\n
|
172
|
+
offset += chunk_size + 2
|
173
|
+
else
|
174
|
+
break
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# chunked-encoding allows headers to occur after the chunks, so parse those
|
179
|
+
if offset < raw_body.size
|
180
|
+
save.merge!(parse_headers(raw_body.slice(offset, raw_body.size).split(/\r?\n/)))
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
content_encoding = save["http_raw_headers"]["content-encoding"]
|
185
|
+
if content_encoding && content_encoding.include?("gzip")
|
186
|
+
begin
|
187
|
+
gunzip = Zlib::GzipReader.new(StringIO.new(body))
|
188
|
+
body = gunzip.read.encode('UTF-8', :invalid=>:replace, :replace=>'?')
|
189
|
+
gunzip.close()
|
190
|
+
rescue
|
191
|
+
end
|
192
|
+
end
|
193
|
+
save["http_body"] = body
|
194
|
+
|
195
|
+
if body =~ /<title>([^>]+)</mi
|
196
|
+
save["http_title"] = $1.strip
|
197
|
+
end
|
198
|
+
|
199
|
+
save
|
200
|
+
end
|
201
|
+
|
202
|
+
def valid_header_name?(name)
|
203
|
+
return name !~ /[\x00-\x1f()<>@,;:\\\"\/\[\]?={}\s]/
|
204
|
+
end
|
205
|
+
|
206
|
+
def parse_headers(lines)
|
207
|
+
headers = {}
|
149
208
|
|
150
209
|
while lines.length > 0
|
151
210
|
hline = lines.shift
|
@@ -154,41 +213,41 @@ class FilterDecodeHTTPReply
|
|
154
213
|
header_name.downcase!
|
155
214
|
|
156
215
|
if valid_header_name?(header_name)
|
157
|
-
|
158
|
-
|
159
|
-
|
216
|
+
headers["http_raw_headers"] ||= {}
|
217
|
+
headers["http_raw_headers"][header_name] ||= []
|
218
|
+
headers["http_raw_headers"][header_name] << header_value
|
160
219
|
|
161
220
|
# XXX: warning, all of these mishandle duplicate headers
|
162
221
|
case header_name
|
163
222
|
when 'etag'
|
164
|
-
|
223
|
+
headers["http_etag"] = header_value
|
165
224
|
|
166
225
|
when 'set-cookie'
|
167
226
|
bits = header_value.gsub(/\;?\s*path=.*/i, '').gsub(/\;?\s*expires=.*/i, '').gsub(/\;\s*HttpOnly.*/, '')
|
168
|
-
|
227
|
+
headers["http_cookie"] = bits
|
169
228
|
|
170
229
|
when 'server'
|
171
|
-
|
230
|
+
headers["http_server"] = header_value
|
172
231
|
|
173
232
|
when 'x-powered-by'
|
174
|
-
|
233
|
+
headers["http_powered"] = header_value
|
175
234
|
|
176
235
|
when 'date'
|
177
236
|
d = DateTime.parse(header_value) rescue nil
|
178
|
-
|
237
|
+
headers["http_date"] = d.to_time.utc.strftime("%Y%m%dT%H:%M:%S%z") if d
|
179
238
|
|
180
239
|
when 'last-modified'
|
181
240
|
d = DateTime.parse(header_value) rescue nil
|
182
|
-
|
241
|
+
headers["http_modified"] = d.to_time.utc.strftime("%Y%m%dT%H:%M:%S%z") if d
|
183
242
|
|
184
243
|
when 'location'
|
185
|
-
|
244
|
+
headers["http_location"] = header_value
|
186
245
|
|
187
246
|
when 'www-authenticate'
|
188
|
-
|
247
|
+
headers["http_auth"] = header_value
|
189
248
|
|
190
249
|
when 'content-length'
|
191
|
-
|
250
|
+
headers["content-length"] = header_value.to_i
|
192
251
|
end
|
193
252
|
else
|
194
253
|
# not a valid header. XXX, eventually we should log or do something more useful here
|
@@ -198,36 +257,8 @@ class FilterDecodeHTTPReply
|
|
198
257
|
end
|
199
258
|
end
|
200
259
|
|
201
|
-
|
202
|
-
|
203
|
-
# Some buggy systems exclude the header entirely
|
204
|
-
body ||= head
|
205
|
-
|
206
|
-
save["http_raw_body"] = [body].pack("m*").gsub(/\s+/n, "")
|
207
|
-
|
208
|
-
content_encoding = save["http_raw_headers"]["content-encoding"]
|
209
|
-
|
210
|
-
if content_encoding && content_encoding.include?("gzip")
|
211
|
-
begin
|
212
|
-
gunzip = Zlib::GzipReader.new(StringIO.new(body))
|
213
|
-
body = gunzip.read.encode('UTF-8', :invalid=>:replace, :replace=>'?')
|
214
|
-
gunzip.close()
|
215
|
-
rescue
|
216
|
-
end
|
217
|
-
end
|
218
|
-
save["http_body"] = body
|
219
|
-
|
220
|
-
if body =~ /<title>([^>]+)</mi
|
221
|
-
save["http_title"] = $1.strip
|
222
|
-
end
|
223
|
-
|
224
|
-
save
|
225
|
-
end
|
226
|
-
|
227
|
-
def valid_header_name?(name)
|
228
|
-
return name !~ /[\x00-\x1f()<>@,;:\\\"\/\[\]?={}\s]/
|
260
|
+
return headers
|
229
261
|
end
|
230
262
|
end
|
231
|
-
|
232
263
|
end
|
233
264
|
end
|
data/lib/dap/version.rb
CHANGED
@@ -72,6 +72,19 @@ describe Dap::Filter::FilterDecodeHTTPReply do
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
+
context 'decoding chunked response' do
|
76
|
+
let(:body) { "5\r\nabcde\r\n0F\r\nfghijklmnopqrst\r\n06\r\nuvwxyz\r\n0\r\n" }
|
77
|
+
let(:decode) { filter.decode("HTTP/1.0 200 OK\r\nTransfer-encoding: chunked\r\n\r\n#{body}\r\nSecret: magic\r\n") }
|
78
|
+
|
79
|
+
it 'correctly dechunks body' do
|
80
|
+
expect(decode['http_body']).to eq(('a'..'z').to_a.join)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'finds trailing headers' do
|
84
|
+
expect(decode['http_raw_headers']['secret']).to eq(%w(magic))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
75
88
|
context 'decoding responses that are missing the "reason phrase", an RFC anomaly' do
|
76
89
|
let(:decode) { filter.decode("HTTP/1.1 301\r\nDate: Tue, 28 Mar 2017 20:46:52 GMT\r\nContent-Type: text/html\r\nContent-Length: 177\r\nConnection: close\r\nLocation: http://www.example.com/\r\n\r\nstuff") }
|
77
90
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rapid7 Research
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03-
|
11
|
+
date: 2017-03-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|