You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by am...@apache.org on 2017/12/20 20:49:42 UTC

[trafficserver] branch master updated: Add pparam to url_sig plugin to make it authenticate pristine URL, eliminate sheme check, other minor changes.

This is an automated email from the ASF dual-hosted git repository.

amc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 5fe7236  Add pparam to url_sig plugin to make it authenticate pristine URL, eliminate sheme check, other minor changes.
5fe7236 is described below

commit 5fe7236975506514ed79d9f624c28bb84e60af60
Author: Walt Karas <wk...@yahoo-inc.com>
AuthorDate: Tue Nov 7 17:11:10 2017 +0000

    Add pparam to url_sig plugin to make it authenticate pristine URL, eliminate sheme check, other minor changes.
---
 doc/admin-guide/plugins/url_sig.en.rst             |  10 +-
 plugins/experimental/url_sig/sign.pl               |   8 +-
 plugins/experimental/url_sig/url_sig.c             | 236 +++++++++++--------
 tests/gold_tests/logging/ccid_ctid.test.py         |   2 +-
 tests/gold_tests/pluginTest/url_sig/run_sign.sh    | 138 +++++++++++
 tests/gold_tests/pluginTest/url_sig/url_sig.config |  17 ++
 tests/gold_tests/pluginTest/url_sig/url_sig.gold   |  24 ++
 .../gold_tests/pluginTest/url_sig/url_sig.test.py  | 261 +++++++++++++++++++++
 8 files changed, 592 insertions(+), 104 deletions(-)

diff --git a/doc/admin-guide/plugins/url_sig.en.rst b/doc/admin-guide/plugins/url_sig.en.rst
index cabbcf1..caee1cf 100644
--- a/doc/admin-guide/plugins/url_sig.en.rst
+++ b/doc/admin-guide/plugins/url_sig.en.rst
@@ -110,12 +110,14 @@ To require a valid signature, verified by a key from the list you generated
 earlier, modify your :file:`remap.config` configuration to include this plugin
 for any rules you wish it to affect.
 
-Two parameters for each remap rule are required::
+Two parameters for each remap rule are required, and a third one is optional::
 
-    @plugin=url_sig.so @pparam=<config file>
+    @plugin=url_sig.so @pparam=<config file> @pparam=pristineurl
 
 The first simply enables this plugin for the rule. The second specifies the
-location of the configuration file containing your signing keys.
+location of the configuration file containing your signing keys.  The third one,
+if present, causes authentication to be performed on the original (pristine) URL
+as received from the client. (The value of the parameter is not case sensitive.)
 
 For example, if we wanted to restrict all paths under a ``/download`` directory
 on our website ``foo.com`` we might have a remap line like this::
@@ -184,7 +186,7 @@ Key Index
 
 Parts
     Configures which components of the URL to use for signature verification.
