trust_dns_proto/https/
request.rs1use std::str::FromStr;
11
12use http::header::{ACCEPT, CONTENT_LENGTH, CONTENT_TYPE};
13use http::{header, uri, Request, Uri, Version};
14use tracing::debug;
15
16use crate::error::ProtoError;
17use crate::https::HttpsResult;
18
19#[allow(clippy::field_reassign_with_default)] pub fn new(name_server_name: &str, message_len: usize) -> HttpsResult<Request<()>> {
31 let mut parts = uri::Parts::default();
44 parts.path_and_query = Some(uri::PathAndQuery::from_static(crate::https::DNS_QUERY_PATH));
45 parts.scheme = Some(uri::Scheme::HTTPS);
46 parts.authority = Some(
47 uri::Authority::from_str(name_server_name)
48 .map_err(|e| ProtoError::from(format!("invalid authority: {e}")))?,
49 );
50
51 let url =
52 Uri::from_parts(parts).map_err(|e| ProtoError::from(format!("uri parse error: {e}")))?;
53
54 let request = Request::builder()
56 .method("POST")
57 .uri(url)
58 .version(Version::HTTP_2)
59 .header(CONTENT_TYPE, crate::https::MIME_APPLICATION_DNS)
60 .header(ACCEPT, crate::https::MIME_APPLICATION_DNS)
61 .header(CONTENT_LENGTH, message_len)
62 .body(())
63 .map_err(|e| ProtoError::from(format!("h2 stream errored: {e}")))?;
64
65 Ok(request)
66}
67
68pub fn verify<T>(name_server: Option<&str>, request: &Request<T>) -> HttpsResult<()> {
70 let uri = request.uri();
72
73 if uri.path() != crate::https::DNS_QUERY_PATH {
75 return Err(format!(
76 "bad path: {}, expected: {}",
77 uri.path(),
78 crate::https::DNS_QUERY_PATH
79 )
80 .into());
81 }
82
83 if Some(&uri::Scheme::HTTPS) != uri.scheme() {
85 return Err("must be HTTPS scheme".into());
86 }
87
88 if let Some(name_server) = name_server {
90 if let Some(authority) = uri.authority() {
91 if authority.host() != name_server {
92 return Err("incorrect authority".into());
93 }
94 } else {
95 return Err("no authority in HTTPS request".into());
96 }
97 }
98
99 match request.headers().get(CONTENT_TYPE).map(|v| v.to_str()) {
101 Some(Ok(ctype)) if ctype == crate::https::MIME_APPLICATION_DNS => {}
102 _ => return Err("unsupported content type".into()),
103 };
104
105 match request.headers().get(ACCEPT).map(|v| v.to_str()) {
107 Some(Ok(ctype)) => {
108 let mut found = false;
109 for mime_and_quality in ctype.split(',') {
110 let mut parts = mime_and_quality.splitn(2, ';');
111 match parts.next() {
112 Some(mime) if mime.trim() == crate::https::MIME_APPLICATION_DNS => {
113 found = true;
114 break;
115 }
116 Some(mime) if mime.trim() == "application/*" => {
117 found = true;
118 break;
119 }
120 _ => continue,
121 }
122 }
123
124 if !found {
125 return Err("does not accept content type".into());
126 }
127 }
128 Some(Err(e)) => return Err(e.into()),
129 None => return Err("Accept is unspecified".into()),
130 };
131
132 if request.version() != Version::HTTP_2 {
133 return Err("only HTTP/2 supported".into());
134 }
135
136 debug!(
137 "verified request from: {}",
138 request
139 .headers()
140 .get(header::USER_AGENT)
141 .map(|h| h.to_str().unwrap_or("bad user agent"))
142 .unwrap_or("unknown user agent")
143 );
144
145 Ok(())
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_new_verify() {
154 let request = new("ns.example.com", 512).expect("error converting to http");
155 assert!(verify(Some("ns.example.com"), &request).is_ok());
156 }
157}