+ int maxlen;
+ char *ret = NULL, *p = NULL;
+ char *path = NULL, *q = NULL;
+ char pathsep = trie_pathsep(t);
+ int maxpathlen = trie_maxpathlen(t);
+
+ while (fmt) {
+ struct format_option opt = get_format_option(&fmt);
+ if (index && !opt.fmttype)
+ continue; /* option is only good for the root, which this isn't */
+
+ maxlen = opt.prefixlen + opt.suffixlen + 1;
+ switch (opt.fmttype) {
+ case 'n':
+ maxlen += 40; /* generous length for an integer */
+ break;
+ case 'p':
+ maxlen += 3*maxpathlen; /* might have to escape everything */
+ break;
+ }
+ ret = snewn(maxlen, char);
+ p = ret;
+ while (opt.prefixlen-- > 0) {
+ if ((*p++ = *opt.prefix++) == '%')
+ opt.prefix++;
+ }
+ switch (opt.fmttype) {
+ case 'n':
+ p += sprintf(p, "%lu", index);
+ break;
+ case 'p':
+ path = snewn(1+trie_maxpathlen(t), char);
+ if (opt.shorten_path) {
+ trie_getpath(t, 0, path);
+ q = path + strlen(path);
+ trie_getpath(t, index, path);
+ if (*q == pathsep)
+ q++;
+ } else {
+ trie_getpath(t, index, path);
+ q = path;
+ }
+ while (*q) {
+ char c = *q++;
+ if (c == pathsep && opt.translate_pathsep)
+ *p++ = '/';
+ else if (!isalnum((unsigned char)c) && !strchr("-.@_", c))
+ p += sprintf(p, "=%02X", (unsigned char)c);
+ else
+ *p++ = c;
+ }
+ sfree(path);
+ break;
+ }
+ while (opt.suffixlen-- > 0) {
+ if ((*p++ = *opt.suffix++) == '%')
+ opt.suffix++;
+ }
+ *p = '\0';
+ assert(p - ret < maxlen);
+ return ret;
+ }
+ assert(!"Getting here implies an incomplete set of formats");
+}
+
+char *html_format_path(const void *t, const struct html_config *cfg,
+ unsigned long index)
+{
+ return format_string(cfg->uriformat, index, t);
+}
+
+int html_parse_path(const void *t, const char *path,
+ const struct html_config *cfg, unsigned long *index)
+{
+ int len = strlen(path);
+ int midlen;
+ const char *p, *q;
+ char *r;
+ char pathsep = trie_pathsep(t);
+ const char *fmt = cfg->uriformat;
+
+ while (fmt) {
+ struct format_option opt = get_format_option(&fmt);
+
+ /*
+ * Check prefix and suffix.
+ */
+ midlen = len - opt.prefixlen - opt.suffixlen;
+ if (midlen < 0)
+ continue; /* prefix and suffix don't even fit */
+
+ p = path;
+ while (opt.prefixlen > 0) {
+ char c = *opt.prefix++;
+ if (c == '%')
+ opt.prefix++;
+ if (*p != c)
+ break;
+ p++;
+ opt.prefixlen--;
+ }
+ if (opt.prefixlen > 0)
+ continue; /* prefix didn't match */
+
+ q = path + len - opt.suffixlen;
+ while (opt.suffixlen > 0) {
+ char c = *opt.suffix++;
+ if (c == '%')
+ opt.suffix++;
+ if (*q != c)
+ break;
+ q++;
+ opt.suffixlen--;
+ }
+ if (opt.suffixlen > 0)
+ continue; /* suffix didn't match */
+
+ /*
+ * Check the data in between. p points at it, and it's midlen
+ * characters long.
+ */
+ if (opt.fmttype == '\0') {
+ if (midlen == 0) {
+ /*
+ * Successful match against a root format.
+ */
+ *index = 0;
+ return 1;
+ }
+ } else if (opt.fmttype == 'n') {
+ *index = 0;
+ while (midlen > 0) {
+ if (*p >= '0' && *p <= '9')
+ *index = *index * 10 + (*p - '0');
+ else
+ break;
+ midlen--;
+ p++;
+ }
+ if (midlen == 0) {
+ /*
+ * Successful match against a numeric format.
+ */
+ return 1;
+ }
+ } else {
+ assert(opt.fmttype == 'p');
+
+ int maxoutlen = trie_maxpathlen(t) + 1;
+ int maxinlen = midlen + 1;
+ char triepath[maxinlen+maxoutlen];
+
+ if (opt.shorten_path) {
+ trie_getpath(t, 0, triepath);
+ r = triepath + strlen(triepath);
+ if (r > triepath && r[-1] != pathsep)
+ *r++ = pathsep;
+ } else {
+ r = triepath;
+ }
+
+ while (midlen > 0) {
+ if (*p == '/' && opt.translate_pathsep) {
+ *r++ = pathsep;
+ p++;
+ midlen--;
+ } else if (*p == '=') {
+ if (midlen < 3 ||
+ !isxdigit((unsigned char)p[1]) ||
+ !isxdigit((unsigned char)p[2]))
+ break; /* faulty escape encoding */
+ char x[3];
+ unsigned cval;
+ x[0] = p[1];
+ x[1] = p[2];
+ x[2] = '\0';
+ sscanf(x, "%x", &cval);
+ *r++ = cval;
+ p += 3;
+ midlen -= 3;
+ } else {
+ *r++ = *p;
+ p++;
+ midlen--;
+ }
+ }
+ if (midlen > 0)
+ continue; /* something went wrong in that loop */
+ assert(r - triepath < maxinlen+maxoutlen);
+ *r = '\0';
+
+ unsigned long gotidx = trie_before(t, triepath);
+ if (gotidx >= trie_count(t))
+ continue; /* index out of range */
+ char retpath[1+maxoutlen];
+ trie_getpath(t, gotidx, retpath);
+ if (strcmp(triepath, retpath))
+ continue; /* exact path not found in trie */
+ if (!index_has_root(t, gotidx))
+ continue; /* path is not a directory */
+
+ /*
+ * Successful path-based match.
+ */
+ *index = gotidx;
+ return 1;
+ }
+ }
+
+ return 0; /* no match from any format option */
+}
+
+char *make_href(const char *source, const char *target)
+{
+ /*
+ * We insist that both source and target URIs start with a /, or
+ * else we won't be reliably able to construct relative hrefs
+ * between them (e.g. because we've got a suffix on the end of
+ * some CGI pathname that this function doesn't know the final
+ * component of).
+ */
+ assert(*source == '/');
+ assert(*target == '/');
+
+ /*
+ * Find the last / in source. Everything up to but not including
+ * that is the directory to which the output href will be
+ * relative. We enforce by assertion that there must be a /
+ * somewhere in source, or else we can't construct a relative href
+ * at all
+ */
+ const char *sourceend = strrchr(source, '/');
+ assert(sourceend != NULL);
+
+ /*
+ * See how far the target URI agrees with the source one, up to
+ * and including that /.
+ */
+ const char *s = source, *t = target;
+ while (s <= sourceend && *s == *t)
+ s++, t++;
+
+ /*
+ * We're only interested in agreement of complete path components,
+ * so back off until we're sitting just after a shared /.
+ */
+ while (s > source && s[-1] != '/')
+ s--, t--;
+ assert(s > source);
+
+ /*
+ * Now we need some number of levels of "../" to get from source
+ * to here, and then we just replicate the rest of 'target'.
+ */
+ int levels = 0;
+ while (s <= sourceend) {
+ if (*s == '/')
+ levels++;
+ s++;
+ }
+ int len = 3*levels + strlen(t);
+ if (len == 0) {
+ /* One last special case: if target has no tail _and_ we
+ * haven't written out any "../". */
+ return dupstr("./");
+ } else {
+ char *ret = snewn(len+1, char);
+ char *p = ret;
+ while (levels-- > 0) {
+ *p++ = '.';
+ *p++ = '.';
+ *p++ = '/';
+ }
+ strcpy(p, t);
+ return ret;
+ }