1 /*
2   Licensed to the Apache Software Foundation (ASF) under one
3   or more contributor license agreements.  See the NOTICE file
4   distributed with this work for additional information
5   regarding copyright ownership.  The ASF licenses this file
6   to you under the Apache License, Version 2.0 (the
7   "License"); you may not use this file except in compliance
8   with the License.  You may obtain a copy of the License at
9 
10   http://www.apache.org/licenses/LICENSE-2.0
11 
12   Unless required by applicable law or agreed to in writing, software
13   distributed under the License is distributed on an "AS IS" BASIS,
14   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   See the License for the specific language governing permissions and
16   limitations under the License.
17 */
18 
19 /*
20  * These are misc unit tests for header rewrite
21  */
22 
23 #include <cstdio>
24 #include <cstdarg>
25 #include <iostream>
26 #include <ostream>
27 
28 #include "parser.h"
29 
30 const char PLUGIN_NAME[]     = "TEST_header_rewrite";
31 const char PLUGIN_NAME_DBG[] = "TEST_dbg_header_rewrite";
32 
33 extern "C" void
TSError(const char * fmt,...)34 TSError(const char *fmt, ...)
35 {
36   va_list args;
37 
38   va_start(args, fmt);
39   vfprintf(stderr, fmt, args);
40   va_end(args);
41   fprintf(stderr, "\n");
42 }
43 
44 class ParserTest : public Parser
45 {
46 public:
ParserTest(const std::string & line)47   ParserTest(const std::string &line) : res(true)
48   {
49     Parser::parse_line(line);
50     std::cout << "Finished parser test: " << line << std::endl;
51   }
52 
53   std::vector<std::string>
getTokens() const54   getTokens() const
55   {
56     return _tokens;
57   }
58 
59   template <typename T, typename U>
60   void
do_parser_check(T x,U y,int line=0)61   do_parser_check(T x, U y, int line = 0)
62   {
63     if (x != y) {
64       std::cerr << "CHECK FAILED on line " << line << ": " << x << " != " << y << std::endl;
65       res = false;
66     }
67   }
68 
69   bool res;
70 };
71 
72 class SimpleTokenizerTest : public HRWSimpleTokenizer
73 {
74 public:
SimpleTokenizerTest(const std::string & line)75   SimpleTokenizerTest(const std::string &line) : HRWSimpleTokenizer(line), res(true)
76   {
77     std::cout << "Finished tokenizer test: " << line << std::endl;
78   }
79 
80   template <typename T, typename U>
81   void
do_parser_check(T x,U y,int line=0)82   do_parser_check(T x, U y, int line = 0)
83   {
84     if (x != y) {
85       std::cerr << "CHECK FAILED on line " << line << ": |" << x << "| != |" << y << "|" << std::endl;
86       res = false;
87     }
88   }
89 
90   bool res;
91 };
92 
93 #define CHECK_EQ(x, y)                     \
94   do {                                     \
95     p.do_parser_check((x), (y), __LINE__); \
96   } while (false)
97 
98 #define END_TEST(s) \
99   do {              \
100     if (!p.res) {   \
101       ++errors;     \
102     }               \
103   } while (false)
104 
105 int
test_parsing()106 test_parsing()
107 {
108   int errors = 0;
109 
110   {
111     ParserTest p("cond      %{READ_REQUEST_HDR_HOOK}");
112 
113     CHECK_EQ(p.getTokens().size(), 2U);
114     CHECK_EQ(p.getTokens()[0], "cond");
115     CHECK_EQ(p.getTokens()[1], "%{READ_REQUEST_HDR_HOOK}");
116 
117     END_TEST();
118   }
119 
120   {
121     ParserTest p("cond %{CLIENT-HEADER:Host}    =a");
122 
123     CHECK_EQ(p.getTokens().size(), 4UL);
124     CHECK_EQ(p.getTokens()[0], "cond");
125     CHECK_EQ(p.getTokens()[1], "%{CLIENT-HEADER:Host}");
126     CHECK_EQ(p.getTokens()[2], "=");
127     CHECK_EQ(p.getTokens()[3], "a");
128 
129     END_TEST();
130   }
131 
132   {
133     ParserTest p(" # COMMENT!");
134 
135     CHECK_EQ(p.getTokens().size(), 0UL);
136     CHECK_EQ(p.empty(), true);
137 
138     END_TEST();
139   }
140 
141   {
142     ParserTest p("# COMMENT");
143 
144     CHECK_EQ(p.getTokens().size(), 0UL);
145     CHECK_EQ(p.empty(), true);
146 
147     END_TEST();
148   }
149 
150   {
151     ParserTest p("cond %{Client-HEADER:Foo} =b");
152 
153     CHECK_EQ(p.getTokens().size(), 4UL);
154     CHECK_EQ(p.getTokens()[0], "cond");
155     CHECK_EQ(p.getTokens()[1], "%{Client-HEADER:Foo}");
156     CHECK_EQ(p.getTokens()[2], "=");
157     CHECK_EQ(p.getTokens()[3], "b");
158 
159     END_TEST();
160   }
161 
162   {
163     ParserTest p("cond %{Client-HEADER:Blah}       =        x");
164 
165     CHECK_EQ(p.getTokens().size(), 4UL);
166     CHECK_EQ(p.getTokens()[0], "cond");
167     CHECK_EQ(p.getTokens()[1], "%{Client-HEADER:Blah}");
168     CHECK_EQ(p.getTokens()[2], "=");
169     CHECK_EQ(p.getTokens()[3], "x");
170 
171     END_TEST();
172   }
173 
174   {
175     ParserTest p(R"(cond %{CLIENT-HEADER:non_existent_header} =  "shouldnt_   exist    _anyway"          [AND])");
176 
177     CHECK_EQ(p.getTokens().size(), 5UL);
178     CHECK_EQ(p.getTokens()[0], "cond");
179     CHECK_EQ(p.getTokens()[1], "%{CLIENT-HEADER:non_existent_header}");
180     CHECK_EQ(p.getTokens()[2], "=");
181     CHECK_EQ(p.getTokens()[3], "shouldnt_   exist    _anyway");
182     CHECK_EQ(p.getTokens()[4], "[AND]");
183 
184     END_TEST();
185   }
186 
187   {
188     ParserTest p(R"(cond %{CLIENT-HEADER:non_existent_header} =  "shouldnt_   =    _anyway"          [AND])");
189 
190     CHECK_EQ(p.getTokens().size(), 5UL);
191     CHECK_EQ(p.getTokens()[0], "cond");
192     CHECK_EQ(p.getTokens()[1], "%{CLIENT-HEADER:non_existent_header}");
193     CHECK_EQ(p.getTokens()[2], "=");
194     CHECK_EQ(p.getTokens()[3], "shouldnt_   =    _anyway");
195     CHECK_EQ(p.getTokens()[4], "[AND]");
196 
197     END_TEST();
198   }
199 
200   {
201     ParserTest p(R"(cond %{CLIENT-HEADER:non_existent_header} ="="          [AND])");
202 
203     CHECK_EQ(p.getTokens().size(), 5UL);
204     CHECK_EQ(p.getTokens()[0], "cond");
205     CHECK_EQ(p.getTokens()[1], "%{CLIENT-HEADER:non_existent_header}");
206     CHECK_EQ(p.getTokens()[2], "=");
207     CHECK_EQ(p.getTokens()[3], "=");
208     CHECK_EQ(p.getTokens()[4], "[AND]");
209 
210     END_TEST();
211   }
212 
213   {
214     ParserTest p(R"(cond %{CLIENT-HEADER:non_existent_header} =""          [AND])");
215 
216     CHECK_EQ(p.getTokens().size(), 5UL);
217     CHECK_EQ(p.getTokens()[0], "cond");
218     CHECK_EQ(p.getTokens()[1], "%{CLIENT-HEADER:non_existent_header}");
219     CHECK_EQ(p.getTokens()[2], "=");
220     CHECK_EQ(p.getTokens()[3], "");
221     CHECK_EQ(p.getTokens()[4], "[AND]");
222 
223     END_TEST();
224   }
225 
226   {
227     ParserTest p(R"(cond %{CLIENT-URL:PATH} /\/foo\/bar/ [OR])");
228 
229     CHECK_EQ(p.getTokens().size(), 4UL);
230     CHECK_EQ(p.getTokens()[0], "cond");
231     CHECK_EQ(p.getTokens()[1], "%{CLIENT-URL:PATH}");
232     CHECK_EQ(p.getTokens()[2], R"(/\/foo\/bar/)");
233     CHECK_EQ(p.getTokens()[3], "[OR]");
234 
235     END_TEST();
236   }
237 
238   {
239     ParserTest p("add-header X-HeaderRewriteApplied true");
240 
241     CHECK_EQ(p.getTokens().size(), 3UL);
242     CHECK_EQ(p.getTokens()[0], "add-header");
243     CHECK_EQ(p.getTokens()[1], "X-HeaderRewriteApplied");
244     CHECK_EQ(p.getTokens()[2], "true");
245 
246     END_TEST();
247   }
248 
249   /* backslash-escape */
250   {
251     ParserTest p(R"(add-header foo \ \=\<\>\"\#\\)");
252 
253     CHECK_EQ(p.getTokens().size(), 3UL);
254     CHECK_EQ(p.getTokens()[0], "add-header");
255     CHECK_EQ(p.getTokens()[1], "foo");
256     CHECK_EQ(p.getTokens()[2], R"( =<>"#\)");
257 
258     END_TEST();
259   }
260 
261   {
262     ParserTest p(R"(add-header foo \<bar\>)");
263 
264     CHECK_EQ(p.getTokens().size(), 3UL);
265     CHECK_EQ(p.getTokens()[0], "add-header");
266     CHECK_EQ(p.getTokens()[1], "foo");
267     CHECK_EQ(p.getTokens()[2], "<bar>");
268 
269     END_TEST();
270   }
271 
272   {
273     ParserTest p(R"(add-header foo \bar\)");
274 
275     CHECK_EQ(p.getTokens().size(), 3UL);
276     CHECK_EQ(p.getTokens()[0], "add-header");
277     CHECK_EQ(p.getTokens()[1], "foo");
278     CHECK_EQ(p.getTokens()[2], "bar");
279 
280     END_TEST();
281   }
282 
283   {
284     ParserTest p(R"(add-header foo "bar")");
285 
286     CHECK_EQ(p.getTokens().size(), 3UL);
287     CHECK_EQ(p.getTokens()[0], "add-header");
288     CHECK_EQ(p.getTokens()[1], "foo");
289     CHECK_EQ(p.getTokens()[2], "bar");
290 
291     END_TEST();
292   }
293 
294   {
295     ParserTest p(R"(add-header foo "\"bar\"")");
296 
297     CHECK_EQ(p.getTokens().size(), 3UL);
298     CHECK_EQ(p.getTokens()[0], "add-header");
299     CHECK_EQ(p.getTokens()[1], "foo");
300     CHECK_EQ(p.getTokens()[2], R"("bar")");
301 
302     END_TEST();
303   }
304 
305   {
306     ParserTest p(R"(add-header foo "\"\\\"bar\\\"\"")");
307 
308     CHECK_EQ(p.getTokens().size(), 3UL);
309     CHECK_EQ(p.getTokens()[0], "add-header");
310     CHECK_EQ(p.getTokens()[1], "foo");
311     CHECK_EQ(p.getTokens()[2], R"("\"bar\"")");
312 
313     END_TEST();
314   }
315 
316   {
317     ParserTest p(R"(add-header Public-Key-Pins "max-age=3000; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\"")");
318 
319     CHECK_EQ(p.getTokens().size(), 3UL);
320     CHECK_EQ(p.getTokens()[0], "add-header");
321     CHECK_EQ(p.getTokens()[1], "Public-Key-Pins");
322     CHECK_EQ(p.getTokens()[2], R"(max-age=3000; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=")");
323 
324     END_TEST();
325   }
326 
327   {
328     ParserTest p(R"(add-header Public-Key-Pins max-age\=3000;\ pin-sha256\=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM\=\")");
329 
330     CHECK_EQ(p.getTokens().size(), 3UL);
331     CHECK_EQ(p.getTokens()[0], "add-header");
332     CHECK_EQ(p.getTokens()[1], "Public-Key-Pins");
333     CHECK_EQ(p.getTokens()[2], R"(max-age=3000; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=")");
334 
335     END_TEST();
336   }
337 
338   {
339     ParserTest p(R"(add-header X-Url "http://trafficserver.apache.org/")");
340 
341     CHECK_EQ(p.getTokens().size(), 3UL);
342     CHECK_EQ(p.getTokens()[0], "add-header");
343     CHECK_EQ(p.getTokens()[1], "X-Url");
344     CHECK_EQ(p.getTokens()[2], "http://trafficserver.apache.org/");
345 
346     END_TEST();
347   }
348 
349   {
350     ParserTest p(R"(add-header X-Url http://trafficserver.apache.org/)");
351 
352     CHECK_EQ(p.getTokens().size(), 3UL);
353     CHECK_EQ(p.getTokens()[0], "add-header");
354     CHECK_EQ(p.getTokens()[1], "X-Url");
355     CHECK_EQ(p.getTokens()[2], "http://trafficserver.apache.org/");
356 
357     END_TEST();
358   }
359 
360   {
361     ParserTest p(R"(set-header Alt-Svc "quic=\":443\"; v=\"35\"" [L])");
362 
363     CHECK_EQ(p.getTokens().size(), 4UL);
364     CHECK_EQ(p.getTokens()[0], "set-header");
365     CHECK_EQ(p.getTokens()[1], "Alt-Svc");
366     CHECK_EQ(p.getTokens()[2], R"(quic=":443"; v="35")");
367     CHECK_EQ(p.get_value(), R"(quic=":443"; v="35")");
368 
369     END_TEST();
370   }
371 
372   /*
373    * test some failure scenarios
374    */
375 
376   { /* unterminated quote */
377     ParserTest p(R"(cond %{CLIENT-HEADER:non_existent_header} =" [AND])");
378 
379     CHECK_EQ(p.getTokens().size(), 0UL);
380 
381     END_TEST();
382   }
383 
384   { /* quote in a token */
385     ParserTest p(R"(cond %{CLIENT-HEADER:non_existent_header} =a"b [AND])");
386 
387     CHECK_EQ(p.getTokens().size(), 0UL);
388 
389     END_TEST();
390   }
391 
392   return errors;
393 }
394 
395 int
396 test_processing()
397 {
398   int errors = 0;
399   /*
400    * These tests are designed to verify that the processing of the parsed input is correct.
401    */
402   {
403     ParserTest p(R"(cond %{CLIENT-HEADER:non_existent_header} ="="          [AND])");
404 
405     CHECK_EQ(p.getTokens().size(), 5UL);
406     CHECK_EQ(p.get_op(), "CLIENT-HEADER:non_existent_header");
407     CHECK_EQ(p.get_arg(), "==");
408     CHECK_EQ(p.is_cond(), true);
409 
410     END_TEST();
411   }
412 
413   {
414     ParserTest p(R"(cond %{CLIENT-HEADER:non_existent_header} =  "shouldnt_   =    _anyway"          [AND])");
415 
416     CHECK_EQ(p.getTokens().size(), 5UL);
417     CHECK_EQ(p.get_op(), "CLIENT-HEADER:non_existent_header");
418     CHECK_EQ(p.get_arg(), "=shouldnt_   =    _anyway");
419     CHECK_EQ(p.is_cond(), true);
420 
421     END_TEST();
422   }
423 
424   {
425     ParserTest p(R"(cond %{CLIENT-URL:PATH} /\.html|\.txt/)");
426 
427     CHECK_EQ(p.getTokens().size(), 3UL);
428     CHECK_EQ(p.get_op(), "CLIENT-URL:PATH");
429     CHECK_EQ(p.get_arg(), R"(/\.html|\.txt/)");
430     CHECK_EQ(p.is_cond(), true);
431 
432     END_TEST();
433   }
434 
435   {
436     ParserTest p(R"(cond %{CLIENT-URL:PATH} /\/foo\/bar/)");
437 
438     CHECK_EQ(p.getTokens().size(), 3UL);
439     CHECK_EQ(p.get_op(), "CLIENT-URL:PATH");
440     CHECK_EQ(p.get_arg(), R"(/\/foo\/bar/)");
441     CHECK_EQ(p.is_cond(), true);
442 
443     END_TEST();
444   }
445 
446   {
447     ParserTest p("add-header X-HeaderRewriteApplied true");
448 
449     CHECK_EQ(p.getTokens().size(), 3UL);
450     CHECK_EQ(p.get_op(), "add-header");
451     CHECK_EQ(p.get_arg(), "X-HeaderRewriteApplied");
452     CHECK_EQ(p.get_value(), "true");
453     CHECK_EQ(p.is_cond(), false);
454 
455     END_TEST();
456   }
457 
458   return errors;
459 }
460 
461 int
462 test_tokenizer()
463 {
464   int errors = 0;
465 
466   {
467     SimpleTokenizerTest p("a simple test");
468     CHECK_EQ(p.get_tokens().size(), 1UL);
469     CHECK_EQ(p.get_tokens()[0], "a simple test");
470 
471     END_TEST();
472   }
473 
474   {
475     SimpleTokenizerTest p(R"(quic=":443"; v="35")");
476     CHECK_EQ(p.get_tokens().size(), 1UL);
477     CHECK_EQ(p.get_tokens()[0], R"(quic=":443"; v="35")");
478 
479     END_TEST();
480   }
481 
482   {
483     SimpleTokenizerTest p(R"(let's party like it's  %{NOW:YEAR})");
484     CHECK_EQ(p.get_tokens().size(), 2UL);
485     CHECK_EQ(p.get_tokens()[0], "let's party like it's  ");
486     CHECK_EQ(p.get_tokens()[1], "%{NOW:YEAR}");
487 
488     END_TEST();
489   }
490   {
491     SimpleTokenizerTest p("A racoon's favorite tag is %{METHOD} in %{NOW:YEAR}!");
492     CHECK_EQ(p.get_tokens().size(), 5UL);
493     CHECK_EQ(p.get_tokens()[0], "A racoon's favorite tag is ");
494     CHECK_EQ(p.get_tokens()[1], "%{METHOD}");
495     CHECK_EQ(p.get_tokens()[2], " in ");
496     CHECK_EQ(p.get_tokens()[3], "%{NOW:YEAR}");
497     CHECK_EQ(p.get_tokens()[4], "!");
498 
499     END_TEST();
500   }
501 
502   {
503     SimpleTokenizerTest p(R"(Hello from %{IP:SERVER}:%{INBOUND:LOCAL-PORT})");
504     CHECK_EQ(p.get_tokens().size(), 4UL);
505     CHECK_EQ(p.get_tokens()[0], "Hello from ");
506     CHECK_EQ(p.get_tokens()[1], "%{IP:SERVER}");
507     CHECK_EQ(p.get_tokens()[2], ":");
508     CHECK_EQ(p.get_tokens()[3], "%{INBOUND:LOCAL-PORT}");
509 
510     END_TEST();
511   }
512 
513   return errors;
514 }
515 
516 int
517 main()
518 {
519   if (test_parsing() || test_processing() || test_tokenizer()) {
520     return 1;
521   }
522 
523   return 0;
524 }
525