From: duerst@...Date: 2016-11-04T21:23:23+00:00Subject: [ruby-core:77933] [Ruby trunk Bug#12890] Net::HTTP should treat unexpected 1XX responses as non-final.Issue #12890 has been updated by Martin D��rst.A testing endpoint is available at https://nghttp2.org/?103-eh (see https://lists.w3.org/Archives/Public/ietf-http-wg/2016OctDec/0408_html).----------------------------------------Bug #12890: Net::HTTP should treat unexpected 1XX responses as non-final.https://bugs.ruby-lang.org/issues/12890#change-61252* Author: Cory Benfield* Status: Open* Priority: Normal* Assignee:* ruby -v: ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin16]* Backport: 2.1: UNKNOWN, 2.2: UNKNOWN, 2.3: UNKNOWN----------------------------------------Long story shortNet::HTTP's client does not tolerate non-100 status codes from the 1XX block in a sensible manner: it treats them as final responses, rather than as provisional ones.Expected behaviourWhen Net::HTTP receives a 1XX status code that it does not recognise, it should either surface these to a user in such a way that allows the user to access the subsequent final response, or it should ignore them and only show the user the eventual final status code. This is required by RFC 7231 Section 6.2 (Informational 1xx), which reads:> A client MUST be able to parse one or more 1xx responses received prior to a final response, even if the client does not expect one. A user agent MAY ignore unexpected 1xx responses.Actual behaviourNet::HTTP treats the 1XX status code as final. It parses the header block as though it were a response in the 2XX or higher range. Net::HTTP rightly assumes that 1XX responses have no body, which means that it leaves the following 2XX response unconsumed in the socket buffer, which will hide critical data from the user.Steps to reproduceThe following Python "server" can demonstrate the issue:~~~ pythonimport socketimport timedocument = b''' title

Hello, world!

'''s = socket.socket()s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)s.bind(('localhost', 8080))s.listen(5)while True: new_socket, _ = s.accept() data = b'' while not data.endswith(b'\r\n\r\n'): data += new_socket.recv(8192) new_socket.sendall( b'HTTP/1.1 103 Early Hints\r\n' b'Server: socketserver/1.0.0\r\n' b'Link: ; rel=preload; as=style\r\n' b'Link: ; rel=preload; as=script\r\n' b'\r\n' ) time.sleep(1) new_socket.sendall( b'HTTP/1.1 200 OK\r\n' b'Server: socketserver/1.0.0\r\n' b'Content-Type: text/html\r\n' b'Content-Length: %s\r\n' b'Link: ; rel=preload; as=style\r\n' b'Link: ; rel=preload; as=script\r\n' b'Connection: close\r\n' b'\r\n' % len(document) ) new_socket.sendall(document) new_socket.close()~~~If this server is run directly, the following client can be used to test it:~~~ rubyrequire 'net/http'uri = URI('http://localhost:8080/')res = Net::HTTP.get_response(uri)puts "Status: #{res.code}"puts "Body: #{res.body}"~~~This client will print~~~Status: 103Body:~~~Note that the client has treated the 1XX response as final, and has left the 200 response unconsumed in the socket.---Files--------------------------------patch1.diff (2.17 KB)--https://bugs.ruby-lang.org/Unsubscribe: