Fix relative redirect

This commit is contained in:
Fabrice Reix 2022-10-09 16:30:07 +02:00
parent 1c2e2ffc72
commit 1ae68efd82
No known key found for this signature in database
GPG Key ID: BF5213154B2E7155
10 changed files with 181 additions and 35 deletions

View File

@ -1,2 +1,5 @@
curl 'http://localhost:8000/redirect'
curl 'http://localhost:8000/redirected'
curl 'http://localhost:8000/redirect-absolute'
curl 'http://localhost:8000/redirect-absolute' -L
curl 'http://localhost:8000/redirect-relative'
curl 'http://localhost:8000/redirect-relative' -L

View File

@ -1,13 +1,37 @@
<pre><code class="language-hurl"><span class="hurl-entry"><span class="request"><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/redirect</span></span>
<pre><code class="language-hurl"><span class="hurl-entry"><span class="request"><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/redirected</span></span>
</span><span class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="number">200</span></span>
<span class="raw"><span class="line">```Redirected```</span></span>
</span></span><span class="hurl-entry"><span class="request"><span class="line"></span>
<span class="line"></span>
<span class="line"></span><span class="comment"># Absolute redirects</span>
<span class="line"></span>
<span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/redirect-absolute</span></span>
</span><span class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="number">302</span></span>
<span class="line"><span class="string">Location</span><span>:</span> <span class="string">http://localhost:8000/redirected</span></span>
</span></span><span class="hurl-entry"><span class="request"><span class="line"></span>
<span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/redirected</span></span>
</span><span class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="number">200</span></span>
<span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/redirect-absolute</span></span>
<span class="line section-header">[Options]</span>
<span class="line"><span class="string">location</span><span>:</span> <span class="boolean">true</span></span>
</span><span class="response"><span class="line"></span>
<span class="line"><span class="version">HTTP/1.0</span> <span class="number">200</span></span>
<span class="raw"><span class="line">```Redirected```</span></span>
</span></span><span class="hurl-entry"><span class="request"><span class="line"></span>
<span class="line"></span>
<span class="line"></span><span class="comment"># Relative redirects</span>
<span class="line"></span>
<span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/redirect-relative</span></span>
</span><span class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="number">302</span></span>
<span class="line"><span class="string">Location</span><span>:</span> <span class="string">/redirected</span></span>
</span></span><span class="hurl-entry"><span class="request"><span class="line"></span>
<span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/redirect-relative</span></span>
<span class="line section-header">[Options]</span>
<span class="line"><span class="string">location</span><span>:</span> <span class="boolean">true</span></span>
</span><span class="response"><span class="line"></span>
<span class="line"><span class="version">HTTP/1.0</span> <span class="number">200</span></span>
<span class="raw"><span class="line">```Redirected```</span></span>
</span></span><span class="line"></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"></span>
</code></pre>

View File

@ -1,10 +1,34 @@
GET http://localhost:8000/redirect
GET http://localhost:8000/redirected
HTTP/1.0 200
```Redirected```
# Absolute redirects
GET http://localhost:8000/redirect-absolute
HTTP/1.0 302
Location: http://localhost:8000/redirected
GET http://localhost:8000/redirected
GET http://localhost:8000/redirect-absolute
[Options]
location: true
HTTP/1.0 200
```Redirected```
# Relative redirects
GET http://localhost:8000/redirect-relative
HTTP/1.0 302
Location: /redirected
GET http://localhost:8000/redirect-relative
[Options]
location: true
HTTP/1.0 200
```Redirected```

View File

@ -1 +1 @@
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/redirect"},"response":{"version":"HTTP/1.0","status":302,"headers":[{"name":"Location","value":"http://localhost:8000/redirected"}]}},{"request":{"method":"GET","url":"http://localhost:8000/redirected"},"response":{"version":"HTTP/1.0","status":200}}]}
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/redirected"},"response":{"version":"HTTP/1.0","status":200,"body":{"type":"raw-string","value":"Redirected"}}},{"request":{"method":"GET","url":"http://localhost:8000/redirect-absolute"},"response":{"version":"HTTP/1.0","status":302,"headers":[{"name":"Location","value":"http://localhost:8000/redirected"}]}},{"request":{"method":"GET","url":"http://localhost:8000/redirect-absolute"},"response":{"version":"HTTP/1.0","status":200,"body":{"type":"raw-string","value":"Redirected"}}},{"request":{"method":"GET","url":"http://localhost:8000/redirect-relative"},"response":{"version":"HTTP/1.0","status":302,"headers":[{"name":"Location","value":"/redirected"}]}},{"request":{"method":"GET","url":"http://localhost:8000/redirect-relative"},"response":{"version":"HTTP/1.0","status":200,"body":{"type":"raw-string","value":"Redirected"}}}]}

