Coverage Report

Created: 2026-08-14 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
/**@}*/