1 /* This file is part of Metaproxy.
2 Copyright (C) 2005-2013 Index Data
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
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
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
23 #include "filter_http_client.hpp"
24 #include <metaproxy/util.hpp>
25 #include "router_chain.hpp"
26 #include <metaproxy/package.hpp>
28 #include <boost/regex.hpp>
29 #include <boost/lexical_cast.hpp>
31 #define BOOST_AUTO_TEST_MAIN
32 #define BOOST_TEST_DYN_LINK
34 #include <boost/test/auto_unit_test.hpp>
36 using namespace boost::unit_test;
37 namespace mp = metaproxy_1;
39 typedef std::pair<std::string, std::string> string_pair;
40 typedef std::vector<string_pair> spair_vec;
41 typedef spair_vec::iterator spv_iter;
43 class FilterHeaderRewrite: public mp::filter::Base {
45 void process(mp::Package & package) const {
46 Z_GDU *gdu = package.request().get();
47 //map of request/response vars
48 std::map<std::string, std::string> vars;
50 if (gdu && gdu->which == Z_GDU_HTTP_Request)
52 Z_HTTP_Request *hreq = gdu->u.HTTP_Request;
54 //rewrite the request line
56 if (strstr(hreq->path, "http://") == hreq->path)
58 std::cout << "Path in the method line is absolute, "
59 "possibly a proxy request\n";
64 //TODO what about proto
65 path += z_HTTP_header_lookup(hreq->headers, "Host");
68 std::cout << "Proxy request URL is " << path << std::endl;
70 test_patterns(vars, path, req_uri_pats, req_groups_bynum);
71 std::cout << "Resp request URL is " << npath << std::endl;
73 hreq->path = odr_strdup(o, npath.c_str());
74 std::cout << ">> Request headers" << std::endl;
76 for (Z_HTTP_Header *header = hreq->headers;
78 header = header->next)
80 std::cout << header->name << ": " << header->value << std::endl;
81 std::string out = test_patterns(vars,
82 std::string(header->value),
83 req_uri_pats, req_groups_bynum);
85 header->value = odr_strdup(o, out.c_str());
87 package.request() = gdu;
90 gdu = package.response().get();
91 if (gdu && gdu->which == Z_GDU_HTTP_Response)
93 Z_HTTP_Response *hr = gdu->u.HTTP_Response;
94 std::cout << "Response " << hr->code;
95 std::cout << "<< Respose headers" << std::endl;
98 for (Z_HTTP_Header *header = hr->headers;
100 header = header->next)
102 std::cout << header->name << ": " << header->value << std::endl;
103 std::string out = test_patterns(vars,
104 std::string(header->value),
105 res_uri_pats, res_groups_bynum);
107 header->value = odr_strdup(o, out.c_str());
109 package.response() = gdu;
113 void configure(const xmlNode* ptr, bool test_only, const char *path) {};
116 * Tests pattern from the vector in order and executes recipe on
119 const std::string test_patterns(
120 std::map<std::string, std::string> & vars,
121 const std::string & txt,
122 const spair_vec & uri_pats,
123 const std::vector<std::map<int, std::string> > & groups_bynum_vec)
126 for (int i = 0; i < uri_pats.size(); i++)
128 std::string out = search_replace(vars, txt,
129 uri_pats[i].first, uri_pats[i].second,
130 groups_bynum_vec[i]);
131 if (!out.empty()) return out;
137 const std::string search_replace(
138 std::map<std::string, std::string> & vars,
139 const std::string & txt,
140 const std::string & uri_re,
141 const std::string & uri_pat,
142 const std::map<int, std::string> & groups_bynum) const
144 //exec regex against value
145 boost::regex re(uri_re);
147 std::string::const_iterator start, end;
151 while (regex_search(start, end, what, re)) //find next full match
154 for (i = 1; i < what.size(); ++i)
156 //check if the group is named
157 std::map<int, std::string>::const_iterator it
158 = groups_bynum.find(i);
159 if (it != groups_bynum.end())
161 std::string name = it->second;
162 if (!what[i].str().empty())
163 vars[name] = what[i];
167 //prepare replacement string
168 std::string rvalue = sub_vars(uri_pat, vars);
170 std::string rhvalue = what.prefix().str()
171 + rvalue + what.suffix().str();
172 std::cout << "! Rewritten '"+what.str(0)+"' to '"+rvalue+"'\n";
174 start = what[0].second; //move search forward
179 static void parse_groups(
180 const spair_vec & uri_pats,
181 std::vector<std::map<int, std::string> > & groups_bynum_vec)
183 for (int h = 0; h < uri_pats.size(); h++)
187 //regex is first, subpat is second
188 std::string str = uri_pats[h].first;
189 //for each pair we have an indexing map
190 std::map<int, std::string> groups_bynum;
191 for (int i = 0; i < str.size(); ++i)
193 if (!esc && str[i] == '\\')
198 if (!esc && str[i] == '(') //group starts
201 if (i+1 < str.size() && str[i+1] == '?') //group with attrs
204 if (i+1 < str.size() && str[i+1] == ':') //non-capturing
206 if (gnum > 0) gnum--;
210 if (i+1 < str.size() && str[i+1] == 'P') //optional, python
212 if (i+1 < str.size() && str[i+1] == '<') //named
217 while (++i < str.size())
219 if (str[i] == '>') { term = true; break; }
220 if (!isalnum(str[i]))
221 throw mp::filter::FilterException
222 ("Only alphanumeric chars allowed, found "
226 + boost::lexical_cast<std::string>(i));
230 throw mp::filter::FilterException
231 ("Unterminated group name '" + gname
232 + " in '" + str +"'");
233 groups_bynum[gnum] = gname;
234 std::cout << "Found named group '" << gname
235 << "' at $" << gnum << std::endl;
241 groups_bynum_vec.push_back(groups_bynum);
245 static std::string sub_vars (const std::string & in,
246 const std::map<std::string, std::string> & vars)
250 for (int i = 0; i < in.size(); ++i)
252 if (!esc && in[i] == '\\')
257 if (!esc && in[i] == '$') //var
259 if (i+1 < in.size() && in[i+1] == '{') //ref prefix
264 while (++i < in.size())
266 if (in[i] == '}') { term = true; break; }
269 if (!term) throw mp::filter::FilterException
270 ("Unterminated var ref in '"+in+"' at "
271 + boost::lexical_cast<std::string>(i));
272 std::map<std::string, std::string>::const_iterator it
274 if (it != vars.end())
281 throw mp::filter::FilterException
282 ("Malformed or trimmed var ref in '"
283 +in+"' at "+boost::lexical_cast<std::string>(i));
295 const spair_vec req_uri_pats,
296 const spair_vec res_uri_pats)
298 //TODO should we really copy them out?
299 this->req_uri_pats = req_uri_pats;
300 this->res_uri_pats = res_uri_pats;
302 parse_groups(req_uri_pats, req_groups_bynum);
303 parse_groups(res_uri_pats, res_groups_bynum);
307 std::map<std::string, std::string> vars;
308 spair_vec req_uri_pats;
309 spair_vec res_uri_pats;
310 std::vector<std::map<int, std::string> > req_groups_bynum;
311 std::vector<std::map<int, std::string> > res_groups_bynum;
316 BOOST_AUTO_TEST_CASE( test_filter_rewrite_1 )
320 FilterHeaderRewrite fhr;
327 BOOST_AUTO_TEST_CASE( test_filter_rewrite_2 )
331 mp::RouterChain router;
333 FilterHeaderRewrite fhr;
336 vec_req.push_back(std::make_pair(
337 "(?<proto>http\\:\\/\\/s?)(?<pxhost>[^\\/?#]+)\\/(?<pxpath>[^\\/]+)"
341 vec_req.push_back(std::make_pair(
347 vec_res.push_back(std::make_pair(
348 "(?<proto>http\\:\\/\\/s?)(?<host>[^\\/?#]+)\\/(?<path>[^ >]+)",
349 "http://${pxhost}/${pxpath}/${host}/${path}"
352 fhr.configure(vec_req, vec_res);
354 mp::filter::HTTPClient hc;
359 // create an http request
363 Z_GDU *gdu_req = z_get_HTTP_Request_uri(odr,
364 "http://proxyhost/proxypath/localhost:80/~jakub/targetsite.php", 0, 1);
366 pack.request() = gdu_req;
369 pack.router(router).move();
371 //analyze the response
372 Z_GDU *gdu_res = pack.response().get();
373 BOOST_CHECK(gdu_res);
374 BOOST_CHECK_EQUAL(gdu_res->which, Z_GDU_HTTP_Response);
376 Z_HTTP_Response *hres = gdu_res->u.HTTP_Response;
380 catch (std::exception & e) {
381 std::cout << e.what();
389 * c-file-style: "Stroustrup"
390 * indent-tabs-mode: nil
392 * vim: shiftwidth=4 tabstop=8 expandtab