View File

@ -1,12 +1,20 @@
from app import app
from flask import redirect
from flask import redirect, Response
@app.route("/redirect")
def redirectme():
@app.route("/redirect-absolute")
def redirect_absolute():
return redirect("http://localhost:8000/redirected")
@app.route("/redirect-relative")
def redirect_relative():
response = Response(status=302)
response.headers["Location"] = "/redirected"
response.autocorrect_location_header = False
return response
@app.route("/redirected")
def redirected():
return ""
return "Redirected"

View File

@ -82,12 +82,13 @@ impl Client {
self.redirect_count = 0;
loop {
let (request, response) = self.execute(&request_spec, options, logger)?;
calls.push((request, response.clone()));
calls.push((request.clone(), response.clone()));
if !options.follow_location {
break;
}
if let Some(url) = self.get_follow_location(&response) {
let base_url = request.base_url()?;
if let Some(url) = self.get_follow_location(&response, &base_url) {
logger.debug("");
logger.debug(format!("=> Redirect to {}", url).as_str());
logger.debug("");
@ -513,14 +514,14 @@ impl Client {
/// 1. the option follow_location set to true
/// 2. a 3xx response code
/// 3. a header Location
fn get_follow_location(&mut self, response: &Response) -> Option<String> {
fn get_follow_location(&mut self, response: &Response, base_url: &str) -> Option<String> {
let response_code = response.status;
if !(300..400).contains(&response_code) {
return None;
}
let location = match response.get_header_values("Location").get(0) {
None => return None,
Some(value) => value.clone(),
Some(value) => get_redirect_url(value, base_url),
};
if location.is_empty() {
@ -590,6 +591,15 @@ impl Client {
}
}
/// Returns the redirect url.
fn get_redirect_url(location: &str, base_url: &str) -> String {
if location.starts_with('/') {
format!("{}{}", base_url, location)
} else {
location.to_string()
}
}
/// Returns cookies from both cookies from the cookie storage and the request.
pub fn all_cookies(cookie_storage: &[Cookie], request_spec: &RequestSpec) -> Vec<RequestCookie> {
let mut cookies = request_spec.cookies.clone();
@ -739,4 +749,16 @@ mod tests {
assert!(match_cookie(&cookie, "http://sub.example.com/toto"));
assert!(!match_cookie(&cookie, "http://example.com/tata"));
}
#[test]
fn test_redirect_url() {
assert_eq!(
get_redirect_url("http://localhost:8000/redirected", "http://localhost:8000"),
"http://localhost:8000/redirected".to_string()
);
assert_eq!(
get_redirect_url("/redirected", "http://localhost:8000"),
"http://localhost:8000/redirected".to_string()
);
}
}

View File

@ -40,4 +40,5 @@ pub enum HttpError {
UnsupportedContentEncoding {
description: String,
},
InvalidUrl(String),
}

View File

@ -18,7 +18,7 @@
use super::core::*;
use super::Header;
use crate::http::header;
use crate::http::{header, HttpError};
use url::Url;
#[derive(Clone, Debug, PartialEq, Eq)]
@ -61,6 +61,26 @@ impl Request {
.get(0)
.cloned()
}
/// Returns the base url http(s)://host(:port)
pub fn base_url(&self) -> Result<String, HttpError> {
// FIXME: is it possible to do it with libcurl?
let url = match Url::parse(&self.url) {
Ok(url) => url,
Err(_) => return Err(HttpError::InvalidUrl(self.url.clone())),
};
let base_url = format!(
"{}://{}{}",
url.scheme(),
url.host().unwrap(),
if let Some(port) = url.port() {
format!(":{}", port)
} else {
"".to_string()
}
);
Ok(base_url)
}
}
fn parse_cookies(s: &str) -> Vec<RequestCookie> {
@ -136,20 +156,20 @@ pub mod tests {
vec![
Param {
name: "param1".to_string(),
value: "value1".to_string()
value: "value1".to_string(),
},
Param {
name: "param2".to_string(),
value: "".to_string()
value: "".to_string(),
},
Param {
name: "param3".to_string(),
value: "a=b".to_string()
value: "a=b".to_string(),
},
Param {
name: "param4".to_string(),
value: "1,2,3".to_string()
}
value: "1,2,3".to_string(),
},
]
)
}
@ -162,12 +182,12 @@ pub mod tests {
vec![
RequestCookie {
name: "cookie1".to_string(),
value: "value1".to_string()
value: "value1".to_string(),
},
RequestCookie {
name: "cookie2".to_string(),
value: "value2".to_string()
}
value: "value2".to_string(),
},
]
)
}
@ -179,12 +199,12 @@ pub mod tests {
vec![
RequestCookie {
name: "cookie1".to_string(),
value: "value1".to_string()
value: "value1".to_string(),
},
RequestCookie {
name: "cookie2".to_string(),
value: "value2".to_string()
}
value: "value2".to_string(),
},
]
)
}
@ -195,8 +215,45 @@ pub mod tests {
parse_cookie("cookie1=value1"),
RequestCookie {
name: "cookie1".to_string(),
value: "value1".to_string()
value: "value1".to_string(),
},
)
}
#[test]
fn test_base_url() {
assert_eq!(
Request {
url: "http://localhost".to_string(),
method: "".to_string(),
headers: vec![],
body: vec![],
}
.base_url()
.unwrap(),
"http://localhost".to_string()
);
assert_eq!(
Request {
url: "http://localhost:8000/redirect-relative".to_string(),
method: "".to_string(),
headers: vec![],
body: vec![],
}
.base_url()
.unwrap(),
"http://localhost:8000".to_string()
);
assert_eq!(
Request {
url: "https://localhost:8000".to_string(),
method: "".to_string(),
headers: vec![],
body: vec![],
}
.base_url()
.unwrap(),
"https://localhost:8000".to_string()
);
}
}

