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