You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by bc...@apache.org on 2019/07/16 17:11:38 UTC

[trafficserver] branch master updated: Add optional normalization of scheme and host to lower case letters in effective URLs.

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

bcall 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 9b567e4  Add optional normalization of scheme and host to lower case letters in effective URLs.
9b567e4 is described below

commit 9b567e4ee0b4cd5e32158b414e0b5383b998407e
Author: Walter Karas <wk...@verizonmedia.com>
AuthorDate: Mon Apr 1 13:08:29 2019 -0500

    Add optional normalization of scheme and host to lower case letters in effective URLs.
    
    - Adds new TS API function TSHttpHdrEffectiveUrlBuffGet() (which also puts the URL in a user-allocated buffer).
    - Adds 'normalized' parameter (defaults to false) to HTTPHdr::url_string_get().
    - Adds 'normalized' parameter (defaults to false) to url_print(), url_string_get(), url_string_get_ref().
    - Adds 'normalized' parameter (defaults to false) to URL::string_get(), URL::string_get_ref(), URL::print().
    
    This will make it possible to check URLs output by these functions for equivalence by doing a simple string compare.
---
 .../api/functions/TSUrlStringGet.en.rst            | 28 +++++++++++++-
 include/ts/ts.h                                    | 13 ++++++-
 proxy/hdrs/HTTP.cc                                 | 20 ++++++++--
 proxy/hdrs/HTTP.h                                  | 19 +++++++---
 proxy/hdrs/MIME.cc                                 | 35 ++++++++++++++---
 proxy/hdrs/MIME.h                                  |  2 +
 proxy/hdrs/URL.cc                                  | 16 ++++----
 proxy/hdrs/URL.h                                   | 24 ++++++------
 src/traffic_server/InkAPI.cc                       | 37 ++++++++++++++++++
 tests/gold_tests/pluginTest/tsapi/log.gold         |  8 +++-
 tests/gold_tests/pluginTest/tsapi/tsapi.test.py    |  6 +--
 tests/tools/plugins/test_tsapi.cc                  | 44 ++++++++++++++++++++++
 12 files changed, 210 insertions(+), 42 deletions(-)

diff --git a/doc/developer-guide/api/functions/TSUrlStringGet.en.rst b/doc/developer-guide/api/functions/TSUrlStringGet.en.rst
index 46d7e23..506a5e8 100644
--- a/doc/developer-guide/api/functions/TSUrlStringGet.en.rst
+++ b/doc/developer-guide/api/functions/TSUrlStringGet.en.rst
@@ -31,6 +31,7 @@ Synopsis
 
 .. function:: char * TSUrlStringGet(TSMBuffer bufp, TSMLoc offset, int * length)
 .. function:: char * TSHttpTxnEffectiveUrlStringGet(TSHttpTxn txn, int * length)
+.. function:: TSReturnCode TSHttpHdrEffectiveUrlBufGet(TSMBuffer hdr_buf, TSMLoc hdr_loc, char * buf, int64_t size, int64_t* length)
 .. function:: int TSUrlLengthGet(TSMBuffer bufp, TSMLoc offset)
 .. function:: void TSUrlPrint(TSMBuffer bufp, TSMLoc offset, TSIOBuffer iobufp)
 
@@ -50,7 +51,7 @@ call to :func:`TSmalloc` and must be freed by a call to :func:`TSfree`. If lengt
 then no attempt is made to de-reference it. The returned string is not guaranteed to have a null
 terminator - :arg:`length` must be used to correctly display the string.
 
-:func:`TSHttpTxnEffectiveUrlStringGet` is similar to :func:`TSUrlStringGet`. The two differences are
+:func:`TSHttpTxnEffectiveUrlStringGet` is similar to :func:`TSUrlStringGet`. The two differences are:
 
 *  The source is transaction :arg:`txn` and the URL is retrieved from the client request in that
    transaction.
@@ -61,6 +62,31 @@ terminator - :arg:`length` must be used to correctly display the string.
 This function is useful to guarantee a URL that is as complete as possible given the specific
 request.
 
