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
47 Z_GDU *gdu = package.request().get();
48 //map of request/response vars
49 std::map<std::string, std::string> vars;
51 if (gdu && gdu->which == Z_GDU_HTTP_Request)
53 Z_HTTP_Request *hreq = gdu->u.HTTP_Request;
55 std::cout << ">> Request headers" << std::endl;
56 rewrite_reqline(o, hreq, vars);
57 rewrite_headers(o, hreq->headers, vars);
58 rewrite_body(o, &hreq->content_buf, &hreq->content_len, vars);
59 package.request() = gdu;
62 gdu = package.response().get();
63 if (gdu && gdu->which == Z_GDU_HTTP_Response)
65 Z_HTTP_Response *hres = gdu->u.HTTP_Response;
66 std::cout << "Response " << hres->code;
67 std::cout << "<< Respose headers" << std::endl;
69 rewrite_headers(o, hres->headers, vars);
70 rewrite_body(o, &hres->content_buf, &hres->content_len, vars);
71 package.response() = gdu;
75 void rewrite_reqline (mp::odr & o, Z_HTTP_Request *hreq,
76 std::map<std::string, std::string> & vars) const
78 //rewrite the request line
80 if (strstr(hreq->path, "http://") == hreq->path)
82 std::cout << "Path in the method line is absolute, "
83 "possibly a proxy request\n";
88 //TODO what about proto
89 path += z_HTTP_header_lookup(hreq->headers, "Host");
92 std::cout << "Proxy request URL is " << path << std::endl;
94 test_patterns(vars, path, req_uri_pats, req_groups_bynum);
95 std::cout << "Resp request URL is " << npath << std::endl;
97 hreq->path = odr_strdup(o, npath.c_str());
100 void rewrite_headers (mp::odr & o, Z_HTTP_Header *headers,
101 std::map<std::string, std::string> & vars) const
103 for (Z_HTTP_Header *header = headers;
105 header = header->next)
107 std::string sheader(header->name);
109 sheader += header->value;
110 std::cout << header->name << ": " << header->value << std::endl;
111 std::string out = test_patterns(vars,
113 req_uri_pats, req_groups_bynum);
116 size_t pos = out.find(": ");
117 if (pos == std::string::npos)
119 std::cout << "Header malformed during rewrite, ignoring";
122 header->name = odr_strdup(o, out.substr(0, pos).c_str());
123 header->value = odr_strdup(o, out.substr(pos+2,
124 std::string::npos).c_str());
129 void rewrite_body (mp::odr & o, char **content_buf, int *content_len,
130 std::map<std::string, std::string> & vars) const
134 std::string body(*content_buf);
136 test_patterns(vars, body, req_uri_pats, req_groups_bynum);
139 *content_buf = odr_strdup(o, nbody.c_str());
140 *content_len = nbody.size();
146 void configure(const xmlNode* ptr, bool test_only, const char *path) {};
149 * Tests pattern from the vector in order and executes recipe on
152 const std::string test_patterns(
153 std::map<std::string, std::string> & vars,
154 const std::string & txt,
155 const spair_vec & uri_pats,
156 const std::vector<std::map<int, std::string> > & groups_bynum_vec)
159 for (int i = 0; i < uri_pats.size(); i++)
161 std::string out = search_replace(vars, txt,
162 uri_pats[i].first, uri_pats[i].second,
163 groups_bynum_vec[i]);
164 if (!out.empty()) return out;
170 const std::string search_replace(
171 std::map<std::string, std::string> & vars,
172 const std::string & txt,
173 const std::string & uri_re,
174 const std::string & uri_pat,
175 const std::map<int, std::string> & groups_bynum) const
177 //exec regex against value
178 boost::regex re(uri_re);
180 std::string::const_iterator start, end;
184 while (regex_search(start, end, what, re)) //find next full match
187 for (i = 1; i < what.size(); ++i)
189 //check if the group is named
190 std::map<int, std::string>::const_iterator it
191 = groups_bynum.find(i);
192 if (it != groups_bynum.end())
194 std::string name = it->second;
195 if (!what[i].str().empty())
196 vars[name] = what[i];
200 //prepare replacement string
201 std::string rvalue = sub_vars(uri_pat, vars);
203 std::string rhvalue = what.prefix().str()
204 + rvalue + what.suffix().str();
205 std::cout << "! Rewritten '"+what.str(0)+"' to '"+rvalue+"'\n";
207 start = what[0].second; //move search forward
212 static void parse_groups(
213 const spair_vec & uri_pats,
214 std::vector<std::map<int, std::string> > & groups_bynum_vec)
216 for (int h = 0; h < uri_pats.size(); h++)
220 //regex is first, subpat is second
221 std::string str = uri_pats[h].first;
222 //for each pair we have an indexing map
223 std::map<int, std::string> groups_bynum;
224 for (int i = 0; i < str.size(); ++i)
226 if (!esc && str[i] == '\\')
231 if (!esc && str[i] == '(') //group starts
234 if (i+1 < str.size() && str[i+1] == '?') //group with attrs
237 if (i+1 < str.size() && str[i+1] == ':') //non-capturing
239 if (gnum > 0) gnum--;
243 if (i+1 < str.size() && str[i+1] == 'P') //optional, python
245 if (i+1 < str.size() && str[i+1] == '<') //named
250 while (++i < str.size())
252 if (str[i] == '>') { term = true; break; }
253 if (!isalnum(str[i]))
254 throw mp::filter::FilterException
255 ("Only alphanumeric chars allowed, found "
259 + boost::lexical_cast<std::string>(i));
263 throw mp::filter::FilterException
264 ("Unterminated group name '" + gname
265 + " in '" + str +"'");
266 groups_bynum[gnum] = gname;
267 std::cout << "Found named group '" << gname
268 << "' at $" << gnum << std::endl;
274 groups_bynum_vec.push_back(groups_bynum);
278 static std::string sub_vars (const std::string & in,
279 const std::map<std::string, std::string> & vars)
283 for (int i = 0; i < in.size(); ++i)
285 if (!esc && in[i] == '\\')
290 if (!esc && in[i] == '$') //var
292 if (i+1 < in.size() && in[i+1] == '{') //ref prefix
297 while (++i < in.size())
299 if (in[i] == '}') { term = true; break; }
302 if (!term) throw mp::filter::FilterException
303 ("Unterminated var ref in '"+in+"' at "
304 + boost::lexical_cast<std::string>(i));
305 std::map<std::string, std::string>::const_iterator it
307 if (it != vars.end())
314 throw mp::filter::FilterException
315 ("Malformed or trimmed var ref in '"
316 +in+"' at "+boost::lexical_cast<std::string>(i));
328 const spair_vec req_uri_pats,
329 const spair_vec res_uri_pats)
331 //TODO should we really copy them out?
332 this->req_uri_pats = req_uri_pats;
333 this->res_uri_pats = res_uri_pats;
335 parse_groups(req_uri_pats, req_groups_bynum);
336 parse_groups(res_uri_pats, res_groups_bynum);
340 spair_vec req_uri_pats;
341 spair_vec res_uri_pats;
342 std::vector<std::map<int, std::string> > req_groups_bynum;
343 std::vector<std::map<int, std::string> > res_groups_bynum;
348 BOOST_AUTO_TEST_CASE( test_filter_rewrite_1 )
352 FilterHeaderRewrite fhr;
359 BOOST_AUTO_TEST_CASE( test_filter_rewrite_2 )
363 mp::RouterChain router;
365 FilterHeaderRewrite fhr;
368 vec_req.push_back(std::make_pair(
369 "(?<proto>http\\:\\/\\/s?)(?<pxhost>[^\\/?#]+)\\/(?<pxpath>[^\\/]+)"
370 "\\/(?<host>[^\\/]+)(?<path>.*)",
371 "${proto}${host}${path}"
373 vec_req.push_back(std::make_pair(
379 vec_res.push_back(std::make_pair(
380 "(?<proto>http\\:\\/\\/s?)(?<host>[^\\/?#]+)\\/(?<path>[^ >]+)",
381 "http://${pxhost}/${pxpath}/${host}/${path}"
384 fhr.configure(vec_req, vec_res);
386 mp::filter::HTTPClient hc;
391 // create an http request
395 Z_GDU *gdu_req = z_get_HTTP_Request_uri(odr,
396 "http://proxyhost/proxypath/localhost:80/~jakub/targetsite.php", 0, 1);
398 pack.request() = gdu_req;
401 pack.router(router).move();
403 //analyze the response
404 Z_GDU *gdu_res = pack.response().get();
405 BOOST_CHECK(gdu_res);
406 BOOST_CHECK_EQUAL(gdu_res->which, Z_GDU_HTTP_Response);
408 Z_HTTP_Response *hres = gdu_res->u.HTTP_Response;
412 catch (std::exception & e) {
413 std::cout << e.what();
421 * c-file-style: "Stroustrup"
422 * indent-tabs-mode: nil
424 * vim: shiftwidth=4 tabstop=8 expandtab