use std::str::FromStr;
use http::{header, uri, Method, Request, Uri, Version};
use typed_headers::{
mime::Mime, Accept, ContentLength, ContentType, HeaderMapExt, Quality, QualityItem,
};
use trust_dns_proto::error::ProtoError;
use crate::HttpsResult;
pub fn new(name_server_name: &str, message_len: usize) -> HttpsResult<Request<()>> {
let mut parts = uri::Parts::default();
parts.path_and_query = Some(uri::PathAndQuery::from_static(crate::DNS_QUERY_PATH));
parts.scheme = Some(uri::Scheme::HTTPS);
parts.authority = Some(
uri::Authority::from_str(&name_server_name)
.map_err(|e| ProtoError::from(format!("invalid authority: {}", e)))?,
);
let url =
Uri::from_parts(parts).map_err(|e| ProtoError::from(format!("uri parse error: {}", e)))?;
let accepts_dns = Mime::from_str(crate::MIME_APPLICATION_DNS).unwrap();
let content_type = ContentType(accepts_dns.clone());
let accept = Accept(vec![QualityItem::new(accepts_dns, Quality::from_u16(1000))]);
let mut request = Request::post(url)
.version(Version::HTTP_2)
.body(())
.map_err(|e| ProtoError::from(format!("h2 stream errored: {}", e)))?;
request.headers_mut().typed_insert(&content_type);
request.headers_mut().typed_insert(&accept);
if Method::POST == request.method() {
request
.headers_mut()
.typed_insert(&ContentLength(message_len as u64));
}
Ok(request)
}
pub fn verify<T>(name_server: &str, request: &Request<T>) -> HttpsResult<()> {
let uri = request.uri();
if uri.path() != crate::DNS_QUERY_PATH {
return Err(format!(
"bad path: {}, expected: {}",
uri.path(),
crate::DNS_QUERY_PATH
)
.into());
}
if Some(&uri::Scheme::HTTPS) != uri.scheme() {
return Err("must be HTTPS scheme".into());
}
if let Some(authority) = uri.authority() {
if authority.host() != name_server {
return Err("incorrect authority".into());
}
} else {
return Err("no authority in HTTPS request".into());
}
let content_type: Option<ContentType> = request.headers().typed_get()?;
let accept: Option<Accept> = request.headers().typed_get()?;
if !content_type
.map(|c| (c.type_() == crate::MIME_APPLICATION && c.subtype() == crate::MIME_DNS_BINARY))
.unwrap_or(true)
{
return Err("unsupported content type".into());
}
let accept = accept.ok_or_else(|| "Accept is unspecified")?;
let any_application_and_dns = |q: &QualityItem<Mime>| -> bool {
(q.item.type_() == crate::MIME_APPLICATION && q.item.subtype() == crate::MIME_DNS_BINARY)
};
if !accept.iter().any(any_application_and_dns) {
return Err("does not accept content type".into());
}
if request.version() != Version::HTTP_2 {
return Err("only HTTP/2 supported".into());
}
debug!(
"verified request from: {}",
request
.headers()
.get(header::USER_AGENT)
.map(|h| h.to_str().unwrap_or("bad user agent"))
.unwrap_or("unknown user agent")
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_verify() {
let request = new("ns.example.com", 512).expect("error converting to http");
assert!(verify("ns.example.com", &request).is_ok());
}
}