+:func:`TSHttpHdrEffectiveUrlBufGet` returns the effective URL for any HTTP request (not just the client request).
+If the request has a Host header field (and the URL does not contain a host specifier), the host specifier the header
+provides is inserted into the URL.  The host and
+scheme in the returned URL will be normalized to lower case letters (to make URL comparisons simple and fast).
+This prints the effective URL for the header specified by :arg:`hdr_buf` and
+:arg:`hdr_loc` to the buffer starting at :arg:`buf`. If the effective URL is longer than :arg:`size`, nothing is
+written to :arg:`buf`.  Note that this is not considered an error case, the function will still return `TS_SUCCESS`.
+It is the responsibility of the caller to check this result to determine if output was generated.
+The full length of the URL is always returned in :arg:`*length` when the function returns `TS_SUCCESS`.
+
+The typical usage would be
+::
+
+  TSMBuffer hdr_buf;
+  TSMLoc hdr_loc;
+  TSHttpTxnServerReqGet(txn, &hdr_buf, &hdr_loc);
+  int64_t length;
+  char store[2048];
+  char *buf = store;
+  TSHttpHdrEffectiveUrlBufGet(hdr_buf, hdr_loc, buf, sizeof(store), &length);
+  if (length > sizeof(store)) {
+    buf = static_cast<char *>(malloc(length));
+    TSHttpHdrEffectiveUrlBufGet(hdr_buf, hdr_loc, buf, length, &length);
+  }
+
 :func:`TSUrlLengthGet` calculates the length of the URL located at :arg:`offset` within the marshal
 buffer bufp as if it were returned as a string. This length will be the same as the length returned
 by :func:`TSUrlStringGet`.
diff --git a/include/ts/ts.h b/include/ts/ts.h
index 99b2dc2..9ad65e7 100644
--- a/include/ts/ts.h
+++ b/include/ts/ts.h
@@ -387,7 +387,8 @@ tsapi int TSUrlLengthGet(TSMBuffer bufp, TSMLoc offset);
     The length parameter must present, providing storage for the URL
     string length value.
     Note: To get the effective URL from a request, use the alternative
-          TSHttpTxnEffectiveUrlStringGet API.
+          TSHttpTxnEffectiveUrlStringGet or
+          TSHttpHdrEffectiveUrlBufGet APIs.
 
     @param bufp marshal buffer containing the URL you want to get.
     @param offset location of the URL within bufp.
@@ -1297,6 +1298,16 @@ tsapi TSReturnCode TSHttpTxnPristineUrlGet(TSHttpTxn txnp, TSMBuffer *bufp, TSML
 tsapi char *TSHttpTxnEffectiveUrlStringGet(TSHttpTxn txnp, int *length /**< String length return, may be @c NULL. */
 );
 
+/** Get the effective URL for in the header (if any), with the scheme and host normalized to lower case letter.
+    The effective URL is the URL taking in to account both the explicit
+    URL in the request and the HOST field.
+
+    A possibly non-null terminated string is returned.
+
+    @return TS_SUCCESS if successful, TS_ERROR if no URL in header or other error.
+*/
+tsapi TSReturnCode TSHttpHdrEffectiveUrlBufGet(TSMBuffer hdr_buf, TSMLoc hdr_loc, char *buf, int64_t size, int64_t *length);
+
 tsapi void TSHttpTxnRespCacheableSet(TSHttpTxn txnp, int flag);
 tsapi void TSHttpTxnReqCacheableSet(TSHttpTxn txnp, int flag);
 
diff --git a/proxy/hdrs/HTTP.cc b/proxy/hdrs/HTTP.cc
index 0bbde74..6f86c80 100644
--- a/proxy/hdrs/HTTP.cc
+++ b/proxy/hdrs/HTTP.cc
@@ -1747,7 +1747,7 @@ class UrlPrintHack
 };
 
 char *
-HTTPHdr::url_string_get(Arena *arena, int *length)
+HTTPHdr::url_string_get(Arena *arena, int *length, bool normalized)
 {
   char *zret = nullptr;
   UrlPrintHack hack(this);
@@ -1757,13 +1757,14 @@ HTTPHdr::url_string_get(Arena *arena, int *length)
     // even uglier but it's less so than duplicating this entire method to
     // change that one thing.
 
-    zret = (arena == USE_HDR_HEAP_MAGIC) ? m_url_cached.string_get_ref(length) : m_url_cached.string_get(arena, length);
+    zret = (arena == USE_HDR_HEAP_MAGIC) ? m_url_cached.string_get_ref(length, normalized) :
+                                           m_url_cached.string_get(arena, length, normalized);
   }
   return zret;
 }
 
 int
-HTTPHdr::url_print(char *buff, int length, int *offset, int *skip)
+HTTPHdr::url_print(char *buff, int length, int *offset, int *skip, bool normalized)
 {
   ink_release_assert(offset);
   ink_release_assert(skip);
@@ -1771,7 +1772,18 @@ HTTPHdr::url_print(char *buff, int length, int *offset, int *skip)
   int zret = 0;
   UrlPrintHack hack(this);
   if (hack.is_valid()) {
-    zret = m_url_cached.print(buff, length, offset, skip);
+    zret = m_url_cached.print(buff, length, offset, skip, normalized);
+  }
+  return zret;
+}
+
+int
+HTTPHdr::url_printed_length()
+{
+  int zret = -1;
+  UrlPrintHack hack(this);
+  if (hack.is_valid()) {
+    zret = m_url_cached.length_get();
   }
   return zret;
 }
diff --git a/proxy/hdrs/HTTP.h b/proxy/hdrs/HTTP.h
index 41c91c9..9b8d3e5 100644
--- a/proxy/hdrs/HTTP.h
+++ b/proxy/hdrs/HTTP.h
@@ -542,8 +542,9 @@ public:
       and invoking @c URL::string_get if the host is in a header
       field and not explicitly in the URL.
    */
