juanthropic commented on code in PR #13424:
URL: https://github.com/apache/trafficserver/pull/13424#discussion_r4150267461


##########
plugins/header_rewrite/operators.cc:
##########
@@ -532,6 +533,67 @@ OperatorRMDestination::exec(const Resources &res) const
   return true;
 }
 
+// OperatorSortDestination
+void
+OperatorSortDestination::initialize(Parser &p)
+{
+  Operator::initialize(p);
+
+  _url_qual = parse_url_qualifier(p.get_arg());
+
+  require_resources(RSRC_CLIENT_REQUEST_HEADERS);
+  require_resources(RSRC_SERVER_REQUEST_HEADERS);
+}
+
+bool
+OperatorSortDestination::exec(const Resources &res) const
+{
+  if (res._rri || (res.bufp && res.hdr_loc)) {
+    TSMBuffer bufp;
+    TSMLoc    url_m_loc;
+
+    // Determine which TSMBuffer and TSMLoc to use
+    if (res._rri) {
+      bufp      = res._rri->requestBufp;
+      url_m_loc = res._rri->requestUrl;
+    } else {
+      bufp = res.bufp;
+      if (TSHttpHdrUrlGet(res.bufp, res.hdr_loc, &url_m_loc) != TS_SUCCESS) {
+        Dbg(pi_dbg_ctl, "TSHttpHdrUrlGet was unable to return the url m_loc");
+        return true;
+      }
+    }

Review Comment:
   `TSHttpHdrUrlGet()` returns a pointer to the header's existing `URLImpl` and 
allocates nothing (`src/api/InkAPI.cc`, `TSHttpHdrUrlGet`). 
`TSHandleMLocRelease()` returns `TS_SUCCESS` with no work for 
`HdrHeapObjType::URL`; it only frees `FIELD_SDK_HANDLE`. So there is no leak. 
`set-destination` and `rm-destination` use the same pattern. Leaving this as is 
to stay consistent with them.



##########
plugins/header_rewrite/url_query.cc:
##########
@@ -0,0 +1,91 @@
+/*
+  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.
+*/
+#include "url_query.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "swoc/TextView.h"
+
+namespace
+{
+
+std::vector<std::string_view>
+split(std::string_view text, char delimiter)
+{
+  std::vector<std::string_view> tokens;
+  swoc::TextView                view(text);
+
+  while (view) {
+    tokens.push_back(view.take_prefix_at(delimiter));
+  }
+
+  return tokens;
+}
+
+std::string_view
+param_name(std::string_view param)
+{
+  return param.substr(0, param.find('='));
+}
+
+} // namespace
+
+std::string
+sort_query(std::string_view query)
+{
+  if (query.empty()) {
+    return {};
+  }
+
+  std::vector<std::string_view> params = split(query, '&');

Review Comment:
   Fixed in 78b245fd9



##########
plugins/header_rewrite/url_query.cc:
##########
@@ -0,0 +1,91 @@
+/*
+  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.
+*/
+#include "url_query.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "swoc/TextView.h"
+
+namespace
+{
+
+std::vector<std::string_view>
+split(std::string_view text, char delimiter)
+{
+  std::vector<std::string_view> tokens;
+  swoc::TextView                view(text);
+
+  while (view) {
+    tokens.push_back(view.take_prefix_at(delimiter));
+  }
+
+  return tokens;
+}
+
+std::string_view
+param_name(std::string_view param)
+{
+  return param.substr(0, param.find('='));
+}
+
+} // namespace
+
+std::string
+sort_query(std::string_view query)
+{
+  if (query.empty()) {
+    return {};
+  }
+
+  std::vector<std::string_view> params = split(query, '&');
+
+  std::stable_sort(params.begin(), params.end(),
+                   [](std::string_view a, std::string_view b) { return 
param_name(a) < param_name(b); });
+
+  std::string result;
+  result.reserve(query.size()); // same length as query, capped at 64KB by 
request_line_max_size
+
+  for (const auto &param : params) {
+    if (!result.empty()) {
+      result += '&';
+    }
+    result.append(param);
+  }

Review Comment:
   Fixed in 78b245fd9



##########
plugins/header_rewrite/operators.cc:
##########
@@ -532,6 +533,67 @@ OperatorRMDestination::exec(const Resources &res) const
   return true;
 }
 
+// OperatorSortDestination
+void
+OperatorSortDestination::initialize(Parser &p)
+{
+  Operator::initialize(p);
+
+  _url_qual = parse_url_qualifier(p.get_arg());
+
+  require_resources(RSRC_CLIENT_REQUEST_HEADERS);
+  require_resources(RSRC_SERVER_REQUEST_HEADERS);
+}
+
+bool
+OperatorSortDestination::exec(const Resources &res) const
+{
+  if (res._rri || (res.bufp && res.hdr_loc)) {
+    TSMBuffer bufp;
+    TSMLoc    url_m_loc;
+
+    // Determine which TSMBuffer and TSMLoc to use
+    if (res._rri) {
+      bufp      = res._rri->requestBufp;
+      url_m_loc = res._rri->requestUrl;
+    } else {
+      bufp = res.bufp;
+      if (TSHttpHdrUrlGet(res.bufp, res.hdr_loc, &url_m_loc) != TS_SUCCESS) {
+        Dbg(pi_dbg_ctl, "TSHttpHdrUrlGet was unable to return the url m_loc");
+        return true;
+      }
+    }
+
+    switch (_url_qual) {
+    case URL_QUAL_QUERY: {
+      int              q_len = 0;
+      const char      *q_ptr = TSUrlHttpQueryGet(bufp, url_m_loc, &q_len);
+      std::string_view query = q_len > 0 ? std::string_view(q_ptr, 
static_cast<size_t>(q_len)) : std::string_view();
+
+      if (is_query_sorted(query)) {
+        Dbg(pi_dbg_ctl, "OperatorSortDestination::exec() QUERY already sorted, 
leaving it unchanged");
+        break;
+      }
+
+      std::string sorted = sort_query(query);
+
+      const_cast<Resources &>(res).changed_url = true;
+      TSUrlHttpQuerySet(bufp, url_m_loc, sorted.c_str(), sorted.size());

Review Comment:
   Return code handled in 6baa69bc7. Left the length uncast to match the other 
`TSUrl*Set()` calls in header_rewrite; the query is capped by 
`request_line_max_size`, far below `INT_MAX`.



##########
plugins/header_rewrite/unit_tests/test_url_query.cc:
##########
@@ -0,0 +1,109 @@
+/*
+  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.
+*/
+#include <catch2/catch_test_macros.hpp>
+
+#include "url_query.h"
+
+TEST_CASE("sort_query orders params by name", "[header_rewrite][url_query]")
+{
+  SECTION("out-of-order params are sorted")
+  {
+    CHECK(sort_query("b=2&a=1") == "a=1&b=2");
+  }
+
+  SECTION("valueless params sort by their own name")
+  {
+    CHECK(sort_query("b&a=1") == "a=1&b");
+  }
+
+  SECTION("params with duplicate names keep their relative order")
+  {
+    CHECK(sort_query("x=1&a=2&x=3") == "a=2&x=1&x=3");
+  }
+
+  SECTION("empty query stays empty")
+  {
+    CHECK(sort_query("") == "");
+  }
+
+  SECTION("single param is unaffected")
+  {
+    CHECK(sort_query("a=1") == "a=1");
+  }
+
+  SECTION("trailing '&' produces a clean drop, not an empty trailing token")
+  {
+    CHECK(sort_query("a=1&") == "a=1");
+  }
+
+  SECTION("param value containing '=' is preserved and sorts by its name only")
+  {
+    CHECK(sort_query("a=1=2") == "a=1=2");
+  }
+
+  SECTION("leading '&' produces a clean drop, not an empty leading token")
+  {
+    CHECK(sort_query("&a=1") == "a=1");
+  }
+
+  SECTION("consecutive '&'s produce a clean drop, not empty middle tokens")
+  {
+    CHECK(sort_query("b=2&&a=1&&c=3") == "a=1&b=2&c=3");
+  }
+}
+
+TEST_CASE("is_query_sorted detects queries sort_query would leave unchanged", 
"[header_rewrite][url_query]")
+{
+  SECTION("params in name order are sorted")
+  {
+    CHECK(is_query_sorted("a=1&b=2&c=3"));
+  }
+
+  SECTION("out-of-order params are not sorted")
+  {
+    CHECK_FALSE(is_query_sorted("b=2&a=1"));
+  }
+
+  SECTION("duplicate names in any value order are sorted, since the sort is 
stable")
+  {
+    CHECK(is_query_sorted("a=2&x=3&x=1"));
+  }
+
+  SECTION("empty query and single param are sorted")
+  {
+    CHECK(is_query_sorted(""));
+    CHECK(is_query_sorted("a=1"));
+  }
+
+  SECTION("empty params are not sorted, since sort_query drops them")
+  {
+    CHECK_FALSE(is_query_sorted("&a=1"));
+    CHECK_FALSE(is_query_sorted("a=1&"));
+    CHECK_FALSE(is_query_sorted("a=1&&b=2"));
+    CHECK_FALSE(is_query_sorted("&"));
+  }
+
+  SECTION("agrees with sort_query on every edge case")
+  {
+    for (std::string_view q : {"", "&", "&&", "a", "a=1", "a=1&", "&a=1", 
"a=1&&b=2", "b=2&a=1", "a=1&b=2", "b&a=1", "x=1&a=2&x=3",
+                               "a=2&x=3&x=1", "a=1=2", "=x&a=1", "a=1&=x", 
"a1=x&a=x", "a=x&a1=x"}) {
+      INFO("query: \"" << q << "\"");
+      CHECK(is_query_sorted(q) == (sort_query(q) == q));
+    }
+  }

Review Comment:
   Fixed in 78b245fd9. `"=x&=&=y"` is not covered: `=` is an empty-name param, 
not an empty token, so it is kept.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to