1#![deny(unsafe_code)]
9#![deny(missing_docs)]
10
11use std::ops::Deref;
12use std::str::FromStr;
13
14mod base64;
15pub mod client;
16pub mod errors;
17#[allow(unsafe_code)]
18pub mod fmt;
19pub mod jose;
20pub mod request;
21pub mod response;
22
23pub use base64::Base64Data;
24pub use base64::Base64JSON;
25pub use client::Client;
26pub use errors::AcmeError;
27
28#[doc(no_inline)]
29pub use request::Request;
30#[doc(no_inline)]
31pub use response::Response;
32use serde::{Deserialize, Serialize};
33
34pub type Result<T> = ::std::result::Result<T, AcmeError>;
36
37#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
43pub struct Url(url::Url);
44
45impl Url {
46 pub fn as_str(&self) -> &str {
48 self.0.as_str()
49 }
50
51 pub fn path(&self) -> &str {
53 self.0.path()
54 }
55
56 pub fn host(&self) -> Option<&str> {
58 self.0.host_str()
59 }
60}
61
62impl Deref for Url {
63 type Target = url::Url;
64 fn deref(&self) -> &Self::Target {
65 &self.0
66 }
67}
68
69impl From<url::Url> for Url {
70 fn from(value: url::Url) -> Self {
71 Url(value)
72 }
73}
74
75impl From<Url> for url::Url {
76 fn from(value: Url) -> Self {
77 value.0
78 }
79}
80
81impl AsRef<str> for Url {
82 fn as_ref(&self) -> &str {
83 self.0.as_str()
84 }
85}
86
87impl std::fmt::Debug for Url {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 f.debug_tuple("Url").field(&self.0.as_str()).finish()
90 }
91}
92
93impl FromStr for Url {
94 type Err = url::ParseError;
95
96 fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
97 s.parse().map(Url)
98 }
99}
100
101#[cfg(test)]
102pub(crate) mod test {
103 use std::sync::Arc;
104
105 #[macro_export]
106 macro_rules! example {
107 ($name:tt) => {
108 include_str!(concat!(
109 env!("CARGO_MANIFEST_DIR"),
110 "/test-examples/",
111 $name
112 ))
113 };
114 }
115
116 #[macro_export]
117 macro_rules! key {
118 ($name:tt) => {
119 $crate::test::key(include_str!(concat!(
120 env!("CARGO_MANIFEST_DIR"),
121 "/test-examples/",
122 $name,
123 ".pem"
124 )))
125 };
126 }
127
128 #[macro_export]
129 macro_rules! response {
130 ($name:tt) => {
131 $crate::test::parse($crate::example!($name))
132 };
133 }
134
135 #[allow(dead_code)]
136 pub(crate) fn key(private: &str) -> Arc<yacme_key::SigningKey> {
137 let key = yacme_key::SigningKey::from_pkcs8_pem(
138 private,
139 yacme_key::SignatureKind::Ecdsa(yacme_key::EcdsaAlgorithm::P256),
140 )
141 .unwrap();
142
143 Arc::new(key)
144 }
145
146 pub(crate) fn parse(data: &str) -> http::Response<String> {
147 let mut lines = data.lines();
148
149 let status = {
150 let status_line = lines.next().unwrap().trim();
151 let (version, status) = status_line.split_once(' ').unwrap();
152
153 if !matches!(version, "HTTP/1.1") {
154 panic!("Expected HTTP/1.1, got {version}");
155 }
156
157 let (code, _reason) = status.split_once(' ').unwrap();
158 http::StatusCode::from_u16(code.parse().unwrap()).unwrap()
159 };
160
161 let mut headers = http::HeaderMap::new();
162
163 for line in lines.by_ref() {
164 if line.is_empty() {
165 break;
166 } else {
167 let (name, value) = line.trim().split_once(": ").unwrap();
168 headers.append(
169 http::header::HeaderName::from_bytes(name.as_bytes()).unwrap(),
170 value.parse().unwrap(),
171 );
172 }
173 }
174
175 let body: String = lines.collect();
176 let mut response = http::Response::new(body);
177 *response.headers_mut() = headers;
178 *response.status_mut() = status;
179 *response.version_mut() = http::Version::HTTP_11;
180 response
181 }
182}