-  char *url_string_get(Arena *arena = nullptr, ///< Arena to use, or @c malloc if NULL.
-                       int *length  = nullptr  ///< Store string length here.
+  char *url_string_get(Arena *arena    = nullptr, ///< Arena to use, or @c malloc if NULL.
+                       int *length     = nullptr, ///< Store string length here.
+                       bool normalized = false    ///< Scheme and host normalized to lower case letters.
   );
   /** Get a string with the effective URL in it.
       This is automatically allocated if needed in the request heap.
@@ -557,12 +558,18 @@ public:
       Output is not null terminated.
       @return 0 on failure, non-zero on success.
    */
-  int url_print(char *buff,  ///< Output buffer
-                int length,  ///< Length of @a buffer
-                int *offset, ///< [in,out] ???
-                int *skip    ///< [in,out] ???
+  int url_print(char *buff,             ///< Output buffer
+                int length,             ///< Length of @a buffer
+                int *offset,            ///< [in,out] ???
+                int *skip,              ///< [in,out] ???
+                bool normalized = false ///< host/scheme normalized to lower case
   );
 
+  /** Return the length of the URL that url_print() will create.
+      @return -1 on failure, non-negative on success.
+   */
+  int url_printed_length();
+
   /** Get the URL path.
       This is a reference, not allocated.
       @return A pointer to the path or @c NULL if there is no valid URL.
diff --git a/proxy/hdrs/MIME.cc b/proxy/hdrs/MIME.cc
index 4ce7740..59aa34d 100644
--- a/proxy/hdrs/MIME.cc
+++ b/proxy/hdrs/MIME.cc
@@ -28,6 +28,8 @@
 #include <cassert>
 #include <cstdio>
 #include <cstring>
+#include <cctype>
+#include <algorithm>
 #include "MIME.h"
 #include "HdrHeap.h"
 #include "HdrToken.h"
@@ -2705,11 +2707,12 @@ mime_hdr_print(HdrHeap * /* heap ATS_UNUSED */, MIMEHdrImpl *mh, char *buf_start
   return 1;
 }
 
+namespace
+{
 int
-mime_mem_print(const char *src_d, int src_l, char *buf_start, int buf_length, int *buf_index_inout, int *buf_chars_to_skip_inout)
+mime_mem_print_(const char *src_d, int src_l, char *buf_start, int buf_length, int *buf_index_inout, int *buf_chars_to_skip_inout,
+                int (*char_transform)(int char_in))
 {
-  int copy_l;
-
   if (buf_start == nullptr) { // this case should only be used by test_header
     ink_release_assert(buf_index_inout == nullptr);
     ink_release_assert(buf_chars_to_skip_inout == nullptr);
@@ -2719,7 +2722,6 @@ mime_mem_print(const char *src_d, int src_l, char *buf_start, int buf_length, in
     return 1;
   }
 
-  ink_assert(buf_start != nullptr);
   ink_assert(src_d != nullptr);
 
   if (*buf_chars_to_skip_inout > 0) {
@@ -2733,15 +2735,36 @@ mime_mem_print(const char *src_d, int src_l, char *buf_start, int buf_length, in
     }
   }
 
-  copy_l = std::min(buf_length - *buf_index_inout, src_l);
+  int copy_l = std::min(buf_length - *buf_index_inout, src_l);
   if (copy_l > 0) {
-    memcpy(buf_start + *buf_index_inout, src_d, copy_l);
+    buf_start += *buf_index_inout;
+    std::transform(src_d, src_d + copy_l, buf_start, char_transform);
     *buf_index_inout += copy_l;
   }
   return (src_l == copy_l);
 }
 
 int
+to_same_char(int ch)
+{
+  return ch;
+}
+
+} // end anonymous namespace
+
+int
+mime_mem_print(const char *src_d, int src_l, char *buf_start, int buf_length, int *buf_index_inout, int *buf_chars_to_skip_inout)
+{
+  return mime_mem_print_(src_d, src_l, buf_start, buf_length, buf_index_inout, buf_chars_to_skip_inout, to_same_char);
+}
+
+int
+mime_mem_print_lc(const char *src_d, int src_l, char *buf_start, int buf_length, int *buf_index_inout, int *buf_chars_to_skip_inout)
+{
+  return mime_mem_print_(src_d, src_l, buf_start, buf_length, buf_index_inout, buf_chars_to_skip_inout, std::tolower);
+}
+
+int
 mime_field_print(MIMEField *field, char *buf_start, int buf_length, int *buf_index_inout, int *buf_chars_to_skip_inout)
 {
 #define TRY(x) \
diff --git a/proxy/hdrs/MIME.h b/proxy/hdrs/MIME.h
index 6ff23f7..37b1e27 100644
--- a/proxy/hdrs/MIME.h
+++ b/proxy/hdrs/MIME.h
@@ -766,6 +766,8 @@ int mime_hdr_print(HdrHeap *heap, MIMEHdrImpl *mh, char *buf_start, int buf_leng
                    int *buf_chars_to_skip_inout);
 int mime_mem_print(const char *src_d, int src_l, char *buf_start, int buf_length, int *buf_index_inout,
                    int *buf_chars_to_skip_inout);
+int mime_mem_print_lc(const char *src_d, int src_l, char *buf_start, int buf_length, int *buf_index_inout,
+                      int *buf_chars_to_skip_inout);
 int mime_field_print(MIMEField *field, char *buf_start, int buf_length, int *buf_index_inout, int *buf_chars_to_skip_inout);
 
 const char *mime_str_u16_set(HdrHeap *heap, const char *s_str, int s_len, const char **d_str, uint16_t *d_len, bool must_copy);
diff --git a/proxy/hdrs/URL.cc b/proxy/hdrs/URL.cc
index 6137add..58f76d9 100644
--- a/proxy/hdrs/URL.cc
+++ b/proxy/hdrs/URL.cc
@@ -611,7 +611,7 @@ url_clear_string_ref(URLImpl *url)
 }
 
 char *
-url_string_get_ref(HdrHeap *heap, URLImpl *url, int *length)
+url_string_get_ref(HdrHeap *heap, URLImpl *url, int *length, bool normalized)
 {
   if (!url) {
     return nullptr;
@@ -630,7 +630,7 @@ url_string_get_ref(HdrHeap *heap, URLImpl *url, int *length)
 
     /* stuff alloc'd here gets gc'd on HdrHeap::destroy() */
     buf = heap->allocate_str(len + 1);
-    url_print(url, buf, len, &index, &offset);
+    url_print(url, buf, len, &index, &offset, normalized);
     buf[len] = '\0';
 
     if (length) {
@@ -644,7 +644,7 @@ url_string_get_ref(HdrHeap *heap, URLImpl *url, int *length)
 }
 
 char *
-url_string_get(URLImpl *url, Arena *arena, int *length, HdrHeap *heap)
+url_string_get(URLImpl *url, Arena *arena, int *length, HdrHeap *heap, bool normalized)
 {
   int len = url_length_get(url);
   char *buf;
@@ -654,7 +654,7 @@ url_string_get(URLImpl *url, Arena *arena, int *length, HdrHeap *heap)
 
   buf = arena ? arena->str_alloc(len) : (char *)ats_malloc(len + 1);
 
-  url_print(url, buf, len, &index, &offset);
+  url_print(url, buf, len, &index, &offset, normalized);
   buf[len] = '\0';
 
   /* see string_get_ref() */
@@ -1516,14 +1516,15 @@ url_parse_http_no_path_component_breakdown(HdrHeap *heap, URLImpl *url, const ch
  ***********************************************************************/
 
 int
-url_print(URLImpl *url, char *buf_start, int buf_length, int *buf_index_inout, int *buf_chars_to_skip_inout)
+url_print(URLImpl *url, char *buf_start, int buf_length, int *buf_index_inout, int *buf_chars_to_skip_inout, bool normalize)
 {
 #define TRY(x) \
   if (!x)      \
   return 0
 
   if (url->m_ptr_scheme) {
-    TRY(mime_mem_print(url->m_ptr_scheme, url->m_len_scheme, buf_start, buf_length, buf_index_inout, buf_chars_to_skip_inout));
+    TRY((normalize ? mime_mem_print_lc : mime_mem_print)(url->m_ptr_scheme, url->m_len_scheme, buf_start, buf_length,
+                                                         buf_index_inout, buf_chars_to_skip_inout));
     // [amc] Why is "file:" special cased to be wrong?
     //    if ((url->m_scheme_wks_idx >= 0) && (hdrtoken_index_to_wks(url->m_scheme_wks_idx) == URL_SCHEME_FILE)) {
     //      TRY(mime_mem_print(":", 1, buf_start, buf_length, buf_index_inout, buf_chars_to_skip_inout));
@@ -1550,7 +1551,8 @@ url_print(URLImpl *url, char *buf_start, int buf_length, int *buf_index_inout, i
     if (bracket_p) {
       TRY(mime_mem_print("[", 1, buf_start, buf_length, buf_index_inout, buf_chars_to_skip_inout));
     }
-    TRY(mime_mem_print(url->m_ptr_host, url->m_len_host, buf_start, buf_length, buf_index_inout, buf_chars_to_skip_inout));
+    TRY((normalize ? mime_mem_print_lc : mime_mem_print)(url->m_ptr_host, url->m_len_host, buf_start, buf_length, buf_index_inout,
+                                                         buf_chars_to_skip_inout));
     if (bracket_p) {
       TRY(mime_mem_print("]", 1, buf_start, buf_length, buf_index_inout, buf_chars_to_skip_inout));
     }
diff --git a/proxy/hdrs/URL.h b/proxy/hdrs/URL.h
index 0cecc70..025d2db 100644
--- a/proxy/hdrs/URL.h
+++ b/proxy/hdrs/URL.h
@@ -164,13 +164,13 @@ URLImpl *url_copy(URLImpl *s_url, HdrHeap *s_heap, HdrHeap *d_heap, bool inherit
 void url_copy_onto(URLImpl *s_url, HdrHeap *s_heap, URLImpl *d_url, HdrHeap *d_heap, bool inherit_strs = true);
 void url_copy_onto_as_server_url(URLImpl *s_url, HdrHeap *s_heap, URLImpl *d_url, HdrHeap *d_heap, bool inherit_strs = true);
 
-int url_print(URLImpl *u, char *buf, int bufsize, int *bufindex, int *dumpoffset);
+int url_print(URLImpl *u, char *buf, int bufsize, int *bufindex, int *dumpoffset, bool normalized = false);
 void url_describe(HdrHeapObjImpl *raw, bool recurse);
 
 int url_length_get(URLImpl *url);
-char *url_string_get(URLImpl *url, Arena *arena, int *length, HdrHeap *heap);
+char *url_string_get(URLImpl *url, Arena *arena, int *length, HdrHeap *heap, bool normalized = false);
 void url_clear_string_ref(URLImpl *url);
-char *url_string_get_ref(HdrHeap *heap, URLImpl *url, int *length);
+char *url_string_get_ref(HdrHeap *heap, URLImpl *url, int *length, bool normalized = false);
 void url_called_set(URLImpl *url);
 char *url_string_get_buf(URLImpl *url, char *dstbuf, int dstbuf_size, int *length);
 
@@ -238,12 +238,12 @@ public:
   // Note that URL::destroy() is inherited from HdrHeapSDKHandle.
   void nuke_proxy_stuff();
 
-  int print(char *buf, int bufsize, int *bufindex, int *dumpoffset);
+  int print(char *buf, int bufsize, int *bufindex, int *dumpoffset, bool normalized = false);
 
   int length_get();
   void clear_string_ref();
-  char *string_get(Arena *arena, int *length = nullptr);
-  char *string_get_ref(int *length = nullptr);
+  char *string_get(Arena *arena, int *length = nullptr, bool normalized = false);
+  char *string_get_ref(int *length = nullptr, bool normalized = false);
   char *string_get_buf(char *dstbuf, int dsbuf_size, int *length = nullptr);
   void hash_get(CryptoHash *hash, cache_generation_t generation = -1) const;
   void host_hash_get(CryptoHash *hash);
@@ -372,10 +372,10 @@ URL::nuke_proxy_stuff()
   -------------------------------------------------------------------------*/
 
 inline int
-URL::print(char *buf, int bufsize, int *bufindex, int *dumpoffset)
+URL::print(char *buf, int bufsize, int *bufindex, int *dumpoffset, bool normalized)
 {
   ink_assert(valid());
-  return url_print(m_url_impl, buf, bufsize, bufindex, dumpoffset);
+  return url_print(m_url_impl, buf, bufsize, bufindex, dumpoffset, normalized);
 }
 
 /*-------------------------------------------------------------------------
@@ -392,17 +392,17 @@ URL::length_get()
   -------------------------------------------------------------------------*/
 
 inline char *
-URL::string_get(Arena *arena_or_null_for_malloc, int *length)
+URL::string_get(Arena *arena_or_null_for_malloc, int *length, bool normalized)
 {
   ink_assert(valid());
-  return url_string_get(m_url_impl, arena_or_null_for_malloc, length, m_heap);
+  return url_string_get(m_url_impl, arena_or_null_for_malloc, length, m_heap, normalized);
 }
 
 inline char *
-URL::string_get_ref(int *length)
+URL::string_get_ref(int *length, bool normalized)
 {
   ink_assert(valid());
-  return url_string_get_ref(m_heap, m_url_impl, length);
+  return url_string_get_ref(m_heap, m_url_impl, length, normalized);
 }
 
 inline void
diff --git a/src/traffic_server/InkAPI.cc b/src/traffic_server/InkAPI.cc
index 8e1e1ba..c103e0f 100644
--- a/src/traffic_server/InkAPI.cc
+++ b/src/traffic_server/InkAPI.cc
@@ -4934,6 +4934,43 @@ TSHttpTxnEffectiveUrlStringGet(TSHttpTxn txnp, int *length)
 }
 
 TSReturnCode
+TSHttpHdrEffectiveUrlBufGet(TSMBuffer hdr_buf, TSMLoc hdr_loc, char *buf, int64_t size, int64_t *length)
+{
+  sdk_assert(sdk_sanity_check_mbuffer(hdr_buf) == TS_SUCCESS);
+  sdk_assert(sdk_sanity_check_http_hdr_handle(hdr_loc) == TS_SUCCESS);
+  if (size) {
+    sdk_assert(sdk_sanity_check_null_ptr(buf) == TS_SUCCESS);
+  }
+  sdk_assert(sdk_sanity_check_null_ptr(length) == TS_SUCCESS);
+
+  auto buf_handle = reinterpret_cast<HTTPHdr *>(hdr_buf);
+  auto hdr_handle = reinterpret_cast<HTTPHdrImpl *>(hdr_loc);
+
+  if (hdr_handle->m_polarity != HTTP_TYPE_REQUEST) {
+    Debug("plugin", "Trying to get a URL from response header %p", hdr_loc);
+    return TS_ERROR;
+  }
+
+  int url_length = buf_handle->url_printed_length();
+
+  sdk_assert(url_length >= 0);
+
+  *length = url_length;
+
+  // If the user-provided buffer is too small to hold the URL string, do not put anything in it.  This is not considered
+  // an error case.
+  //
+  if (url_length <= size) {
+    int index  = 0;
+    int offset = 0;
+
+    buf_handle->url_print(buf, size, &index, &offset, true);
+  }
+
+  return TS_SUCCESS;
+}
+
+TSReturnCode
 TSHttpTxnClientRespGet(TSHttpTxn txnp, TSMBuffer *bufp, TSMLoc *obj)
 {
   sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);
diff --git a/tests/gold_tests/pluginTest/tsapi/log.gold b/tests/gold_tests/pluginTest/tsapi/log.gold
index 3960bfe..da86247 100644
--- a/tests/gold_tests/pluginTest/tsapi/log.gold
+++ b/tests/gold_tests/pluginTest/tsapi/log.gold
@@ -1,10 +1,14 @@
 Global: event=TS_EVENT_HTTP_TXN_START
 Global: event=TS_EVENT_HTTP_READ_REQUEST_HDR
-TSHttpTxnEffectiveUrlStringGet():  http://myhost.test:SERVER_PORT/
+TSHttpTxnEffectiveUrlStringGet():  http://mYhOsT.teSt:SERVER_PORT/
+TSHttpHdrEffectiveUrlBuffGet():  http://myhost.test:SERVER_PORT/
 Transaction: event=TS_EVENT_HTTP_READ_REQUEST_HDR
-TSHttpTxnEffectiveUrlStringGet():  http://myhost.test:SERVER_PORT/
+TSHttpTxnEffectiveUrlStringGet():  http://mYhOsT.teSt:SERVER_PORT/
+TSHttpHdrEffectiveUrlBuffGet():  http://myhost.test:SERVER_PORT/
 Global: event=TS_EVENT_HTTP_TXN_START
 Global: event=TS_EVENT_HTTP_READ_REQUEST_HDR
 TSHttpTxnEffectiveUrlStringGet():  https://myhost.test:SERVER_PORT/
+TSHttpHdrEffectiveUrlBuffGet():  https://myhost.test:SERVER_PORT/
 Transaction: event=TS_EVENT_HTTP_READ_REQUEST_HDR
 TSHttpTxnEffectiveUrlStringGet():  https://myhost.test:SERVER_PORT/
+TSHttpHdrEffectiveUrlBuffGet():  https://myhost.test:SERVER_PORT/
diff --git a/tests/gold_tests/pluginTest/tsapi/tsapi.test.py b/tests/gold_tests/pluginTest/tsapi/tsapi.test.py
index 6591674..65d0711 100644
--- a/tests/gold_tests/pluginTest/tsapi/tsapi.test.py
+++ b/tests/gold_tests/pluginTest/tsapi/tsapi.test.py
@@ -65,17 +65,17 @@ tr = Test.AddTestRun()
 # Probe server port to check if ready.
 tr.Processes.Default.StartBefore(server, ready=When.PortOpen(server.Variables.Port))
 # Probe TS cleartext port to check if ready.
-tr.Processes.Default.StartBefore(Test.Processes.ts)
+tr.Processes.Default.StartBefore(Test.Processes.ts, ready=When.PortOpen(ts.Variables.port))
 #
 tr.Processes.Default.Command = (
-    'curl --verbose --ipv4 --header "Host: myhost.test:{0}" http://localhost:{1}/'.format(server.Variables.Port, ts.Variables.port)
+    'curl --verbose --ipv4 --header "Host: mYhOsT.teSt:{0}" hTtP://loCalhOst:{1}/'.format(server.Variables.Port, ts.Variables.port)
 )
 tr.Processes.Default.ReturnCode = 0
 
 tr = Test.AddTestRun()
 tr.Processes.Default.Command = (
     'curl --verbose --ipv4 --http2 --insecure --header ' +
-    '"Host: myhost.test:{0}" https://localhost:{1}/'.format(server.Variables.Port, ts.Variables.ssl_port)
+    '"Host: myhost.test:{0}" HttPs://LocalHost:{1}/'.format(server.Variables.Port, ts.Variables.ssl_port)
 )
 tr.Processes.Default.ReturnCode = 0
 
diff --git a/tests/tools/plugins/test_tsapi.cc b/tests/tools/plugins/test_tsapi.cc
index 986a904..747db32 100644
--- a/tests/tools/plugins/test_tsapi.cc
+++ b/tests/tools/plugins/test_tsapi.cc
@@ -23,8 +23,10 @@ Regression testing code for TS API.  Not comprehensive, hopefully will be built
 #include <fstream>
 #include <cstdlib>
 #include <string_view>
+#include <string>
 
 #include <ts/ts.h>
+#include <tscpp/util/PostScript.h>
 
 // TSReleaseAssert() doesn't seem to produce any logging output for a debug build, so do both kinds of assert.
 //
@@ -63,6 +65,48 @@ testsForReadReqHdrHook(TSHttpTxn txn)
 
     TSfree(urlStr);
   }
+
+  logFile << "TSHttpHdrEffectiveUrlBufGet():  ";
+  {
+    TSMBuffer hbuf;
+    TSMLoc hloc;
+
+    if (TSHttpTxnClientReqGet(txn, &hbuf, &hloc) != TS_SUCCESS) {
+      logFile << "failed to get client request" << std::endl;
+
+    } else {
+      ts::PostScript ps([=]() -> void { TSHandleMLocRelease(hbuf, TS_NULL_MLOC, hloc); });
+
+      int64_t url_length;
+
+      if (TSHttpHdrEffectiveUrlBufGet(hbuf, hloc, nullptr, 0, &url_length) != TS_SUCCESS) {
+        logFile << "sizing call failed " << std::endl;
+
+      } else if (0 == url_length) {
+        logFile << "zero URL length returned" << std::endl;
+
+      } else {
+        std::string s(url_length, '?');
+
+        s += "yada";
+
+        int64_t url_length2;
+
+        if (TSHttpHdrEffectiveUrlBufGet(hbuf, hloc, s.data(), url_length + 4, &url_length2) != TS_SUCCESS) {
+          logFile << "data-obtaining call failed" << std::endl;
+
+        } else if (url_length2 != url_length) {
+          logFile << "second size does not match first" << std::endl;
+
+        } else if (s.substr(url_length, 4) != "yada") {
+          logFile << "overwrite" << std::endl;
+
+        } else {
+          logFile << s.substr(0, url_length) << std::endl;
+        }
+      }
+    }
+  }
 }
 
 int