]> Lady’s Gitweb - CGirls/blob - request.c
ca58f0b49a40740b572e02afad94666aeb3b3df3
[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 void cgirls_freereq (cgirls_req req) {
8 free(req.cgirls_project);
9 free(req.cgirls_id);
10 if (req.cgirls_subpath) {
11 size_t i = 0;
12 char* c = req.cgirls_subpath[i];
13 while (c) {
14 free(c);
15 c = req.cgirls_subpath[++i];
16 }
17 free(req.cgirls_subpath);
18 }
19 free(req.cgirls_baseid);
20 free(req.cgirls_status.cgirls_message);
21 }
22
23 cgirls_req cgirls_path2req(char const*const pathinfo) {
24 assert(pathinfo != nullptr);
25
26 // Initialize the result.
27 cgirls_req req = {
28 .cgirls_action = cgirls_vb_index,
29 .cgirls_type = cgirls_mediatype_any,
30 .cgirls_project = nullptr,
31 .cgirls_id = nullptr,
32 .cgirls_subpath = nullptr,
33 .cgirls_baseid = nullptr,
34 .cgirls_status = {
35 .cgirls_code = 200,
36 .cgirls_message = nullptr,
37 },
38 };
39
40 // `sont´ stores the start of the next term; `eopi´ stores the end of
41 // the `pathinfo´ string, excluding any extension.
42 char const* sont = pathinfo;
43 char const*const eopi = strchr(pathinfo, 0);
44
45 // The portion of the pathinfo which precedes the first slash gives
46 // the project of the request. If there is no first slash, the
47 // project extends to the end of the string. An empty string is
48 // equivalent to having no project.
49 char const* eopj = strchr(sont, '/');
50 if (!eopj) {
51 eopj = eopi;
52 }
53 if (eopj > sont) {
54 req.cgirls_project = strndup(sont, eopj - sont);
55 }
56 if (eopj < eopi) {
57 sont = eopj + 1;
58 } else {
59 sont = eopi;
60 }
61
62 // The portion of the pathinfo which follows the first slash but
63 // precedes the second gives the verb of the request. If there is no
64 // second slash, the verb extends to the end of the string. If the
65 // verb is not present, or is the empty string, it is treated as
66 // `"index"´, unless the second slash is present, in which case it is
67 // treated as `"unknown"´.
68 //
69 // Verbs may be suffixed with one of a small number of extensions to
70 // request a specific type of response.
71 //
72 // Only a few verbs are recognized (corresponding to the `cgirls_vb´
73 // constants). If a verb is present, but unrecognized, it is assigned
74 // the special value `cgirls_vb_unknown´, which should generally be
75 // interpreted as an error.
76 char const* eovb = strchr(sont, '/');
77 if (!eovb) {
78 eovb = eopi;
79 }
80 char const*const eove = eovb;
81 char* verb = nullptr;
82 if (eovb - sont > 4) {
83 // If the verb is at least 5 characters, extract the extension if
84 // present (it will be the last 4), and then set the end of the
85 // verb to the start of the extension.
86 char const* exts = eovb - 4;
87 do {
88 // This “loop” encapsulates extension checking for readability.
89 // If an extension matches, `eovb´ is re·assigned to point to the
90 // beginning of the extension. Otherwise, the loop exits early
91 // and `eovb´ keeps pointing at the end of the string.
92 if (strncmp(exts, ".txt", 4) == 0) {
93 req.cgirls_type = cgirls_mediatype_txt;
94 } else if (strncmp(exts, ".htm", 4) == 0) {
95 req.cgirls_type = cgirls_mediatype_htm;
96 } else if (strncmp(exts, ".xml", 4) == 0) {
97 req.cgirls_type = cgirls_mediatype_xml;
98 } else if (strncmp(exts, ".rdf", 4) == 0) {
99 req.cgirls_type = cgirls_mediatype_rdf;
100 } else {
101 break; // do not re·assign `eovb´
102 }
103 eovb = exts;
104 } while (false);
105 }
106 if (eovb > sont) {
107 verb = strndup(sont, eovb - sont);
108 }
109 if (eove < eopi) {
110 sont = eove + 1;
111 } else {
112 sont = eopi;
113 }
114 if (verb) {
115 if (strcmp(verb, "branches") == 0) {
116 req.cgirls_action = cgirls_vb_branches;
117 } else if (strcmp(verb, "tags") == 0) {
118 req.cgirls_action = cgirls_vb_tags;
119 } else if (strcmp(verb, "show") == 0) {
120 req.cgirls_action = cgirls_vb_show;
121 } else if (strcmp(verb, "raw") == 0) {
122 req.cgirls_action = cgirls_vb_raw;
123 } else if (strcmp(verb, "blame") == 0) {
124 req.cgirls_action = cgirls_vb_blame;
125 } else if (strcmp(verb, "log") == 0) {
126 req.cgirls_action = cgirls_vb_log;
127 } else if (strcmp(verb, "shortlog") == 0) {
128 req.cgirls_action = cgirls_vb_shortlog;
129 } else if (strcmp(verb, "atom") == 0) {
130 req.cgirls_action = cgirls_vb_atom;
131 } else if (strcmp(verb, "patch") == 0) {
132 req.cgirls_action = cgirls_vb_patch;
133 } else if (strcmp(verb, "index") != 0) {
134 req.cgirls_action = cgirls_vb_unknown;
135 }
136 free(verb);
137 } else if (eovb < eopi) {
138 req.cgirls_action = cgirls_vb_unknown;
139 }
140
141 // The portion of the pathinfo which follows the second slash but
142 // precedes the third identifies the identifiers for the request. If
143 // there is no third slash, the identifiers extend to the end of the
144 // string. A single identifier may be given, or two identifiers may
145 // be given separated by two periods. An empty string is equivalent
146 // to no identifier.
147 char const* eoid = strchr(sont, '/');
148 if (!eoid) {
149 eoid = eopi;
150 }
151 char* idid = nullptr;
152 if (eoid > sont) {
153 idid = strndup(sont, eoid - sont);
154 }
155 if (eoid < eopi) {
156 sont = eoid + 1;
157 } else {
158 sont = eopi;
159 }
160 if (idid) {
161 // If the identifier string contains two successive dots, the base
162 // and target identifiers must be extracted and the original
163 // identifier string freed. Otherwise, the identifier string is the
164 // target identifier, and there is no base.
165 char const*const dots = strstr(idid, "..");
166 if (dots) {
167 char const*const eods = dots + 2;
168 char const*const eoii = strchr(idid, 0);
169 if (dots > idid) {
170 req.cgirls_baseid = strndup(idid, dots - idid);
171 }
172 if (eods < eoii) {
173 req.cgirls_id = strndup(eods, eoii - eods);
174 }
175 free(idid);
176 } else {
177 req.cgirls_id = idid;
178 }
179 }
180
181 // The portion of the pathinfo which follows the third slash is the
182 // subpath of the request. An empty sting is equivalent to having no
183 // subpath. Trailing and successive slashes are dropped.
184 char const* soct = sont;
185 char const* psep = nullptr;
186 size_t npth = 0;
187 while (eopi > soct) {
188 // Count the number of segments in the pathinfo so that the correct
189 // amount of space can be allocated.
190 psep = strchr(soct, '/');
191 if (!psep) {
192 psep = eopi;
193 }
194 if (psep > soct) {
195 ++npth;
196 }
197 if (eopi > psep) {
198 soct = psep + 1;
199 } else {
200 soct = eopi;
201 }
202 }
203 req.cgirls_subpath = calloc(npth + 1, sizeof(char*));
204 if (!req.cgirls_subpath) {
205 return req;
206 }
207 size_t pthi = 0;
208 while (eopi > sont) {
209 // Add the segments to the newly allocated array.
210 psep = strchr(sont, '/');
211 if (!psep) {
212 psep = eopi;
213 }
214 if (psep > sont) {
215 req.cgirls_subpath[pthi++] = strndup(sont, psep - sont);
216 }
217 if (eopi > psep) {
218 sont = psep + 1;
219 } else {
220 sont = eopi;
221 }
222 }
223 assert(pthi == npth);
224 req.cgirls_subpath[pthi] = nullptr;
225
226 // Return the result.
227 return req;
228 }
229
230 char* cgirls_req2path(cgirls_req req) {
231 char* action = "unknown";
232 char* extnsn = "";
233 size_t length = 8; // length of `action´ plus 1, to start
234
235 // Get the length of the various parts, saving the verb and the
236 // extension. This length includes a trailing slash, but in practice
237 // this will be replaced by the final null byte.
238 switch (req.cgirls_action) {
239 case cgirls_vb_index:
240 action = "index";
241 length = 6;
242 break;
243 case cgirls_vb_branches:
244 action = "branches";
245 length = 9;
246 break;
247 case cgirls_vb_tags:
248 action = "tags";
249 length = 5;
250 break;
251 case cgirls_vb_show:
252 action = "show";
253 length = 5;
254 break;
255 case cgirls_vb_raw:
256 action = "raw";
257 length = 4;
258 break;
259 case cgirls_vb_blame:
260 action = "blame";
261 length = 6;
262 break;
263 case cgirls_vb_log:
264 action = "log";
265 length = 4;
266 break;
267 case cgirls_vb_shortlog:
268 action = "shortlog";
269 length = 9;
270 break;
271 case cgirls_vb_atom:
272 action = "atom";
273 length = 5;
274 break;
275 case cgirls_vb_patch:
276 action = "patch";
277 length = 6;
278 break;
279 default:
280 break;
281 }
282 switch (req.cgirls_type) {
283 case cgirls_mediatype_txt:
284 extnsn = ".txt";
285 break;
286 case cgirls_mediatype_htm:
287 extnsn = ".htm";
288 break;
289 case cgirls_mediatype_xml:
290 extnsn = ".xml";
291 break;
292 case cgirls_mediatype_rdf:
293 extnsn = ".rdf";
294 break;
295 default:
296 break;
297 }
298 if (req.cgirls_project) {
299 length += strlen(req.cgirls_project) + 1;
300 if (req.cgirls_type != cgirls_mediatype_any) {
301 length += 4;
302 }
303 if (req.cgirls_baseid || req.cgirls_id) {
304 if (req.cgirls_baseid) {
305 length += strlen(req.cgirls_baseid) + 2;
306 }
307 if (req.cgirls_id) {
308 length += strlen(req.cgirls_id);
309 }
310 length += 1;
311 } else if (req.cgirls_subpath && req.cgirls_subpath[0]) {
312 length += 3;
313 }
314 if (req.cgirls_subpath) {
315 size_t i = 0;
316 char* c = req.cgirls_subpath[i];
317 while (c) {
318 length += strlen(c) + 1;
319 c = req.cgirls_subpath[++i];
320 }
321 }
322 } else {
323 // If there is no project, then the action must be removed, and the
324 // length is just that of the trailing slash.
325 length = 1;
326 }
327 // Create and compose the final path.
328 char* result = calloc(length, sizeof(char*));
329 if (!result) {
330 return nullptr;
331 }
332 char* cursor = result;
333 if (req.cgirls_project) {
334 cursor = stpcpy(cursor, req.cgirls_project);
335 (cursor++)[0] = '/';
336 cursor = stpcpy(cursor, action);
337 if (req.cgirls_type != cgirls_mediatype_any) {
338 cursor = stpcpy(cursor, extnsn);
339 }
340 (cursor++)[0] = '/';
341 if (req.cgirls_baseid || req.cgirls_id) {
342 if (req.cgirls_baseid) {
343 cursor = stpcpy(cursor, req.cgirls_baseid);
344 cursor[0] = '.';
345 cursor[1] = '.';
346 cursor += 2;
347 }
348 if (req.cgirls_id) {
349 cursor = stpcpy(cursor, req.cgirls_id);
350 }
351 (cursor++)[0] = '/';
352 } else if (req.cgirls_subpath && req.cgirls_subpath[0]) {
353 cursor = stpcpy(cursor, "../");
354 }
355 if (req.cgirls_subpath) {
356 size_t i = 0;
357 char* c = req.cgirls_subpath[i];
358 while (c) {
359 cursor = stpcpy(cursor, c);
360 c = req.cgirls_subpath[++i];
361 (cursor++)[0] = '/';
362 }
363 }
364 } else {
365 (cursor++)[0] = '/';
366 }
367
368 // At this point, `cursor´ points one ⹐past⹑ the last element of the
369 // array (this is allowed in C), and the last element is a slash.
370 // Rewind and set it to the null byte, and assert that everything was
371 // done correctly.
372 (--cursor)[0] = 0;
373 assert((cursor + 1) - result == length);
374 return result;
375 }
This page took 0.185473 seconds and 3 git commands to generate.