main.c (17672B)
/* $Id$ */ /* * Copyright (c) 2016, 2017, 2020 Kristaps Dzonsons <kristaps@bsd.lv> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.h" #if HAVE_SYS_QUEUE # include <sys/queue.h> #endif #include <sys/param.h> #if HAVE_CAPSICUM # include <sys/resource.h> # include <sys/capsicum.h> #endif #include <sys/ioctl.h> #include <sys/stat.h> #include <assert.h> #if HAVE_ERR # include <err.h> #endif #include <errno.h> #include <fcntl.h> #include <getopt.h> #include <limits.h> /* INT_MAX */ #include <locale.h> /* set_locale() */ #if HAVE_SANDBOX_INIT # include <sandbox.h> #endif #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <termios.h> /* struct winsize */ #include <unistd.h> #include "lowdown.h" /* * Start with all of the sandboxes. * The sandbox_pre() happens before we open our input file for reading, * while the sandbox_post() happens afterward. */ #if HAVE_PLEDGE static void sandbox_post(int fdin, int fddin, int fdout) { if (pledge("stdio", NULL) == -1) err(1, "pledge"); } static void sandbox_pre(void) { if (pledge("stdio rpath wpath cpath", NULL) == -1) err(1, "pledge"); } #elif HAVE_SANDBOX_INIT static void sandbox_post(int fdin, int fddin, int fdout) { char *ep; int rc; rc = sandbox_init (kSBXProfilePureComputation, SANDBOX_NAMED, &ep); if (rc != 0) errx(1, "sandbox_init: %s", ep); } static void sandbox_pre(void) { /* Do nothing. */ } #elif HAVE_CAPSICUM static void sandbox_post(int fdin, int fddin, int fdout) { cap_rights_t rights; cap_rights_init(&rights); cap_rights_init(&rights, CAP_EVENT, CAP_READ, CAP_FSTAT); if (cap_rights_limit(fdin, &rights) < 0) err(1, "cap_rights_limit"); if (fddin != -1) { cap_rights_init(&rights, CAP_EVENT, CAP_READ, CAP_FSTAT); if (cap_rights_limit(fddin, &rights) < 0) err(1, "cap_rights_limit"); } cap_rights_init(&rights, CAP_EVENT, CAP_WRITE, CAP_FSTAT); if (cap_rights_limit(STDERR_FILENO, &rights) < 0) err(1, "cap_rights_limit"); cap_rights_init(&rights, CAP_EVENT, CAP_WRITE, CAP_FSTAT); if (cap_rights_limit(fdout, &rights) < 0) err(1, "cap_rights_limit"); if (cap_enter()) err(1, "cap_enter"); } static void sandbox_pre(void) { /* Do nothing. */ } #else /* No sandbox. */ #warning Compiling without sandbox support. static void sandbox_post(int fdin, int fddin, int fdout) { /* Do nothing. */ } static void sandbox_pre(void) { /* Do nothing. */ } #endif static size_t get_columns(void) { struct winsize size; memset(&size, 0, sizeof(struct winsize)); if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) == -1) return 72; return size.ws_col; } /* * Recognise the metadata format of "foo = bar" and "foo: bar". * Translates from the former into the latter. * This way "foo = : bar" -> "foo : : bar", etc. * Errors out if the metadata is malformed (no colon or equal sign). */ static void metadata_parse(char opt, char ***vals, size_t *valsz, const char *arg) { const char *loceq, *loccol; char *cp; loceq = strchr(arg, '='); loccol = strchr(arg, ':'); if ((loceq != NULL && loccol == NULL) || (loceq != NULL && loccol != NULL && loceq < loccol)) { if (asprintf(&cp, "%.*s: %s\n", (int)(loceq - arg), arg, loceq + 1) == -1) err(1, NULL); *vals = reallocarray(*vals, *valsz + 1, sizeof(char *)); if (*vals == NULL) err(1, NULL); (*vals)[*valsz] = cp; (*valsz)++; return; } if ((loccol != NULL && loceq == NULL) || (loccol != NULL && loceq != NULL && loccol < loceq)) { if (asprintf(&cp, "%s\n", arg) == -1) err(1, NULL); *vals = reallocarray(*vals, *valsz + 1, sizeof(char *)); if (*vals == NULL) err(1, NULL); (*vals)[*valsz] = cp; (*valsz)++; return; } errx(1, "-%c: malformed metadata", opt); } int main(int argc, char *argv[]) { FILE *fin = stdin, *fout = stdout, *din = NULL; const char *fnin = "<stdin>", *fnout = NULL, *fndin = NULL, *extract = NULL, *er, *mainopts = "M:m:sT:t:o:X:", *diffopts = "M:m:sT:t:o:", *odtstyfn = NULL; struct lowdown_opts opts; struct stat st; int c, diff = 0, fd, status = 0, aoflag = 0, roflag = 0, aiflag = 0, riflag = 0, centre = 0; char *ret = NULL, *cp, *odtsty = NULL; size_t i, retsz = 0, rcols, sz; ssize_t ssz; struct lowdown_meta *m; struct lowdown_metaq mq; struct option lo[] = { { "html-skiphtml", no_argument, &aoflag, LOWDOWN_HTML_SKIP_HTML }, { "html-no-skiphtml", no_argument, &roflag, LOWDOWN_HTML_SKIP_HTML }, { "html-escapehtml", no_argument, &aoflag, LOWDOWN_HTML_ESCAPE }, { "html-no-escapehtml", no_argument, &roflag, LOWDOWN_HTML_ESCAPE }, { "html-hardwrap", no_argument, &aoflag, LOWDOWN_HTML_HARD_WRAP }, { "html-no-hardwrap", no_argument, &roflag, LOWDOWN_HTML_HARD_WRAP }, { "html-head-ids", no_argument, &aoflag, LOWDOWN_HTML_HEAD_IDS }, { "html-no-head-ids", no_argument, &roflag, LOWDOWN_HTML_HEAD_IDS }, { "html-owasp", no_argument, &aoflag, LOWDOWN_HTML_OWASP }, { "html-no-owasp", no_argument, &roflag, LOWDOWN_HTML_OWASP }, { "html-num-ent", no_argument, &aoflag, LOWDOWN_HTML_NUM_ENT }, { "html-no-num-ent", no_argument, &roflag, LOWDOWN_HTML_NUM_ENT }, { "latex-numbered", no_argument, &aoflag, LOWDOWN_LATEX_NUMBERED }, { "latex-no-numbered", no_argument, &roflag, LOWDOWN_LATEX_NUMBERED }, { "latex-skiphtml", no_argument, &aoflag, LOWDOWN_LATEX_SKIP_HTML }, { "latex-no-skiphtml", no_argument, &roflag, LOWDOWN_LATEX_SKIP_HTML }, { "nroff-skiphtml", no_argument, &aoflag, LOWDOWN_NROFF_SKIP_HTML }, { "nroff-no-skiphtml", no_argument, &roflag, LOWDOWN_NROFF_SKIP_HTML }, { "nroff-groff", no_argument, &aoflag, LOWDOWN_NROFF_GROFF }, { "nroff-no-groff", no_argument, &roflag, LOWDOWN_NROFF_GROFF }, { "nroff-numbered", no_argument, &aoflag, LOWDOWN_NROFF_NUMBERED }, { "nroff-no-numbered", no_argument, &roflag, LOWDOWN_NROFF_NUMBERED }, { "nroff-shortlinks", no_argument, &aoflag, LOWDOWN_NROFF_SHORTLINK }, { "nroff-no-shortlinks",no_argument, &roflag, LOWDOWN_NROFF_SHORTLINK }, { "nroff-nolinks", no_argument, &aoflag, LOWDOWN_NROFF_NOLINK }, { "nroff-no-nolinks", no_argument, &roflag, LOWDOWN_NROFF_NOLINK }, { "odt-skiphtml", no_argument, &aoflag, LOWDOWN_ODT_SKIP_HTML }, { "odt-no-skiphtml", no_argument, &roflag, LOWDOWN_ODT_SKIP_HTML }, { "odt-style", required_argument, NULL, 6 }, { "term-width", required_argument, NULL, 1 }, { "term-hmargin", required_argument, NULL, 2 }, { "term-vmargin", required_argument, NULL, 3 }, { "term-columns", required_argument, NULL, 4 }, { "gemini-link-end", no_argument, &aoflag, LOWDOWN_GEMINI_LINK_END }, { "gemini-no-link-end", no_argument, &roflag, LOWDOWN_GEMINI_LINK_END }, { "gemini-link-roman", no_argument, &aoflag, LOWDOWN_GEMINI_LINK_ROMAN }, { "gemini-no-link-roman", no_argument, &roflag, LOWDOWN_GEMINI_LINK_ROMAN }, { "gemini-link-noref", no_argument, &aoflag, LOWDOWN_GEMINI_LINK_NOREF }, { "gemini-no-link-noref", no_argument, &roflag, LOWDOWN_GEMINI_LINK_NOREF }, { "gemini-link-inline", no_argument, &aoflag, LOWDOWN_GEMINI_LINK_IN }, { "gemini-no-link-inline",no_argument, &roflag, LOWDOWN_GEMINI_LINK_IN }, { "gemini-metadata", no_argument, &aoflag, LOWDOWN_GEMINI_METADATA }, { "gemini-no-metadata", no_argument, &roflag, LOWDOWN_GEMINI_METADATA }, { "term-shortlinks", no_argument, &aoflag, LOWDOWN_TERM_SHORTLINK }, { "term-no-shortlinks", no_argument, &roflag, LOWDOWN_TERM_SHORTLINK }, { "term-nolinks", no_argument, &aoflag, LOWDOWN_TERM_NOLINK }, { "term-no-nolinks", no_argument, &roflag, LOWDOWN_TERM_NOLINK }, { "term-no-colour", no_argument, &aoflag, LOWDOWN_TERM_NOCOLOUR }, { "term-colour", no_argument, &roflag, LOWDOWN_TERM_NOCOLOUR }, { "term-no-ansi", no_argument, &aoflag, LOWDOWN_TERM_NOANSI }, { "term-ansi", no_argument, &roflag, LOWDOWN_TERM_NOANSI }, { "out-smarty", no_argument, &aoflag, LOWDOWN_SMARTY }, { "out-no-smarty", no_argument, &roflag, LOWDOWN_SMARTY }, { "out-standalone", no_argument, &aoflag, LOWDOWN_STANDALONE }, { "out-no-standalone", no_argument, &roflag, LOWDOWN_STANDALONE }, { "parse-hilite", no_argument, &aiflag, LOWDOWN_HILITE }, { "parse-no-hilite", no_argument, &riflag, LOWDOWN_HILITE }, { "parse-tables", no_argument, &aiflag, LOWDOWN_TABLES }, { "parse-no-tables", no_argument, &riflag, LOWDOWN_TABLES }, { "parse-fenced", no_argument, &aiflag, LOWDOWN_FENCED }, { "parse-no-fenced", no_argument, &riflag, LOWDOWN_FENCED }, { "parse-footnotes", no_argument, &aiflag, LOWDOWN_FOOTNOTES }, { "parse-no-footnotes", no_argument, &riflag, LOWDOWN_FOOTNOTES }, { "parse-autolink", no_argument, &aiflag, LOWDOWN_AUTOLINK }, { "parse-no-autolink", no_argument, &riflag, LOWDOWN_AUTOLINK }, { "parse-strike", no_argument, &aiflag, LOWDOWN_STRIKE }, { "parse-no-strike", no_argument, &riflag, LOWDOWN_STRIKE }, { "parse-super", no_argument, &aiflag, LOWDOWN_SUPER }, { "parse-no-super", no_argument, &riflag, LOWDOWN_SUPER }, { "parse-math", no_argument, &aiflag, LOWDOWN_MATH }, { "parse-no-math", no_argument, &riflag, LOWDOWN_MATH }, { "parse-codeindent", no_argument, &riflag, LOWDOWN_NOCODEIND }, { "parse-no-codeindent",no_argument, &aiflag, LOWDOWN_NOCODEIND }, { "parse-intraemph", no_argument, &riflag, LOWDOWN_NOINTEM }, { "parse-no-intraemph", no_argument, &aiflag, LOWDOWN_NOINTEM }, { "parse-metadata", no_argument, &aiflag, LOWDOWN_METADATA }, { "parse-no-metadata", no_argument, &riflag, LOWDOWN_METADATA }, { "parse-cmark", no_argument, &aiflag, LOWDOWN_COMMONMARK }, { "parse-no-cmark", no_argument, &riflag, LOWDOWN_COMMONMARK }, { "parse-deflists", no_argument, &aiflag, LOWDOWN_DEFLIST }, { "parse-no-deflists", no_argument, &riflag, LOWDOWN_DEFLIST }, { "parse-img-ext", no_argument, &aiflag, LOWDOWN_IMG_EXT }, /* TODO: remove */ { "parse-no-img-ext", no_argument, &riflag, LOWDOWN_IMG_EXT }, /* TODO: remove */ { "parse-ext-attrs", no_argument, &aiflag, LOWDOWN_ATTRS }, { "parse-no-ext-attrs", no_argument, &riflag, LOWDOWN_ATTRS }, { "parse-tasklists", no_argument, &aiflag, LOWDOWN_TASKLIST }, { "parse-no-tasklists", no_argument, &riflag, LOWDOWN_TASKLIST }, { "parse-maxdepth", required_argument, NULL, 5 }, { NULL, 0, NULL, 0 } }; /* Get the real number of columns or 72. */ rcols = get_columns(); sandbox_pre(); TAILQ_INIT(&mq); memset(&opts, 0, sizeof(struct lowdown_opts)); opts.maxdepth = 128; opts.type = LOWDOWN_HTML; opts.feat = LOWDOWN_ATTRS | LOWDOWN_AUTOLINK | LOWDOWN_COMMONMARK | LOWDOWN_DEFLIST | LOWDOWN_FENCED | LOWDOWN_FOOTNOTES | LOWDOWN_METADATA | LOWDOWN_STRIKE | LOWDOWN_SUPER | LOWDOWN_TABLES | LOWDOWN_TASKLIST; opts.oflags = LOWDOWN_HTML_ESCAPE | LOWDOWN_HTML_HEAD_IDS | LOWDOWN_HTML_NUM_ENT | LOWDOWN_HTML_OWASP | LOWDOWN_HTML_SKIP_HTML | LOWDOWN_NROFF_GROFF | LOWDOWN_NROFF_NUMBERED | LOWDOWN_NROFF_SKIP_HTML | LOWDOWN_ODT_SKIP_HTML | LOWDOWN_LATEX_SKIP_HTML | LOWDOWN_LATEX_NUMBERED | LOWDOWN_SMARTY; if (strcasecmp(getprogname(), "lowdown-diff") == 0) diff = 1; while ((c = getopt_long(argc, argv, diff ? diffopts : mainopts, lo, NULL)) != -1) switch (c) { case 'M': metadata_parse(c, &opts.metaovr, &opts.metaovrsz, optarg); break; case 'm': metadata_parse(c, &opts.meta, &opts.metasz, optarg); break; case 'o': fnout = optarg; break; case 's': opts.oflags |= LOWDOWN_STANDALONE; break; case 't': case 'T': if (strcasecmp(optarg, "ms") == 0) opts.type = LOWDOWN_NROFF; else if (strcasecmp(optarg, "gemini") == 0) opts.type = LOWDOWN_GEMINI; else if (strcasecmp(optarg, "html") == 0) opts.type = LOWDOWN_HTML; else if (strcasecmp(optarg, "latex") == 0) opts.type = LOWDOWN_LATEX; else if (strcasecmp(optarg, "man") == 0) opts.type = LOWDOWN_MAN; else if (strcasecmp(optarg, "fodt") == 0) opts.type = LOWDOWN_FODT; else if (strcasecmp(optarg, "term") == 0) opts.type = LOWDOWN_TERM; else if (strcasecmp(optarg, "tree") == 0) opts.type = LOWDOWN_TREE; else if (strcasecmp(optarg, "null") == 0) opts.type = LOWDOWN_NULL; else goto usage; break; case 'X': extract = optarg; break; case 0: if (roflag) opts.oflags &= ~roflag; if (aoflag) opts.oflags |= aoflag; if (riflag) opts.feat &= ~riflag; if (aiflag) opts.feat |= aiflag; break; case 1: opts.cols = strtonum(optarg, 0, INT_MAX, &er); if (er == NULL) break; errx(1, "--term-width: %s", er); case 2: if (strcmp(optarg, "centre") == 0 || strcmp(optarg, "centre") == 0) { centre = 1; break; } opts.hmargin = strtonum (optarg, 0, INT_MAX, &er); if (er == NULL) break; errx(1, "--term-hmargin: %s", er); case 3: opts.vmargin = strtonum(optarg, 0, INT_MAX, &er); if (er == NULL) break; errx(1, "--term-vmargin: %s", er); case 4: rcols = strtonum(optarg, 1, INT_MAX, &er); if (er == NULL) break; errx(1, "--term-columns: %s", er); case 5: opts.maxdepth = strtonum(optarg, 0, INT_MAX, &er); if (er == NULL) break; errx(1, "--parse-maxdepth: %s", er); case 6: odtstyfn = optarg; break; default: goto usage; } argc -= optind; argv += optind; if (opts.type == LOWDOWN_TERM || opts.type == LOWDOWN_GEMINI) setlocale(LC_CTYPE, ""); /* * By default, try to show 80 columns. * Don't show more than the number of available columns. */ if (opts.cols == 0) { if ((opts.cols = rcols) > 80) opts.cols = 80; } else if (opts.cols > rcols) opts.cols = rcols; /* If we're centred, set our margins. */ if (centre && opts.cols < rcols) opts.hmargin = (rcols - opts.cols) / 2; /* * Diff mode takes two arguments: the first is mandatory (the * old file) and the second (the new one) is optional. * Non-diff mode takes an optional single argument. */ if ((diff && (argc == 0 || argc > 2)) || (!diff && argc > 1)) goto usage; if (diff) { if (argc > 1 && strcmp(argv[1], "-")) { fnin = argv[1]; if ((fin = fopen(fnin, "r")) == NULL) err(1, "%s", fnin); } fndin = argv[0]; if ((din = fopen(fndin, "r")) == NULL) err(1, "%s", fndin); } else { if (argc && strcmp(argv[0], "-")) { fnin = argv[0]; if ((fin = fopen(fnin, "r")) == NULL) err(1, "%s", fnin); } } /* * If we have a style sheet specified for -Tfodt, load it now * before we drop privileges. */ if (opts.type == LOWDOWN_FODT && odtstyfn != NULL) { if ((fd = open(odtstyfn, O_RDONLY)) == -1) err(1, "%s", odtstyfn); if (fstat(fd, &st) == -1) err(1, "%s", odtstyfn); if ((uint64_t)st.st_size > SIZE_MAX - 1) errx(1, "%s: file too long", odtstyfn); sz = (size_t)st.st_size; if ((odtsty = cp = malloc(sz + 1)) == NULL) err(1, NULL); while (sz > 0) { if ((ssz = read(fd, cp, sz)) == -1) err(1, "%s", odtstyfn); if (ssz == 0) errx(1, "%s: short file", odtstyfn); sz -= (size_t)ssz; cp += ssz; } *cp = '\0'; close(fd); opts.odt.sty = odtsty; } /* Configure the output file. */ if (fnout != NULL && strcmp(fnout, "-") && (fout = fopen(fnout, "w")) == NULL) err(1, "%s", fnout); sandbox_post(fileno(fin), din == NULL ? -1 : fileno(din), fileno(fout)); /* We're now completely sandboxed. */ /* Require metadata when extracting. */ if (extract) opts.feat |= LOWDOWN_METADATA; /* * Allow NO_COLOUR to dictate colours. * This only works for -Tterm output when not in diff mode. */ if (getenv("NO_COLOR") != NULL || getenv("NO_COLOUR") != NULL) opts.oflags |= LOWDOWN_TERM_NOCOLOUR; if (diff) { opts.oflags &= ~LOWDOWN_TERM_NOCOLOUR; if (!lowdown_file_diff (&opts, fin, din, &ret, &retsz)) errx(1, "%s: failed parse", fnin); } else { if (!lowdown_file(&opts, fin, &ret, &retsz, &mq)) errx(1, "%s: failed parse", fnin); } if (extract != NULL) { assert(!diff); TAILQ_FOREACH(m, &mq, entries) if (strcasecmp(m->key, extract) == 0) break; if (m != NULL) { fprintf(fout, "%s\n", m->value); } else { status = 1; warnx("%s: unknown keyword", extract); } } else fwrite(ret, 1, retsz, fout); free(ret); free(odtsty); if (fout != stdout) fclose(fout); if (din != NULL) fclose(din); if (fin != stdin) fclose(fin); for (i = 0; i < opts.metasz; i++) free(opts.meta[i]); for (i = 0; i < opts.metaovrsz; i++) free(opts.metaovr[i]); free(opts.meta); free(opts.metaovr); lowdown_metaq_free(&mq); return status; usage: if (!diff) { fprintf(stderr, "usage: lowdown [-s] [input_options] [output_options] [-M metadata]\n" " [-m metadata] [-o output] [-t mode] [-X keyword] [file]\n"); } else fprintf(stderr, "usage: lowdown-diff [-s] [input_options] [output_options] [-M metadata]\n" " [-m metadata] [-o output] [-t mode] oldfile [newfile]\n"); return 1; }