]> Lady’s Gitweb - CGirls/blob - request.c
Dot‐prefix version and flags files
[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* sos = ndx[0];
168 char const* sep = nullptr;
169 size_t n·s = 0;
170 while (end[0] > sos) {
171 // Count the number of segments in the pathinfo so that the correct
172 // amount of space can be allocated.
173 sep = strchr(sos, '/');
174 if (!sep) {
175 sep = end[0];
176 }
177 if (sep > sos) {
178 ++n·s;
179 }
180 if (end[0] > sep) {
181 sos = sep + 1;
182 } else {
183 sos = end[0];
184 }
185 }
186 req.subpath = calloc(n·s + 1, sizeof(char*));
187 if (!req.subpath) {
188 return req;
189 }
190 size_t i·s = 0;
191 while (end[0] > ndx[0]) {
192 // Add the segments to the newly allocated array.
193 sep = strchr(ndx[0], '/');
194 if (!sep) {
195 sep = end[0];
196 }
197 if (sep > ndx[0]) {
198 req.subpath[i·s++] = strndup(ndx[0], sep - ndx[0]);
199 }
200 if (end[0] > sep) {
201 ndx[0] = sep + 1;
202 } else {
203 ndx[0] = end[0];
204 }
205 }
206 assert(i·s == n·s);
207 req.subpath[i·s] = nullptr;
208
209 // Return the result.
210 return req;
211 }
212
213 char* cgirls_req·to·path(cgirls_req req) {
214 char const* vb = nullptr;
215 char const* mtype = nullptr;
216 bool has·ids = req.baseid_THIS_WILL_CHANGE || req.id_THIS_WILL_CHANGE;
217 bool has·subpath = req.subpath && req.subpath[0];
218 size_t length = 0;
219
220 // Get the string corresponding to the verb. Do not assume that the
221 // verb is welbehaved (actually corresponding to an enumeration
222 // constant).
223 if (req.verb < cgirls_n·vbs) {
224 vb = cgirls_vbs[req.verb];
225 }
226 if (!vb) {
227 vb = cgirls_vbs[cgirls_vb_unknown];
228 }
229
230 // Get the string corresponding to the mediatype, or `nullptr´. Do
231 // not assume that the verb is welbehaved (actually corresponding to
232 // an enumeration constant).
233 if (req.mtype < cgirls_n·mtypes) {
234 mtype = cgirls_mtypes[req.mtype];
235 }
236
237 // Get the length of the various parts. This length includes a
238 // trailing slash, but in practice this will be replaced by the final
239 // null byte.
240 if (req.project) {
241 length += strlen(req.project) + 1;
242 if (req.verb != cgirls_vb_index || mtype || has·ids || has·subpath) {
243 length += strlen(vb) + 1;
244 }
245 if (mtype) {
246 length += strlen(mtype);
247 }
248 if (has·ids) {
249 if (req.baseid_THIS_WILL_CHANGE) {
250 length += strlen(req.baseid_THIS_WILL_CHANGE) + 2;
251 }
252 if (req.id_THIS_WILL_CHANGE) {
253 length += strlen(req.id_THIS_WILL_CHANGE);
254 }
255 length += 1;
256 } else if (has·subpath) {
257 length += 3;
258 }
259 if (has·subpath) {
260 size_t i = 0;
261 char* c = req.subpath[i];
262 while (c) {
263 length += strlen(c) + 1;
264 c = req.subpath[++i];
265 }
266 }
267 } else {
268 // If there is no project, then the action must be removed, and the
269 // length is just that of the trailing slash.
270 length = 1;
271 }
272 // Create and compose the final path.
273 char* result = calloc(length, sizeof(char*));
274 if (!result) {
275 return nullptr;
276 }
277 char* cursor = result;
278 if (req.project) {
279 cursor = stpcpy(cursor, req.project);
280 (cursor++)[0] = '/';
281 if (req.verb != cgirls_vb_index || mtype || has·ids || has·subpath) {
282 cursor = stpcpy(cursor, vb);
283 if (mtype) {
284 cursor = stpcpy(cursor, mtype);
285 }
286 (cursor++)[0] = '/';
287 }
288 if (has·ids) {
289 if (req.baseid_THIS_WILL_CHANGE) {
290 cursor = stpcpy(cursor, req.baseid_THIS_WILL_CHANGE);
291 cursor = stpcpy(cursor, "..");
292 }
293 if (req.id_THIS_WILL_CHANGE) {
294 cursor = stpcpy(cursor, req.id_THIS_WILL_CHANGE);
295 }
296 (cursor++)[0] = '/';
297 } else if (has·subpath) {
298 cursor = stpcpy(cursor, "../");
299 }
300 if (has·subpath) {
301 size_t i = 0;
302 char* c = req.subpath[i];
303 while (c) {
304 cursor = stpcpy(cursor, c);
305 c = req.subpath[++i];
306 (cursor++)[0] = '/';
307 }
308 }
309 } else {
310 (cursor++)[0] = '/';
311 }
312
313 // At this point, `cursor´ points one ⹐past⹑ the last element of the
314 // array (this is allowed in C), and the last element is a slash.
315 // Rewind and set it to the null byte, and assert that everything was
316 // done correctly.
317 (--cursor)[0] = 0;
318 assert((cursor + 1) - result == length);
319 return result;
320 }
This page took 0.389778 seconds and 5 git commands to generate.