+/* ARGSUSED */
+static enum rofferr
+roff_so(ROFF_ARGS)
+{
+ char *name;
+
+ (*r->msg)(MANDOCERR_SO, r->data, ln, ppos, NULL);
+
+ /*
+ * Handle `so'. Be EXTREMELY careful, as we shouldn't be
+ * opening anything that's not in our cwd or anything beneath
+ * it. Thus, explicitly disallow traversing up the file-system
+ * or using absolute paths.
+ */
+
+ name = *bufp + pos;
+ if ('/' == *name || strstr(name, "../") || strstr(name, "/..")) {
+ (*r->msg)(MANDOCERR_SOPATH, r->data, ln, pos, NULL);
+ return(ROFF_ERR);
+ }
+
+ *offs = pos;
+ return(ROFF_SO);
+}
+
+/* ARGSUSED */
+static enum rofferr
+roff_userdef(ROFF_ARGS)
+{
+ const char *arg[9];
+ char *cp, *n1, *n2;
+ int i, quoted, pairs;
+
+ /*
+ * Collect pointers to macro argument strings
+ * and null-terminate them.
+ */
+ cp = *bufp + pos;
+ for (i = 0; i < 9; i++) {
+ /* Quoting can only start with a new word. */
+ if ('"' == *cp) {
+ quoted = 1;
+ cp++;
+ } else
+ quoted = 0;
+ arg[i] = cp;
+ for (pairs = 0; '\0' != *cp; cp++) {
+ /* Unquoted arguments end at blanks. */
+ if (0 == quoted) {
+ if (' ' == *cp)
+ break;
+ continue;
+ }
+ /* After pairs of quotes, move left. */
+ if (pairs)
+ cp[-pairs] = cp[0];
+ /* Pairs of quotes do not end words, ... */
+ if ('"' == cp[0] && '"' == cp[1]) {
+ pairs++;
+ cp++;
+ continue;
+ }
+ /* ... but solitary quotes do. */
+ if ('"' != *cp)
+ continue;
+ if (pairs)
+ cp[-pairs] = '\0';
+ *cp = ' ';
+ break;
+ }
+ /* Last argument; the remaining ones are empty strings. */
+ if ('\0' == *cp)
+ continue;
+ /* Null-terminate argument and move to the next one. */
+ *cp++ = '\0';
+ while (' ' == *cp)
+ cp++;
+ }
+
+ /*
+ * Expand macro arguments.
+ */
+ *szp = 0;
+ n1 = cp = mandoc_strdup(r->current_string);
+ while (NULL != (cp = strstr(cp, "\\$"))) {
+ i = cp[2] - '1';
+ if (0 > i || 8 < i) {
+ /* Not an argument invocation. */
+ cp += 2;
+ continue;
+ }
+
+ *szp = strlen(n1) - 3 + strlen(arg[i]) + 1;
+ n2 = mandoc_malloc(*szp);
+
+ strlcpy(n2, n1, (size_t)(cp - n1 + 1));
+ strlcat(n2, arg[i], *szp);
+ strlcat(n2, cp + 3, *szp);
+
+ cp = n2 + (cp - n1);
+ free(n1);
+ n1 = n2;
+ }
+
+ /*
+ * Replace the macro invocation
+ * by the expanded macro.
+ */
+ free(*bufp);
+ *bufp = n1;
+ if (0 == *szp)
+ *szp = strlen(*bufp) + 1;
+
+ return(*szp > 1 && '\n' == (*bufp)[(int)*szp - 2] ?
+ ROFF_REPARSE : ROFF_APPEND);
+}