File: test_http11.rb

package info (click to toggle)
puma 6.6.0-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,884 kB
  • sloc: ruby: 17,542; ansic: 2,003; java: 1,006; sh: 379; makefile: 10
file content (285 lines) | stat: -rw-r--r-- 9,321 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# Copyright (c) 2011 Evan Phoenix
# Copyright (c) 2005 Zed A. Shaw

require_relative "helper"
require_relative "helpers/integration"
require "digest"

require "puma/puma_http11"

class Http11ParserTest < TestIntegration

  parallelize_me!

  def test_parse_simple
    parser = Puma::HttpParser.new
    req = {}
    http = "GET /?a=1 HTTP/1.1\r\n\r\n"
    nread = parser.execute(req, http, 0)

    assert nread == http.length, "Failed to parse the full HTTP request"
    assert parser.finished?, "Parser didn't finish"
    assert !parser.error?, "Parser had error"
    assert nread == parser.nread, "Number read returned from execute does not match"

    assert_equal '/', req['REQUEST_PATH']
    assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
    assert_equal '/?a=1', req['REQUEST_URI']
    assert_equal 'GET', req['REQUEST_METHOD']
    assert_nil req['FRAGMENT']
    assert_equal "a=1", req['QUERY_STRING']

    parser.reset
    assert parser.nread == 0, "Number read after reset should be 0"
  end

  def test_parse_escaping_in_query
    parser = Puma::HttpParser.new
    req = {}
    http = "GET /admin/users?search=%27%%27 HTTP/1.1\r\n\r\n"
    nread = parser.execute(req, http, 0)

    assert nread == http.length, "Failed to parse the full HTTP request"
    assert parser.finished?, "Parser didn't finish"
    assert !parser.error?, "Parser had error"
    assert nread == parser.nread, "Number read returned from execute does not match"

    assert_equal '/admin/users?search=%27%%27', req['REQUEST_URI']
    assert_equal "search=%27%%27", req['QUERY_STRING']

    parser.reset
    assert parser.nread == 0, "Number read after reset should be 0"
  end

  def test_parse_absolute_uri
    parser = Puma::HttpParser.new
    req = {}
    http = "GET http://192.168.1.96:3000/api/v1/matches/test?1=1 HTTP/1.1\r\n\r\n"
    nread = parser.execute(req, http, 0)

    assert nread == http.length, "Failed to parse the full HTTP request"
    assert parser.finished?, "Parser didn't finish"
    assert !parser.error?, "Parser had error"
    assert nread == parser.nread, "Number read returned from execute does not match"

    assert_equal "GET", req['REQUEST_METHOD']
    assert_equal 'http://192.168.1.96:3000/api/v1/matches/test?1=1', req['REQUEST_URI']
    assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']

    assert_nil req['REQUEST_PATH']
    assert_nil req['FRAGMENT']
    assert_nil req['QUERY_STRING']

    parser.reset
    assert parser.nread == 0, "Number read after reset should be 0"

  end

  def test_parse_dumbfuck_headers
    parser = Puma::HttpParser.new
    req = {}
    should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
    nread = parser.execute(req, should_be_good, 0)
    assert_equal should_be_good.length, nread
    assert parser.finished?
    assert !parser.error?
  end

  def test_parse_error
    parser = Puma::HttpParser.new
    req = {}
    bad_http = "GET / SsUTF/1.1"

    error = false
    begin
      parser.execute(req, bad_http, 0)
    rescue
      error = true
    end

    assert error, "failed to throw exception"
    assert !parser.finished?, "Parser shouldn't be finished"
    assert parser.error?, "Parser SHOULD have error"
  end

  def test_fragment_in_uri
    parser = Puma::HttpParser.new
    req = {}
    get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"

    parser.execute(req, get, 0)

    assert parser.finished?
    assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
    assert_equal 'posts-17408', req['FRAGMENT']
  end

  def test_semicolon_in_path
    parser = Puma::HttpParser.new
    req = {}
    get = "GET /forums/1/path;stillpath/2375?page=1 HTTP/1.1\r\n\r\n"

    parser.execute(req, get, 0)

    assert parser.finished?
    assert_equal '/forums/1/path;stillpath/2375?page=1', req['REQUEST_URI']
    assert_equal '/forums/1/path;stillpath/2375', req['REQUEST_PATH']
  end

  # lame random garbage maker
  def rand_data(min, max, readable=true)
    count = min + ((rand(max)+1) *10).to_i
    res = count.to_s + "/"

    if readable
      res << Digest(:SHA1).hexdigest(rand(count * 100).to_s) * (count / 40)
    else
      res << Digest(:SHA1).digest(rand(count * 100).to_s) * (count / 20)
    end

    res
  end

  def test_get_const_length
    skip_unless :jruby

    envs = %w[PUMA_REQUEST_URI_MAX_LENGTH PUMA_REQUEST_PATH_MAX_LENGTH PUMA_QUERY_STRING_MAX_LENGTH]
    default_exp = [1024 * 12, 8192, 10 * 1024]
    tests = [{ envs: %w[60000 61000 62000], exp: [60000, 61000, 62000], error_indexes: [] },
             { envs: ['', 'abc', nil], exp: default_exp, error_indexes: [1] },
             { envs: %w[-4000 0 3000.45], exp: default_exp, error_indexes: [0, 1, 2] }]
    cli_config = <<~CONFIG
        app do |_|
          require 'json'
          [200, {}, [{ MAX_REQUEST_URI_LENGTH: org.jruby.puma.Http11::MAX_REQUEST_URI_LENGTH,
                       MAX_REQUEST_PATH_LENGTH: org.jruby.puma.Http11::MAX_REQUEST_PATH_LENGTH,
                       MAX_QUERY_STRING_LENGTH: org.jruby.puma.Http11::MAX_QUERY_STRING_LENGTH,
                       MAX_REQUEST_URI_LENGTH_ERR: org.jruby.puma.Http11::MAX_REQUEST_URI_LENGTH_ERR,
                       MAX_REQUEST_PATH_LENGTH_ERR: org.jruby.puma.Http11::MAX_REQUEST_PATH_LENGTH_ERR,
                       MAX_QUERY_STRING_LENGTH_ERR: org.jruby.puma.Http11::MAX_QUERY_STRING_LENGTH_ERR }.to_json]]
        end
    CONFIG

    tests.each do |conf|
      cli_server 'test/rackup/hello.ru',
                      env: {envs[0]  => conf[:envs][0], envs[1] => conf[:envs][1], envs[2] => conf[:envs][2]},
                      merge_err: true,
                      config: cli_config
      result = JSON.parse read_body(connect)

      assert_equal conf[:exp][0], result['MAX_REQUEST_URI_LENGTH']
      assert_equal conf[:exp][1], result['MAX_REQUEST_PATH_LENGTH']
      assert_equal conf[:exp][2], result['MAX_QUERY_STRING_LENGTH']

      assert_includes result['MAX_REQUEST_URI_LENGTH_ERR'], "longer than the #{conf[:exp][0]} allowed length"
      assert_includes result['MAX_REQUEST_PATH_LENGTH_ERR'], "longer than the #{conf[:exp][1]} allowed length"
      assert_includes result['MAX_QUERY_STRING_LENGTH_ERR'], "longer than the #{conf[:exp][2]} allowed length"

      conf[:error_indexes].each do |index|
        assert_includes @server_log, "The value #{conf[:envs][index]} for #{envs[index]} is invalid. "\
          "Using default value #{default_exp[index]} instead"
      end

      stop_server
     end
  end

  def test_max_uri_path_length
    parser = Puma::HttpParser.new
    req = {}

    # Support URI path length to a max of 8192
    path = "/" + rand_data(7000, 100)
    http = "GET #{path} HTTP/1.1\r\n\r\n"
    parser.execute(req, http, 0)
    assert_equal path, req['REQUEST_PATH']
    parser.reset

    # Raise exception if URI path length > 8192
    path = "/" + rand_data(9000, 100)
    http = "GET #{path} HTTP/1.1\r\n\r\n"
    assert_raises Puma::HttpParserError do
      parser.execute(req, http, 0)
      parser.reset
    end
  end

  def test_horrible_queries
    parser = Puma::HttpParser.new

    # then that large header names are caught
    10.times do |c|
      get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
      assert_raises Puma::HttpParserError do
        parser.execute({}, get, 0)
        parser.reset
      end
    end

    # then that large mangled field values are caught
    10.times do |c|
      get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
      assert_raises Puma::HttpParserError do
        parser.execute({}, get, 0)
        parser.reset
      end
    end

    # then large headers are rejected too
    get  = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
    get += "X-Test: test\r\n" * (80 * 1024)
    assert_raises Puma::HttpParserError do
      parser.execute({}, get, 0)
      parser.reset
    end

    # finally just that random garbage gets blocked all the time
    10.times do |c|
      get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
      assert_raises Puma::HttpParserError do
        parser.execute({}, get, 0)
        parser.reset
      end
    end
  end

  def test_trims_whitespace_from_headers
    parser = Puma::HttpParser.new
    req = {}
    http = "GET / HTTP/1.1\r\nX-Strip-Me: Strip This       \r\n\r\n"

    parser.execute(req, http, 0)

    assert_equal "Strip This", req["HTTP_X_STRIP_ME"]
  end

  def test_newline_smuggler
    parser = Puma::HttpParser.new
    req = {}
    http = "GET / HTTP/1.1\r\nHost: localhost:8080\r\nDummy: x\nDummy2: y\r\n\r\n"

    parser.execute(req, http, 0) rescue nil # We test the raise elsewhere.

    assert parser.error?, "Parser SHOULD have error"
  end

  def test_newline_smuggler_two
    parser = Puma::HttpParser.new
    req = {}
    http = "GET / HTTP/1.1\r\nHost: localhost:8080\r\nDummy: x\r\nDummy: y\nDummy2: z\r\n\r\n"

    parser.execute(req, http, 0) rescue nil

    assert parser.error?, "Parser SHOULD have error"
  end

  def test_htab_in_header_val
    parser = Puma::HttpParser.new
    req = {}
    http = "GET / HTTP/1.1\r\nHost: localhost:8080\r\nDummy: Valid\tValue\r\n\r\n"

    parser.execute(req, http, 0)

    assert_equal "Valid\tValue", req['HTTP_DUMMY']
  end
end