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 "tscore/Tokenizer.h"
28 #include "tscore/TextBuffer.h"
29 #include "mgmtapi.h"
30 #include <cstdio>
31 #include <cstring>
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), next(nullptr) { memset(viaData, 0, sizeof(viaData)); }
~VIAVIA47   ~VIA() { delete next; }
48   const char *title;
49   const char *viaData[128];
50   VIA *next;
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(const char * header)189 printViaHeader(const char *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 (const char *c = header; *c; ++c) {
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         printf("%-55s:", viaEntry->title);
212         printf("%s\n", viaEntry->viaData[static_cast<unsigned char>(*c)] ? viaEntry->viaData[static_cast<unsigned char>(*c)] :
213                                                                            "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(const char * str)223 decodeViaHeader(const char *str)
224 {
225   size_t viaHdrLength = strlen(str);
226   char tmp[viaHdrLength + 2];
227   char *Via = tmp;
228 
229   memcpy(Via, str, viaHdrLength);
230   Via[viaHdrLength] = '\0'; // null terminate
231 
232   // Via header inside square brackets
233   if (Via[0] == '[' && Via[viaHdrLength - 1] == ']') {
234     viaHdrLength = viaHdrLength - 2;
235     Via++;
236     Via[viaHdrLength] = '\0'; // null terminate the string after trimming
237   }
238 
239   printf("Via header is [%s], Length is %zu\n", Via, viaHdrLength);
240 
241   if (viaHdrLength == 5) {
242     Via = strcat(Via, " "); // Add one space character before decoding via header
243     ++viaHdrLength;
244   }
245 
246   if (viaHdrLength == 22 || viaHdrLength == 6) {
247     // Decode via header
248     printViaHeader(Via);
249     return TS_ERR_OKAY;
250   }
251   // Invalid header size, come out.
252   printf("\nInvalid VIA header. VIA header length should be 6 or 22 characters\n");
253   printf("Valid via header format is "
254          "[u<client-stuff>c<cache-lookup-stuff>s<server-stuff>f<cache-fill-stuff>p<proxy-stuff>e<error-codes>:t<tunneling-info>c<"
255          "cache type><cache-lookup-result>p<parent-proxy-conn-info>s<server-conn-info>]\n");
256   return TS_ERR_FAIL;
257 }
258 
259 // Read user input from stdin
260 static TSMgmtError
filterViaHeader()261 filterViaHeader()
262 {
263   const pcre *compiledReg;
264   const pcre_extra *extraReg = nullptr;
265   int subStringVector[SUBSTRING_VECTOR_COUNT];
266   const char *err;
267   int errOffset;
268   int pcreExecCode;
269   int i;
270   const char *viaPattern =
271     R"(\[([ucsfpe]+[^\]]+)\])"; // Regex to match via header with in [] which can start with character class ucsfpe
272   char *viaHeaderString;
273   char viaHeader[1024];
274 
275   // Compile PCRE via header pattern
276   compiledReg = pcre_compile(viaPattern, 0, &err, &errOffset, nullptr);
277 
278   if (compiledReg == nullptr) {
279     printf("PCRE regex compilation failed with error %s at offset %d\n", err, errOffset);
280     return TS_ERR_FAIL;
281   }
282 
283   // Read all lines from stdin
284   while (fgets(viaHeader, sizeof(viaHeader), stdin)) {
285     // Trim new line character and null terminate it
286     char *newLinePtr = strchr(viaHeader, '\n');
287     if (newLinePtr) {
288       *newLinePtr = '\0';
289     }
290     // Match for via header pattern
291     pcreExecCode = pcre_exec(compiledReg, extraReg, viaHeader, static_cast<int>(sizeof(viaHeader)), 0, 0, subStringVector,
292                              SUBSTRING_VECTOR_COUNT);
293 
294     // Match failed, don't worry. Continue to next line.
295     if (pcreExecCode < 0) {
296       continue;
297     }
298 
299     // Match successful, but too many substrings
300     if (pcreExecCode == 0) {
301       pcreExecCode = SUBSTRING_VECTOR_COUNT / 3;
302       printf("Too many substrings were found. %d substrings couldn't fit into subStringVector\n", pcreExecCode - 1);
303     }
304 
305     // Loop based on number of matches found
306     for (i = 1; i < pcreExecCode; i++) {
307       // Point to beginning of matched substring
308       char *subStringBegin = viaHeader + subStringVector[2 * i];
309       // Get length of matched substring
310       int subStringLen = subStringVector[2 * i + 1] - subStringVector[2 * i];
311       viaHeaderString  = subStringBegin;
312       sprintf(viaHeaderString, "%.*s", subStringLen, subStringBegin);
313       // Decode matched substring
314       decodeViaHeader(viaHeaderString);
315     }
316   }
317   return TS_ERR_OKAY;
318 }
319 
320 int
main(int,const char ** argv)321 main(int /* argc ATS_UNUSED */, const char **argv)
322 {
323   TSMgmtError status;
324 
325   // build the application information structure
326   appVersionInfo.setup(PACKAGE_NAME, "traffic_via", PACKAGE_VERSION, __DATE__, __TIME__, BUILD_MACHINE, BUILD_PERSON, "");
327 
328   /* see 'ink_args.h' for meanings of the various fields */
329   ArgumentDescription argument_descriptions[] = {
330     VERSION_ARGUMENT_DESCRIPTION(),
331     HELP_ARGUMENT_DESCRIPTION(),
332   };
333 
334   process_args(&appVersionInfo, argument_descriptions, countof(argument_descriptions), argv);
335 
336   for (unsigned i = 0; i < n_file_arguments; ++i) {
337     if (strcmp(file_arguments[i], "-") == 0) {
338       // Filter arguments provided from stdin
339       status = filterViaHeader();
340     } else {
341       status = decodeViaHeader(file_arguments[i]);
342     }
343 
344     if (status != TS_ERR_OKAY) {
345       return 1;
346     }
347   }
348 
349   return 0;
350 }
351