Blame


1 014c66b6 2023-06-25 op /*
2 014c66b6 2023-06-25 op * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
3 014c66b6 2023-06-25 op *
4 014c66b6 2023-06-25 op * Permission to use, copy, modify, and distribute this software for any
5 014c66b6 2023-06-25 op * purpose with or without fee is hereby granted, provided that the above
6 014c66b6 2023-06-25 op * copyright notice and this permission notice appear in all copies.
7 014c66b6 2023-06-25 op *
8 014c66b6 2023-06-25 op * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 014c66b6 2023-06-25 op * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 014c66b6 2023-06-25 op * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 014c66b6 2023-06-25 op * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 014c66b6 2023-06-25 op * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 014c66b6 2023-06-25 op * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 014c66b6 2023-06-25 op * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 014c66b6 2023-06-25 op */
16 014c66b6 2023-06-25 op
17 014c66b6 2023-06-25 op #include <sys/tree.h>
18 014c66b6 2023-06-25 op
19 014c66b6 2023-06-25 op #include <ctype.h>
20 014c66b6 2023-06-25 op #include <event.h>
21 014c66b6 2023-06-25 op #include <fnmatch.h>
22 014c66b6 2023-06-25 op #include <limits.h>
23 014c66b6 2023-06-25 op #include <signal.h>
24 014c66b6 2023-06-25 op #include <stdlib.h>
25 014c66b6 2023-06-25 op #include <string.h>
26 014c66b6 2023-06-25 op #include <unistd.h>
27 014c66b6 2023-06-25 op
28 014c66b6 2023-06-25 op #include <sqlite3.h>
29 014c66b6 2023-06-25 op
30 014c66b6 2023-06-25 op #include "log.h"
31 014c66b6 2023-06-25 op #include "pkg.h"
32 4218a914 2024-01-08 op
33 4218a914 2024-01-08 op #if template
34 014c66b6 2023-06-25 op #include "tmpl.h"
35 4218a914 2024-01-08 op #endif
36 014c66b6 2023-06-25 op
37 014c66b6 2023-06-25 op #ifndef nitems
38 014c66b6 2023-06-25 op #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
39 014c66b6 2023-06-25 op #endif
40 014c66b6 2023-06-25 op
41 014c66b6 2023-06-25 op char dbpath[PATH_MAX];
42 014c66b6 2023-06-25 op
43 014c66b6 2023-06-25 op void server_sig_handler(int, short, void *);
44 014c66b6 2023-06-25 op void server_open_db(struct env *);
45 014c66b6 2023-06-25 op void server_close_db(struct env *);
46 014c66b6 2023-06-25 op __dead void server_shutdown(struct env *);
47 014c66b6 2023-06-25 op int server_reply(struct client *, int, const char *);
48 014c66b6 2023-06-25 op
49 014c66b6 2023-06-25 op int route_dispatch(struct env *, struct client *);
50 014c66b6 2023-06-25 op int route_home(struct env *, struct client *);
51 014c66b6 2023-06-25 op int route_search(struct env *, struct client *);
52 014c66b6 2023-06-25 op int route_categories(struct env *, struct client *);
53 014c66b6 2023-06-25 op int route_listing(struct env *, struct client *);
54 014c66b6 2023-06-25 op int route_port(struct env *, struct client *);
55 014c66b6 2023-06-25 op
56 014c66b6 2023-06-25 op typedef int (*route_t)(struct env *, struct client *);
57 014c66b6 2023-06-25 op
58 014c66b6 2023-06-25 op static const struct route {
59 014c66b6 2023-06-25 op const char *r_path;
60 014c66b6 2023-06-25 op route_t r_fn;
61 014c66b6 2023-06-25 op } routes[] = {
62 014c66b6 2023-06-25 op { "/", route_home },
63 014c66b6 2023-06-25 op { "/search", route_search },
64 014c66b6 2023-06-25 op { "/all", route_categories },
65 014c66b6 2023-06-25 op { "/*", route_port },
66 014c66b6 2023-06-25 op };
67 014c66b6 2023-06-25 op
68 014c66b6 2023-06-25 op void
69 014c66b6 2023-06-25 op server_sig_handler(int sig, short ev, void *arg)
70 014c66b6 2023-06-25 op {
71 014c66b6 2023-06-25 op struct env *env = arg;
72 014c66b6 2023-06-25 op
73 014c66b6 2023-06-25 op /*
74 014c66b6 2023-06-25 op * Normal signal handler rules don't apply because libevent
75 014c66b6 2023-06-25 op * decouples for us.
76 014c66b6 2023-06-25 op */
77 014c66b6 2023-06-25 op
78 014c66b6 2023-06-25 op switch (sig) {
79 014c66b6 2023-06-25 op case SIGHUP:
80 014c66b6 2023-06-25 op log_info("re-opening the db");
81 014c66b6 2023-06-25 op server_close_db(env);
82 014c66b6 2023-06-25 op server_open_db(env);
83 014c66b6 2023-06-25 op break;
84 014c66b6 2023-06-25 op case SIGTERM:
85 014c66b6 2023-06-25 op case SIGINT:
86 014c66b6 2023-06-25 op server_shutdown(env);
87 014c66b6 2023-06-25 op break;
88 014c66b6 2023-06-25 op default:
89 014c66b6 2023-06-25 op fatalx("unexpected signal %d", sig);
90 014c66b6 2023-06-25 op }
91 014c66b6 2023-06-25 op }
92 014c66b6 2023-06-25 op
93 014c66b6 2023-06-25 op static inline void
94 014c66b6 2023-06-25 op loadstmt(sqlite3 *db, sqlite3_stmt **stmt, const char *sql)
95 014c66b6 2023-06-25 op {
96 014c66b6 2023-06-25 op int err;
97 014c66b6 2023-06-25 op
98 014c66b6 2023-06-25 op err = sqlite3_prepare_v2(db, sql, -1, stmt, NULL);
99 014c66b6 2023-06-25 op if (err != SQLITE_OK)
100 014c66b6 2023-06-25 op fatalx("failed prepare statement \"%s\": %s",
101 014c66b6 2023-06-25 op sql, sqlite3_errstr(err));
102 014c66b6 2023-06-25 op }
103 014c66b6 2023-06-25 op
104 014c66b6 2023-06-25 op void
105 014c66b6 2023-06-25 op server_open_db(struct env *env)
106 014c66b6 2023-06-25 op {
107 014c66b6 2023-06-25 op int err;
108 014c66b6 2023-06-25 op
109 014c66b6 2023-06-25 op err = sqlite3_open_v2(dbpath, &env->env_db,
110 014c66b6 2023-06-25 op SQLITE_OPEN_READONLY, NULL);
111 014c66b6 2023-06-25 op if (err != SQLITE_OK)
112 014c66b6 2023-06-25 op fatalx("can't open database %s: %s", dbpath,
113 014c66b6 2023-06-25 op sqlite3_errmsg(env->env_db));
114 014c66b6 2023-06-25 op
115 014c66b6 2023-06-25 op /* load prepared statements */
116 014c66b6 2023-06-25 op loadstmt(env->env_db, &env->env_qsearch,
117 014c66b6 2023-06-25 op "select webpkg_fts.pkgstem, webpkg_fts.comment, paths.fullpkgpath"
118 014c66b6 2023-06-25 op " from webpkg_fts"
119 014c66b6 2023-06-25 op " join _ports p on p.fullpkgpath = webpkg_fts.id"
120 014c66b6 2023-06-25 op " join _paths paths on paths.id = webpkg_fts.id"
121 014c66b6 2023-06-25 op " where webpkg_fts match ?"
122 014c66b6 2023-06-25 op " order by bm25(webpkg_fts)");
123 014c66b6 2023-06-25 op
124 014c66b6 2023-06-25 op loadstmt(env->env_db, &env->env_qfullpkgpath,
125 014c66b6 2023-06-25 op "select p.fullpkgpath, pp.pkgstem, pp.comment, pp.pkgname,"
126 014c66b6 2023-06-25 op " d.value, e.value, r.value, pp.homepage"
127 014c66b6 2023-06-25 op " from _paths p"
128 014c66b6 2023-06-25 op " join _descr d on d.fullpkgpath = p.id"
129 014c66b6 2023-06-25 op " join _ports pp on pp.fullpkgpath = p.id"
130 014c66b6 2023-06-25 op " join _email e on e.keyref = pp.maintainer"
131 014c66b6 2023-06-25 op " left join _readme r on r.fullpkgpath = p.id"
132 014c66b6 2023-06-25 op " where p.fullpkgpath = ?");
133 014c66b6 2023-06-25 op
134 014c66b6 2023-06-25 op loadstmt(env->env_db, &env->env_qcats,
135 014c66b6 2023-06-25 op "select distinct value from categories order by value");
136 014c66b6 2023-06-25 op
137 014c66b6 2023-06-25 op loadstmt(env->env_db, &env->env_qbycat,
138 014c66b6 2023-06-25 op "select fullpkgpath from categories where value = ?"
139 014c66b6 2023-06-25 op " order by fullpkgpath");
140 014c66b6 2023-06-25 op }
141 014c66b6 2023-06-25 op
142 014c66b6 2023-06-25 op void
143 014c66b6 2023-06-25 op server_close_db(struct env *env)
144 014c66b6 2023-06-25 op {
145 014c66b6 2023-06-25 op int err;
146 014c66b6 2023-06-25 op
147 014c66b6 2023-06-25 op sqlite3_finalize(env->env_qsearch);
148 014c66b6 2023-06-25 op sqlite3_finalize(env->env_qfullpkgpath);
149 014c66b6 2023-06-25 op sqlite3_finalize(env->env_qcats);
150 014c66b6 2023-06-25 op sqlite3_finalize(env->env_qbycat);
151 014c66b6 2023-06-25 op
152 014c66b6 2023-06-25 op if ((err = sqlite3_close(env->env_db)) != SQLITE_OK)
153 014c66b6 2023-06-25 op log_warnx("sqlite3_close %s", sqlite3_errstr(err));
154 014c66b6 2023-06-25 op }
155 014c66b6 2023-06-25 op
156 014c66b6 2023-06-25 op int
157 014c66b6 2023-06-25 op server_main(const char *db)
158 014c66b6 2023-06-25 op {
159 014c66b6 2023-06-25 op struct env env;
160 014c66b6 2023-06-25 op struct event sighup;
161 014c66b6 2023-06-25 op struct event sigint;
162 014c66b6 2023-06-25 op struct event sigterm;
163 014c66b6 2023-06-25 op
164 014c66b6 2023-06-25 op signal(SIGPIPE, SIG_IGN);
165 014c66b6 2023-06-25 op
166 014c66b6 2023-06-25 op memset(&env, 0, sizeof(env));
167 014c66b6 2023-06-25 op
168 014c66b6 2023-06-25 op if (pledge("stdio rpath flock unix", NULL) == -1)
169 014c66b6 2023-06-25 op fatal("pledge");
170 014c66b6 2023-06-25 op
171 014c66b6 2023-06-25 op if (realpath(db, dbpath) == NULL)
172 014c66b6 2023-06-25 op fatal("realpath %s", db);
173 014c66b6 2023-06-25 op
174 014c66b6 2023-06-25 op server_open_db(&env);
175 014c66b6 2023-06-25 op
176 014c66b6 2023-06-25 op event_init();
177 014c66b6 2023-06-25 op
178 014c66b6 2023-06-25 op env.env_sockfd = 3;
179 014c66b6 2023-06-25 op
180 014c66b6 2023-06-25 op event_set(&env.env_sockev, env.env_sockfd, EV_READ | EV_PERSIST,
181 014c66b6 2023-06-25 op fcgi_accept, &env);
182 014c66b6 2023-06-25 op event_add(&env.env_sockev, NULL);
183 014c66b6 2023-06-25 op
184 014c66b6 2023-06-25 op evtimer_set(&env.env_pausev, fcgi_accept, &env);
185 014c66b6 2023-06-25 op
186 014c66b6 2023-06-25 op signal_set(&sighup, SIGHUP, server_sig_handler, &env);
187 014c66b6 2023-06-25 op signal_set(&sigint, SIGINT, server_sig_handler, &env);
188 014c66b6 2023-06-25 op signal_set(&sigterm, SIGTERM, server_sig_handler, &env);
189 014c66b6 2023-06-25 op
190 014c66b6 2023-06-25 op signal_add(&sighup, NULL);
191 014c66b6 2023-06-25 op signal_add(&sigint, NULL);
192 014c66b6 2023-06-25 op signal_add(&sigterm, NULL);
193 014c66b6 2023-06-25 op
194 014c66b6 2023-06-25 op log_info("ready");
195 014c66b6 2023-06-25 op event_dispatch();
196 014c66b6 2023-06-25 op
197 014c66b6 2023-06-25 op server_shutdown(&env);
198 014c66b6 2023-06-25 op }
199 014c66b6 2023-06-25 op
200 014c66b6 2023-06-25 op void __dead
201 014c66b6 2023-06-25 op server_shutdown(struct env *env)
202 014c66b6 2023-06-25 op {
203 014c66b6 2023-06-25 op log_info("shutting down");
204 014c66b6 2023-06-25 op server_close_db(env);
205 014c66b6 2023-06-25 op exit(0);
206 014c66b6 2023-06-25 op }
207 014c66b6 2023-06-25 op
208 014c66b6 2023-06-25 op int
209 014c66b6 2023-06-25 op server_reply(struct client *clt, int status, const char *ctype)
210 014c66b6 2023-06-25 op {
211 014c66b6 2023-06-25 op if (clt_printf(clt, "%02d %s\r\n", status, ctype) == -1)
212 014c66b6 2023-06-25 op return (-1);
213 014c66b6 2023-06-25 op return (0);
214 014c66b6 2023-06-25 op }
215 014c66b6 2023-06-25 op
216 014c66b6 2023-06-25 op int
217 014c66b6 2023-06-25 op server_handle(struct env *env, struct client *clt)
218 014c66b6 2023-06-25 op {
219 883611a9 2023-08-08 op log_debug("SCRIPT_NAME %s", clt->clt_script_name);
220 883611a9 2023-08-08 op log_debug("PATH_INFO %s", clt->clt_path_info);
221 014c66b6 2023-06-25 op return (route_dispatch(env, clt));
222 014c66b6 2023-06-25 op }
223 014c66b6 2023-06-25 op
224 014c66b6 2023-06-25 op void
225 014c66b6 2023-06-25 op server_client_free(struct client *clt)
226 014c66b6 2023-06-25 op {
227 014c66b6 2023-06-25 op #if template
228 014c66b6 2023-06-25 op template_free(clt->clt_tp);
229 014c66b6 2023-06-25 op #endif
230 014c66b6 2023-06-25 op free(clt->clt_server_name);
231 014c66b6 2023-06-25 op free(clt->clt_script_name);
232 014c66b6 2023-06-25 op free(clt->clt_path_info);
233 014c66b6 2023-06-25 op free(clt->clt_query);
234 014c66b6 2023-06-25 op free(clt);
235 014c66b6 2023-06-25 op }
236 014c66b6 2023-06-25 op
237 014c66b6 2023-06-25 op static inline int
238 014c66b6 2023-06-25 op unquote(char *str)
239 014c66b6 2023-06-25 op {
240 014c66b6 2023-06-25 op char *p, *q;
241 014c66b6 2023-06-25 op char hex[3];
242 014c66b6 2023-06-25 op unsigned long x;
243 014c66b6 2023-06-25 op
244 014c66b6 2023-06-25 op hex[2] = '\0';
245 014c66b6 2023-06-25 op p = q = str;
246 014c66b6 2023-06-25 op while (*p) {
247 014c66b6 2023-06-25 op switch (*p) {
248 014c66b6 2023-06-25 op case '%':
249 014c66b6 2023-06-25 op if (!isxdigit((unsigned char)p[1]) ||
250 014c66b6 2023-06-25 op !isxdigit((unsigned char)p[2]) ||
251 014c66b6 2023-06-25 op (p[1] == '0' && p[2] == '0'))
252 014c66b6 2023-06-25 op return (-1);
253 014c66b6 2023-06-25 op
254 014c66b6 2023-06-25 op hex[0] = p[1];
255 014c66b6 2023-06-25 op hex[1] = p[2];
256 014c66b6 2023-06-25 op
257 014c66b6 2023-06-25 op x = strtoul(hex, NULL, 16);
258 014c66b6 2023-06-25 op *q++ = (char)x;
259 014c66b6 2023-06-25 op p += 3;
260 014c66b6 2023-06-25 op break;
261 014c66b6 2023-06-25 op default:
262 014c66b6 2023-06-25 op *q++ = *p++;
263 014c66b6 2023-06-25 op break;
264 014c66b6 2023-06-25 op }
265 014c66b6 2023-06-25 op }
266 014c66b6 2023-06-25 op *q = '\0';
267 014c66b6 2023-06-25 op return (0);
268 014c66b6 2023-06-25 op }
269 014c66b6 2023-06-25 op
270 014c66b6 2023-06-25 op static inline int
271 014c66b6 2023-06-25 op fts_escape(const char *p, char *buf, size_t bufsize)
272 014c66b6 2023-06-25 op {
273 014c66b6 2023-06-25 op char *q;
274 014c66b6 2023-06-25 op
275 014c66b6 2023-06-25 op /*
276 014c66b6 2023-06-25 op * split p into words and quote them into buf.
277 014c66b6 2023-06-25 op * quoting means wrapping each word into "..." and
278 014c66b6 2023-06-25 op * replace every " with "".
279 014c66b6 2023-06-25 op * i.e. 'C++ "framework"' -> '"C++" """framework"""'
280 014c66b6 2023-06-25 op * flatting all the whitespaces seems fine too.
281 014c66b6 2023-06-25 op */
282 014c66b6 2023-06-25 op
283 014c66b6 2023-06-25 op q = buf;
284 014c66b6 2023-06-25 op while (bufsize != 0) {
285 014c66b6 2023-06-25 op p += strspn(p, " \f\n\r\t\v");
286 014c66b6 2023-06-25 op if (*p == '\0')
287 014c66b6 2023-06-25 op break;
288 014c66b6 2023-06-25 op
289 014c66b6 2023-06-25 op *q++ = '"';
290 014c66b6 2023-06-25 op bufsize--;
291 014c66b6 2023-06-25 op while (*p && !isspace((unsigned char)*p) && bufsize != 0) {
292 014c66b6 2023-06-25 op if (*p == '"') { /* double the quote character */
293 014c66b6 2023-06-25 op *q++ = '"';
294 014c66b6 2023-06-25 op bufsize--;
295 014c66b6 2023-06-25 op if (bufsize == 0)
296 014c66b6 2023-06-25 op break;
297 014c66b6 2023-06-25 op }
298 014c66b6 2023-06-25 op *q++ = *p++;
299 014c66b6 2023-06-25 op bufsize--;
300 014c66b6 2023-06-25 op }
301 014c66b6 2023-06-25 op
302 014c66b6 2023-06-25 op if (bufsize < 2)
303 014c66b6 2023-06-25 op break;
304 014c66b6 2023-06-25 op *q++ = '"';
305 014c66b6 2023-06-25 op *q++ = ' ';
306 014c66b6 2023-06-25 op bufsize -= 2;
307 014c66b6 2023-06-25 op }
308 014c66b6 2023-06-25 op if ((*p == '\0') && bufsize != 0) {
309 014c66b6 2023-06-25 op *q = '\0';
310 014c66b6 2023-06-25 op return (0);
311 014c66b6 2023-06-25 op }
312 014c66b6 2023-06-25 op
313 014c66b6 2023-06-25 op return (-1);
314 014c66b6 2023-06-25 op }
315 014c66b6 2023-06-25 op
316 014c66b6 2023-06-25 op int
317 014c66b6 2023-06-25 op route_dispatch(struct env *env, struct client *clt)
318 014c66b6 2023-06-25 op {
319 014c66b6 2023-06-25 op const struct route *r;
320 014c66b6 2023-06-25 op size_t i;
321 014c66b6 2023-06-25 op
322 014c66b6 2023-06-25 op for (i = 0; i < nitems(routes); ++i) {
323 014c66b6 2023-06-25 op r = &routes[i];
324 014c66b6 2023-06-25 op
325 014c66b6 2023-06-25 op if (fnmatch(r->r_path, clt->clt_path_info, 0) != 0)
326 014c66b6 2023-06-25 op continue;
327 014c66b6 2023-06-25 op return (r->r_fn(env, clt));
328 014c66b6 2023-06-25 op }
329 014c66b6 2023-06-25 op
330 014c66b6 2023-06-25 op if (server_reply(clt, 51, "not found") == -1)
331 014c66b6 2023-06-25 op return (-1);
332 014c66b6 2023-06-25 op return (fcgi_end_request(clt, 0));
333 014c66b6 2023-06-25 op }
334 014c66b6 2023-06-25 op
335 014c66b6 2023-06-25 op int
336 014c66b6 2023-06-25 op route_home(struct env *env, struct client *clt)
337 014c66b6 2023-06-25 op {
338 014c66b6 2023-06-25 op if (server_reply(clt, 20, "text/gemini") == -1)
339 014c66b6 2023-06-25 op return (-1);
340 014c66b6 2023-06-25 op
341 014c66b6 2023-06-25 op #if 1
342 014c66b6 2023-06-25 op if (clt_printf(clt, "# pkg_fcgi\n\n") == -1)
343 014c66b6 2023-06-25 op return (-1);
344 014c66b6 2023-06-25 op if (clt_printf(clt, "Welcome to pkg_fcgi, the Gemini interface "
345 014c66b6 2023-06-25 op "for the OpenBSD ports collection.\n\n") == -1)
346 014c66b6 2023-06-25 op return (-1);
347 dd58c961 2023-08-08 op if (clt_printf(clt, "=> %s/search Search for a package\n",
348 dd58c961 2023-08-08 op clt->clt_script_name) == -1)
349 014c66b6 2023-06-25 op return (-1);
350 106d2b7a 2023-08-09 op if (clt_printf(clt, "=> %s/all All categories\n",
351 dd58c961 2023-08-08 op clt->clt_script_name) == -1)
352 014c66b6 2023-06-25 op return (-1);
353 014c66b6 2023-06-25 op if (clt_printf(clt, "\n") == -1)
354 014c66b6 2023-06-25 op return (-1);
355 014c66b6 2023-06-25 op if (clt_printf(clt, "What you search will be matched against the "
356 014c66b6 2023-06-25 op "package name (pkgstem), comment, DESCR and maintainer.\n") == -1)
357 014c66b6 2023-06-25 op return (-1);
358 014c66b6 2023-06-25 op #else
359 014c66b6 2023-06-25 op if (tp_home(clt->clt_tp) == -1)
360 014c66b6 2023-06-25 op return (-1);
361 014c66b6 2023-06-25 op #endif
362 014c66b6 2023-06-25 op
363 014c66b6 2023-06-25 op return (fcgi_end_request(clt, 0));
364 014c66b6 2023-06-25 op }
365 014c66b6 2023-06-25 op
366 014c66b6 2023-06-25 op int
367 014c66b6 2023-06-25 op route_search(struct env *env, struct client *clt)
368 014c66b6 2023-06-25 op {
369 014c66b6 2023-06-25 op const char *stem, *comment, *fullpkgpath;
370 014c66b6 2023-06-25 op char *query = clt->clt_query;
371 014c66b6 2023-06-25 op char equery[1024];
372 014c66b6 2023-06-25 op int err;
373 014c66b6 2023-06-25 op int found = 0;
374 014c66b6 2023-06-25 op
375 014c66b6 2023-06-25 op if (query == NULL || *query == '\0') {
376 014c66b6 2023-06-25 op if (server_reply(clt, 10, "search for a package") == -1)
377 014c66b6 2023-06-25 op return (-1);
378 014c66b6 2023-06-25 op return (fcgi_end_request(clt, 0));
379 014c66b6 2023-06-25 op }
380 014c66b6 2023-06-25 op
381 014c66b6 2023-06-25 op if (unquote(query) == -1 ||
382 014c66b6 2023-06-25 op fts_escape(query, equery, sizeof(equery)) == -1) {
383 014c66b6 2023-06-25 op if (server_reply(clt, 59, "bad request") == -1)
384 014c66b6 2023-06-25 op return (-1);
385 014c66b6 2023-06-25 op return (fcgi_end_request(clt, 1));
386 014c66b6 2023-06-25 op }
387 014c66b6 2023-06-25 op
388 014c66b6 2023-06-25 op log_debug("searching for %s", equery);
389 014c66b6 2023-06-25 op
390 014c66b6 2023-06-25 op err = sqlite3_bind_text(env->env_qsearch, 1, equery, -1, NULL);
391 014c66b6 2023-06-25 op if (err != SQLITE_OK) {
392 014c66b6 2023-06-25 op log_warnx("%s: sqlite3_bind_text \"%s\": %s", __func__,
393 014c66b6 2023-06-25 op query, sqlite3_errstr(err));
394 014c66b6 2023-06-25 op sqlite3_reset(env->env_qsearch);
395 014c66b6 2023-06-25 op
396 014c66b6 2023-06-25 op if (server_reply(clt, 42, "internal error") == -1)
397 014c66b6 2023-06-25 op return (-1);
398 014c66b6 2023-06-25 op return (fcgi_end_request(clt, 1));
399 014c66b6 2023-06-25 op }
400 014c66b6 2023-06-25 op
401 014c66b6 2023-06-25 op if (server_reply(clt, 20, "text/gemini") == -1)
402 014c66b6 2023-06-25 op goto err;
403 014c66b6 2023-06-25 op
404 014c66b6 2023-06-25 op if (clt_printf(clt, "# search results for %s\n\n", query) == -1)
405 014c66b6 2023-06-25 op goto err;
406 014c66b6 2023-06-25 op
407 014c66b6 2023-06-25 op for (;;) {
408 014c66b6 2023-06-25 op err = sqlite3_step(env->env_qsearch);
409 014c66b6 2023-06-25 op if (err == SQLITE_DONE)
410 014c66b6 2023-06-25 op break;
411 014c66b6 2023-06-25 op if (err != SQLITE_ROW) {
412 014c66b6 2023-06-25 op log_warnx("%s: sqlite3_step %s", __func__,
413 014c66b6 2023-06-25 op sqlite3_errstr(err));
414 014c66b6 2023-06-25 op break;
415 014c66b6 2023-06-25 op }
416 014c66b6 2023-06-25 op found = 1;
417 014c66b6 2023-06-25 op
418 014c66b6 2023-06-25 op stem = sqlite3_column_text(env->env_qsearch, 0);
419 014c66b6 2023-06-25 op comment = sqlite3_column_text(env->env_qsearch, 1);
420 014c66b6 2023-06-25 op fullpkgpath = sqlite3_column_text(env->env_qsearch, 2);
421 014c66b6 2023-06-25 op
422 dd58c961 2023-08-08 op if (clt_printf(clt, "=> %s/%s %s: %s\n", clt->clt_script_name,
423 dd58c961 2023-08-08 op fullpkgpath, stem, comment) == -1)
424 014c66b6 2023-06-25 op goto err;
425 014c66b6 2023-06-25 op }
426 014c66b6 2023-06-25 op
427 014c66b6 2023-06-25 op sqlite3_reset(env->env_qsearch);
428 014c66b6 2023-06-25 op
429 014c66b6 2023-06-25 op if (!found && clt_printf(clt, "No ports found\n") == -1)
430 014c66b6 2023-06-25 op return (-1);
431 014c66b6 2023-06-25 op
432 014c66b6 2023-06-25 op return (fcgi_end_request(clt, 0));
433 014c66b6 2023-06-25 op
434 014c66b6 2023-06-25 op err:
435 014c66b6 2023-06-25 op sqlite3_reset(env->env_qsearch);
436 014c66b6 2023-06-25 op return (-1);
437 014c66b6 2023-06-25 op }
438 014c66b6 2023-06-25 op
439 014c66b6 2023-06-25 op int
440 014c66b6 2023-06-25 op route_categories(struct env *env, struct client *clt)
441 014c66b6 2023-06-25 op {
442 014c66b6 2023-06-25 op const char *fullpkgpath;
443 014c66b6 2023-06-25 op int err;
444 014c66b6 2023-06-25 op
445 014c66b6 2023-06-25 op if (server_reply(clt, 20, "text/gemini") == -1)
446 014c66b6 2023-06-25 op return (-1);
447 014c66b6 2023-06-25 op if (clt_printf(clt, "# list of all categories\n") == -1)
448 014c66b6 2023-06-25 op return (-1);
449 014c66b6 2023-06-25 op
450 014c66b6 2023-06-25 op if (clt_puts(clt, "\n") == -1)
451 014c66b6 2023-06-25 op return (-1);
452 014c66b6 2023-06-25 op
453 014c66b6 2023-06-25 op for (;;) {
454 014c66b6 2023-06-25 op err = sqlite3_step(env->env_qcats);
455 014c66b6 2023-06-25 op if (err == SQLITE_DONE)
456 014c66b6 2023-06-25 op break;
457 014c66b6 2023-06-25 op if (err != SQLITE_ROW) {
458 014c66b6 2023-06-25 op log_warnx("%s: sqlite3_step %s", __func__,
459 014c66b6 2023-06-25 op sqlite3_errstr(err));
460 014c66b6 2023-06-25 op break;
461 014c66b6 2023-06-25 op }
462 014c66b6 2023-06-25 op
463 014c66b6 2023-06-25 op fullpkgpath = sqlite3_column_text(env->env_qcats, 0);
464 014c66b6 2023-06-25 op
465 dd58c961 2023-08-08 op if (clt_printf(clt, "=> %s/%s %s\n", clt->clt_script_name,
466 dd58c961 2023-08-08 op fullpkgpath, fullpkgpath) == -1) {
467 014c66b6 2023-06-25 op sqlite3_reset(env->env_qcats);
468 014c66b6 2023-06-25 op return (-1);
469 014c66b6 2023-06-25 op }
470 014c66b6 2023-06-25 op }
471 014c66b6 2023-06-25 op
472 014c66b6 2023-06-25 op sqlite3_reset(env->env_qcats);
473 014c66b6 2023-06-25 op return (fcgi_end_request(clt, 0));
474 014c66b6 2023-06-25 op }
475 014c66b6 2023-06-25 op
476 014c66b6 2023-06-25 op int
477 014c66b6 2023-06-25 op route_listing(struct env *env, struct client *clt)
478 014c66b6 2023-06-25 op {
479 014c66b6 2023-06-25 op char buf[128], *s;
480 014c66b6 2023-06-25 op const char *path = clt->clt_path_info + 1;
481 014c66b6 2023-06-25 op const char *fullpkgpath;
482 014c66b6 2023-06-25 op int err;
483 014c66b6 2023-06-25 op
484 014c66b6 2023-06-25 op strlcpy(buf, path, sizeof(buf));
485 014c66b6 2023-06-25 op while ((s = strrchr(buf, '/')) != NULL)
486 014c66b6 2023-06-25 op *s = '\0';
487 014c66b6 2023-06-25 op
488 014c66b6 2023-06-25 op err = sqlite3_bind_text(env->env_qbycat, 1, buf, -1, NULL);
489 014c66b6 2023-06-25 op if (err != SQLITE_OK) {
490 014c66b6 2023-06-25 op log_warnx("%s: sqlite3_bind_text \"%s\": %s", __func__,
491 014c66b6 2023-06-25 op path, sqlite3_errstr(err));
492 014c66b6 2023-06-25 op sqlite3_reset(env->env_qbycat);
493 014c66b6 2023-06-25 op
494 014c66b6 2023-06-25 op if (server_reply(clt, 42, "internal error") == -1)
495 014c66b6 2023-06-25 op return (-1);
496 014c66b6 2023-06-25 op return (fcgi_end_request(clt, 1));
497 014c66b6 2023-06-25 op }
498 014c66b6 2023-06-25 op
499 014c66b6 2023-06-25 op if (server_reply(clt, 20, "text/gemini") == -1)
500 014c66b6 2023-06-25 op goto err;
501 014c66b6 2023-06-25 op
502 014c66b6 2023-06-25 op if (clt_printf(clt, "# port(s) under %s\n\n", path) == -1)
503 014c66b6 2023-06-25 op goto err;
504 014c66b6 2023-06-25 op
505 014c66b6 2023-06-25 op for (;;) {
506 014c66b6 2023-06-25 op err = sqlite3_step(env->env_qbycat);
507 014c66b6 2023-06-25 op if (err == SQLITE_DONE)
508 014c66b6 2023-06-25 op break;
509 014c66b6 2023-06-25 op if (err != SQLITE_ROW) {
510 014c66b6 2023-06-25 op log_warnx("%s: sqlite3_step %s", __func__,
511 014c66b6 2023-06-25 op sqlite3_errstr(err));
512 014c66b6 2023-06-25 op break;
513 014c66b6 2023-06-25 op }
514 014c66b6 2023-06-25 op
515 014c66b6 2023-06-25 op fullpkgpath = sqlite3_column_text(env->env_qbycat, 0);
516 014c66b6 2023-06-25 op
517 dd58c961 2023-08-08 op if (clt_printf(clt, "=> %s/%s %s\n", clt->clt_script_name,
518 dd58c961 2023-08-08 op fullpkgpath, fullpkgpath) == -1) {
519 014c66b6 2023-06-25 op sqlite3_reset(env->env_qbycat);
520 014c66b6 2023-06-25 op return (-1);
521 014c66b6 2023-06-25 op }
522 014c66b6 2023-06-25 op }
523 014c66b6 2023-06-25 op
524 014c66b6 2023-06-25 op sqlite3_reset(env->env_qbycat);
525 014c66b6 2023-06-25 op return (fcgi_end_request(clt, 0));
526 014c66b6 2023-06-25 op
527 014c66b6 2023-06-25 op err:
528 014c66b6 2023-06-25 op sqlite3_reset(env->env_qbycat);
529 014c66b6 2023-06-25 op return (-1);
530 014c66b6 2023-06-25 op }
531 014c66b6 2023-06-25 op
532 5219f09d 2023-06-25 op static int
533 5219f09d 2023-06-25 op print_maintainer(struct client *clt, const char *mail)
534 5219f09d 2023-06-25 op {
535 5219f09d 2023-06-25 op int r, in_addr;
536 5219f09d 2023-06-25 op
537 5219f09d 2023-06-25 op for (in_addr = 0; *mail != '\0'; ++mail) {
538 5219f09d 2023-06-25 op if (!in_addr) {
539 5219f09d 2023-06-25 op if (clt_putc(clt, *mail) == -1)
540 5219f09d 2023-06-25 op return (-1);
541 5219f09d 2023-06-25 op if (*mail == '<')
542 5219f09d 2023-06-25 op in_addr = 1;
543 5219f09d 2023-06-25 op continue;
544 5219f09d 2023-06-25 op }
545 5219f09d 2023-06-25 op
546 5219f09d 2023-06-25 op switch (*mail) {
547 5219f09d 2023-06-25 op case '@':
548 5219f09d 2023-06-25 op r = clt_puts(clt, " at ");
549 5219f09d 2023-06-25 op break;
550 5219f09d 2023-06-25 op case '.':
551 5219f09d 2023-06-25 op r = clt_puts(clt, " dot ");
552 5219f09d 2023-06-25 op break;
553 5219f09d 2023-06-25 op case '>':
554 5219f09d 2023-06-25 op in_addr = 0;
555 5219f09d 2023-06-25 op /* fallthrough */
556 5219f09d 2023-06-25 op default:
557 5219f09d 2023-06-25 op r = clt_putc(clt, *mail);
558 5219f09d 2023-06-25 op break;
559 5219f09d 2023-06-25 op }
560 5219f09d 2023-06-25 op if (r == -1)
561 5219f09d 2023-06-25 op return (-1);
562 5219f09d 2023-06-25 op }
563 5219f09d 2023-06-25 op
564 5219f09d 2023-06-25 op return (0);
565 5219f09d 2023-06-25 op }
566 5219f09d 2023-06-25 op
567 014c66b6 2023-06-25 op int
568 014c66b6 2023-06-25 op route_port(struct env *env, struct client *clt)
569 014c66b6 2023-06-25 op {
570 014c66b6 2023-06-25 op const char *path = clt->clt_path_info + 1;
571 014c66b6 2023-06-25 op const char *fullpkgpath, *stem, *pkgname, *descr;
572 014c66b6 2023-06-25 op const char *comment, *maintainer, *readme, *www;
573 014c66b6 2023-06-25 op const char *version;
574 014c66b6 2023-06-25 op int err;
575 014c66b6 2023-06-25 op
576 014c66b6 2023-06-25 op err = sqlite3_bind_text(env->env_qfullpkgpath, 1, path, -1, NULL);
577 014c66b6 2023-06-25 op if (err != SQLITE_OK) {
578 014c66b6 2023-06-25 op log_warnx("%s: sqlite3_bind_text \"%s\": %s", __func__,
579 014c66b6 2023-06-25 op path, sqlite3_errstr(err));
580 014c66b6 2023-06-25 op sqlite3_reset(env->env_qfullpkgpath);
581 014c66b6 2023-06-25 op
582 014c66b6 2023-06-25 op if (server_reply(clt, 42, "internal error") == -1)
583 014c66b6 2023-06-25 op return (-1);
584 014c66b6 2023-06-25 op return (fcgi_end_request(clt, 1));
585 014c66b6 2023-06-25 op }
586 014c66b6 2023-06-25 op
587 014c66b6 2023-06-25 op err = sqlite3_step(env->env_qfullpkgpath);
588 014c66b6 2023-06-25 op if (err == SQLITE_DONE) {
589 014c66b6 2023-06-25 op /* No rows, retry as a category */
590 014c66b6 2023-06-25 op sqlite3_reset(env->env_qfullpkgpath);
591 014c66b6 2023-06-25 op return (route_listing(env, clt));
592 014c66b6 2023-06-25 op }
593 014c66b6 2023-06-25 op
594 014c66b6 2023-06-25 op if (err != SQLITE_ROW) {
595 014c66b6 2023-06-25 op log_warnx("%s: sqlite3_step %s", __func__,
596 014c66b6 2023-06-25 op sqlite3_errstr(err));
597 014c66b6 2023-06-25 op if (server_reply(clt, 42, "internal error") == -1)
598 014c66b6 2023-06-25 op goto err;
599 014c66b6 2023-06-25 op goto done;
600 014c66b6 2023-06-25 op }
601 014c66b6 2023-06-25 op
602 014c66b6 2023-06-25 op fullpkgpath = sqlite3_column_text(env->env_qfullpkgpath, 0);
603 014c66b6 2023-06-25 op stem = sqlite3_column_text(env->env_qfullpkgpath, 1);
604 014c66b6 2023-06-25 op comment = sqlite3_column_text(env->env_qfullpkgpath, 2);
605 014c66b6 2023-06-25 op pkgname = sqlite3_column_text(env->env_qfullpkgpath, 3);
606 014c66b6 2023-06-25 op descr = sqlite3_column_text(env->env_qfullpkgpath, 4);
607 014c66b6 2023-06-25 op maintainer = sqlite3_column_text(env->env_qfullpkgpath, 5);
608 014c66b6 2023-06-25 op readme = sqlite3_column_text(env->env_qfullpkgpath, 6);
609 014c66b6 2023-06-25 op www = sqlite3_column_text(env->env_qfullpkgpath, 7);
610 014c66b6 2023-06-25 op
611 014c66b6 2023-06-25 op if ((version = strrchr(pkgname, '-')) != NULL)
612 014c66b6 2023-06-25 op version++;
613 014c66b6 2023-06-25 op else
614 014c66b6 2023-06-25 op version = " unknown";
615 014c66b6 2023-06-25 op
616 014c66b6 2023-06-25 op if (server_reply(clt, 20, "text/gemini") == -1)
617 014c66b6 2023-06-25 op goto err;
618 014c66b6 2023-06-25 op
619 014c66b6 2023-06-25 op if (clt_printf(clt, "# %s v%s\n", path, version) == -1 ||
620 014c66b6 2023-06-25 op clt_puts(clt, "\n") == -1 ||
621 014c66b6 2023-06-25 op clt_printf(clt, "``` Command to install the package %s\n",
622 014c66b6 2023-06-25 op stem) == -1 ||
623 014c66b6 2023-06-25 op clt_printf(clt, "# pkg_add %s\n", stem) == -1 ||
624 014c66b6 2023-06-25 op clt_printf(clt, "```\n") == -1 ||
625 014c66b6 2023-06-25 op clt_printf(clt, "\n") == -1 ||
626 014c66b6 2023-06-25 op clt_printf(clt, "> %s\n", comment) == -1 ||
627 014c66b6 2023-06-25 op clt_printf(clt, "\n") == -1 ||
628 014c66b6 2023-06-25 op clt_printf(clt, "=> https://cvsweb.openbsd.org/ports/%s "
629 014c66b6 2023-06-25 op "CVS Web\n", fullpkgpath) == -1)
630 014c66b6 2023-06-25 op goto err;
631 014c66b6 2023-06-25 op
632 014c66b6 2023-06-25 op if (www && *www != '\0' &&
633 014c66b6 2023-06-25 op clt_printf(clt, "=> %s Port Homepage (WWW)\n", www) == -1)
634 014c66b6 2023-06-25 op goto err;
635 014c66b6 2023-06-25 op
636 014c66b6 2023-06-25 op if (clt_printf(clt, "\n") == -1 ||
637 5219f09d 2023-06-25 op clt_printf(clt, "Maintainer: ") == -1 ||
638 5219f09d 2023-06-25 op print_maintainer(clt, maintainer) == -1 ||
639 5219f09d 2023-06-25 op clt_puts(clt, "\n\n") == -1 ||
640 c436ee2d 2023-06-25 op clt_printf(clt, "## Description\n\n") == -1 ||
641 014c66b6 2023-06-25 op clt_printf(clt, "``` %s description\n", stem) == -1 ||
642 014c66b6 2023-06-25 op clt_puts(clt, descr) == -1 ||
643 014c66b6 2023-06-25 op clt_puts(clt, "```\n") == -1 ||
644 014c66b6 2023-06-25 op clt_puts(clt, "\n") == -1)
645 014c66b6 2023-06-25 op goto err;
646 014c66b6 2023-06-25 op
647 014c66b6 2023-06-25 op if (readme && *readme != '\0') {
648 c436ee2d 2023-06-25 op if (clt_puts(clt, "## Readme\n\n") == -1 ||
649 014c66b6 2023-06-25 op clt_puts(clt, "\n") == -1 ||
650 014c66b6 2023-06-25 op clt_printf(clt, "``` README for %s\n", stem) == -1 ||
651 014c66b6 2023-06-25 op clt_puts(clt, readme) == -1 ||
652 014c66b6 2023-06-25 op clt_puts(clt, "\n") == -1)
653 014c66b6 2023-06-25 op goto err;
654 014c66b6 2023-06-25 op }
655 014c66b6 2023-06-25 op
656 014c66b6 2023-06-25 op done:
657 014c66b6 2023-06-25 op sqlite3_reset(env->env_qfullpkgpath);
658 014c66b6 2023-06-25 op return (fcgi_end_request(clt, 0));
659 014c66b6 2023-06-25 op
660 014c66b6 2023-06-25 op err:
661 014c66b6 2023-06-25 op sqlite3_reset(env->env_qfullpkgpath);
662 014c66b6 2023-06-25 op return (-1);
663 014c66b6 2023-06-25 op }