/* $Id: mandoc.c,v 1.63 2012/05/31 22:29:13 schwarze Exp $ */ /* * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2011, 2012 Ingo Schwarze * * 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 AUTHORS DISCLAIM ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include "mandoc.h" #include "libmandoc.h" #define DATESIZE 32 static int a2time(time_t *, const char *, const char *); static char *time2a(time_t); static int numescape(const char *); /* * Pass over recursive numerical expressions. This context of this * function is important: it's only called within character-terminating * escapes (e.g., \s[xxxyyy]), so all we need to do is handle initial * recursion: we don't care about what's in these blocks. * This returns the number of characters skipped or -1 if an error * occurs (the caller should bail). */ static int numescape(const char *start) { int i; size_t sz; const char *cp; i = 0; /* The expression consists of a subexpression. */ if ('\\' == start[i]) { cp = &start[++i]; /* * Read past the end of the subexpression. * Bail immediately on errors. */ if (ESCAPE_ERROR == mandoc_escape(&cp, NULL, NULL)) return(-1); return(i + cp - &start[i]); } if ('(' != start[i++]) return(0); /* * A parenthesised subexpression. Read until the closing * parenthesis, making sure to handle any nested subexpressions * that might ruin our parse. */ while (')' != start[i]) { sz = strcspn(&start[i], ")\\"); i += (int)sz; if ('\0' == start[i]) return(-1); else if ('\\' != start[i]) continue; cp = &start[++i]; if (ESCAPE_ERROR == mandoc_escape(&cp, NULL, NULL)) return(-1); i += cp - &start[i]; } /* Read past the terminating ')'. */ return(++i); } enum mandoc_esc mandoc_escape(const char **end, const char **start, int *sz) { char c, term, numeric; int i, lim, ssz, rlim; const char *cp, *rstart; enum mandoc_esc gly; cp = *end; rstart = cp; if (start) *start = rstart; i = lim = 0; gly = ESCAPE_ERROR; term = numeric = '\0'; switch ((c = cp[i++])) { /* * First the glyphs. There are several different forms of * these, but each eventually returns a substring of the glyph * name. */ case ('('): gly = ESCAPE_SPECIAL; lim = 2; break; case ('['): gly = ESCAPE_SPECIAL; /* * Unicode escapes are defined in groff as \[uXXXX] to * \[u10FFFF], where the contained value must be a valid * Unicode codepoint. Here, however, only check whether * it's not a zero-width escape. */ if ('u' == cp[i] && ']' != cp[i + 1]) gly = ESCAPE_UNICODE; term = ']'; break; case ('C'): if ('\'' != cp[i]) return(ESCAPE_ERROR); gly = ESCAPE_SPECIAL; term = '\''; break; /* * The \z escape is supposed to output the following * character without advancing the cursor position. * Since we are mostly dealing with terminal mode, * let us just skip the next character. */ case ('z'): (*end)++; return(ESCAPE_SKIPCHAR); /* * Handle all triggers matching \X(xy, \Xx, and \X[xxxx], where * 'X' is the trigger. These have opaque sub-strings. */ case ('F'): /* FALLTHROUGH */ case ('g'): /* FALLTHROUGH */ case ('k'): /* FALLTHROUGH */ case ('M'): /* FALLTHROUGH */ case ('m'): /* FALLTHROUGH */ case ('n'): /* FALLTHROUGH */ case ('V'): /* FALLTHROUGH */ case ('Y'): gly = ESCAPE_IGNORE; /* FALLTHROUGH */ case ('f'): if (ESCAPE_ERROR == gly) gly = ESCAPE_FONT; rstart= &cp[i]; if (start) *start = rstart; switch (cp[i++]) { case ('('): lim = 2; break; case ('['): term = ']'; break; default: lim = 1; i--; break; } break; /* * These escapes are of the form \X'Y', where 'X' is the trigger * and 'Y' is any string. These have opaque sub-strings. */ case ('A'): /* FALLTHROUGH */ case ('b'): /* FALLTHROUGH */ case ('D'): /* FALLTHROUGH */ case ('o'): /* FALLTHROUGH */ case ('R'): /* FALLTHROUGH */ case ('X'): /* FALLTHROUGH */ case ('Z'): if ('\'' != cp[i++]) return(ESCAPE_ERROR); gly = ESCAPE_IGNORE; term = '\''; break; /* * These escapes are of the form \X'N', where 'X' is the trigger * and 'N' resolves to a numerical expression. */ case ('B'): /* FALLTHROUGH */ case ('h'): /* FALLTHROUGH */ case ('H'): /* FALLTHROUGH */ case ('L'): /* FALLTHROUGH */ case ('l'): gly = ESCAPE_NUMBERED; /* FALLTHROUGH */ case ('S'): /* FALLTHROUGH */ case ('v'): /* FALLTHROUGH */ case ('w'): /* FALLTHROUGH */ case ('x'): if (ESCAPE_ERROR == gly) gly = ESCAPE_IGNORE; if ('\'' != cp[i++]) return(ESCAPE_ERROR); term = numeric = '\''; break; /* * Special handling for the numbered character escape. * XXX Do any other escapes need similar handling? */ case ('N'): if ('\0' == cp[i]) return(ESCAPE_ERROR); *end = &cp[++i]; if (isdigit((unsigned char)cp[i-1])) return(ESCAPE_IGNORE); while (isdigit((unsigned char)**end)) (*end)++; if (start) *start = &cp[i]; if (sz) *sz = *end - &cp[i]; if ('\0' != **end) (*end)++; return(ESCAPE_NUMBERED); /* * Sizes get a special category of their own. */ case ('s'): gly = ESCAPE_IGNORE; rstart = &cp[i]; if (start) *start = rstart; /* See +/- counts as a sign. */ c = cp[i]; if ('+' == c || '-' == c || ASCII_HYPH == c) ++i; switch (cp[i++]) { case ('('): lim = 2; break; case ('['): term = numeric = ']'; break; case ('\''): term = numeric = '\''; break; default: lim = 1; i--; break; } /* See +/- counts as a sign. */ c = cp[i]; if ('+' == c || '-' == c || ASCII_HYPH == c) ++i; break; /* * Anything else is assumed to be a glyph. */ default: gly = ESCAPE_SPECIAL; lim = 1; i--; break; } assert(ESCAPE_ERROR != gly); rstart = &cp[i]; if (start) *start = rstart; /* * If a terminating block has been specified, we need to * handle the case of recursion, which could have their * own terminating blocks that mess up our parse. This, by the * way, means that the "start" and "size" values will be * effectively meaningless. */ ssz = 0; if (numeric && -1 == (ssz = numescape(&cp[i]))) return(ESCAPE_ERROR); i += ssz; rlim = -1; /* * We have a character terminator. Try to read up to that * character. If we can't (i.e., we hit the nil), then return * an error; if we can, calculate our length, read past the * terminating character, and exit. */ if ('\0' != term) { *end = strchr(&cp[i], term); if ('\0' == *end) return(ESCAPE_ERROR); rlim = *end - &cp[i]; if (sz) *sz = rlim; (*end)++; goto out; } assert(lim > 0); /* * We have a numeric limit. If the string is shorter than that, * stop and return an error. Else adjust our endpoint, length, * and return the current glyph. */ if ((size_t)lim > strlen(&cp[i])) return(ESCAPE_ERROR); rlim = lim; if (sz) *sz = rlim; *end = &cp[i] + lim; out: assert(rlim >= 0 && rstart); /* Run post-processors. */ switch (gly) { case (ESCAPE_FONT): /* * Pretend that the constant-width font modes are the * same as the regular font modes. */ if (2 == rlim && 'C' == *rstart) rstart++; else if (1 != rlim) break; switch (*rstart) { case ('3'): /* FALLTHROUGH */ case ('B'): gly = ESCAPE_FONTBOLD; break; case ('2'): /* FALLTHROUGH */ case ('I'): gly = ESCAPE_FONTITALIC; break; case ('P'): gly = ESCAPE_FONTPREV; break; case ('1'): /* FALLTHROUGH */ case ('R'): gly = ESCAPE_FONTROMAN; break; } break; case (ESCAPE_SPECIAL): if (1 != rlim) break; if ('c' == *rstart) gly = ESCAPE_NOSPACE; break; default: break; } return(gly); } void * mandoc_calloc(size_t num, size_t size) { void *ptr; ptr = calloc(num, size); if (NULL == ptr) { perror(NULL); exit((int)MANDOCLEVEL_SYSERR); } return(ptr); } void * mandoc_malloc(size_t size) { void *ptr; ptr = malloc(size); if (NULL == ptr) { perror(NULL); exit((int)MANDOCLEVEL_SYSERR); } return(ptr); } void * mandoc_realloc(void *ptr, size_t size) { ptr = realloc(ptr, size); if (NULL == ptr) { perror(NULL); exit((int)MANDOCLEVEL_SYSERR); } return(ptr); } char * mandoc_strndup(const char *ptr, size_t sz) { char *p; p = mandoc_malloc(sz + 1); memcpy(p, ptr, sz); p[(int)sz] = '\0'; return(p); } char * mandoc_strdup(const char *ptr) { char *p; p = strdup(ptr); if (NULL == p) { perror(NULL); exit((int)MANDOCLEVEL_SYSERR); } return(p); } /* * Parse a quoted or unquoted roff-style request or macro argument. * Return a pointer to the parsed argument, which is either the original * pointer or advanced by one byte in case the argument is quoted. * Null-terminate the argument in place. * Collapse pairs of quotes inside quoted arguments. * Advance the argument pointer to the next argument, * or to the null byte terminating the argument line. */ char * mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos) { char *start, *cp; int quoted, pairs, white; /* Quoting can only start with a new word. */ start = *cpp; quoted = 0; if ('"' == *start) { quoted = 1; start++; } pairs = 0; white = 0; for (cp = start; '\0' != *cp; cp++) { /* Move left after quoted quotes and escaped backslashes. */ if (pairs) cp[-pairs] = cp[0]; if ('\\' == cp[0]) { if ('\\' == cp[1]) { /* Poor man's copy mode. */ pairs++; cp++; } else if (0 == quoted && ' ' == cp[1]) /* Skip escaped blanks. */ cp++; } else if (0 == quoted) { if (' ' == cp[0]) { /* Unescaped blanks end unquoted args. */ white = 1; break; } } else if ('"' == cp[0]) { if ('"' == cp[1]) { /* Quoted quotes collapse. */ pairs++; cp++; } else { /* Unquoted quotes end quoted args. */ quoted = 2; break; } } } /* Quoted argument without a closing quote. */ if (1 == quoted) mandoc_msg(MANDOCERR_BADQUOTE, parse, ln, *pos, NULL); /* Null-terminate this argument and move to the next one. */ if (pairs) cp[-pairs] = '\0'; if ('\0' != *cp) { *cp++ = '\0'; while (' ' == *cp) cp++; } *pos += (int)(cp - start) + (quoted ? 1 : 0); *cpp = cp; if ('\0' == *cp && (white || ' ' == cp[-1])) mandoc_msg(MANDOCERR_EOLNSPACE, parse, ln, *pos, NULL); return(start); } static int a2time(time_t *t, const char *fmt, const char *p) { struct tm tm; char *pp; memset(&tm, 0, sizeof(struct tm)); pp = NULL; #ifdef HAVE_STRPTIME pp = strptime(p, fmt, &tm); #endif if (NULL != pp && '\0' == *pp) { *t = mktime(&tm); return(1); } return(0); } static char * time2a(time_t t) { struct tm *tm; char *buf, *p; size_t ssz; int isz; tm = localtime(&t); /* * Reserve space: * up to 9 characters for the month (September) + blank * up to 2 characters for the day + comma + blank * 4 characters for the year and a terminating '\0' */ p = buf = mandoc_malloc(10 + 4 + 4 + 1); if (0 == (ssz = strftime(p, 10 + 1, "%B ", tm))) goto fail; p += (int)ssz; if (-1 == (isz = snprintf(p, 4 + 1, "%d, ", tm->tm_mday))) goto fail; p += isz; if (0 == strftime(p, 4 + 1, "%Y", tm)) goto fail; return(buf); fail: free(buf); return(NULL); } char * mandoc_normdate(struct mparse *parse, char *in, int ln, int pos) { char *out; time_t t; if (NULL == in || '\0' == *in || 0 == strcmp(in, "$" "Mdocdate$")) { mandoc_msg(MANDOCERR_NODATE, parse, ln, pos, NULL); time(&t); } else if (a2time(&t, "%Y-%m-%d", in)) t = 0; else if (!a2time(&t, "$" "Mdocdate: %b %d %Y $", in) && !a2time(&t, "%b %d, %Y", in)) { mandoc_msg(MANDOCERR_BADDATE, parse, ln, pos, NULL); t = 0; } out = t ? time2a(t) : NULL; return(out ? out : mandoc_strdup(in)); } int mandoc_eos(const char *p, size_t sz, int enclosed) { const char *q; int found; if (0 == sz) return(0); /* * End-of-sentence recognition must include situations where * some symbols, such as `)', allow prior EOS punctuation to * propagate outward. */ found = 0; for (q = p + (int)sz - 1; q >= p; q--) { switch (*q) { case ('\"'): /* FALLTHROUGH */ case ('\''): /* FALLTHROUGH */ case (']'): /* FALLTHROUGH */ case (')'): if (0 == found) enclosed = 1; break; case ('.'): /* FALLTHROUGH */ case ('!'): /* FALLTHROUGH */ case ('?'): found = 1; break; default: return(found && (!enclosed || isalnum((unsigned char)*q))); } } return(found && !enclosed); } /* * Find out whether a line is a macro line or not. If it is, adjust the * current position and return one; if it isn't, return zero and don't * change the current position. */ int mandoc_getcontrol(const char *cp, int *ppos) { int pos; pos = *ppos; if ('\\' == cp[pos] && '.' == cp[pos + 1]) pos += 2; else if ('.' == cp[pos] || '\'' == cp[pos]) pos++; else return(0); while (' ' == cp[pos] || '\t' == cp[pos]) pos++; *ppos = pos; return(1); } /* * Convert a string to a long that may not be <0. * If the string is invalid, or is less than 0, return -1. */ int mandoc_strntoi(const char *p, size_t sz, int base) { char buf[32]; char *ep; long v; if (sz > 31) return(-1); memcpy(buf, p, sz); buf[(int)sz] = '\0'; errno = 0; v = strtol(buf, &ep, base); if (buf[0] == '\0' || *ep != '\0') return(-1); if (v > INT_MAX) v = INT_MAX; if (v < INT_MIN) v = INT_MIN; return((int)v); }