]> Lady’s Gitweb - CGirls/blob - request.c
Drop `assert()´ check in subpath parsing
[CGirls] / request.c
1 // SPDX-FileCopyrightText: 2025 Lady <https://www.ladys.computer/about/#lady>
2 // SPDX-License-Identifier: GPL-2.0-only
3
4 #include "aa.h"
5 #include "request.h"
6
7 static char const*const cgirls_mtypes[] = {
8 [cgirls_mtype_txt] = ".txt",
9 [cgirls_mtype_htm] = ".htm",
10 [cgirls_mtype_xml] = ".xml",
11 [cgirls_mtype_rdf] = ".rdf",
12 };
13 constexpr size_t cgirls_n·mtypes =
14 sizeof(cgirls_mtypes) / sizeof(char*);
15
16 static char const*const cgirls_vbs[] = {
17 [cgirls_vb_unknown] = "unknown",
18 [cgirls_vb_index] = "index",
19 [cgirls_vb_show] = "show",
20 };
21 constexpr size_t cgirls_n·vbs =
22 sizeof(cgirls_vbs) / sizeof(char*);
23 static cgirls_vb const cgirls_parsable·vbs[] = {
24 cgirls_vb_index,
25 cgirls_vb_show,
26 };
27 constexpr size_t cgirls_n·parsable·vbs =
28 sizeof(cgirls_parsable·vbs) / sizeof(cgirls_vb);
29
30 void cgirls_req·free (cgirls_req req) {
31 free(req.project);
32 free(req.id_THIS_WILL_CHANGE);
33 if (req.subpath) {
34 size_t i = 0;
35 char* c = req.subpath[i];
36 while (c) {
37 free(c);
38 c = req.subpath[++i];
39 }
40 free(req.subpath);
41 }
42 free(req.baseid_THIS_WILL_CHANGE);
43 free(req.status.message);
44 }
45
46 static char* cgirls_gobble·path(char const* ndx[1], char const*const end[1]) {
47 char const* eor = strchr(ndx[0], '/');
48 char* result = nullptr;
49 if (!eor) {
50 eor = end[0];
51 }
52 if (eor > ndx[0]) {
53 result = strndup(ndx[0], eor - ndx[0]);
54 }
55 if (eor < end[0]) {
56 ndx[0] = eor + 1;
57 } else {
58 ndx[0] = end[0];
59 }
60 return result;
61 }
62
63 cgirls_req cgirls_path·to·req(char const*const pathinfo) {
64 assert(pathinfo != nullptr);
65
66 // Initialize the result.
67 cgirls_req req = {
68 .verb = cgirls_vb_unknown,
69 .mtype = cgirls_mtype_any,
70 .project = nullptr,
71 .id_THIS_WILL_CHANGE = nullptr,
72 .subpath = nullptr,
73 .baseid_THIS_WILL_CHANGE = nullptr,
74 .status = {
75 .code = 200,
76 .message = nullptr,
77 },
78 };
79
80 // `ndx´ stores the start of the next term; `end´ stores the end of
81 // the `pathinfo´ string.
82 char const* ndx[1] = { pathinfo };
83 char const*const end[1] = { strchr(pathinfo, 0) };
84 assert(end[0] != nullptr);
85
86 // The portion of the pathinfo which precedes the first slash gives
87 // the project of the request. If there is no first slash, the
88 // project extends to the end of the string. An empty string is
89 // equivalent to having no project.
90 req.project = cgirls_gobble·path(ndx, end);
91
92 // The portion of the pathinfo which follows the first slash but
93 // precedes the second gives the action of the request. If there is
94 // no second slash, the action extends to the end of the string. If
95 // the action is not present, or is the empty string, it is treated
96 // as `"index"´, unless the second slash is present, in which case it
97 // is treated as `"unknown"´.
98 //
99 // Actions consist of verbs optionally suffixed with one of a small
100 // number of extensions to request a specific type of response.
101 //
102 // Only a few verbs are recognized (corresponding to the `cgirls_vb´
103 // constants). If a verb is present, but unrecognized, it is assigned
104 // the special value `cgirls_vb_unknown´, which should generally be
105 // interpreted as an error.
106 char* soa = cgirls_gobble·path(ndx, end);
107 if (soa) {
108 char*const eoa = strchr(soa, 0);
109 if (eoa - soa > 4) {
110 // If the verb is at least 5 characters, extract the extension if
111 // present (it will be the last 4). Then set the first character
112 // of the extension to null, effectively trimming the verb.
113 char* ext = eoa - 4;
114 for (cgirls_mtype i = 0; i < cgirls_n·mtypes; ++i) {
115 char const*const ixt = cgirls_mtypes[i];
116 if (ixt && strncmp(ext, ixt, 4) == 0) {
117 req.mtype = i;
118 ext[0] = 0;
119 break;
120 }
121 }
122 }
123 for (size_t i = 0; i < cgirls_n·parsable·vbs; ++i) {
124 cgirls_vb ivb = cgirls_parsable·vbs[i];
125 char const*const svb = cgirls_vbs[ivb];
126 if (svb && strcmp(soa, svb) == 0) {
127 req.verb = ivb;
128 break;
129 }
130 }
131 free(soa);
132 } else if (ndx[0] == end[0]) {
133 req.verb = cgirls_vb_index;
134 }
135
136 // The portion of the pathinfo which follows the second slash but
137 // precedes the third identifies the identifiers for the request. If
138 // there is no third slash, the identifiers extend to the end of the
139 // string. A single identifier may be given, or two identifiers may
140 // be given separated by two periods. An empty string is equivalent
141 // to no identifier.
142 char* idid = cgirls_gobble·path(ndx, end);
143 if (idid) {
144 // If the identifier string contains two successive dots, the base
145 // and target identifiers must be extracted and the original
146 // identifier string freed. Otherwise, the identifier string is the
147 // target identifier, and there is no base.
148 char const*const dots = strstr(idid, "..");
149 if (dots) {
150 char const*const eods = dots + 2;
151 char const*const eoii = strchr(idid, 0);
152 if (dots > idid) {
153 req.baseid_THIS_WILL_CHANGE = strndup(idid, dots - idid);
154 }
155 if (eods < eoii) {
156 req.id_THIS_WILL_CHANGE = strndup(eods, eoii - eods);
157 }
158 free(idid);
159 } else {
160 req.id_THIS_WILL_CHANGE = idid;
161 }
162 }
163
164 // The portion of the pathinfo which follows the third slash is the
165 // subpath of the request. An empty sting is equivalent to having no
166 // subpath. Trailing and successive slashes are dropped.
167 char const* sep = nullptr;
168 size_t n·s = 0;
169 for (
170 char const* sos = ndx[0];
171 sos < end[0];
172 sos = (end[0] > sep ? sep + 1 : end[0])
173 ) {
174 // Count the number of segments in the pathinfo so that the correct
175 // amount of space can be allocated.
176 sep = strchr(sos, '/');
177 if (!sep) {
178 sep = end[0];
179 }
180 if (sep > sos) {
181 ++n·s;
182 }
183 }
184 if (n·s > 0) {
185 req.subpath = calloc(n·s + 1, sizeof(char*));
186 if (!req.subpath) {
187 return req;
188 }
189 for (
190 size_t i·s = 0;
191 i·s < n·s;
192 ndx[0] = (end[0] > sep ? sep + 1 : end[0])
193 ) {
194 // Add the segments to the newly allocated array.
195 sep = strchr(ndx[0], '/');
196 if (!sep) {
197 sep = end[0];
198 }
199 if (sep > ndx[0]) {
200 req.subpath[i·s++] = strndup(ndx[0], sep - ndx[0]);
201 }
202 }
203 req.subpath[n·s] = nullptr;
204 }
205
206 // Return the result.
207 return req;
208 }
209
210 char* cgirls_req·to·path(cgirls_req req) {
211 char const* vb = nullptr;
212 char const* mtype = nullptr;
213 bool has·ids = req.baseid_THIS_WILL_CHANGE || req.id_THIS_WILL_CHANGE;
214 bool has·subpath = req.subpath && req.subpath[0];
215 size_t length = 0;
216
217 // Get the string corresponding to the verb. Do not assume that the
218 // verb is welbehaved (actually corresponding to an enumeration
219 // constant).
220 if (req.verb < cgirls_n·vbs) {
221 vb = cgirls_vbs[req.verb];
222 }
223 if (!vb) {
224 vb = cgirls_vbs[cgirls_vb_unknown];
225 }
226
227 // Get the string corresponding to the mediatype, or `nullptr´. Do
228 // not assume that the verb is welbehaved (actually corresponding to
229 // an enumeration constant).
230 if (req.mtype < cgirls_n·mtypes) {
231 mtype = cgirls_mtypes[req.mtype];
232 }
233
234 // Get the length of the various parts. This length includes a
235 // trailing slash, but in practice this will be replaced by the final
236 // null byte.
237 if (req.project) {
238 length += strlen(req.project) + 1;
239 if (req.verb != cgirls_vb_index || mtype || has·ids || has·subpath) {
240 length += strlen(vb) + 1;
241 }
242 if (mtype) {
243 length += strlen(mtype);
244 }
245 if (has·ids) {
246 if (req.baseid_THIS_WILL_CHANGE) {
247 length += strlen(req.baseid_THIS_WILL_CHANGE) + 2;
248 }
249 if (req.id_THIS_WILL_CHANGE) {
250 length += strlen(req.id_THIS_WILL_CHANGE);
251 }
252 length += 1;
253 } else if (has·subpath) {
254 length += 3;
255 }
256 if (has·subpath) {
257 size_t i = 0;
258 char* c = req.subpath[i];
259 while (c) {
260 length += strlen(c) + 1;
261 c = req.subpath[++i];
262 }
263 }
264 } else {
265 // If there is no project, then the action must be removed, and the
266 // length is just that of the trailing slash.
267 length = 1;
268 }
269 // Create and compose the final path.
270 char* result = calloc(length, sizeof(char*));
271 if (!result) {
272 return nullptr;
273 }
274 char* cursor = result;
275 if (req.project) {
276 cursor = stpcpy(cursor, req.project);
277 (cursor++)[0] = '/';
278 if (req.verb != cgirls_vb_index || mtype || has·ids || has·subpath) {
279 cursor = stpcpy(cursor, vb);
280 if (mtype) {
281 cursor = stpcpy(cursor, mtype);
282 }
283 (cursor++)[0] = '/';
284 }
285 if (has·ids) {
286 if (req.baseid_THIS_WILL_CHANGE) {
287 cursor = stpcpy(cursor, req.baseid_THIS_WILL_CHANGE);
288 cursor = stpcpy(cursor, "..");
289 }
290 if (req.id_THIS_WILL_CHANGE) {
291 cursor = stpcpy(cursor, req.id_THIS_WILL_CHANGE);
292 }
293 (cursor++)[0] = '/';
294 } else if (has·subpath) {
295 cursor = stpcpy(cursor, "../");
296 }
297 if (has·subpath) {
298 size_t i = 0;
299 char* c = req.subpath[i];
300 while (c) {
301 cursor = stpcpy(cursor, c);
302 c = req.subpath[++i];
303 (cursor++)[0] = '/';
304 }
305 }
306 } else {
307 (cursor++)[0] = '/';
308 }
309
310 // At this point, `cursor´ points one ⹐past⹑ the last element of the
311 // array (this is allowed in C), and the last element is a slash.
312 // Rewind and set it to the null byte, and assert that everything was
313 // done correctly.
314 (--cursor)[0] = 0;
315 assert((cursor + 1) - result == length);
316 return result;
317 }
This page took 0.192383 seconds and 5 git commands to generate.