View File

@ -172,6 +172,7 @@ impl From<HttpError> for RunnerError {
HttpError::UnsupportedContentEncoding { description } => {
RunnerError::UnsupportedContentEncoding(description)
}
HttpError::InvalidUrl(url) => RunnerError::InvalidUrl(url),
}
}
}

View File

@ -358,13 +358,16 @@ fn test_form_params() {
#[test]
fn test_redirect() {
let request_spec = default_get_request("http://localhost:8000/redirect");
let request_spec = default_get_request("http://localhost:8000/redirect-absolute");
let logger = Logger::new(false, false, "", "");
let options = ClientOptions::default();
let mut client = Client::new(None);
let (request, response) = client.execute(&request_spec, &options, &logger).unwrap();
assert_eq!(request.method, "GET".to_string());
assert_eq!(request.url, "http://localhost:8000/redirect".to_string());
assert_eq!(
request.url,
"http://localhost:8000/redirect-absolute".to_string()
);
assert_eq!(request.headers.len(), 3);
assert_eq!(response.status, 302);
@ -377,7 +380,7 @@ fn test_redirect() {
#[test]
fn test_follow_location() {
let request_spec = default_get_request("http://localhost:8000/redirect");
let request_spec = default_get_request("http://localhost:8000/redirect-absolute");
let logger = Logger::new(false, false, "", "");
let options = ClientOptions {
follow_location: true,
@ -388,7 +391,7 @@ fn test_follow_location() {
assert_eq!(options.curl_args(), vec!["-L".to_string()]);
assert_eq!(
client.curl_command_line(&request_spec, &context_dir, &options),
"curl 'http://localhost:8000/redirect' -L".to_string()
"curl 'http://localhost:8000/redirect-absolute' -L".to_string()
);
let calls = client
@ -398,7 +401,10 @@ fn test_follow_location() {
let (request1, response1) = calls.get(0).unwrap();
assert_eq!(request1.method, "GET".to_string());
assert_eq!(request1.url, "http://localhost:8000/redirect".to_string());
assert_eq!(
request1.url,
"http://localhost:8000/redirect-absolute".to_string()
);
assert_eq!(request1.headers.len(), 3);
assert_eq!(response1.status, 302);
assert!(response1.headers.contains(&Header {