1 /** @file
2 
3   A brief file description
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 #include "tscore/ink_platform.h"
25 #include "tscore/ink_args.h"
26 #include "tscore/I_Version.h"
27 #include "mgmtapi.h"
28 #include <cstdio>
29 #include <cstring>
30 #include <iostream>
31 #include <string_view>
32 #include "tscore/Regex.h"
33 
34 /// XXX Use DFA or Regex wrappers?
35 #ifdef HAVE_PCRE_PCRE_H
36 #include <pcre/pcre.h>
37 #else
38 #include <pcre.h>
39 #endif
40 
41 #define SUBSTRING_VECTOR_COUNT 30 // Should be multiple of 3
42 
43 static AppVersionInfo appVersionInfo;
44 
45 struct VIA {
VIAVIA46   VIA(const char *t) : title(t) {}
~VIAVIA47   ~VIA() { delete next; }
48   const char *title;
49   const char *viaData[128] = {}; // zero initialize
50   VIA *next                = nullptr;
51 };
52 
53 // Function to get via header table for every field/category in the via header
54 static VIA *
detailViaLookup(char flag)55 detailViaLookup(char flag)
56 {
57   VIA *viaTable;
58 
59   // Detailed via codes after ":"
60   switch (flag) {
61   case 't':
62     viaTable                                           = new VIA("Tunnel info");
63     viaTable->viaData[static_cast<unsigned char>(' ')] = "no tunneling";
64     viaTable->viaData[static_cast<unsigned char>('U')] = "tunneling because of url (url suggests dynamic content)";
65     viaTable->viaData[static_cast<unsigned char>('M')] = "tunneling due to a method (e.g. CONNECT)";
66     viaTable->viaData[static_cast<unsigned char>('O')] = "tunneling because cache is turned off";
67     viaTable->viaData[static_cast<unsigned char>('F')] = "tunneling due to a header field (such as presence of If-Range header)";
68     viaTable->viaData[static_cast<unsigned char>('N')] = "tunneling due to no forward";
69     viaTable->viaData[static_cast<unsigned char>('A')] = "tunnel authorization";
70     break;
71   case 'c':
72     // Cache type
73     viaTable                                           = new VIA("Cache Type");
74     viaTable->viaData[static_cast<unsigned char>('C')] = "cache";
75     viaTable->viaData[static_cast<unsigned char>('L')] = "cluster, (not used)";
76     viaTable->viaData[static_cast<unsigned char>('P')] = "parent";
77     viaTable->viaData[static_cast<unsigned char>('S')] = "server";
78     viaTable->viaData[static_cast<unsigned char>(' ')] = "unknown";
79 
80     // Cache Lookup Result
81     viaTable->next                                           = new VIA("Cache Lookup Result");
82     viaTable->next->viaData[static_cast<unsigned char>('C')] = "cache hit but config forces revalidate";
83     viaTable->next->viaData[static_cast<unsigned char>('I')] =
84       "conditional miss (client sent conditional, fresh in cache, returned 412)";
85     viaTable->next->viaData[static_cast<unsigned char>(' ')] = "cache miss or no cache lookup";
86     viaTable->next->viaData[static_cast<unsigned char>('U')] = "cache hit, but client forces revalidate (e.g. Pragma: no-cache)";
87     viaTable->next->viaData[static_cast<unsigned char>('D')] = "cache hit, but method forces revalidated (e.g. ftp, not anonymous)";
88     viaTable->next->viaData[static_cast<unsigned char>('M')] = "cache miss (url not in cache)";
89     viaTable->next->viaData[static_cast<unsigned char>('N')] =
90       "conditional hit (client sent conditional, doc fresh in cache, returned 304)";
91     viaTable->next->viaData[static_cast<unsigned char>('H')] = "cache hit";
92     viaTable->next->viaData[static_cast<unsigned char>('S')] = "cache hit, but expired";
93     viaTable->next->viaData[static_cast<unsigned char>('K')] = "cookie miss";
94     break;
95   case 'p':
96     viaTable                                           = new VIA("Parent proxy connection status");
97     viaTable->viaData[static_cast<unsigned char>(' ')] = "no parent proxy or unknown";
98     viaTable->viaData[static_cast<unsigned char>('S')] = "connection opened successfully";
99     viaTable->viaData[static_cast<unsigned char>('F')] = "connection open failed";
100     break;
101   case 's':
102     viaTable                                           = new VIA("Origin server connection status");
103     viaTable->viaData[static_cast<unsigned char>(' ')] = "no server connection needed";
104     viaTable->viaData[static_cast<unsigned char>('S')] = "connection opened successfully";
105     viaTable->viaData[static_cast<unsigned char>('F')] = "connection open failed";
106     break;
107   default:
108     viaTable = nullptr;
109     fprintf(stderr, "%s: %s: %c\n", appVersionInfo.AppStr, "Invalid VIA header character", flag);
110     break;
111   }
112 
113   return viaTable;
114 }
115 
116 // Function to get via header table for every field/category in the via header
117 static VIA *
standardViaLookup(char flag)118 standardViaLookup(char flag)
119 {
120   VIA *viaTable;
121 
122   // Via codes before ":"
123   switch (flag) {
124   case 'u':
125     viaTable                                           = new VIA("Request headers received from client");
126     viaTable->viaData[static_cast<unsigned char>('C')] = "cookie";
127     viaTable->viaData[static_cast<unsigned char>('E')] = "error in request";
128     viaTable->viaData[static_cast<unsigned char>('S')] = "simple request (not conditional)";
129     viaTable->viaData[static_cast<unsigned char>('N')] = "no-cache";
130     viaTable->viaData[static_cast<unsigned char>('I')] = "IMS";
131     viaTable->viaData[static_cast<unsigned char>(' ')] = "unknown";
132     break;
133   case 'c':
134     viaTable                                           = new VIA("Result of Traffic Server cache lookup for URL");
135     viaTable->viaData[static_cast<unsigned char>('A')] = "in cache, not acceptable (a cache \"MISS\")";
136     viaTable->viaData[static_cast<unsigned char>('H')] = "in cache, fresh (a cache \"HIT\")";
137     viaTable->viaData[static_cast<unsigned char>('S')] = "in cache, stale (a cache \"MISS\")";
138     viaTable->viaData[static_cast<unsigned char>('R')] = "in cache, fresh Ram hit (a cache \"HIT\")";
139     viaTable->viaData[static_cast<unsigned char>('M')] = "miss (a cache \"MISS\")";
140     viaTable->viaData[static_cast<unsigned char>(' ')] = "no cache lookup";
141     break;
142   case 's':
143     viaTable                                           = new VIA("Response information received from origin server");
144     viaTable->viaData[static_cast<unsigned char>('E')] = "error in response";
145     viaTable->viaData[static_cast<unsigned char>('S')] = "connection opened successfully";
146     viaTable->viaData[static_cast<unsigned char>('N')] = "not-modified";
147     viaTable->viaData[static_cast<unsigned char>(' ')] = "no server connection needed";
148     break;
149   case 'f':
150     viaTable                                           = new VIA("Result of document write-to-cache:");
151     viaTable->viaData[static_cast<unsigned char>('U')] = "updated old cache copy";
152     viaTable->viaData[static_cast<unsigned char>('D')] = "cached copy deleted";
153     viaTable->viaData[static_cast<unsigned char>('W')] = "written into cache (new copy)";
154     viaTable->viaData[static_cast<unsigned char>(' ')] = "no cache write performed";
155     break;
156   case 'p':
157     viaTable                                           = new VIA("Proxy operation result");
158     viaTable->viaData[static_cast<unsigned char>('R')] = "origin server revalidated";
159     viaTable->viaData[static_cast<unsigned char>(' ')] = "unknown";
160     viaTable->viaData[static_cast<unsigned char>('S')] = "served or connection opened successfully";
161     viaTable->viaData[static_cast<unsigned char>('N')] = "not-modified";
162     break;
163   case 'e':
164     viaTable                                           = new VIA("Error codes (if any)");
165     viaTable->viaData[static_cast<unsigned char>('A')] = "authorization failure";
166     viaTable->viaData[static_cast<unsigned char>('H')] = "header syntax unacceptable";
167     viaTable->viaData[static_cast<unsigned char>('C')] = "connection to server failed";
168     viaTable->viaData[static_cast<unsigned char>('T')] = "connection timed out";
169     viaTable->viaData[static_cast<unsigned char>('S')] = "server related error";
170     viaTable->viaData[static_cast<unsigned char>('D')] = "dns failure";
171     viaTable->viaData[static_cast<unsigned char>('N')] = "no error";
172     viaTable->viaData[static_cast<unsigned char>('F')] = "request forbidden";
173     viaTable->viaData[static_cast<unsigned char>('R')] = "cache read error";
174     viaTable->viaData[static_cast<unsigned char>('M')] = "moved temporarily";
175     viaTable->viaData[static_cast<unsigned char>('L')] = "looped detected";
176     viaTable->viaData[static_cast<unsigned char>(' ')] = "unknown";
177     break;
178   default:
179     viaTable = nullptr;
180     fprintf(stderr, "%s: %s: %c\n", appVersionInfo.AppStr, "Invalid VIA header character", flag);
181     break;
182   }
183 
184   return viaTable;
185 }
186 
187 // Function to print via header
188 static void
printViaHeader(std::string_view header)189 printViaHeader(std::string_view header)
190 {
191   VIA *viaTable = nullptr;
192   VIA *viaEntry = nullptr;
193   bool isDetail = false;
194 
195   printf("Via Header Details:\n");
196 
197   // Loop through input via header flags
198   for (char c : header) {
199     if ((c == ':') || (c == ';')) {
200       isDetail = true;
201       continue;
202     }
203 
204     if (islower(c)) {
205       // Get the via header table
206       delete viaTable;
207       viaEntry = viaTable = isDetail ? detailViaLookup(c) : standardViaLookup(c);
208     } else {
209       // This is a one of the sequence of (uppercase) VIA codes.
210       if (viaEntry) {
211         unsigned char idx = c;
212         printf("%-55s:", viaEntry->title);
213         printf("%s\n", viaEntry->viaData[idx] ? viaEntry->viaData[idx] : "Invalid sequence");
214         viaEntry = viaEntry->next;
215       }
216     }
217   }
218   delete viaTable;
219 }
220 
221 // Check validity of via header and then decode it
222 static TSMgmtError
decodeViaHeader(std::string_view text)223 decodeViaHeader(std::string_view text)
224 {
225   // Via header inside square brackets
226   if (!text.empty() && text.front() == '[' && text.back() == ']') {
227     text.remove_prefix(1);
228     text.remove_suffix(1);
229   }
230   if (text.empty()) {
231     return TS_ERR_FAIL;
232   }
233 
234   printf("Via header is [%.*s], Length is %zu\n", int(text.size()), text.data(), text.size());
235 
236   char extender[6];
237   if (text.size() == 5) { // add a trailing space in this case.
238     memcpy(extender, text.data(), text.size());
239     extender[5] = ' ';
240     text        = std::string_view{extender, 6};
241   }
242 
243   if (text.size() == 22 || text.size() == 6) {
244     // Decode via header
245     printViaHeader(text);
246     return TS_ERR_OKAY;
247   }
248   // Invalid header size, come out.
249   printf("\nInvalid VIA header. VIA header length should be 6 or 22 characters\n");
250   printf("Valid via header format is "
251          "[u<client-stuff>c<cache-lookup-stuff>s<server-stuff>f<cache-fill-stuff>p<proxy-stuff>e<error-codes>:t<tunneling-info>c<"
252          "cache type><cache-lookup-result>p<parent-proxy-conn-info>s<server-conn-info>]\n");
253   return TS_ERR_FAIL;
254 }
255 
256 // Read user input from stdin
257 static TSMgmtError
filterViaHeader()258 filterViaHeader()
259 {
260   const pcre *compiledReg;
261   const pcre_extra *extraReg = nullptr;
262   int subStringVector[SUBSTRING_VECTOR_COUNT];
263   const char *err;
264   int errOffset;
265   int pcreExecCode;
266   int i;
267   const char *viaPattern =
268     R"(\[([ucsfpe]+[^\]]+)\])"; // Regex to match via header with in [] which can start with character class ucsfpe
269   std::string line;
270 
271   // Compile PCRE via header pattern
272   compiledReg = pcre_compile(viaPattern, 0, &err, &errOffset, nullptr);
273 
274   if (compiledReg == nullptr) {
275     printf("PCRE regex compilation failed with error %s at offset %d\n", err, errOffset);
276     return TS_ERR_FAIL;
277   }
278 
279   // Read all lines from stdin
280   while (std::getline(std::cin, line)) {
281     // Match for via header pattern
282     pcreExecCode = pcre_exec(compiledReg, extraReg, line.data(), line.size(), 0, 0, subStringVector, SUBSTRING_VECTOR_COUNT);
283 
284     // Match failed, don't worry. Continue to next line.
285     if (pcreExecCode < 0) {
286       continue;
287     }
288 
289     // Match successful, but too many substrings
290     if (pcreExecCode == 0) {
291       pcreExecCode = SUBSTRING_VECTOR_COUNT / 3;
292       printf("Too many substrings were found. %d substrings couldn't fit into subStringVector\n", pcreExecCode - 1);
293     }
294 
295     // Loop based on number of matches found
296     for (i = 1; i < pcreExecCode; i++) {
297       std::string_view match{line.data() + subStringVector[2 * i], size_t(subStringVector[2 * i + 1] - subStringVector[2 * i])};
298       // Decode matched substring
299       decodeViaHeader(match);
300     }
301   }
302   return TS_ERR_OKAY;
303 }
304 
305 int
main(int,const char ** argv)306 main(int /* argc ATS_UNUSED */, const char **argv)
307 {
308   TSMgmtError status;
309 
310   // build the application information structure
311   appVersionInfo.setup(PACKAGE_NAME, "traffic_via", PACKAGE_VERSION, __DATE__, __TIME__, BUILD_MACHINE, BUILD_PERSON, "");
312 
313   /* see 'ink_args.h' for meanings of the various fields */
314   ArgumentDescription argument_descriptions[] = {
315     VERSION_ARGUMENT_DESCRIPTION(),
316     HELP_ARGUMENT_DESCRIPTION(),
317   };
318 
319   process_args(&appVersionInfo, argument_descriptions, countof(argument_descriptions), argv);
320 
321   for (unsigned i = 0; i < n_file_arguments; ++i) {
322     if (strcmp(file_arguments[i], "-") == 0) {
323       // Filter arguments provided from stdin
324       status = filterViaHeader();
325     } else {
326       status = decodeViaHeader(std::string_view{file_arguments[i], strlen(file_arguments[i])});
327     }
328 
329     if (status != TS_ERR_OKAY) {
330       return 1;
331     }
332   }
333 
334   return 0;
335 }
336