Drop boost::xpressive
[metaproxy-moved-to-github.git] / src / test_filter_rewrite.cpp
1 /* This file is part of Metaproxy.
2    Copyright (C) 2005-2013 Index Data
3
4 Metaproxy is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Metaproxy is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18
19 #include "config.hpp"
20 #include <iostream>
21 #include <stdexcept>
22
23 #include "filter_http_client.hpp"
24 #include <metaproxy/util.hpp>
25 #include "router_chain.hpp"
26 #include <metaproxy/package.hpp>
27
28 #include <boost/regex.hpp>
29 #include <boost/lexical_cast.hpp>
30
31 #define BOOST_AUTO_TEST_MAIN
32 #define BOOST_TEST_DYN_LINK
33
34 #include <boost/test/auto_unit_test.hpp>
35
36 using namespace boost::unit_test;
37 namespace mp = metaproxy_1;
38
39 class FilterHeaderRewrite: public mp::filter::Base {
40 public:
41     void process(mp::Package & package) const {
42         Z_GDU *gdu = package.request().get();
43         //we have an http req
44         if (gdu && gdu->which == Z_GDU_HTTP_Request)
45         {
46           std::cout << "Request headers" << std::endl;
47           Z_HTTP_Request *hreq = gdu->u.HTTP_Request;
48           //dump req headers
49           for (Z_HTTP_Header *header = hreq->headers;
50               header != 0; 
51               header = header->next) 
52           {
53             std::cout << header->name << ": " << header->value << std::endl;
54             rewrite_req_header(header);
55           }
56         }
57         package.move();
58         gdu = package.response().get();
59         if (gdu && gdu->which == Z_GDU_HTTP_Response)
60         {
61           std::cout << "Respose headers" << std::endl;
62           Z_HTTP_Response *hr = gdu->u.HTTP_Response;
63           //dump resp headers
64           for (Z_HTTP_Header *header = hr->headers;
65               header != 0; 
66               header = header->next) 
67           {
68             std::cout << header->name << ": " << header->value << std::endl;
69           }
70         }
71
72     };
73     void configure(const xmlNode* ptr, bool test_only, const char *path) {};
74
75     void rewrite_req_header(Z_HTTP_Header *header) const
76     {
77         //exec regex against value
78         boost::regex re(req_uri_rx);
79         boost::smatch what;
80         std::string hvalue(header->value);
81         std::map<std::string, std::string> vars;
82         if (regex_match(hvalue, what, re))
83         {
84             unsigned i;
85             for (i = 1; i < what.size(); ++i)
86             {
87                 //check if the group is named
88                 std::map<int, std::string>::const_iterator it
89                     = groups_by_num.find(i);
90                 if (it != groups_by_num.end()) 
91                 {   //it is
92                     std::string name = it->second;
93                     vars[name] = what[i];
94                 }
95             }
96             //rewrite the header according to the recipe
97             std::string rvalue = sub_vars(req_uri_pat, vars);
98             std::cout << "Rewritten '"+hvalue+"' to '"+rvalue+"'\n";
99         }
100         else
101         {
102             std::cout << "No match found in '" + hvalue + "'\n";
103         }
104     };
105
106     static void parse_groups(const std::string & str,
107             std::map<int, std::string> & groups_bynum,
108             std::map<std::string, int> & groups_byname)
109     {
110        int gnum = 0;
111        bool esc = false;
112        for (int i = 0; i < str.size(); ++i)
113        {
114            if (!esc && str[i] == '\\')
115            {
116                esc = true;
117                continue;
118            }
119            if (!esc && str[i] == '(') //group starts
120            {
121                gnum++;
122                if (i+1 < str.size() && str[i+1] == '?') //group with attrs 
123                {
124                    i++;
125                    if (i+1 < str.size() && str[i+1] == 'P') //optional, python
126                        i++;
127                    if (i+1 < str.size() && str[i+1] == '<') //named
128                    {
129                        i++;
130                        std::string gname;
131                        bool term = false;
132                        while (++i < str.size())
133                        {
134                            if (str[i] == '>') { term = true; break; }
135                            if (!isalnum(str[i])) 
136                                throw mp::filter::FilterException
137                                    ("Only alphanumeric chars allowed, found "
138                                     " in '" 
139                                     + str 
140                                     + "' at " 
141                                     + boost::lexical_cast<std::string>(i)); 
142                            gname += str[i];
143                        }
144                        if (!term)
145                            throw mp::filter::FilterException
146                                ("Unterminated group name '" + gname 
147                                 + " in '" + str +"'");
148                       groups_bynum[gnum] = gname;
149                       groups_byname[gname] = gnum;
150                       std::cout << "Found named group '" << gname 
151                           << "' at $" << gnum << std::endl;
152                    }
153                }
154            }
155            esc = false;
156        }
157     }
158
159     static std::string sub_vars (const std::string & in, 
160             const std::map<std::string, std::string> & vars)
161     {
162         std::string out;
163         bool esc = false;
164         for (int i = 0; i < in.size(); ++i)
165         {
166             if (!esc && in[i] == '\\')
167             {
168                 esc = true;
169                 continue;
170             }
171             if (!esc && in[i] == '$') //var
172             {
173                 if (i+1 < in.size() && in[i+1] == '{') //ref prefix
174                 {
175                     ++i;
176                     std::string name;
177                     bool term = false;
178                     while (++i < in.size()) 
179                     {
180                         if (in[i] == '}') { term = true; break; }
181                         name += in[i];
182                     }
183                     if (!term) throw mp::filter::FilterException
184                         ("Unterminated var ref in '"+in+"' at "
185                          + boost::lexical_cast<std::string>(i));
186                     std::map<std::string, std::string>::const_iterator it
187                         = vars.find(name);
188                     if (it != vars.end())
189                         out += it->second;
190                 }
191                 else
192                 {
193                     throw mp::filter::FilterException
194                         ("Malformed or trimmed var ref in '"
195                          +in+"' at "+boost::lexical_cast<std::string>(i)); 
196                 }
197                 continue;
198             }
199             //passthru
200             out += in[i];
201             esc = false;
202         }
203         return out;
204     }
205     
206     void configure(
207             const std::string & req_uri_rx, 
208             const std::string & req_uri_pat, 
209             const std::string & resp_uri_rx, 
210             const std::string & resp_uri_pat) 
211     {
212        this->req_uri_rx = req_uri_rx;
213        this->req_uri_pat = req_uri_pat;
214        //pick up names
215        parse_groups(req_uri_rx, groups_by_num, groups_by_name);
216        this->resp_uri_rx = resp_uri_rx;
217        this->resp_uri_pat = resp_uri_pat;
218     };
219
220 private:
221     std::map<std::string, std::string> vars;
222     std::string req_uri_rx;
223     std::string resp_uri_rx;
224     std::string req_uri_pat;
225     std::string resp_uri_pat;
226     std::map<int, std::string> groups_by_num;
227     std::map<std::string, int> groups_by_name;
228
229 };
230
231
232 BOOST_AUTO_TEST_CASE( test_filter_rewrite_1 )
233 {
234     try
235     {
236        FilterHeaderRewrite fhr;
237     }
238     catch ( ... ) {
239         BOOST_CHECK (false);
240     }
241 }
242
243 BOOST_AUTO_TEST_CASE( test_filter_rewrite_2 )
244 {
245     try
246     {
247         mp::RouterChain router;
248
249         FilterHeaderRewrite fhr;
250         fhr.configure(
251                 ".*?(?<host>[^:]+):(?<port>\\d+).*",
252                 "http://${host}:${port}/somepath",
253                 ".*(localhost).*",
254                 "http:://g");
255         mp::filter::HTTPClient hc;
256         
257         router.append(fhr);
258         router.append(hc);
259
260         // create an http request
261         mp::Package pack;
262
263         mp::odr odr;
264         Z_GDU *gdu_req = z_get_HTTP_Request_uri(odr, 
265             "http://localhost:80/~jakub/targetsite.php", 0, 1);
266
267         pack.request() = gdu_req;
268
269         //feed to the router
270         pack.router(router).move();
271
272         //analyze the response
273         Z_GDU *gdu_res = pack.response().get();
274         BOOST_CHECK(gdu_res);
275         BOOST_CHECK_EQUAL(gdu_res->which, Z_GDU_HTTP_Response);
276         
277         Z_HTTP_Response *hres = gdu_res->u.HTTP_Response;
278         BOOST_CHECK(hres);
279
280     }
281     catch (std::exception & e) {
282         std::cout << e.what();
283         BOOST_CHECK (false);
284     }
285 }
286
287 /*
288  * Local variables:
289  * c-basic-offset: 4
290  * c-file-style: "Stroustrup"
291  * indent-tabs-mode: nil
292  * End:
293  * vim: shiftwidth=4 tabstop=8 expandtab
294  */
295