1mod assign;
2mod build;
3mod directives;
4mod entity;
5mod fieldset;
6mod page;
7mod scan;
8mod sitemap;
9
10pub(crate) use sitemap::Sitemap;
11
12use crate::core::lookup;
13
14pub(crate) const WEB_SCHEMES: [&str; 2] = ["http", "https"];
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
17#[non_exhaustive]
18pub enum FormMethod {
19 #[default]
20 Get,
21 Post,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
25#[non_exhaustive]
26pub enum FormEnctype {
27 #[default]
28 UrlEncoded,
29 Multipart,
30 TextPlain,
31}
32
33impl FormEnctype {
34 const ALL: [FormEnctype; 3] = [Self::UrlEncoded, Self::Multipart, Self::TextPlain];
35
36 #[must_use]
37 pub fn as_str(self) -> &'static str {
38 match self {
39 Self::UrlEncoded => "application/x-www-form-urlencoded",
40 Self::Multipart => "multipart/form-data",
41 Self::TextPlain => "text/plain",
42 }
43 }
44
45 fn parse(value: &str) -> Self {
46 let value = value.trim();
47 Self::ALL
48 .into_iter()
49 .find(|enctype| enctype.as_str().eq_ignore_ascii_case(value))
50 .unwrap_or_default()
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Default)]
55#[non_exhaustive]
56pub struct Form {
57 action: String,
58 method: FormMethod,
59 enctype: FormEnctype,
60 base: Option<String>,
61 id: Option<String>,
62 name: Option<String>,
63 fields: Vec<(String, String)>,
64 buttons: Vec<Button>,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
68struct Button {
69 name: String,
70 value: String,
71 image: bool,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
75#[non_exhaustive]
76pub struct Anchor {
77 href: String,
78 text: String,
79 rel: Option<String>,
80}
81
82#[must_use]
83pub fn meta(document: &str, name: &str) -> Option<String> {
84 page::meta(document, name)
85}
86
87#[must_use]
88pub fn links(document: &str) -> Vec<Anchor> {
89 page::links(document)
90}
91
92#[must_use]
93pub fn text(document: &str) -> String {
94 page::text(document)
95}
96
97pub(crate) fn base(document: &str) -> Option<String> {
98 page::base(document)
99}
100
101#[must_use]
102pub fn canonical(document: &str) -> Option<String> {
103 page::canonical(document)
104}
105
106pub(crate) fn sitemap(document: &str) -> Sitemap {
107 sitemap::read(document)
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111#[non_exhaustive]
112pub struct Directives {
113 index: bool,
114 follow: bool,
115 ai: bool,
116}
117
118impl Directives {
119 #[must_use]
120 pub fn read(document: &str, headers: &http::HeaderMap, agent: &str) -> Directives {
121 directives::read(document, headers, agent)
122 }
123
124 #[must_use]
125 pub fn index(&self) -> bool {
126 self.index
127 }
128
129 #[must_use]
130 pub fn follow(&self) -> bool {
131 self.follow
132 }
133
134 #[must_use]
135 pub fn ai(&self) -> bool {
136 self.ai
137 }
138}
139
140impl Anchor {
141 #[must_use]
142 pub fn href(&self) -> &str {
143 &self.href
144 }
145
146 #[must_use]
147 pub fn text(&self) -> &str {
148 &self.text
149 }
150
151 #[must_use]
152 pub fn rel(&self) -> Option<&str> {
153 self.rel.as_deref()
154 }
155}
156
157#[must_use]
158pub fn forms(document: &str) -> Vec<Form> {
159 build::forms(document)
160}
161
162impl Form {
163 #[must_use]
164 pub fn find(document: &str, selector: &str) -> Option<Form> {
165 forms(document).into_iter().find(|form| {
166 form.id.as_deref() == Some(selector) || form.name.as_deref() == Some(selector)
167 })
168 }
169
170 #[must_use]
171 pub fn action(&self) -> &str {
172 &self.action
173 }
174
175 #[must_use]
176 pub fn method(&self) -> FormMethod {
177 self.method
178 }
179
180 #[must_use]
181 pub fn enctype(&self) -> FormEnctype {
182 self.enctype
183 }
184
185 #[must_use]
186 pub fn base(&self) -> Option<&str> {
187 self.base.as_deref()
188 }
189
190 #[must_use]
191 pub fn id(&self) -> Option<&str> {
192 self.id.as_deref()
193 }
194
195 #[must_use]
196 pub fn name(&self) -> Option<&str> {
197 self.name.as_deref()
198 }
199
200 #[must_use]
201 pub fn fields(&self) -> &[(String, String)] {
202 &self.fields
203 }
204
205 #[must_use]
206 pub fn field(&self, name: &str) -> Option<&str> {
207 self.fields
208 .iter()
209 .find(|(key, _)| key == name)
210 .map(|(_, value)| value.as_str())
211 }
212
213 pub fn set(&mut self, name: impl Into<String>, value: impl Into<String>) -> &mut Self {
214 let name = name.into();
215 let value = value.into();
216 match self.fields.iter().position(|(key, _)| *key == name) {
217 Some(first) => {
218 self.fields[first].1 = value;
219 let mut index = 0;
220 self.fields.retain(|(key, _)| {
221 let keep = index <= first || *key != name;
222 index += 1;
223 keep
224 });
225 }
226 None => self.fields.push((name, value)),
227 }
228 self
229 }
230
231 #[must_use]
232 pub fn buttons(&self) -> Vec<&str> {
233 self.buttons.iter().map(|b| b.name.as_str()).collect()
234 }
235
236 pub fn press(&mut self, button: &str) -> bool {
237 let Some(found) = self.buttons.iter().find(|b| b.name == button).cloned() else {
238 return false;
239 };
240 if found.image {
241 self.fields
242 .push((format!("{}.x", found.name), "0".to_owned()));
243 self.fields
244 .push((format!("{}.y", found.name), "0".to_owned()));
245 } else {
246 self.fields.push((found.name, found.value));
247 }
248 true
249 }
250}