Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions src/acme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const MAX_SERVER_RETRY_INTERVAL: Duration = Duration::from_secs(60);

static REPLAY_NONCE: http::HeaderName = http::HeaderName::from_static("replay-nonce");

// Maximum number of redirects to follow for a single request.
const MAX_REDIRECTS: usize = 10;

pub enum NewAccountOutput<'a> {
Created(&'a str),
Found(&'a str),
Expand Down Expand Up @@ -170,12 +173,29 @@ where
}

pub async fn get(&self, url: &Uri) -> Result<http::Response<Bytes>, RequestError> {
let req = http::Request::builder()
.uri(url)
.method(http::Method::GET)
.header(http::header::CONTENT_LENGTH, 0)
.body(String::new())?;
Ok(self.http.request(req).await?)
let mut u = url.clone();

for _ in 0..MAX_REDIRECTS {
let req = http::Request::builder()
.uri(&u)
.method(http::Method::GET)
.header(http::header::CONTENT_LENGTH, 0)
.body(String::new())?;
let res = self.http.request(req).await?;

if res.status().is_redirection() {
if let Some(location) = try_get_header(res.headers(), http::header::LOCATION) {
u = Uri::try_from(location).map_err(RequestError::UrlParse)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's still necessary to handle relative URIs in the Location header.

I don't like the idea of reimplementing RFC3986 § 5 reference resolution algorithm (resolve_location in the previous revision didn't look quite right), so I suggest to use the same dependency as reqwest and tower-http do, iri-string. Here's how it may look: https://docs.rs/tower-http/0.6.6/src/tower_http/follow_redirect/mod.rs.html#394.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have adjusted the code to match your suggestion.

I also split out the error logic to a custom RedirectError, similar to NewAccountError, as the potential issues kind of started to pollute RequestError.

In addition I could also extend it into the Problem and extend some logic there:

impl From<RequestError> for NewAccountError {
    fn from(value: RequestError) -> Self {
        match value {
            RequestError::Protocol(problem) => Self::Protocol(problem),
            _ => Self::Request(value),
        }
    }
}

impl Problem {

Not sure what you'd prefer here though?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind extracting URI resolution code into a function? I suspect we may need it elsewhere in the future, because RFC8555 does not require any of the directory or resource location URIs to be absolute.

I also don't believe we need that many errors:

  • UriAbsoluteString::try_from() should not fail, because self.http.request() already fails on invalid URI. Still need to check the result, but don't expect anything useful.
  • UriReferenceStr::new() returns a rather useless error "Invalid IRI".
  • Uri::try_from() should succeed, because it always receives a valid absolute URI.

With that in mind, fn resolve_uri(base, relative) -> Option<Uri> is a reasonable interface, and the None can be later converted to a single InvalidUri.

"invalid redirect URI"/"missing redirect URI"/"too many redirects" is a sufficient set of errors to handle this, and it will look acceptable both as a separate RedirectError or as members of RequestError. Just ensure that you maintain the sorting order of the enum members.

In addition I could also extend it into the Problem and extend some logic there:

Problem is reserved for ACME protocol errors received from the server. Redirect errors don't belong there.
The conversion you quoted exists to simplify handling Problems that point to a permanent configuration error, i.e. cases when we should stop attempting to process the current item. Also, to make logs prettier.

continue;
} else {
return Err(RequestError::RedirectNoLocation);
}
}

return Ok(res);
}

Err(RequestError::TooManyRedirects)
}

pub async fn post<P: AsRef<[u8]>>(
Expand Down
9 changes: 9 additions & 0 deletions src/acme/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ pub enum RequestError {

#[error("cannot sign request body ({0})")]
Sign(#[from] crate::jws::Error),

#[error("cannot parse URL ({0})")]
UrlParse(#[from] http::uri::InvalidUri),

#[error("redirect response missing Location header")]
RedirectNoLocation,

#[error("too many redirects")]
TooManyRedirects,
}

impl From<HttpClientError> for RequestError {
Expand Down