Allow any string in Location Header when not following redirection

This commit is contained in:
Jean-Christophe Amiel 2024-11-08 15:59:39 +01:00
parent 5aa7ad577b
commit dd6519b82f
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
16 changed files with 140 additions and 74 deletions

View File

@ -2,6 +2,6 @@ error: Invalid URL
--> tests_failed/invalid_protocol.hurl:1:5
|
1 | GET {{url}}
| ^^^^^^^ invalid URL <file:///tmp/foo.txt> (Only http and https protocols are supported)
| ^^^^^^^ invalid URL <file:///tmp/foo.txt> (Only <http://> and <https://> schemes are supported)
|

View File

@ -2,6 +2,6 @@ error: Invalid URL
--> tests_failed/invalid_url_1.hurl:3:5
|
3 | GET {{host}}
| ^^^^^^^^ invalid URL <localhost:8000> (Missing protocol http or https)
| ^^^^^^^^ invalid URL <localhost:8000> (Missing scheme <http://> or <https://>)
|

View File

@ -151,110 +151,117 @@ error: Invalid URL
--> tests_failed/runner_errors.hurl:94:5
|
94 | GET {{url}}
| ^^^^^^^ invalid URL <localhost:8000/runner_errors> (Missing protocol http or https)
| ^^^^^^^ invalid URL <localhost:8000/runner_errors> (Missing scheme <http://> or <https://>)
|
error: Invalid URL
--> tests_failed/runner_errors.hurl:99:5
|
99 | GET http://localhost:8000/runner_errors/redirect-custom-scheme
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid URL <market://details?id=com.example.package> (Only <http://> and <https://> schemes are supported)
|
error: No query result
--> tests_failed/runner_errors.hurl:102:8
--> tests_failed/runner_errors.hurl:107:8
|
| GET http://localhost:8000/runner_errors
| ...
102 | count: header "count"
107 | count: header "count"
| ^^^^^^^^^^^^^^ The query didn't return any result
|
error: Header not found
--> tests_failed/runner_errors.hurl:107:1
--> tests_failed/runner_errors.hurl:112:1
|
| GET http://localhost:8000/runner_errors
| ...
107 | count: 10
112 | count: 10
| ^^^^^ this header has not been found in the response
|
error: Invalid JSON
--> tests_failed/runner_errors.hurl:113:1
--> tests_failed/runner_errors.hurl:118:1
|
| GET http://localhost:8000/runner_errors
| ...
113 | jsonpath "$.count" == 10
118 | jsonpath "$.count" == 10
| ^^^^^^^^^^^^^^^^^^ the HTTP response is not a valid JSON
|
error: Invalid JSONPath
--> tests_failed/runner_errors.hurl:119:10
--> tests_failed/runner_errors.hurl:124:10
|
| GET http://localhost:8000/runner_errors/json-list
| ...
119 | jsonpath "xxx" == 10
124 | jsonpath "xxx" == 10
| ^^^^^ the JSONPath expression 'xxx' is not valid
|
error: Invalid XML
--> tests_failed/runner_errors.hurl:125:1
--> tests_failed/runner_errors.hurl:130:1
|
| GET http://localhost:8000/runner_errors/invalid-xml
| ...
125 | xpath "//a" == 10
130 | xpath "//a" == 10
| ^^^^^^^^^^^ the HTTP response is not a valid XML
|
error: Invalid XPath expression
--> tests_failed/runner_errors.hurl:131:7
--> tests_failed/runner_errors.hurl:136:7
|
| GET http://localhost:8000/runner_errors
| ...
131 | xpath "//" == 10
136 | xpath "//" == 10
| ^^^^ the XPath expression is not valid
|
error: Invalid variable type
--> tests_failed/runner_errors.hurl:137:12
--> tests_failed/runner_errors.hurl:142:12
|
| GET http://localhost:8000/runner_errors
| ...
137 | verbose: {{verbose}}
142 | verbose: {{verbose}}
| ^^^^^^^ expecting boolean, actual value is integer <1>
|
error: Undefined variable
--> tests_failed/runner_errors.hurl:142:10
--> tests_failed/runner_errors.hurl:147:10
|
| GET http://localhost:8000/runner_errors
| ...
142 | param: {{value}}
147 | param: {{value}}
| ^^^^^ you must set the variable value
|
error: HTTP connection
--> tests_failed/runner_errors.hurl:145:5
--> tests_failed/runner_errors.hurl:150:5
|
145 | GET http://localhost:8000/runner_errors/redirect/2
150 | GET http://localhost:8000/runner_errors/redirect/2
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ too many redirect
|
error: Unauthorized file access
--> tests_failed/runner_errors.hurl:152:6
--> tests_failed/runner_errors.hurl:157:6
|
| GET http://localhost:8000/runner_errors
152 | file,/root/file;
157 | file,/root/file;
| ^^^^^^^^^^ unauthorized access to file /root/file, check --file-root option
|
error: Unrenderable variable
--> tests_failed/runner_errors.hurl:160:4
--> tests_failed/runner_errors.hurl:165:4
|
| GET http://localhost:8000/runner_errors
160 | `{{list}}`
165 | `{{list}}`
| ^^^^ variable <list> with value [1,2,3] can not be rendered
|
error: Decompression error
--> tests_failed/runner_errors.hurl:166:1
--> tests_failed/runner_errors.hurl:171:1
|
| GET http://localhost:8000/runner_errors/unsupported-content-encoding
| ...
166 | bytes count == 10
171 | bytes count == 10
| ^^^^^ compression unknown is not supported
|

View File

@ -95,6 +95,11 @@ GET {{url}}
[Options]
variable: url=localhost:8000/runner_errors
# InvalidUrl (none http/https scheme with redirection)
GET http://localhost:8000/runner_errors/redirect-custom-scheme
[Options]
location: true
# NoQueryResult
GET http://localhost:8000/runner_errors
HTTP 200

View File

@ -1,3 +1,4 @@
Set-StrictMode -Version latest
$ErrorActionPreference = 'Stop'
hurl --continue-on-error --no-color tests_failed/runner_errors.hurl

View File

@ -48,6 +48,11 @@ def runner_errors_redirect1():
return redirect("http://localhost:8000/runner_errors")
@app.route("/runner_errors/redirect-custom-scheme")
def runner_errors_redirect_custom_scheme():
return redirect("market://details?id=com.example.package")
@app.route("/runner_errors/unsupported-content-encoding")
def runner_errors_unsupported_content_encoding():
headers = {"Content-Encoding": "unknown"}

View File

@ -1,3 +1,4 @@
#!/bin/bash
set -Eeuo pipefail
hurl --continue-on-error --no-color tests_failed/runner_errors.hurl

View File

@ -151,110 +151,117 @@
--> tests_failed/runner_errors.hurl:94:5
 |
 94 | GET {{url}}
 | ^^^^^^^ invalid URL <localhost:8000/runner_errors> (Missing protocol http or https)
 | ^^^^^^^ invalid URL <localhost:8000/runner_errors> (Missing scheme <http://> or <https://>)
 |
error: Invalid URL
--> tests_failed/runner_errors.hurl:99:5
 |
 99 | GET http://localhost:8000/runner_errors/redirect-custom-scheme
 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid URL <market://details?id=com.example.package> (Only <http://> and <https://> schemes are supported)
 |
error: No query result
--> tests_failed/runner_errors.hurl:102:8
--> tests_failed/runner_errors.hurl:107:8
 |
 | GET http://localhost:8000/runner_errors
 | ...
102 | count: header "count"
107 | count: header "count"
 | ^^^^^^^^^^^^^^ The query didn't return any result
 |
error: Header not found
--> tests_failed/runner_errors.hurl:107:1
--> tests_failed/runner_errors.hurl:112:1
 |
 | GET http://localhost:8000/runner_errors
 | ...
107 | count: 10
112 | count: 10
 | ^^^^^ this header has not been found in the response
 |
error: Invalid JSON
--> tests_failed/runner_errors.hurl:113:1
--> tests_failed/runner_errors.hurl:118:1
 |
 | GET http://localhost:8000/runner_errors
 | ...
113 | jsonpath "$.count" == 10
118 | jsonpath "$.count" == 10
 | ^^^^^^^^^^^^^^^^^^ the HTTP response is not a valid JSON
 |
error: Invalid JSONPath
--> tests_failed/runner_errors.hurl:119:10
--> tests_failed/runner_errors.hurl:124:10
 |
 | GET http://localhost:8000/runner_errors/json-list
 | ...
119 | jsonpath "xxx" == 10
124 | jsonpath "xxx" == 10
 | ^^^^^ the JSONPath expression 'xxx' is not valid
 |
error: Invalid XML
--> tests_failed/runner_errors.hurl:125:1
--> tests_failed/runner_errors.hurl:130:1
 |
 | GET http://localhost:8000/runner_errors/invalid-xml
 | ...
125 | xpath "//a" == 10
130 | xpath "//a" == 10
 | ^^^^^^^^^^^ the HTTP response is not a valid XML
 |
error: Invalid XPath expression
--> tests_failed/runner_errors.hurl:131:7
--> tests_failed/runner_errors.hurl:136:7
 |
 | GET http://localhost:8000/runner_errors
 | ...
131 | xpath "//" == 10
136 | xpath "//" == 10
 | ^^^^ the XPath expression is not valid
 |
error: Invalid variable type
--> tests_failed/runner_errors.hurl:137:12
--> tests_failed/runner_errors.hurl:142:12
 |
 | GET http://localhost:8000/runner_errors
 | ...
137 | verbose: {{verbose}}
142 | verbose: {{verbose}}
 | ^^^^^^^ expecting boolean, actual value is integer <1>
 |
error: Undefined variable
--> tests_failed/runner_errors.hurl:142:10
--> tests_failed/runner_errors.hurl:147:10
 |
 | GET http://localhost:8000/runner_errors
 | ...
142 | param: {{value}}
147 | param: {{value}}
 | ^^^^^ you must set the variable value
 |
error: HTTP connection
--> tests_failed/runner_errors.hurl:145:5
--> tests_failed/runner_errors.hurl:150:5
 |
145 | GET http://localhost:8000/runner_errors/redirect/2
150 | GET http://localhost:8000/runner_errors/redirect/2
 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ too many redirect
 |
error: Unauthorized file access
--> tests_failed/runner_errors.hurl:152:6
--> tests_failed/runner_errors.hurl:157:6
 |
 | GET http://localhost:8000/runner_errors
152 | file,/root/file;
157 | file,/root/file;
 | ^^^^^^^^^^ unauthorized access to file /root/file, check --file-root option
 |
error: Unrenderable variable
--> tests_failed/runner_errors.hurl:160:4
--> tests_failed/runner_errors.hurl:165:4
 |
 | GET http://localhost:8000/runner_errors
160 | `{{list}}`
165 | `{{list}}`
 | ^^^^ variable <list> with value [1,2,3] can not be rendered
 |
error: Decompression error
--> tests_failed/runner_errors.hurl:166:1
--> tests_failed/runner_errors.hurl:171:1
 |
 | GET http://localhost:8000/runner_errors/unsupported-content-encoding
 | ...
166 | bytes count == 10
171 | bytes count == 10
 | ^^^^^ compression unknown is not supported
 |

View File

@ -1,3 +1,4 @@
Set-StrictMode -Version latest
$ErrorActionPreference = 'Stop'
hurl --continue-on-error --color tests_failed/runner_errors.hurl

View File

@ -1,3 +1,4 @@
#!/bin/bash
set -Eeuo pipefail
hurl --continue-on-error --color tests_failed/runner_errors.hurl

View File

@ -37,3 +37,26 @@ variable "fruits" isCollection
variable "fruits" count == 4
variable "fruits" nth 0 == "Banana"
variable "fruits" nth 3 == "Strawberry"
# Header "Location" can use http:// scheme, custom schemes (like market://) or any string. By default, Hurl doesn't
# follow redirection so url in "Location" header doesn't need to be constrained to http:// schemes.
GET http://localhost:8000/assert-header-location-http
HTTP 302
[Asserts]
url == "http://localhost:8000/assert-header-location-http"
header "Location" == "http://localhost:8000"
GET http://localhost:8000/assert-header-location-custom-scheme
HTTP 302
[Asserts]
url == "http://localhost:8000/assert-header-location-custom-scheme"
header "Location" == "market://details?id=com.example.package"
GET http://localhost:8000/assert-header-location-xxx
HTTP 302
[Asserts]
url == "http://localhost:8000/assert-header-location-xxx"
header "Location" == "xxx"

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
from app import app
from flask import make_response, Response
from flask import redirect, Response
@app.route("/assert-header")
@ -16,3 +16,18 @@ def assert_header():
resp.set_cookie("cookie2", "value2")
resp.set_cookie("cookie3", "value3")
return resp
@app.route("/assert-header-location-http")
def assert_header_location_http():
return redirect("http://localhost:8000")
@app.route("/assert-header-location-custom-scheme")
def assert_header_location_custom():
return redirect("market://details?id=com.example.package")
@app.route("/assert-header-location-xxx")
def assert_header_location_xxx():
return redirect("xxx")

View File

@ -121,11 +121,16 @@ impl Client {
let mut redirect_count = 0;
loop {
let call = self.execute(&request_spec, &options, logger)?;
// If we don't follow redirection, we can early exit here.
if !options.follow_location {
calls.push(call);
break;
}
let request_url = call.request.url.clone();
let redirect_url = self.follow_location(&request_url, &call.response)?;
let status = call.response.status;
let redirect_url = self.follow_location(&request_url, &call.response)?;
calls.push(call);
if !options.follow_location || redirect_url.is_none() {
if redirect_url.is_none() {
break;
}
let redirect_url = redirect_url.unwrap();

View File

@ -43,6 +43,7 @@ pub enum HttpError {
description: String,
},
UnsupportedHttpVersion(RequestedHttpVersion),
/// Request URL is invalid (URL and reason)
InvalidUrl(String, String),
/// The maximum response size has been exceeded.
/// This error can be raised even if libcurl has been configured to respect a given maximum

View File

@ -86,7 +86,7 @@ impl Url {
/// The parse method from the url crate does not seem to parse url without scheme
/// For example, "localhost:8000" is parsed with its scheme set to "localhost"
///
fn extract_scheme(url: &str) -> Option<String> {
fn scheme(url: &str) -> Option<String> {
let re = Regex::new("^([a-z]+://).*").unwrap();
if let Some(caps) = re.captures(url) {
let scheme = &caps[1];
@ -101,18 +101,18 @@ impl FromStr for Url {
/// Parses an absolute URL from a string.
fn from_str(value: &str) -> Result<Self, Self::Err> {
match extract_scheme(value) {
match scheme(value) {
None => {
return Err(HttpError::InvalidUrl(
value.to_string(),
"Missing protocol http or https".to_string(),
"Missing scheme <http://> or <https://>".to_string(),
));
}
Some(scheme) => {
if scheme != "http://" && scheme != "https://" {
return Err(HttpError::InvalidUrl(
value.to_string(),
"Only http and https protocols are supported".to_string(),
"Only <http://> and <https://> schemes are supported".to_string(),
));
}
}
@ -136,7 +136,7 @@ mod tests {
use std::str::FromStr;
use super::Url;
use crate::http::url::extract_scheme;
use crate::http::url::scheme;
use crate::http::{HttpError, Param};
#[test]
@ -205,30 +205,24 @@ mod tests {
Url::from_str("localhost:8000").err().unwrap(),
HttpError::InvalidUrl(
"localhost:8000".to_string(),
"Missing protocol http or https".to_string()
"Missing scheme <http://> or <https://>".to_string()
)
);
assert_eq!(
Url::from_str("file://localhost:8000").err().unwrap(),
HttpError::InvalidUrl(
"file://localhost:8000".to_string(),
"Only http and https protocols are supported".to_string()
"Only <http://> and <https://> schemes are supported".to_string()
)
);
}
#[test]
fn test_extract_scheme() {
assert!(extract_scheme("localhost:8000").is_none());
assert!(extract_scheme("http1://localhost:8000").is_none());
assert!(extract_scheme("://localhost:8000").is_none());
assert_eq!(
extract_scheme("file://data").unwrap(),
"file://".to_string()
);
assert_eq!(
extract_scheme("http://data").unwrap(),
"http://".to_string()
);
assert!(scheme("localhost:8000").is_none());
assert!(scheme("http1://localhost:8000").is_none());
assert!(scheme("://localhost:8000").is_none());
assert_eq!(scheme("file://data").unwrap(), "file://".to_string());
assert_eq!(scheme("http://data").unwrap(), "http://".to_string());
}
}