]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
tool_cb_hdr: Turn the Location: into a terminal hyperlink
authorDan Fandrich <dan@coneharvesters.com>
Fri, 5 Nov 2021 05:02:05 +0000 (22:02 -0700)
committerDan Fandrich <dan@telarity.com>
Sat, 12 Mar 2022 01:25:35 +0000 (17:25 -0800)
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.

src/tool_cb_hdr.c

index 67ea104471cc2916e644e654d130fa15f6ecb1d4..f1a2e3f2f1ceec55676e98a3487c480a24557e11 100644 (file)
@@ -47,6 +47,15 @@ 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
+
+#ifdef LINK
+static void write_linked_location(CURL *curl, const char *location,
+    size_t loclen, FILE *stream);
 #endif
 
 /*
@@ -204,7 +213,16 @@ 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)) {
+        write_linked_location(per->curl, &value[1], cb - namelen - 1,
+            outs->stream);
+      }
+      else
+        fwrite(&value[1], cb - namelen - 1, 1, outs->stream);
+#endif
     }
     else
       /* not "handled", just show it */
@@ -311,3 +329,85 @@ static char *parse_filename(const char *ptr, size_t len)
 
   return copy;
 }
+
+#ifdef LINK
+/*
+ * 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.
+ */
+static
+void write_linked_location(CURL *curl, const char *location, size_t loclen,
+                           FILE *stream) {
+  /* This would so simple if CURLINFO_REDIRECT_URL were available here */
+  CURLU *u = NULL;
+  char *copyloc = NULL, *locurl = NULL, *scheme = NULL, *finalurl = NULL;
+  const char *loc = location;
+  size_t llen = loclen;
+
+  /* Strip leading whitespace of the redirect URL */
+  while(llen && *loc == ' ') {
+    ++loc;
+    --llen;
+  }
+
+  /* Strip the trailing end-of-line characters, normally "\r\n" */
+  while(llen && (loc[llen-1] == '\n' || loc[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, loc, llen);
+  copyloc[llen] = 0;
+
+  /* The original URL to use as a base for a relative redirect URL */
+  if(curl_easy_getinfo(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(stream, LINK "%s" LINKST "%.*s" LINKOFF,
+            finalurl, loclen, location);
+    goto locdone;
+  }
+
+  /* Not a "safe" URL: don't linkify it */
+
+locout:
+  /* Write the normal output in case of error or unsafe */
+  fwrite(location, loclen, 1, stream);
+
+locdone:
+  if(u) {
+    curl_free(finalurl);
+    curl_free(scheme);
+    curl_url_cleanup(u);
+    free(copyloc);
+  }
+}
+#endif