Blob


1 /*
2 * Copyright (c) 2021, 2024 Omar Polo <op@omarpolo.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 /*
18 * Handles config and runtime files
19 */
21 #include "compat.h"
23 #include <sys/stat.h>
24 #include <sys/types.h>
26 #include <dirent.h>
27 #include <errno.h>
28 #include <limits.h>
29 #include <libgen.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
35 #include "pages.h"
36 #include "parser.h"
37 #include "session.h"
38 #include "utils.h"
40 #include "fs.h"
42 #ifndef nitems
43 #define nitems(x) (sizeof(x) / sizeof(x[0]))
44 #endif
46 static int select_non_dot(const struct dirent *);
47 static int select_non_dotdot(const struct dirent *);
48 static size_t join_path(char*, const char*, const char*, size_t);
49 static void getenv_default(char*, const char*, const char*, size_t);
50 static void mkdirs(const char*, mode_t);
51 static void init_paths(void);
53 /*
54 * Where to store user data. These are all equal to ~/.telescope if
55 * it exists.
56 */
57 char config_path_base[PATH_MAX];
58 char data_path_base[PATH_MAX];
59 char cache_path_base[PATH_MAX];
61 char ctlsock_path[PATH_MAX];
62 char config_path[PATH_MAX];
63 char lockfile_path[PATH_MAX];
64 char bookmark_file[PATH_MAX];
65 char known_hosts_file[PATH_MAX], known_hosts_tmp[PATH_MAX];
66 char crashed_file[PATH_MAX];
67 char session_file[PATH_MAX], session_file_tmp[PATH_MAX];
68 char history_file[PATH_MAX], history_file_tmp[PATH_MAX];
69 char cert_dir[PATH_MAX], cert_dir_tmp[PATH_MAX];
70 char certs_file[PATH_MAX], certs_file_tmp[PATH_MAX];
72 char cwd[PATH_MAX];
74 static int
75 select_non_dot(const struct dirent *d)
76 {
77 return strcmp(d->d_name, ".");
78 }
80 static int
81 select_non_dotdot(const struct dirent *d)
82 {
83 return strcmp(d->d_name, ".") && strcmp(d->d_name, "..");
84 }
86 static void
87 send_dir(struct tab *tab, const char *path)
88 {
89 struct dirent **names;
90 int (*selector)(const struct dirent *) = select_non_dot;
91 int i, len;
93 #if notyet
94 /*
95 * need something to fake a redirect
96 */
98 if (!has_suffix(path, "/")) {
99 if (asprintf(&s, "%s/", path) == -1)
100 die();
101 send_hdr(peerid, 30, s);
102 free(s);
103 return;
105 #endif
107 if (!strcmp(path, "/"))
108 selector = select_non_dotdot;
110 if ((len = scandir(path, &names, selector, alphasort)) == -1) {
111 load_page_from_str(tab, "# failure reading the directory\n");
112 return;
115 parser_init(tab, gemtext_initparser);
116 parser_parsef(tab, "# Index of %s\n\n", path);
118 for (i = 0; i < len; ++i) {
119 const char *sufx = "";
121 if (names[i]->d_type == DT_DIR)
122 sufx = "/";
124 parser_parsef(tab, "=> %s%s\n", names[i]->d_name, sufx);
127 parser_free(tab);
128 free(names);
131 static int
132 is_dir(FILE *fp)
134 struct stat sb;
136 if (fstat(fileno(fp), &sb) == -1)
137 return 0;
139 return S_ISDIR(sb.st_mode);
142 static parserinit
143 file_type(const char *path)
145 const struct mapping {
146 const char *ext;
147 parserinit fn;
148 } ms[] = {
149 {"diff", textpatch_initparser},
150 {"gemini", gemtext_initparser},
151 {"gmi", gemtext_initparser},
152 {"markdown", textplain_initparser},
153 {"md", textplain_initparser},
154 {"patch", gemtext_initparser},
155 {NULL, NULL},
156 }, *m;
157 const char *dot;
159 if ((dot = strrchr(path, '.')) == NULL)
160 return textplain_initparser;
162 dot++;
164 for (m = ms; m->ext != NULL; ++m)
165 if (!strcmp(m->ext, dot))
166 return m->fn;
168 return textplain_initparser;
171 void
172 fs_load_url(struct tab *tab, const char *url)
174 const char *bpath = "bookmarks.gmi", *fallback = "# Not found\n";
175 parserinit initfn = gemtext_initparser;
176 char path[PATH_MAX];
177 FILE *fp = NULL;
178 size_t i;
179 char buf[BUFSIZ];
180 struct page {
181 const char *name;
182 const char *path;
183 const uint8_t *data;
184 size_t len;
185 } pages[] = {
186 {"about", NULL, about_about, about_about_len},
187 {"blank", NULL, about_blank, about_blank_len},
188 {"bookmarks", bpath, bookmarks, bookmarks_len},
189 {"crash", NULL, about_crash, about_crash_len},
190 {"help", NULL, about_help, about_help_len},
191 {"license", NULL, about_license, about_license_len},
192 {"new", NULL, about_new, about_new_len},
193 }, *page = NULL;
195 if (!strncmp(url, "about:", 6)) {
196 url += 6;
198 for (i = 0; page == NULL && i < nitems(pages); ++i) {
199 if (!strcmp(url, pages[i].name))
200 page = &pages[i];
203 if (page == NULL)
204 goto done;
206 strlcpy(path, data_path_base, sizeof(path));
207 strlcat(path, "/", sizeof(path));
208 if (page->path != NULL)
209 strlcat(path, page->path, sizeof(path));
210 else {
211 strlcat(path, "page/about_", sizeof(path));
212 strlcat(path, page->name, sizeof(path));
213 strlcat(path, ".gmi", sizeof(path));
216 fallback = page->data;
217 } else if (!strncmp(url, "file://", 7)) {
218 url += 7;
219 strlcpy(path, url, sizeof(path));
220 initfn = file_type(url);
221 } else
222 goto done;
224 if ((fp = fopen(path, "r")) == NULL)
225 goto done;
227 if (is_dir(fp)) {
228 fclose(fp);
229 send_dir(tab, path);
230 goto done;
233 parser_init(tab, initfn);
234 for (;;) {
235 size_t r;
237 r = fread(buf, 1, sizeof(buf), fp);
238 if (!parser_parse(tab, buf, r))
239 break;
240 if (r != sizeof(buf))
241 break;
243 parser_free(tab);
245 done:
246 if (fp != NULL)
247 fclose(fp);
248 else
249 load_page_from_str(tab, fallback);
252 static size_t
253 join_path(char *buf, const char *lhs, const char *rhs, size_t buflen)
255 strlcpy(buf, lhs, buflen);
256 return strlcat(buf, rhs, buflen);
259 static void
260 getenv_default(char *buf, const char *name, const char *def, size_t buflen)
262 size_t ret;
263 char *home, *env;
265 if ((home = getenv("HOME")) == NULL)
266 errx(1, "HOME is not defined");
268 if ((env = getenv(name)) != NULL)
269 ret = strlcpy(buf, env, buflen);
270 else
271 ret = join_path(buf, home, def, buflen);
273 if (ret >= buflen)
274 errx(1, "buffer too small for %s", name);
277 static void
278 mkdirs(const char *path, mode_t mode)
280 char copy[PATH_MAX+1], orig[PATH_MAX+1], *parent;
282 strlcpy(copy, path, sizeof(copy));
283 strlcpy(orig, path, sizeof(orig));
284 parent = dirname(copy);
285 if (!strcmp(parent, "/"))
286 return;
287 mkdirs(parent, mode);
289 if (mkdir(orig, mode) != 0) {
290 if (errno == EEXIST)
291 return;
292 err(1, "can't mkdir %s", orig);
296 static void
297 init_paths(void)
299 char xdg_config_base[PATH_MAX];
300 char xdg_data_base[PATH_MAX];
301 char xdg_cache_base[PATH_MAX];
302 char old_path[PATH_MAX];
303 char *home;
304 struct stat info;
306 if (getcwd(cwd, sizeof(cwd)) == NULL)
307 err(1, "getcwd failed");
309 /* old path */
310 if ((home = getenv("HOME")) == NULL)
311 errx(1, "HOME is not defined");
312 join_path(old_path, home, "/.telescope", sizeof(old_path));
314 /* if ~/.telescope exists, use that instead of xdg dirs */
315 if (stat(old_path, &info) == 0 && S_ISDIR(info.st_mode)) {
316 join_path(config_path_base, home, "/.telescope",
317 sizeof(config_path_base));
318 join_path(data_path_base, home, "/.telescope",
319 sizeof(data_path_base));
320 join_path(cache_path_base, home, "/.telescope",
321 sizeof(cache_path_base));
322 return;
325 /* xdg paths */
326 getenv_default(xdg_config_base, "XDG_CONFIG_HOME", "/.config",
327 sizeof(xdg_config_base));
328 getenv_default(xdg_data_base, "XDG_DATA_HOME", "/.local/share",
329 sizeof(xdg_data_base));
330 getenv_default(xdg_cache_base, "XDG_CACHE_HOME", "/.cache",
331 sizeof(xdg_cache_base));
333 join_path(config_path_base, xdg_config_base, "/telescope",
334 sizeof(config_path_base));
335 join_path(data_path_base, xdg_data_base, "/telescope",
336 sizeof(data_path_base));
337 join_path(cache_path_base, xdg_cache_base, "/telescope",
338 sizeof(cache_path_base));
340 mkdirs(xdg_config_base, S_IRWXU);
341 mkdirs(xdg_data_base, S_IRWXU);
342 mkdirs(xdg_cache_base, S_IRWXU);
344 mkdirs(config_path_base, S_IRWXU);
345 mkdirs(data_path_base, S_IRWXU);
346 mkdirs(cache_path_base, S_IRWXU);
349 int
350 fs_init(void)
352 init_paths();
354 join_path(ctlsock_path, cache_path_base, "/ctl",
355 sizeof(ctlsock_path));
356 join_path(config_path, config_path_base, "/config",
357 sizeof(config_path));
358 join_path(lockfile_path, cache_path_base, "/lock",
359 sizeof(lockfile_path));
360 join_path(bookmark_file, data_path_base, "/bookmarks.gmi",
361 sizeof(bookmark_file));
362 join_path(known_hosts_file, data_path_base, "/known_hosts",
363 sizeof(known_hosts_file));
364 join_path(known_hosts_tmp, cache_path_base,
365 "/known_hosts.tmp.XXXXXXXXXX", sizeof(known_hosts_tmp));
366 join_path(session_file, cache_path_base, "/session",
367 sizeof(session_file));
368 join_path(session_file_tmp, cache_path_base, "/session.XXXXXXXXXX",
369 sizeof(session_file_tmp));
370 join_path(history_file, cache_path_base, "/history",
371 sizeof(history_file));
372 join_path(history_file_tmp, cache_path_base, "/history.XXXXXXXXXX",
373 sizeof(history_file_tmp));
374 join_path(cert_dir, data_path_base, "/certs/",
375 sizeof(cert_dir));
376 join_path(cert_dir_tmp, data_path_base, "/certs/id.XXXXXXXXXX",
377 sizeof(cert_dir_tmp));
378 join_path(certs_file, config_path_base, "/certs.conf",
379 sizeof(certs_file));
380 join_path(certs_file_tmp, config_path_base, "/certs.conf.XXXXXXXXXX",
381 sizeof(certs_file_tmp));
382 join_path(crashed_file, cache_path_base, "/crashed",
383 sizeof(crashed_file));
385 mkdirs(cert_dir, S_IRWXU);
387 return 0;