summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Fandrich <dan@coneharvesters.com>2021-11-04 22:02:05 -0700
committerDan Fandrich <dan@coneharvesters.com>2021-11-05 11:41:53 -0700
commitcbbff786b4c347b6ef7c6ae6c18cf9ea09dfc40a (patch)
tree735cf00274cae2a811b9d7542762c137678573d2
parentd1624b94f0a21cefe803b2ccafe40bd71939bf42 (diff)
downloadcurl-dfandrich/hyperlink.tar.gz
tool_cb_hdr: Turn the Location: into a terminal hyperlinkdfandrich/hyperlink
This turns even relative URLs into clickable hyperlinks in a supported terminal when --styled-output is enabled. Many terminals already turn URLs into clickable links but there is not enough information in a relative URL to do this automatically otherwise.
-rw-r--r--src/tool_cb_hdr.c86
1 files changed, 86 insertions, 0 deletions
diff --git a/src/tool_cb_hdr.c b/src/tool_cb_hdr.c
index 67ea10447..a93d5f2ec 100644
--- a/src/tool_cb_hdr.c
+++ b/src/tool_cb_hdr.c
@@ -47,6 +47,10 @@ static char *parse_filename(const char *ptr, size_t len);
bold-off code (21) isn't supported everywhere - like in the mac
Terminal. */
#define BOLDOFF "\x1b[0m"
+/* OSC 8 hyperlink escape sequence */
+#define LINK "\x1b]8;;"
+#define LINKST "\x1b\\"
+#define LINKOFF LINK LINKST
#endif
/*
@@ -204,7 +208,89 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
if(value) {
size_t namelen = value - ptr;
fprintf(outs->stream, BOLD "%.*s" BOLDOFF ":", namelen, ptr);
+#ifndef LINK
fwrite(&value[1], cb - namelen - 1, 1, outs->stream);
+#else
+ if(curl_strnequal("Location", ptr, namelen)) {
+ /* Treat the Location: header specially, by writing a special escape
+ * sequence that adds a hyperlink to the displayed text. This makes
+ * the absolute URL of the redirect clickable in supported terminals,
+ * which couldn't happen otherwise for relative URLs. The Location:
+ * header is supposed to always be absolute so this theoretically
+ * shouldn't be needed but the real world returns plenty of relative
+ * URLs here.
+ */
+
+ /* This would so simple if CURLINFO_REDIRECT_URL were available here */
+ char *location = &value[1];
+ size_t llen = cb - namelen - 1;
+ CURLU *u = NULL;
+ char *copyloc = NULL, *locurl = NULL, *scheme = NULL, *finalurl = NULL;
+
+ /* Strip leading whitespace of the redirect URL */
+ while(llen && *location == ' ') {
+ ++location;
+ --llen;
+ }
+
+ /* Strip the trailing end-of-line characters, normally "\r\n" */
+ while(llen && (location[llen-1] == '\n' || location[llen-1] == '\r'))
+ --llen;
+
+ /* CURLU makes it easy to handle the relative URL case */
+ u = curl_url();
+ if(!u)
+ goto locout;
+
+ /* Create a NUL-terminated and whitespace-stripped copy of Location: */
+ copyloc = malloc(llen + 1);
+ if(!copyloc)
+ goto locout;
+ memcpy(copyloc, location, llen);
+ copyloc[llen] = 0;
+
+ /* The original URL to use as a base for a relative redirect URL */
+ if(curl_easy_getinfo(per->curl, CURLINFO_EFFECTIVE_URL, &locurl))
+ goto locout;
+ if(curl_url_set(u, CURLUPART_URL, locurl, 0))
+ goto locout;
+
+ /* Redirected location. This can be either absolute or relative. */
+ if(curl_url_set(u, CURLUPART_URL, copyloc, 0))
+ goto locout;
+
+ if(curl_url_get(u, CURLUPART_URL, &finalurl, CURLU_NO_DEFAULT_PORT))
+ goto locout;
+
+ if(curl_url_get(u, CURLUPART_SCHEME, &scheme, 0))
+ goto locout;
+
+ if(!strcmp("http", scheme) ||
+ !strcmp("https", scheme) ||
+ !strcmp("ftp", scheme) ||
+ !strcmp("ftps", scheme)) {
+ fprintf(outs->stream, LINK "%s" LINKST "%.*s" LINKOFF,
+ finalurl, cb - namelen - 1, &value[1]);
+ goto locdone;
+ }
+
+ /* Not a "safe" URL: don't linkify it */
+
+locout:
+ /* Write the normal output in case of error or unsafe */
+ fwrite(&value[1], cb - namelen - 1, 1, outs->stream);
+
+locdone:
+ if(u) {
+ curl_url_cleanup(u);
+ free(copyloc);
+ free(scheme);
+ free(finalurl);
+ }
+ }
+ else
+ fwrite(&value[1], cb - namelen - 1, 1, outs->stream);
+#endif
}
else
/* not "handled", just show it */