/src/wget2/libwget/robots.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2012 Tim Ruehsen |
3 | | * Copyright (c) 2015-2026 Free Software Foundation, Inc. |
4 | | * |
5 | | * This file is part of libwget. |
6 | | * |
7 | | * Libwget is free software: you can redistribute it and/or modify |
8 | | * it under the terms of the GNU Lesser General Public License as published by |
9 | | * the Free Software Foundation, either version 3 of the License, or |
10 | | * (at your option) any later version. |
11 | | * |
12 | | * Libwget is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | * GNU Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General Public License |
18 | | * along with libwget. If not, see <https://www.gnu.org/licenses/>. |
19 | | * |
20 | | * |
21 | | * routines to parse robots.txt (RFC 9309) |
22 | | * |
23 | | * Changelog |
24 | | * 28.09.2013 Tim Ruehsen created |
25 | | * 15.03.2024 Avinash Sonawane, Tim Ruehsen updated to RFC 9309 (except for the Allow field) |
26 | | * |
27 | | */ |
28 | | |
29 | | #include <config.h> |
30 | | |
31 | | #include <string.h> |
32 | | #include <ctype.h> |
33 | | #include <stdbool.h> |
34 | | |
35 | | #include <wget.h> |
36 | | #include "private.h" |
37 | | |
38 | | /** |
39 | | * \file |
40 | | * \brief Robots Exclusion file parser |
41 | | * \defgroup libwget-robots Robots Exclusion file parser |
42 | | * @{ |
43 | | * |
44 | | * The purpose of this set of functions is to parse a |
45 | | * Robots Exclusion Standard file into a data structure |
46 | | * for easy access. |
47 | | */ |
48 | | |
49 | | struct wget_robots_st { |
50 | | wget_vector |
51 | | *paths; //!< paths found in robots.txt (element: wget_string) |
52 | | wget_vector |
53 | | *sitemaps; //!< sitemaps found in robots.txt (element: char *) |
54 | | }; |
55 | | |
56 | | static void path_free(void *path) |
57 | 157k | { |
58 | 157k | wget_string *p = path; |
59 | | |
60 | 157k | xfree(p->p); |
61 | 157k | xfree(p); |
62 | 157k | } |
63 | | |
64 | | static inline void advance_ws(const char **s) |
65 | 2.30M | { |
66 | 2.30M | for (; isblank(**s); (*s)++); |
67 | 2.30M | } |
68 | | |
69 | | static bool parse_record_field(const char **data, const char *field, size_t field_length) |
70 | 1.16M | { |
71 | 1.16M | advance_ws(data); |
72 | | |
73 | 1.16M | if (wget_strncasecmp_ascii(*data, field, field_length)) |
74 | 592k | return false; |
75 | | |
76 | 571k | *data += field_length; |
77 | 571k | advance_ws(data); |
78 | | |
79 | 571k | if (**data != ':') |
80 | 1.24k | return false; |
81 | | |
82 | 569k | *data += 1; |
83 | 569k | advance_ws(data); |
84 | | |
85 | 569k | return true; |
86 | 571k | } |
87 | 1.16M | #define parse_record_field(d, f) parse_record_field(d, f, sizeof(f) - 1) |
88 | | |
89 | | |
90 | | /** |
91 | | * \param[in] data Memory with robots.txt content (with trailing 0-byte) |
92 | | * \param[in] client Name of the client / user-agent |
93 | | * \return Return an allocated wget_robots structure or NULL on error |
94 | | * |
95 | | * The function parses the robots.txt \p data in accordance to |
96 | | * https://www.robotstxt.org/orig.html#format and returns a ROBOTS structure |
97 | | * including a list of the disallowed paths and including a list of the sitemap |
98 | | * files. |
99 | | * |
100 | | * The ROBOTS structure has to be freed by calling wget_robots_free(). |
101 | | */ |
102 | | int wget_robots_parse(wget_robots **_robots, const char *data, const char *client) |
103 | 534 | { |
104 | 534 | wget_robots *robots; |
105 | 534 | wget_string path; |
106 | 534 | size_t client_length = client ? strlen(client) : 0; |
107 | 534 | const char *p; |
108 | 534 | bool seek_record_client = false; |
109 | 534 | enum record { |
110 | 534 | NOT_IN_RECORD, |
111 | | /* User-agent:client */ |
112 | 534 | IN_RECORD_CLIENT, |
113 | | /* User-agent:* */ |
114 | 534 | IN_RECORD_STAR, |
115 | | /* Disallow:foo */ |
116 | 534 | ADDED_DISALLOW, |
117 | 534 | NO_MORE_RECORDS |
118 | 534 | } state; |
119 | | |
120 | 534 | if (!data || !*data || !_robots) |
121 | 1 | return WGET_E_INVALID; |
122 | | |
123 | 533 | if (!(robots = wget_calloc(1, sizeof(wget_robots)))) |
124 | 0 | return WGET_E_MEMORY; |
125 | | |
126 | 533 | state = NOT_IN_RECORD; |
127 | 573k | do { |
128 | 573k | if (state != NO_MORE_RECORDS && state != IN_RECORD_CLIENT && parse_record_field(&data, "User-agent")) { |
129 | 1.61k | if (client && !wget_strncasecmp_ascii(data, client, client_length)) { |
130 | 250 | if (!seek_record_client) |
131 | 54 | wget_vector_free(&robots->paths); |
132 | 250 | seek_record_client = true; |
133 | 250 | state = IN_RECORD_CLIENT; |
134 | 1.36k | } else if (!seek_record_client && (*data == '*')) |
135 | 561 | state = IN_RECORD_STAR; |
136 | 804 | else if (state == ADDED_DISALLOW) |
137 | 194 | state = NOT_IN_RECORD; |
138 | 572k | } else if (state != NO_MORE_RECORDS && state != NOT_IN_RECORD && parse_record_field(&data, "Disallow")) { |
139 | 157k | if (!*data || isspace(*data) || *data == '#') { |
140 | | // all allowed |
141 | 69 | wget_vector_free(&robots->paths); |
142 | 69 | if (seek_record_client) |
143 | 26 | state = NO_MORE_RECORDS; |
144 | 43 | else { |
145 | 43 | state = NOT_IN_RECORD; |
146 | 43 | seek_record_client = true; |
147 | 43 | } |
148 | 157k | } else { |
149 | 157k | if (!robots->paths) { |
150 | 100 | if (!(robots->paths = wget_vector_create(32, NULL))) |
151 | 0 | goto oom; |
152 | 100 | wget_vector_set_destructor(robots->paths, path_free); |
153 | 100 | } |
154 | 316k | for (p = data; *p && !isspace(*p) && *p != '#'; p++); |
155 | 157k | path.len = p - data; |
156 | 157k | if (!(path.p = wget_strmemdup(data, path.len))) |
157 | 0 | goto oom; |
158 | 157k | if (wget_vector_add_memdup(robots->paths, &path, sizeof(path)) < 0) { |
159 | 0 | xfree(path.p); |
160 | 0 | goto oom; |
161 | 0 | } |
162 | 157k | state = ADDED_DISALLOW; |
163 | 157k | } |
164 | 414k | } else if (parse_record_field(&data, "Sitemap")) { |
165 | 820k | for (p = data; *p && !isspace(*p) && *p != '#'; p++); |
166 | | |
167 | 410k | if (p > data){ |
168 | 409k | if (!robots->sitemaps) |
169 | 72 | if (!(robots->sitemaps = wget_vector_create(4, NULL))) |
170 | 0 | goto oom; |
171 | | |
172 | 409k | char *sitemap = wget_strmemdup(data, p - data); |
173 | 409k | if (!sitemap) |
174 | 0 | goto oom; |
175 | 409k | if (wget_vector_add(robots->sitemaps, sitemap) < 0) |
176 | 0 | goto oom; |
177 | 409k | } |
178 | 410k | } |
179 | | |
180 | 573k | if ((data = strchr(data, '\n'))) |
181 | 573k | data++; // point to next line |
182 | 573k | } while (data && *data); |
183 | | |
184 | | /* |
185 | | for (int it = 0; it < wget_vector_size(robots->paths); it++) { |
186 | | ROBOTS_PATH *path = wget_vector_get(robots->paths, it); |
187 | | debug_printf("path '%s'\n", path->path); |
188 | | } |
189 | | for (int it = 0; it < wget_vector_size(robots->sitemaps); it++) { |
190 | | const char *sitemap = wget_vector_get(robots->sitemaps, it); |
191 | | debug_printf("sitemap '%s'\n", sitemap); |
192 | | } |
193 | | */ |
194 | | |
195 | 533 | *(_robots) = robots; |
196 | 533 | return WGET_E_SUCCESS; |
197 | | |
198 | 0 | oom: |
199 | 0 | wget_robots_free(&robots); |
200 | 0 | return WGET_E_MEMORY; |
201 | 533 | } |
202 | | |
203 | | /** |
204 | | * \param[in,out] robots Pointer to Pointer to wget_robots structure |
205 | | * |
206 | | * wget_robots_free() free's the formerly allocated wget_robots structure. |
207 | | */ |
208 | | void wget_robots_free(wget_robots **robots) |
209 | 533 | { |
210 | 533 | if (robots && *robots) { |
211 | 533 | wget_vector_free(&(*robots)->paths); |
212 | 533 | wget_vector_free(&(*robots)->sitemaps); |
213 | 533 | xfree(*robots); |
214 | 533 | *robots = NULL; |
215 | 533 | } |
216 | 533 | } |
217 | | |
218 | | /** |
219 | | * @param robots Pointer to instance of wget_robots |
220 | | * @return Returns the number of paths listed in \p robots |
221 | | */ |
222 | | int wget_robots_get_path_count(wget_robots *robots) |
223 | 0 | { |
224 | 0 | if (robots) |
225 | 0 | return wget_vector_size(robots->paths); |
226 | | |
227 | 0 | return 0; |
228 | 0 | } |
229 | | |
230 | | /** |
231 | | * @param robots Pointer to instance of wget_robots |
232 | | * @param index Index of the wanted path |
233 | | * @return Returns the path at \p index or NULL |
234 | | */ |
235 | | wget_string *wget_robots_get_path(wget_robots *robots, int index) |
236 | 0 | { |
237 | 0 | if (robots && robots->paths) |
238 | 0 | return wget_vector_get(robots->paths, index); |
239 | | |
240 | 0 | return NULL; |
241 | 0 | } |
242 | | |
243 | | /** |
244 | | * @param robots Pointer to instance of wget_robots |
245 | | * @return Returns the number of sitemaps listed in \p robots |
246 | | */ |
247 | | int wget_robots_get_sitemap_count(wget_robots *robots) |
248 | 0 | { |
249 | 0 | if (robots) |
250 | 0 | return wget_vector_size(robots->sitemaps); |
251 | | |
252 | 0 | return 0; |
253 | 0 | } |
254 | | |
255 | | /** |
256 | | * @param robots Pointer to instance of wget_robots |
257 | | * @param index Index of the wanted sitemap URL |
258 | | * @return Returns the sitemap URL at \p index or NULL |
259 | | */ |
260 | | const char *wget_robots_get_sitemap(wget_robots *robots, int index) |
261 | 0 | { |
262 | 0 | if (robots && robots->sitemaps) |
263 | 0 | return wget_vector_get(robots->sitemaps, index); |
264 | | |
265 | 0 | return NULL; |
266 | 0 | } |
267 | | |
268 | | /**@}*/ |