Skip to content

Commit 2b9450f

Browse files
committed
Handle empty meta tag attributes during encoding detection
Additionally, handle malformed metatags by assuming no encoding inside of raising a RuntimeError. Fixes #311
1 parent 6f43d6e commit 2b9450f

2 files changed

Lines changed: 108 additions & 34 deletions

File tree

lib/net/http/response.rb

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -472,40 +472,42 @@ def check_bom(str)
472472
def scanning_meta(str)
473473
require 'strscan'
474474
ss = StringScanner.new(str)
475-
if ss.scan_until(/<meta[\t\n\f\r ]*/)
476-
attrs = {} # attribute_list
477-
got_pragma = false
478-
need_pragma = nil
479-
charset = nil
480-
481-
# step: Attributes
482-
while attr = get_attribute(ss)
483-
name, value = *attr
484-
next if attrs[name]
485-
attrs[name] = true
486-
case name
487-
when 'http-equiv'
488-
got_pragma = true if value == 'content-type'
489-
when 'content'
490-
encoding = extracting_encodings_from_meta_elements(value)
491-
unless charset
492-
charset = encoding
475+
catch(:invalid_meta_tag) do
476+
if ss.scan_until(/<meta[\t\n\f\r ]*/)
477+
attrs = {} # attribute_list
478+
got_pragma = false
479+
need_pragma = nil
480+
charset = nil
481+
482+
# step: Attributes
483+
while attr = get_attribute(ss)
484+
name, value = *attr
485+
next if attrs[name]
486+
attrs[name] = true
487+
case name
488+
when 'http-equiv'
489+
got_pragma = true if value == 'content-type'
490+
when 'content'
491+
encoding = extracting_encodings_from_meta_elements(value)
492+
unless charset
493+
charset = encoding
494+
end
495+
need_pragma = true
496+
when 'charset'
497+
need_pragma = false
498+
charset = value
493499
end
494-
need_pragma = true
495-
when 'charset'
496-
need_pragma = false
497-
charset = value
498500
end
499-
end
500501

501-
# step: Processing
502-
return if need_pragma.nil?
503-
return if need_pragma && !got_pragma
502+
# step: Processing
503+
return if need_pragma.nil?
504+
return if need_pragma && !got_pragma
504505

505-
charset = Encoding.find(charset) rescue nil
506-
return unless charset
507-
charset = Encoding::UTF_8 if charset == Encoding::UTF_16
508-
return charset # tentative
506+
charset = Encoding.find(charset) rescue nil
507+
return unless charset
508+
charset = Encoding::UTF_8 if charset == Encoding::UTF_16
509+
return charset # tentative
510+
end
509511
end
510512
nil
511513
end
@@ -518,7 +520,7 @@ def get_attribute(ss)
518520
end
519521
name = ss.scan(/[^=\t\n\f\r \/>]*/)
520522
name.downcase!
521-
raise if name.empty?
523+
throw :invalid_meta_tag if name.empty?
522524
ss.skip(/[\t\n\f\r ]*/)
523525
if ss.getch != '='
524526
value = ''
@@ -528,18 +530,18 @@ def get_attribute(ss)
528530
case ss.peek(1)
529531
when '"'
530532
ss.getch
531-
value = ss.scan(/[^"]+/)
533+
value = ss.scan(/[^"]*/)
532534
value.downcase!
533535
ss.getch
534536
when "'"
535537
ss.getch
536-
value = ss.scan(/[^']+/)
538+
value = ss.scan(/[^']*/)
537539
value.downcase!
538540
ss.getch
539541
when '>'
540542
value = ''
541543
else
542-
value = ss.scan(/[^\t\n\f\r >]+/)
544+
throw :invalid_meta_tag unless value = ss.scan(/[^\t\n\f\r >]+/)
543545
value.downcase!
544546
end
545547
[name, value]

test/net/http/test_httpresponse.rb

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,78 @@ def test_read_body_body_encoding_true_with_iso8859_1_meta_content_charset
313313
assert_equal Encoding::ISO_8859_1, body.encoding
314314
end
315315

316+
def test_read_body_body_encoding_with_empty_attribute
317+
res_body = "<meta http-equiv='content-type' empty1='' empty2="" content='text/html; charset=UTF-8'>hello\u1234</html>"
318+
io = dummy_io(<<EOS)
319+
HTTP/1.1 200 OK
320+
Connection: close
321+
Content-Length: #{res_body.bytesize}
322+
Content-Type: text/html
323+
324+
#{res_body}
325+
EOS
326+
327+
res = Net::HTTPResponse.read_new(io)
328+
res.body_encoding = true
329+
330+
body = nil
331+
332+
res.reading_body io, true do
333+
body = res.read_body
334+
end
335+
336+
assert_equal res_body, body
337+
assert_equal Encoding::UTF_8, body.encoding
338+
end
339+
340+
def test_read_body_body_encoding_unclosed_meta_tag_in_attribute
341+
res_body = "<meta http-equiv='content-type'"
342+
io = dummy_io(<<EOS)
343+
HTTP/1.1 200 OK
344+
Connection: close
345+
Content-Length: #{res_body.bytesize}
346+
Content-Type: text/html
347+
348+
#{res_body}
349+
EOS
350+
351+
res = Net::HTTPResponse.read_new(io)
352+
res.body_encoding = true
353+
354+
body = nil
355+
356+
res.reading_body io, true do
357+
body = res.read_body
358+
end
359+
360+
assert_equal res_body, body
361+
assert_equal Encoding::US_ASCII, body.encoding
362+
end
363+
364+
def test_read_body_body_encoding_unclosed_meta_tag_in_attribute_value
365+
res_body = "<meta http-equiv='content-type' bad="
366+
io = dummy_io(<<EOS)
367+
HTTP/1.1 200 OK
368+
Connection: close
369+
Content-Length: #{res_body.bytesize}
370+
Content-Type: text/html
371+
372+
#{res_body}
373+
EOS
374+
375+
res = Net::HTTPResponse.read_new(io)
376+
res.body_encoding = true
377+
378+
body = nil
379+
380+
res.reading_body io, true do
381+
body = res.read_body
382+
end
383+
384+
assert_equal res_body, body
385+
assert_equal Encoding::US_ASCII, body.encoding
386+
end
387+
316388
def test_read_body_block
317389
io = dummy_io(<<EOS)
318390
HTTP/1.1 200 OK

0 commit comments

Comments
 (0)