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