-    The value of this paramerts is a string of ones and zeroes, each enabling
+    The value of this parameter is a string of ones and zeroes, each enabling
     or disabling the use of a URL part for signatures. The URL scheme (e.g.
     ``http://``) is never part of the signature. The first number of this
     parameter's value indicates whether to include the FQDN, and all remaining
diff --git a/plugins/experimental/url_sig/sign.pl b/plugins/experimental/url_sig/sign.pl
index 7f2cc7b..66fa729 100755
--- a/plugins/experimental/url_sig/sign.pl
+++ b/plugins/experimental/url_sig/sign.pl
@@ -48,7 +48,9 @@ if ( !defined($key) || !defined($url) || !defined($duration) || !defined($keyind
 	exit(1);
 }
 
-$url =~ s/^http:\/\///;
+my $url_prefix = $url;
+$url_prefix =~ s/^([^:]*:\/\/).*$/$1/;
+$url =~ s/^[^:]+:\/\///;
 my $i              = 0;
 my $part_active    = 0;
 my $j              = 0;
@@ -97,13 +99,13 @@ else {
 if ($urlHasParams == -1) {
   my $qstring = ( split( /\?/, $string ) )[1];
 
-  print "curl -s -o /dev/null -v --max-redirs 0 'http://" . $url . "?" . $qstring . $digest . "'\n";
+  print "curl -s -o /dev/null -v --max-redirs 0 '" . $url_prefix . $url . "?" . $qstring . $digest . "'\n";
 }
 else {
   my $url_noparams = ( split( /\?/, $url ) )[0];
   my $qstring = ( split( /\?/, $string ) )[1];
 
-  print "curl -s -o /dev/null -v --max-redirs 0 'http://" . $url_noparams . "?" . $qstring . $digest . "'\n";
+  print "curl -s -o /dev/null -v --max-redirs 0 '" . $url_prefix . $url_noparams . "?" . $qstring . $digest . "'\n";
 }
 
 sub help {
diff --git a/plugins/experimental/url_sig/url_sig.c b/plugins/experimental/url_sig/url_sig.c
index ad583c9..537ef35 100644
--- a/plugins/experimental/url_sig/url_sig.c
+++ b/plugins/experimental/url_sig/url_sig.c
@@ -38,6 +38,7 @@
 #include <arpa/inet.h>
 #include <limits.h>
 #include <ctype.h>
+#include <stdint.h>
 
 #ifdef HAVE_PCRE_PCRE_H
 #include <pcre/pcre.h>
@@ -48,7 +49,7 @@
 #include <ts/ts.h>
 #include <ts/remap.h>
 
-static const char *PLUGIN_NAME = "url_sig";
+static const char PLUGIN_NAME[] = "url_sig";
 
 struct config {
   TSHttpStatus err_status;
@@ -56,6 +57,7 @@ struct config {
   char keys[MAX_KEY_NUM][MAX_KEY_LEN];
   pcre *regex;
   pcre_extra *regex_extra;
+  int pristine_url_flag;
 };
 
 static void
@@ -83,12 +85,12 @@ TSReturnCode
 TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size)
 {
   if (!api_info) {
-    strncpy(errbuf, "[tsremap_init] - Invalid TSRemapInterface argument", (size_t)(errbuf_size - 1));
+    snprintf(errbuf, errbuf_size, "[tsremap_init] - Invalid TSRemapInterface argument");
     return TS_ERROR;
   }
 
   if (api_info->tsremap_version < TSREMAP_VERSION) {
-    snprintf(errbuf, errbuf_size - 1, "[TSRemapInit] - Incorrect API version %ld.%ld", api_info->tsremap_version >> 16,
+    snprintf(errbuf, errbuf_size, "[TSRemapInit] - Incorrect API version %ld.%ld", api_info->tsremap_version >> 16,
              (api_info->tsremap_version & 0xffff));
     return TS_ERROR;
   }
@@ -104,9 +106,11 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, char *errbuf, int errbuf_s
   char config_filepath_buf[PATH_MAX], *config_file;
   struct config *cfg;
 
-  if (argc != 3) {
-    snprintf(errbuf, errbuf_size - 1,
-             "[TSRemapNewKeyInstance] - Argument count wrong (%d)... Need exactly two pparam= (config file name)", argc);
+  if ((argc < 3) || (argc > 4)) {
+    snprintf(errbuf, errbuf_size,
+             "[TSRemapNewInstance] - Argument count wrong (%d)... config file path is required first pparam, \"pristineurl\" is"
+             "optional second pparam.",
+             argc);
     return TS_ERROR;
   }
   TSDebug(PLUGIN_NAME, "Initializing remap function of %s -> %s with config from %s", argv[0], argv[1], argv[2]);
@@ -120,7 +124,7 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, char *errbuf, int errbuf_s
   TSDebug(PLUGIN_NAME, "config file name: %s", config_file);
   FILE *file = fopen(config_file, "r");
   if (file == NULL) {
-    snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] - Error opening file %s", config_file);
+    snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - Error opening file %s", config_file);
     return TS_ERROR;
   }
 
@@ -152,8 +156,7 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, char *errbuf, int errbuf_s
       *pos = '\0';
     }
     if (pos == NULL || strlen(value) >= MAX_KEY_LEN) {
-      snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] - Maximum key length (%d) exceeded on line %d", MAX_KEY_LEN - 1,
-               line_no);
+      snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - Maximum key length (%d) exceeded on line %d", MAX_KEY_LEN - 1, line_no);
       fclose(file);
       free_cfg(cfg);
       return TS_ERROR;
@@ -170,12 +173,12 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, char *errbuf, int errbuf_s
       }
       TSDebug(PLUGIN_NAME, "key number %d == %s", keynum, value);
       if (keynum >= MAX_KEY_NUM || keynum < 0) {
-        snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] - Key number (%d) >= MAX_KEY_NUM (%d) or NaN", keynum, MAX_KEY_NUM);
+        snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - Key number (%d) >= MAX_KEY_NUM (%d) or NaN", keynum, MAX_KEY_NUM);
         fclose(file);
         free_cfg(cfg);
         return TS_ERROR;
       }
-      strncpy(&cfg->keys[keynum][0], value, MAX_KEY_LEN - 1);
+      snprintf(&cfg->keys[keynum][0], MAX_KEY_LEN, "%s", value);
     } else if (strncmp(line, "error_url", 9) == 0) {
       if (atoi(value)) {
         cfg->err_status = atoi(value);
@@ -214,32 +217,40 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, char *errbuf, int errbuf_s
     }
   }
 
+  fclose(file);
+
+  if (argc > 3) {
+    if (strcasecmp(argv[3], "pristineurl") == 0) {
+      cfg->pristine_url_flag = 1;
+
+    } else {
+      snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - second pparam (if present) must be pristineurl");
+      free_cfg(cfg);
+      return TS_ERROR;
+    }
+  }
+
   switch (cfg->err_status) {
   case TS_HTTP_STATUS_MOVED_TEMPORARILY:
     if (cfg->err_url == NULL) {
-      snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] - Invalid config, err_status == 302, but err_url == NULL");
-      fclose(file);
+      snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - Invalid config, err_status == 302, but err_url == NULL");
       free_cfg(cfg);
       return TS_ERROR;
     }
     break;
   case TS_HTTP_STATUS_FORBIDDEN:
     if (cfg->err_url != NULL) {
-      snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] - Invalid config, err_status == 403, but err_url != NULL");
-      fclose(file);
+      snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - Invalid config, err_status == 403, but err_url != NULL");
       free_cfg(cfg);
       return TS_ERROR;
     }
     break;
   default:
-    snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] - Return code %d not supported", cfg->err_status);
-    fclose(file);
+    snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - Return code %d not supported", cfg->err_status);
     free_cfg(cfg);
     return TS_ERROR;
   }
 
-  fclose(file);
-
   *ih = (void *)cfg;
   return TS_SUCCESS;
 }
@@ -251,7 +262,7 @@ TSRemapDeleteInstance(void *ih)
 }
 
 static void
