Edit

kc3-lang/kc3/test/http/10_request_framing.kc3

Branch :

  • test/http/10_request_framing.kc3
  • quote require HTTP
    require HTTP
    quote require HTTP.Request
    require HTTP.Request
    quote require Socket
    require Socket
    quote require Socket.Buf
    require Socket.Buf
    quote require Thread
    require Thread
    
    quote (server = Socket.listen("127.0.0.1", "57004")) ; void
    (server = Socket.listen("127.0.0.1", "57004")) ; void
    quote (client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
    (client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
    quote (server_client = Socket.Buf.accept(server)) ; void
    (server_client = Socket.Buf.accept(server)) ; void
    
    # An opaque body is preserved and cannot become the next request.
    quote Buf.write_str(client.buf_rw.w,
      "POST /json HTTP/1.1\r\nContent-Type: application/json\r\nContent-Length: 7\r\n\r\n{\"x\":1}GET /next HTTP/1.1\r\n\r\n")
    Buf.write_str(client.buf_rw.w,
      "POST /json HTTP/1.1\r\nContent-Type: application/json\r\nContent-Length: 7\r\n\r\n{\"x\":1}GET /next HTTP/1.1\r\n\r\n")
    quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    request.body
    
    quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    {request.method, request.url}
    
    # Multipart parsing is bounded and leaves the following request intact.
    quote Buf.write_str(client.buf_rw.w,
      "POST /multipart HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=X\r\nContent-Length: 67\r\n\r\n--X\r\nContent-Disposition: form-data; name=\"field\"\r\n\r\nvalue\r\n--X--\r\nGET /after-multipart HTTP/1.1\r\n\r\n")
    Buf.write_str(client.buf_rw.w,
      "POST /multipart HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=X\r\nContent-Length: 67\r\n\r\n--X\r\nContent-Disposition: form-data; name=\"field\"\r\n\r\nvalue\r\n--X--\r\nGET /after-multipart HTTP/1.1\r\n\r\n")
    quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    request.body
    quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    {request.method, request.url}
    
    # A body without Content-Type is still consumed and preserved.
    quote Buf.write_str(client.buf_rw.w,
      "POST /raw HTTP/1.1\r\nContent-Length: 3\r\n\r\nabcGET /after-raw HTTP/1.1\r\n\r\n")
    Buf.write_str(client.buf_rw.w,
      "POST /raw HTTP/1.1\r\nContent-Length: 3\r\n\r\nabcGET /after-raw HTTP/1.1\r\n\r\n")
    quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    request.body
    quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    {request.method, request.url}
    
    # Bodies larger than the 64 KiB socket buffer are read in chunks.
    quote (chunk = "a") ; void
    (chunk = "a") ; void
    quote (chunk = chunk + chunk) ; void
    (chunk = chunk + chunk) ; void
    quote (chunk = chunk + chunk) ; void
    (chunk = chunk + chunk) ; void
    quote (chunk = chunk + chunk) ; void
    (chunk = chunk + chunk) ; void
    quote (chunk = chunk + chunk) ; void
    (chunk = chunk + chunk) ; void
    quote (chunk = chunk + chunk) ; void
    (chunk = chunk + chunk) ; void
    quote (chunk = chunk + chunk) ; void
    (chunk = chunk + chunk) ; void
    quote (chunk = chunk + chunk) ; void
    (chunk = chunk + chunk) ; void
    quote (chunk = chunk + chunk) ; void
    (chunk = chunk + chunk) ; void
    quote (chunk = chunk + chunk) ; void
    (chunk = chunk + chunk) ; void
    quote (chunk = chunk + chunk) ; void
    (chunk = chunk + chunk) ; void
    quote (parser_thread = Thread.new(fn () {
      request = HTTP.Request.buf_parse(server_client.buf_rw.r)
      Str.size(request.body)
    })) ; void
    (parser_thread = Thread.new(fn () {
      request = HTTP.Request.buf_parse(server_client.buf_rw.r)
      Str.size(request.body)
    })) ; void
    quote Buf.write_str(client.buf_rw.w,
      "POST /large HTTP/1.1\r\nContent-Length: 66560\r\n\r\n")
    Buf.write_str(client.buf_rw.w,
      "POST /large HTTP/1.1\r\nContent-Length: 66560\r\n\r\n")
    quote do
      i = 0
      while i < 65 do
        Buf.write_str(client.buf_rw.w, chunk)
        i = i + 1
      end
    end
    do
      i = 0
      while i < 65 do
        Buf.write_str(client.buf_rw.w, chunk)
        i = i + 1
      end
    end
    quote Thread.delete(parser_thread)
    Thread.delete(parser_thread)
    quote Buf.write_str(client.buf_rw.w,
      "GET /after-large HTTP/1.1\r\n\r\n")
    Buf.write_str(client.buf_rw.w,
      "GET /after-large HTTP/1.1\r\n\r\n")
    quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    {request.method, request.url}
    
    # Media type parameters do not disable form decoding.
    quote Buf.write_str(client.buf_rw.w,
      "POST /form HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded; charset=UTF-8\r\nContent-Length: 6\r\n\r\nempty=")
    Buf.write_str(client.buf_rw.w,
      "POST /form HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded; charset=UTF-8\r\nContent-Length: 6\r\n\r\nempty=")
    quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    request.body
    
    # Identical duplicate lengths are unambiguous and accepted.
    quote Buf.write_str(client.buf_rw.w,
      "POST /duplicate HTTP/1.1\r\nContent-Length: 3\r\nContent-Length: 3\r\n\r\nabc")
    Buf.write_str(client.buf_rw.w,
      "POST /duplicate HTTP/1.1\r\nContent-Length: 3\r\nContent-Length: 3\r\n\r\nabc")
    quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    request.body
    
    quote Socket.Buf.close(client)
    Socket.Buf.close(client)
    quote Socket.Buf.close(server_client)
    Socket.Buf.close(server_client)
    
    # A malformed multipart body cannot consume bytes beyond Content-Length.
    quote (client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
    (client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
    quote (server_client = Socket.Buf.accept(server)) ; void
    (server_client = Socket.Buf.accept(server)) ; void
    quote Buf.write_str(client.buf_rw.w,
      "POST / HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=X\r\nContent-Length: 10\r\n\r\n--X\r\nabcdeGET /not-consumed HTTP/1.1\r\n\r\n")
    Buf.write_str(client.buf_rw.w,
      "POST / HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=X\r\nContent-Length: 10\r\n\r\n--X\r\nabcdeGET /not-consumed HTTP/1.1\r\n\r\n")
    HTTP.Request.buf_parse(server_client.buf_rw.r)
    quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
    {request.method, request.url}
    quote Socket.Buf.close(client)
    Socket.Buf.close(client)
    quote Socket.Buf.close(server_client)
    Socket.Buf.close(server_client)
    
    # Conflicting lengths are rejected.
    quote (client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
    (client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
    quote (server_client = Socket.Buf.accept(server)) ; void
    (server_client = Socket.Buf.accept(server)) ; void
    quote Buf.write_str(client.buf_rw.w,
      "POST / HTTP/1.1\r\nContent-Length: 3\r\nContent-Length: 4\r\n\r\n")
    Buf.write_str(client.buf_rw.w,
      "POST / HTTP/1.1\r\nContent-Length: 3\r\nContent-Length: 4\r\n\r\n")
    HTTP.Request.buf_parse(server_client.buf_rw.r)
    quote Socket.Buf.close(client)
    Socket.Buf.close(client)
    quote Socket.Buf.close(server_client)
    Socket.Buf.close(server_client)
    
    # Unsupported transfer coding is rejected instead of desynchronizing.
    quote (client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
    (client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
    quote (server_client = Socket.Buf.accept(server)) ; void
    (server_client = Socket.Buf.accept(server)) ; void
    quote Buf.write_str(client.buf_rw.w,
      "POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n")
    Buf.write_str(client.buf_rw.w,
      "POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n")
    HTTP.Request.buf_parse(server_client.buf_rw.r)
    quote Socket.Buf.close(client)
    Socket.Buf.close(client)
    quote Socket.Buf.close(server_client)
    Socket.Buf.close(server_client)
    quote Socket.close(server)
    Socket.close(server)