From d5228fe86b726cc12e555651e7098c58a6efb914 Mon Sep 17 00:00:00 2001 From: Seongmin Lee Date: Sun, 9 Feb 2025 16:28:13 +0000 Subject: [PATCH] fix(curl/cli): parse redirected response as list Don't merge multiple responses from redirected requests into one. Parse them individually and return last response instead. `parser.parse_verbose()` method should return all request&response history in future --- lua/rest-nvim/client/curl/cli.lua | 23 ++++-- spec/client/curl/cli_spec.lua | 131 ++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 9 deletions(-) diff --git a/lua/rest-nvim/client/curl/cli.lua b/lua/rest-nvim/client/curl/cli.lua index 6f093569..36c08b12 100644 --- a/lua/rest-nvim/client/curl/cli.lua +++ b/lua/rest-nvim/client/curl/cli.lua @@ -118,15 +118,19 @@ end ---@param lines string[] ---@return rest.Response function parser.parse_verbose(lines) - local response = { - headers = {}, - statistics = {}, - } + ---@type rest.Response[] + local list = {} + ---@type rest.Response + local response vim.iter(lines):map(parser.parse_verbose_line):each(function(ln) if ln.prefix == VERBOSE_PREFIX_RES_HEADER then - if not response.status then - -- response status - response.status = parser.parse_verbose_status(ln.str) + if vim.startswith(ln.str, "HTTP/") then + response = { + status = parser.parse_verbose_status(ln.str), + headers = {}, + statistics = {}, + } + table.insert(list, response) else -- response header local key, value = parser.parse_header_pair(ln.str) @@ -137,14 +141,15 @@ function parser.parse_verbose(lines) table.insert(response.headers[key], value) end end - elseif ln.prefix == VERBOSE_PREFIX_STAT then + elseif response and ln.prefix == VERBOSE_PREFIX_STAT then local key, value = parser.parse_stat_pair(ln.str) if key then response.statistics[key] = value end end end) - return response + -- TODO: return all response history + return list[#list] end --- Builder --- diff --git a/spec/client/curl/cli_spec.lua b/spec/client/curl/cli_spec.lua index 2d28574f..d56c78cf 100644 --- a/spec/client/curl/cli_spec.lua +++ b/spec/client/curl/cli_spec.lua @@ -161,6 +161,137 @@ describe("curl cli response parser", function() }, }, response) end) + it("Redirected response", function() + local stdin = { + "* Trying 34.78.67.165:80...", + "* Connected to ijhttp-examples.jetbrains.com (34.78.67.165) port 80 (#0)", + -- request + "> POST /post HTTP/1.1", + "> Host: ijhttp-examples.jetbrains.com", + "> User-Agent: curl/7.81.0", + "> Accept: */*", + ">", + "* Mark bundle as not supporting multiuse", + -- resopnse (301) + "< HTTP/1.1 301 Moved Permanently", + "< Date: Sun, 09 Feb 2025 15:25:31 GMT", + "< Content-Type: text/html", + "< Content-Length: 162", + "< Connection: keep-alive", + "< Location: http://examples.http-client.intellij.net/post", + "<", + "* Ignoring the response-body", + "* Connection #0 to host ijhttp-examples.jetbrains.com left intact", + "* Issue another request to this URL: 'http://examples.http-client.intellij.net/post'", + "* Trying 34.78.67.165:80...", + "* Connected to examples.http-client.intellij.net (34.78.67.165) port 80 (#1)", + -- request + "> POST /post HTTP/1.1", + "> Host: examples.http-client.intellij.net", + "> User-Agent: curl/7.81.0", + "> Accept: */*", + ">", + "* Mark bundle as not supporting multiuse", + -- response (308) + "< HTTP/1.1 308 Permanent Redirect", + "< Date: Sun, 09 Feb 2025 15:25:32 GMT", + "< Content-Type: text/html", + "< Content-Length: 164", + "< Connection: keep-alive", + "< Location: https://examples.http-client.intellij.net/post", + "<", + "* Ignoring the response-body", + "* Connection #1 to host examples.http-client.intellij.net left intact", + "* Clear auth, redirects to port from 80 to 443", + "* Issue another request to this URL: 'https://examples.http-client.intellij.net/post'", + "* Trying 34.78.67.165:443...", + "* Connected to examples.http-client.intellij.net (34.78.67.165) port 443 (#2)", + "* ALPN, offering h2", + "* ALPN, offering http/1.1", + "* CAfile: /etc/ssl/certs/ca-certificates.crt", + "* CApath: /etc/ssl/certs", + "* TLSv1.0 (OUT), TLS header, Certificate Status (22):", + "* TLSv1.3 (OUT), TLS handshake, Client hello (1):", + "* TLSv1.2 (IN), TLS header, Certificate Status (22):", + "* TLSv1.3 (IN), TLS handshake, Server hello (2):", + "* TLSv1.2 (IN), TLS header, Finished (20):", + "* TLSv1.2 (IN), TLS header, Supplemental data (23):", + "* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):", + "* TLSv1.2 (IN), TLS header, Supplemental data (23):", + "* TLSv1.3 (IN), TLS handshake, Certificate (11):", + "* TLSv1.2 (IN), TLS header, Supplemental data (23):", + "* TLSv1.3 (IN), TLS handshake, CERT verify (15):", + "* TLSv1.2 (IN), TLS header, Supplemental data (23):", + "* TLSv1.3 (IN), TLS handshake, Finished (20):", + "* TLSv1.2 (OUT), TLS header, Finished (20):", + "* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):", + "* TLSv1.2 (OUT), TLS header, Supplemental data (23):", + "* TLSv1.3 (OUT), TLS handshake, Finished (20):", + "* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384", + "* ALPN, server accepted to use h2", + "* Server certificate:", + "* subject: CN=examples.http-client.intellij.net", + "* start date: Feb 9 08:45:35 2025 GMT", + "* expire date: May 10 08:45:34 2025 GMT", + [[* subjectAltName: host "examples.http-client.intellij.net" matched cert's "examples.http-client.intellij.net"]], + "* issuer: C=US; O=Let's Encrypt; CN=R11", + "* SSL certificate verify ok.", + "* Using HTTP2, server supports multiplexing", + "* Connection state changed (HTTP/2 confirmed)", + "* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0", + "* TLSv1.2 (OUT), TLS header, Supplemental data (23):", + "* TLSv1.2 (OUT), TLS header, Supplemental data (23):", + "* TLSv1.2 (OUT), TLS header, Supplemental data (23):", + "* Using Stream ID: 1 (easy handle 0xaf9f59cebcc0)", + "* TLSv1.2 (OUT), TLS header, Supplemental data (23):", + -- request + "> POST /post HTTP/2", + "> Host: examples.http-client.intellij.net", + "> user-agent: curl/7.81.0", + "> accept: */*", + ">", + "* TLSv1.2 (IN), TLS header, Supplemental data (23):", + "* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):", + "* TLSv1.2 (IN), TLS header, Supplemental data (23):", + "* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):", + "* old SSL session ID is stale, removing", + "* TLSv1.2 (IN), TLS header, Supplemental data (23):", + "* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!", + "* TLSv1.2 (OUT), TLS header, Supplemental data (23):", + "* TLSv1.2 (IN), TLS header, Supplemental data (23):", + "* TLSv1.2 (IN), TLS header, Supplemental data (23):", + -- response (200) + "< HTTP/2 200", + "< date: Sun, 09 Feb 2025 15:25:32 GMT", + "< content-type: application/json", + "< content-length: 419", + "< vary: Accept-Encoding", + "< access-control-allow-origin: https://examples.http-client.intellij.net", + "< access-control-allow-credentials: true", + "< strict-transport-security: max-age=31536000; includeSubDomains", + "<", + "* TLSv1.2 (IN), TLS header, Supplemental data (23):", + "* Connection #2 to host examples.http-client.intellij.net left intact", + } + local response = parser.parse_verbose(stdin) + assert.same({ + status = { + version = "HTTP/2", + code = 200, + text = "", + }, + statistics = {}, + headers = { + date = { "Sun, 09 Feb 2025 15:25:32 GMT" }, + ["content-type"] = { "application/json" }, + ["content-length"] = { "419" }, + ["vary"] = { "Accept-Encoding" }, + ["access-control-allow-origin"] = { "https://examples.http-client.intellij.net" }, + ["access-control-allow-credentials"] = { "true" }, + ["strict-transport-security"] = { "max-age=31536000; includeSubDomains" }, + }, + }, response) + end) end) -- -- don't run real request on test by default