-err_log(char *url, char *msg)
+err_log(const char *url, const char *msg)
 {
   if (msg && url) {
     TSDebug(PLUGIN_NAME, "[URL=%s]: %s", url, msg);
@@ -264,18 +275,18 @@ err_log(char *url, char *msg)
 // See the README.  All Signing parameters must be concatenated to the end
 // of the url and any application query parameters.
 static char *
-getAppQueryString(char *query_string, unsigned int query_length)
+getAppQueryString(const char *query_string, int query_length)
 {
   int done = 0;
   char *p;
-  char buf[MAX_QUERY_LEN];
+  char buf[MAX_QUERY_LEN + 1];
 
-  if (query_length >= sizeof(buf)) {
+  if (query_length > MAX_QUERY_LEN) {
     TSDebug(PLUGIN_NAME, "Cannot process the query string as the length exceeds %d bytes", MAX_QUERY_LEN);
     return NULL;
   }
-  memset(buf, 0, MAX_QUERY_LEN);
-  strncpy(buf, query_string, min(query_length, sizeof(buf) - 1));
+  memset(buf, 0, sizeof(buf));
+  strncpy(buf, query_string, query_length);
   p = buf;
 
   TSDebug(PLUGIN_NAME, "query_string: %s, query_length: %d", query_string, query_length);
@@ -289,7 +300,7 @@ getAppQueryString(char *query_string, unsigned int query_length)
     case 'P':
     case 'S':
       done = 1;
-      if (*(p - 1) == '&') {
+      if ((p > buf) && (*(p - 1) == '&')) {
         *(p - 1) = '\0';
       } else {
         (*p = '\0');
@@ -317,13 +328,13 @@ getAppQueryString(char *query_string, unsigned int query_length)
 TSRemapStatus
 TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri)
 {
-  struct config *cfg;
-  cfg = (struct config *)ih;
+  const struct config *cfg = (const struct config *)ih;
 
-  int url_len       = 0;
-  time_t expiration = 0;
-  int algorithm     = -1;
-  int keyindex      = -1;
+  int url_len         = 0;
+  int current_url_len = 0;
+  uint64_t expiration = 0;
+  int algorithm       = -1;
+  int keyindex        = -1;
   int cmp_res;
   int rval;
   unsigned int i       = 0;
@@ -331,7 +342,8 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri)
   unsigned int sig_len = 0;
 
   /* all strings are locally allocated except url... about 25k per instance */
-  char *url;
+  char *const current_url    = TSUrlStringGet(rri->requestBufp, rri->requestUrl, &current_url_len);
+  const char *url            = current_url;
   char signed_part[8192]     = {'\0'}; // this initializes the whole array and is needed
   char urltokstr[8192]       = {'\0'};
   char client_ip[CIP_STRLEN] = {'\0'};
@@ -339,33 +351,42 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri)
   unsigned char sig[MAX_SIG_SIZE + 1];
   char sig_string[2 * MAX_SIG_SIZE + 1];
 
-  /* these are just pointers into other allocations */
-  char *signature = NULL;
-  char *parts     = NULL;
-  char *part      = NULL;
-  char *p = NULL, *pp = NULL;
-  char *query = NULL, *app_qry = NULL;
-
   int retval, sockfd;
   socklen_t peer_len;
   struct sockaddr_in peer;
 
-  url = TSUrlStringGet(rri->requestBufp, rri->requestUrl, &url_len);
-
-  if (url_len >= MAX_REQ_LEN - 1) {
-    err_log(url, "URL string too long");
+  if (current_url_len >= MAX_REQ_LEN - 1) {
+    err_log(current_url, "URL string too long.");
     goto deny;
   }
 
+  if (cfg->pristine_url_flag) {
+    TSMBuffer mbuf;
+    TSMLoc ul;
+    TSReturnCode rc = TSHttpTxnPristineUrlGet(txnp, &mbuf, &ul);
+    if (rc != TS_SUCCESS) {
+      TSError("[url_sig] Failed call to TSHttpTxnPristineUrlGet()");
+      goto deny;
+    }
+    url = TSUrlStringGet(mbuf, ul, &url_len);
+    if (url_len >= MAX_REQ_LEN - 1) {
+      err_log(url, "Pristine URL string too long.");
+      goto deny;
+    }
+
+  } else {
+    url_len = current_url_len;
+  }
+
   TSDebug(PLUGIN_NAME, "%s", url);
 
-  query = strstr(url, "?");
+  const char *query = strchr(url, '?');
 
   if (cfg->regex) {
     int offset = 0, options = 0;
     int ovector[30];
-    int len      = url_len;
-    char *anchor = strstr(url, "#");
+    int len            = url_len;
+    const char *anchor = strchr(url, '#');
     if (query && !anchor) {
       len -= (query - url);
     } else if (anchor && !query) {
@@ -383,26 +404,26 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri)
     goto deny;
   }
 
-  if (strncmp(url, "http://", strlen("http://")) != 0) {
-    err_log(url, "Invalid URL scheme - only http supported");
-    goto deny;
-  }
-
   /* first, parse the query string */
   query++; /* get rid of the ? */
   TSDebug(PLUGIN_NAME, "Query string is:%s", query);
 
   // Client IP - this one is optional
-  p = strstr(query, CIP_QSTRING "=");
-  if (p != NULL) {
-    p += strlen(CIP_QSTRING + 1);
-    pp = strstr(p, "&");
-    if ((pp - p) > CIP_STRLEN - 1 || (pp - p) < 4) {
+  const char *cp = strstr(query, CIP_QSTRING "=");
+  if (cp != NULL) {
+    int len_cip_qstring = strlen(CIP_QSTRING);
+    cp += len_cip_qstring - 1;
+    const char *cpp = strchr(cp, '&');
+    if (!cpp) {
+      err_log(url, "All required values after IP address string missing");
+      goto deny;
+    }
+    if (!cpp || (cpp - cp) > CIP_STRLEN - 1 || (cpp - cp) < 4) {
       err_log(url, "IP address string too long or short");
       goto deny;
     }
-    strncpy(client_ip, p + strlen(CIP_QSTRING) + 1, min((pp - p - (strlen(CIP_QSTRING) + 1)), sizeof(client_ip) - 1));
-    client_ip[pp - p - (strlen(CIP_QSTRING) + 1)] = '\0';
+    memcpy(client_ip, cp + len_cip_qstring + 1, (cpp - cp - (len_cip_qstring + 1)));
+    client_ip[cpp - cp - (len_cip_qstring + 1)] = '\0';
     TSDebug(PLUGIN_NAME, "CIP: -%s-", client_ip);
     retval = TSHttpTxnClientFdGet(txnp, &sockfd);
     if (retval != TS_SUCCESS) {
@@ -422,24 +443,23 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri)
     }
   }
   // Expiration
-  p = strstr(query, EXP_QSTRING "=");
-  if (p != NULL) {
-    p += strlen(EXP_QSTRING) + 1;
-    expiration = atoi(p);
-    if (expiration == 0 || expiration < time(NULL)) {
+  cp = strstr(query, EXP_QSTRING "=");
+  if (cp != NULL) {
+    cp += strlen(EXP_QSTRING) + 1;
+    if (sscanf(cp, "%" SCNu64, &expiration) != 1 || (time_t)expiration < time(NULL)) {
       err_log(url, "Invalid expiration, or expired");
       goto deny;
     }
-    TSDebug(PLUGIN_NAME, "Exp: %d", (int)expiration);
+    TSDebug(PLUGIN_NAME, "Exp: %" PRIu64, expiration);
   } else {
     err_log(url, "Expiration query string not found");
     goto deny;
   }
   // Algorithm
-  p = strstr(query, ALG_QSTRING "=");
-  if (p != NULL) {
-    p += strlen(ALG_QSTRING) + 1;
-    algorithm = atoi(p);
+  cp = strstr(query, ALG_QSTRING "=");
+  if (cp != NULL) {
+    cp += strlen(ALG_QSTRING) + 1;
+    algorithm = atoi(cp);
     // The check for a valid algorithm is later.
     TSDebug(PLUGIN_NAME, "Algorithm: %d", algorithm);
   } else {
@@ -447,10 +467,10 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri)
     goto deny;
   }
   // Key index
-  p = strstr(query, KIN_QSTRING "=");
-  if (p != NULL) {
-    p += strlen(KIN_QSTRING) + 1;
-    keyindex = atoi(p);
+  cp = strstr(query, KIN_QSTRING "=");
+  if (cp != NULL) {
+    cp += strlen(KIN_QSTRING) + 1;
+    keyindex = atoi(cp);
     if (keyindex < 0 || keyindex >= MAX_KEY_NUM || 0 == cfg->keys[keyindex][0]) {
       err_log(url, "Invalid key index");
       goto deny;
@@ -461,21 +481,27 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri)
     goto deny;
   }
   // Parts
-  p = strstr(query, PAR_QSTRING "=");
-  if (p != NULL) {
-    p += strlen(PAR_QSTRING) + 1;
-    parts = p; // NOTE parts is not NULL terminated it is terminated by "&" of next param
-    p     = strstr(parts, "&");
-    TSDebug(PLUGIN_NAME, "Parts: %.*s", (int)(p - parts), parts);
+  const char *parts = NULL;
+  cp                = strstr(query, PAR_QSTRING "=");
+  if (cp != NULL) {
+    cp += strlen(PAR_QSTRING) + 1;
+    parts = cp; // NOTE parts is not NULL terminated it is terminated by "&" of next param
+    cp    = strchr(parts, '&');
+    if (cp) {
+      TSDebug(PLUGIN_NAME, "Parts: %.*s", (int)(cp - parts), parts);
+    } else {
+      TSDebug(PLUGIN_NAME, "Parts: %s", parts);
+    }
   } else {
     err_log(url, "PartsSigned query string not found");
     goto deny;
   }
   // And finally, the sig (has to be last)
-  p = strstr(query, SIG_QSTRING "=");
-  if (p != NULL) {
-    p += strlen(SIG_QSTRING) + 1;
-    signature = p; // NOTE sig is not NULL terminated, it has to be 20 chars
+  const char *signature = NULL;
+  cp                    = strstr(query, SIG_QSTRING "=");
+  if (cp != NULL) {
+    cp += strlen(SIG_QSTRING) + 1;
+    signature = cp;
     if ((algorithm == USIG_HMAC_SHA1 && strlen(signature) < SHA1_SIG_SIZE) ||
         (algorithm == USIG_HMAC_MD5 && strlen(signature) < MD5_SIG_SIZE)) {
       err_log(url, "Signature query string too short (< 20)");
@@ -487,13 +513,20 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri)
   }
 
   /* have the query string, and parameters passed initial checks */
-  TSDebug(PLUGIN_NAME, "Found all needed parameters: C=%s E=%d A=%d K=%d P=%s S=%s", client_ip, (int)expiration, algorithm,
+  TSDebug(PLUGIN_NAME, "Found all needed parameters: C=%s E=%" PRIu64 " A=%d K=%d P=%s S=%s", client_ip, expiration, algorithm,
           keyindex, parts, signature);
 
   /* find the string that was signed - cycle through the parts letters, adding the part of the fqdn/path if it is 1 */
-  p = strstr(url, "?");
-  memcpy(urltokstr, &url[strlen("http://")], p - url - strlen("http://"));
-  part = strtok_r(urltokstr, "/", &p);
+  cp = strchr(url, '?');
+  // Skip scheme and initial forward slashes.
+  const char *skip = strchr(url, ':');
+  if (!skip || skip[1] != '/' || skip[2] != '/') {
+    goto deny;
+  }
+  skip += 3;
+  memcpy(urltokstr, skip, cp - skip);
+  char *strtok_r_p;
+  const char *part = strtok_r(urltokstr, "/", &strtok_r_p);
   while (part != NULL) {
     if (parts[j] == '1') {
       strncat(signed_part, part, sizeof(signed_part) - strlen(signed_part) - 1);
@@ -503,12 +536,12 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri)
         parts[j + 1] == '1') { // This remembers the last part, meaning, if there are no more valid letters in parts
       j++;                     // will keep repeating the value of the last one
     }
-    part = strtok_r(NULL, "/", &p);
+    part = strtok_r(NULL, "/", &strtok_r_p);
   }
 
   signed_part[strlen(signed_part) - 1] = '?'; // chop off the last /, replace with '?'
-  p                                    = strstr(query, SIG_QSTRING "=");
-  strncat(signed_part, query, min((p - query) + strlen(SIG_QSTRING) + 1, sizeof(signed_part) - strlen(signed_part) - 1));
+  cp                                   = strstr(query, SIG_QSTRING "=");
+  strncat(signed_part, query, (cp - query) + strlen(SIG_QSTRING) + 1);
 
   TSDebug(PLUGIN_NAME, "Signed string=\"%s\"", signed_part);
 
@@ -556,7 +589,10 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri)
 
 /* ********* Deny ********* */
 deny:
-  TSfree(url);
+  if (url != current_url) {
+    TSfree((void *)url);
+  }
+  TSfree((void *)current_url);
 
   switch (cfg->err_status) {
   case TS_HTTP_STATUS_MOVED_TEMPORARILY:
@@ -570,7 +606,7 @@ deny:
     rri->redirect = 1;
     break;
   default:
-    TSHttpTxnErrorBodySet(txnp, TSstrdup("Authorization Denied"), strlen("Authorization Denied") - 1, TSstrdup("text/plain"));
+    TSHttpTxnErrorBodySet(txnp, TSstrdup("Authorization Denied"), sizeof("Authorization Denied") - 1, TSstrdup("text/plain"));
     break;
   }
   /* Always set the return status */
@@ -580,15 +616,23 @@ deny:
 
 /* ********* Allow ********* */
 allow:
-  if (query != NULL) {
-    app_qry = getAppQueryString(query, strlen(query));
+  if (url != current_url) {
+    TSfree((void *)url);
+  }
+
+  const char *current_query = strchr(current_url, '?');
+  const char *app_qry       = NULL;
+  if (current_query != NULL) {
+    current_query++;
+    app_qry = getAppQueryString(current_query, strlen(current_query));
   }
 
-  TSfree(url);
+  TSfree((void *)current_url);
+
   /* drop the query string so we can cache-hit */
   if (app_qry != NULL) {
     rval = TSUrlHttpQuerySet(rri->requestBufp, rri->requestUrl, app_qry, strlen(app_qry));
-    TSfree(app_qry);
+    TSfree((void *)app_qry);
   } else {
     rval = TSUrlHttpQuerySet(rri->requestBufp, rri->requestUrl, NULL, 0);
   }
diff --git a/tests/gold_tests/logging/ccid_ctid.test.py b/tests/gold_tests/logging/ccid_ctid.test.py
index ab783e1..5236313 100644
--- a/tests/gold_tests/logging/ccid_ctid.test.py
+++ b/tests/gold_tests/logging/ccid_ctid.test.py
@@ -41,7 +41,7 @@ ts.addSSLfile("../remap/ssl/server.key")
 
 ts.Variables.ssl_port = 4443
 ts.Disk.records_config.update({
-    # 'proxy.config.diags.debug.enabled': '1',
+    # 'proxy.config.diags.debug.enabled': 1,
     'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
     'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     'proxy.config.http.server_ports': 'ipv4:{0} ipv4:{1}:proto=http2;http:ssl'.format(ts.Variables.port, ts.Variables.ssl_port)
diff --git a/tests/gold_tests/pluginTest/url_sig/run_sign.sh b/tests/gold_tests/pluginTest/url_sig/run_sign.sh
new file mode 100755
index 0000000..fcc404c
--- /dev/null
+++ b/tests/gold_tests/pluginTest/url_sig/run_sign.sh
@@ -0,0 +1,138 @@
+# Script to run sign.pl script.  Single parameter is number 1 or greater selecting a set of script parameters.
+
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+# Generate one or more sets of arguments for the sign.pl perl script.
+#
+cmd_args ()
+{
+if [[ "$1" = "" ]] ; then
+    SELECT=1
+else
+    SELECT="$1"
+fi
+
+FOREVER="$((60 * 60 * 24 * 365 * 1000))"
+
+case "$SELECT" in
+1)
+    echo "--url http://one.two.three/foo/abcde/qrstuvwxyz"
+    echo "--useparts 1"
+    echo "--algorithm 1"
+    echo "--duration $FOREVER"
+    echo "--keyindex 7"
+    echo "--key dqsgopTSM_doT6iAysasQVUKaPykyb6e"
+    ;;
+2)
+    echo "--client 127.0.0.1"
+    echo "--url http://four.five.six/foo/abcde/qrstuvwxyz"
+    echo "--useparts 1"
+    echo "--algorithm 1"
+    echo "--duration $FOREVER"
+    echo "--keyindex 15"
+    echo "--key 9MuXIiZ70HPi_qhqfSgdu9oJHpcj9yaO"
+    ;;
+3)
+    echo "--url http://seven.eight.nine/foo/abcde/qrstuvwxyz"
+    echo "--useparts 1"
+    echo "--algorithm 2"
+    echo "--duration $FOREVER"
+    echo "--keyindex 0"
+    echo "--key hV3wqyq1QxJeF76JkzHf93tuLYv_abw5"
+    ;;
+4)
+    echo "--client 127.0.0.1"
+    echo "--url http://seven.eight.nine/foo/abcde/qrstuvwxyz"
+    echo "--useparts 010"
+    echo "--algorithm 2"
+    echo "--duration $FOREVER"
+    echo "--keyindex 13"
+    echo "--key CGRDwMO96_vRjFCfks6oxkeV7IdTnA6f"
+    ;;
+5)
+    echo "--client 127.0.0.1"
+    echo "--url http://seven.eight.nine/foo/abcde/qrstuvwxyz"
+    echo "--useparts 101"
+    echo "--algorithm 2"
+    echo "--duration $FOREVER"
+    echo "--keyindex 13"
+    echo "--key CGRDwMO96_vRjFCfks6oxkeV7IdTnA6f"
+    ;;
+*h*)
+    ;;
+*)
+    echo "run_sign.sh: bad seletion parameter" 1>&2
+    exit 1
+    ;;
+esac
+}
+
+# Find the path to the sign.pl script in the url_sig (source) directory.
+#
+find_cmd ()
+{
+local D T='..'
+while [[ ! -d $T/.git ]]
+do
+  if [[ ! -d $T/.. ]] ; then
+    echo "Working directory not in a git repo" 1>&2
+    exit 1
+  fi
+  T="$T/.."
+done
+
+for D in $( find $T -name url_sig -type d )
+do
+    if [[ -x $D/sign.pl ]] ; then
+        echo "$D/sign.pl"
+        return 0
+    fi
+done
+
+echo "cannot find sign.pl script" 1>&2
+exit 1
+}
+
+FOUND=N
+echo "$PERL5LIB" | tr ':' ' ' | while read D
+do
+    if [[ -f $D/Digest/HMAC_MD5.pm ]] ; then
+        FOUND=Y
+        break
+    fi
+done
+
+if [[ $FOUND = N ]] ; then
+    P=$( find / 2>/dev/null | grep -F Digest/HMAC_MD5.pm | head -1 )
+    if [[ ! -f $P ]] ; then
+        echo "Cannot find HMAC_MD5.pm" 1>&2
+        exit 1
+    fi
+    export PERL5LIB="$PERL5LIB:$( dirname $( dirname $P ) )"
+fi
+
+CMD=$( find_cmd )
+if [[ "$?" != 0 ]] ; then
+    exit 1
+fi
+
+ARGS=$( cmd_args "$1" )
+if [[ "$?" != 0 ]] ; then
+    exit 1
+fi
+
+$CMD $ARGS | tr ' ' '\n' | tail -1
diff --git a/tests/gold_tests/pluginTest/url_sig/url_sig.config b/tests/gold_tests/pluginTest/url_sig/url_sig.config
new file mode 100644
index 0000000..7c10a6b
--- /dev/null
+++ b/tests/gold_tests/pluginTest/url_sig/url_sig.config
@@ -0,0 +1,17 @@
+key0 = hV3wqyq1QxJeF76JkzHf93tuLYv_abw5
+key1 = nIpyXbVqPFVN7y8yMlfgFBLnOqDSufMy
+key2 = 4UED1ELmHkEcXrS_7yEYPKtgUZdGWaP2
+key3 = mv2vPGJpq2iFDbiV3dJG4ZqCAzRTIpTD
+key4 = 2cnob1tuGEiYhwJLYRLa5bfyuZH1zI0S
+key5 = poC7zK9IrDl3rljvuZ0bbMP3e5f0woKt
+key6 = _k8diypYMebSCEEjYNszZbG906JZI6Bx
+key7 = dqsgopTSM_doT6iAysasQVUKaPykyb6e
+key8 = AzM3mhTDEkyJjyqQctv0NVxCL3FmXDzW
+key9 = iRHQE9ucS44oAhdXmM148wMTJAO4XAVV
+key10 = b1OMb39dGhMSg_wArQnvqGIBgQGFjnNl
+key11 = YpA8qBkvohdamogQ4zTuoPw50PbezdL0
+key12 = 4Q4OCnY_gmcDuw5756Wk1XG7PEi24g1_
+key13 = CGRDwMO96_vRjFCfks6oxkeV7IdTnA6f
+key14 = sXTWfNyHkN2SJ9eKifetPzfcg0_rNhXM
+key15 = 9MuXIiZ70HPi_qhqfSgdu9oJHpcj9yaO
+error_url = 403
diff --git a/tests/gold_tests/pluginTest/url_sig/url_sig.gold b/tests/gold_tests/pluginTest/url_sig/url_sig.gold
new file mode 100644
index 0000000..35f62da
--- /dev/null
+++ b/tests/gold_tests/pluginTest/url_sig/url_sig.gold
@@ -0,0 +1,24 @@
+< HTTP/1.1 403 Forbidden
+Authorization Denied*   Trying 127.0.0.1...
+< HTTP/1.1 403 Forbidden
+Authorization Denied*   Trying 127.0.0.1...
+< HTTP/1.1 403 Forbidden
+Authorization Denied*   Trying 127.0.0.1...
+< HTTP/1.1 403 Forbidden
+Authorization Denied*   Trying 127.0.0.1...
+< HTTP/1.1 403 Forbidden
+Authorization Denied*   Trying 127.0.0.1...
+< HTTP/1.1 403 Forbidden
+Authorization Denied*   Trying 127.0.0.1...
+< HTTP/1.1 403 Forbidden
+Authorization Denied*   Trying 127.0.0.1...
+< HTTP/1.1 403 Forbidden
+Authorization Denied*   Trying 127.0.0.1...
+< HTTP/1.1 403 Forbidden
+Authorization Denied*   Trying 127.0.0.1...
+< HTTP/1.1 200 OK
+< HTTP/1.1 200 OK
+< HTTP/1.1 200 OK
+< HTTP/1.1 200 OK
+< HTTP/1.1 200 OK
+< HTTP/1.1 200 OK
diff --git a/tests/gold_tests/pluginTest/url_sig/url_sig.test.py b/tests/gold_tests/pluginTest/url_sig/url_sig.test.py
new file mode 100644
index 0000000..78b0228
--- /dev/null
+++ b/tests/gold_tests/pluginTest/url_sig/url_sig.test.py
@@ -0,0 +1,261 @@
+'''
+'''
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import os
+import subprocess
+Test.Summary = '''
+Test url_sig plugin
+'''
+
+Test.SkipUnless(
+    Condition.HasATSFeature('TS_USE_TLS_ALPN'),
+)
+
+# Skip if plugins not present.
+Test.SkipUnless(Condition.PluginExists('url_sig.so'))
+Test.SkipUnless(Condition.PluginExists('balancer.so'))
+
+# Set up to check the output after the tests have run.
+#
+url_sig_log_id = Test.Disk.File("url_sig_short.log")
+url_sig_log_id.Content = "url_sig.gold"
+
+server = Test.MakeOriginServer("server")
+
+request_header = {
+    "headers": "GET /foo/abcde/qrstuvwxyz HTTP/1.1\r\nHost: just.any.thing\r\n\r\n", "timestamp": "1469733493.993", "body": ""
+}
+# expected response from the origin server
+response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+# add response to the server dictionary
+server.addResponse("sessionfile.log", request_header, response_header)
+
+# Define default ATS
+ts = Test.MakeATSProcess("ts", select_ports=False)
+
+ts.addSSLfile("../../remap/ssl/server.pem")
+ts.addSSLfile("../../remap/ssl/server.key")
+
+ts.Variables.ssl_port = 4443
+
+ts.Disk.records_config.update({
+    # 'proxy.config.diags.debug.enabled': 1,
+    # 'proxy.config.diags.debug.tags': 'http|url_sig',
+    'proxy.config.http.cache.http': 0, # Make sure each request is forwarded to the origin server.
+    'proxy.config.proxy_name': 'Poxy_Proxy', # This will be the server name.
+    'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
+    'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
+    'proxy.config.http.server_ports': (
+        'ipv4:{0} ipv4:{1}:proto=http:ssl'.format(ts.Variables.port, ts.Variables.ssl_port))
+})
+
+ts.Disk.ssl_multicert_config.AddLine(
+    'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key'
+)
+
+# Use unchanged incoming URL.
+#
+ts.Disk.remap_config.AddLine(
+    'map http://one.two.three/ http://127.0.0.1:{}/'.format(server.Variables.Port) +
+    ' @plugin=url_sig.so @pparam={}/url_sig.config'.format(Test.TestDirectory)
+)
+
+# Use unchanged incoming HTTPS URL.
+#
+ts.Disk.remap_config.AddLine(
+    'map https://one.two.three/ http://127.0.0.1:{}/'.format(server.Variables.Port) +
+    ' @plugin=url_sig.so @pparam={}/url_sig.config'.format(Test.TestDirectory)
+)
+
+# Use pristine URL, incoming URL unchanged.
+#
+ts.Disk.remap_config.AddLine(
+    'map http://four.five.six/ http://127.0.0.1:{}/'.format(server.Variables.Port) +
+    ' @plugin=url_sig.so @pparam={}/url_sig.config @pparam=pristineurl'.format(Test.TestDirectory)
+)
+
+# Use pristine URL, incoming URL changed.
+#
+ts.Disk.remap_config.AddLine(
+    'map http://seven.eight.nine/ http://dummy' +
+    ' @plugin=balancer.so @pparam=--policy=hash,url @pparam=127.0.0.1:{}'.format(server.Variables.Port) +
+    ' @plugin=url_sig.so @pparam={}/url_sig.config @pparam=PristineUrl'.format(Test.TestDirectory)
+)
+
+# Ask the OS if the port is ready for connect()
+#
+def CheckPort(Port):
+    return lambda: 0 == subprocess.call('netstat --listen --tcp -n | grep -q :{}'.format(Port), shell=True)
+
+# Validation failure tests.
+
+LogTee = " 2>&1 | tee -a {}/url_sig_long.log".format(Test.RunDirectory)
+
+# Bad client / MD5 / P=101 / URL pristine / URL altered.
+#
+tr = Test.AddTestRun()
+tr.Processes.Default.StartBefore(ts, ready=CheckPort(ts.Variables.ssl_port))
+tr.Processes.Default.StartBefore(server, ready=CheckPort(server.Variables.Port))
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Command = (
+    "curl --verbose --proxy http://127.0.0.1:{} 'http://seven.eight.nine/".format(ts.Variables.port) +
+    "foo/abcde/qrstuvwxyz?C=127.0.0.2&E=33046620008&A=2&K=13&P=101&S=d1f352d4f1d931ad2f441013402d93f8'" +
+    LogTee
+)
+
+# With client / MD5 / P=010 / URL pristine / URL altered -- Expired.
+#
+tr = Test.AddTestRun()
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Command = (
+    "curl --verbose --proxy http://127.0.0.1:{} 'http://seven.eight.nine/".format(ts.Variables.port) +
+    "foo/abcde/qrstuvwxyz?C=127.0.0.1&E=1&A=2&K=13&P=010&S=f237aad1fa010234d7bf8108a0e36387'" +
+    LogTee
+)
+
+# With client / No algorithm / P=101 / URL pristine / URL altered.
+#
+tr = Test.AddTestRun()
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Command = (
+    "curl --verbose --proxy http://127.0.0.1:{} 'http://seven.eight.nine/".format(ts.Variables.port) +
+    "foo/abcde/qrstuvwxyz?C=127.0.0.1&E=33046620008&K=13&P=101&S=d1f352d4f1d931ad2f441013402d93f8'" +
+    LogTee
+)
+
+# With client / Bad algorithm / P=101 / URL pristine / URL altered.
+#
+tr = Test.AddTestRun()
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Command = (
+    "curl --verbose --proxy http://127.0.0.1:{} 'http://seven.eight.nine/".format(ts.Variables.port) +
+    "foo/abcde/qrstuvwxyz?C=127.0.0.1&E=33046620008&A=3&K=13&P=101&S=d1f352d4f1d931ad2f441013402d93f8'" +
+    LogTee
+)
+
+# With client / MD5 / No parts / URL pristine / URL altered.
+#
+tr = Test.AddTestRun()
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Command = (
+    "curl --verbose --proxy http://127.0.0.1:{} 'http://seven.eight.nine/".format(ts.Variables.port) +
+    "foo/abcde/qrstuvwxyz?C=127.0.0.1&E=33046620008&A=2&K=13&S=d1f352d4f1d931ad2f441013402d93f8'" +
+    LogTee
+)
+
+# With client / MD5 / P=10 (bad) / URL pristine / URL altered.
+#
+tr = Test.AddTestRun()
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Command = (
+    "curl --verbose --proxy http://127.0.0.1:{} 'http://seven.eight.nine/".format(ts.Variables.port) +
+    "foo/abcde/qrstuvwxyz?C=127.0.0.1&E=33046620008&A=2&K=13&P=10&S=d1f352d4f1d931ad2f441013402d93f8'" +
+    LogTee
+)
+
+# With client / MD5 / P=101 / URL pristine / URL altered -- No signature.
+#
+tr = Test.AddTestRun()
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Command = (
+    "curl --verbose --proxy http://127.0.0.1:{} 'http://seven.eight.nine/".format(ts.Variables.port) +
+    "foo/abcde/qrstuvwxyz?C=127.0.0.1&E=33046620008&A=2&K=13&P=101'" +
+    LogTee
+)
+
+# With client / MD5 / P=101 / URL pristine / URL altered  -- Bad signature.
+#
+tr = Test.AddTestRun()
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Command = (
+    "curl --verbose --proxy http://127.0.0.1:{} 'http://seven.eight.nine/".format(ts.Variables.port) +
+    "foo/abcde/qrstuvwxyz?C=127.0.0.1&E=33046620008&A=2&K=13&P=101&S=d1f452d4f1d931ad2f441013402d93f8'" +
+    LogTee
+)
+
+# With client / MD5 / P=101 / URL pristine / URL altered -- Spurious &.
+#
+tr = Test.AddTestRun()
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Command = (
+    "curl --verbose --proxy http://127.0.0.1:{} 'http://seven.eight.nine/".format(ts.Variables.port) +
+    "foo/abcde/qrstuvwxyz?C=127.0.0.1&E=33046620008&A=2&&K=13&P=101&S=d1f352d4f1d931ad2f441013402d93f8#'" +
+    LogTee
+)
+
+# Success tests.
+
+# No client / SHA1 / P=1 / URL not pristine / URL not altered.
+#
+tr = Test.AddTestRun()
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Command = (
+    "curl --verbose --proxy http://127.0.0.1:{} 'http://one.two.three/".format(ts.Variables.port) +
+    "foo/abcde/qrstuvwxyz?E=33046618506&A=1&K=7&P=1&S=acae22b0e1ba6ea6fbb5d26018dbf152558e98cb'" +
+    LogTee
+)
+
+# With client / SHA1 / P=1 / URL pristine / URL not altered.
+#
+tr = Test.AddTestRun()
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Command = (
+    "curl --verbose --proxy http://127.0.0.1:{} 'http://four.five.six/".format(ts.Variables.port) +
+    "foo/abcde/qrstuvwxyz?C=127.0.0.1&E=33046618556&A=1&K=15&P=1&S=f4103561a23adab7723a89b9831d77e0afb61d92'" +
+    LogTee
+)
+
+# No client / MD5 / P=1 / URL pristine / URL altered.
+#
+tr = Test.AddTestRun()
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Command = (
+    "curl --verbose --proxy http://127.0.0.1:{} 'http://seven.eight.nine/".format(ts.Variables.port) +
+    "foo/abcde/qrstuvwxyz?E=33046618586&A=2&K=0&P=1&S=0364efa28afe345544596705b92d20ac'" +
+    LogTee
+)
+
+# With client / MD5 / P=010 / URL pristine / URL altered.
+#
+tr = Test.AddTestRun()
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Command = (
+    "curl --verbose --proxy http://127.0.0.1:{} 'http://seven.eight.nine/".format(ts.Variables.port) +
+    "foo/abcde/qrstuvwxyz?C=127.0.0.1&E=33046619717&A=2&K=13&P=010&S=f237aad1fa010234d7bf8108a0e36387'" +
+    LogTee
+)
+
+# With client / MD5 / P=101 / URL pristine / URL altered.
+#
+tr = Test.AddTestRun()
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Command = (
+    "curl --verbose --proxy http://127.0.0.1:{} 'http://seven.eight.nine/".format(ts.Variables.port) +
+    "foo/abcde/qrstuvwxyz?C=127.0.0.1&E=33046620008&A=2&K=13&P=101&S=d1f352d4f1d931ad2f441013402d93f8'" +
+    LogTee
+)
+
+# No client / SHA1 / P=1 / URL not pristine / URL not altered -- HTTPS.
+#
+tr = Test.AddTestRun()
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Command = (
+    "curl --verbose --http1.1 --insecure --header 'Host: one.two.three' 'https://127.0.0.1:{}/".format(ts.Variables.ssl_port) +
+    "foo/abcde/qrstuvwxyz?E=33046618506&A=1&K=7&P=1&S=acae22b0e1ba6ea6fbb5d26018dbf152558e98cb'" +
+    LogTee + " ; grep -F -e '< HTTP' -e Authorization {0}/url_sig_long.log > {0}/url_sig_short.log ".format(ts.RunDirectory)
+)

-- 
To stop receiving notification emails like this one, please contact
['"commits@trafficserver.apache.org" <co...@trafficserver.apache.org>'].