TFCweb  1.0.4 $Rev: 483 $
TFC Primavera 2012: Nucli d'un servidor web
Uri.cc
Veure la documentació d'aquest fitxer.
1 
8 /*
9  * Copyright (c) 2012 Toni Corvera
10  *
11  * This file is part of TFCWeb.
12  *
13  * TFCWeb is free software: you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation, either version 3 of the License, or
16  * (at your option) any later version.
17  *
18  * TFCWeb is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with TFCWeb. If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 #include "Uri.h"
28 #include "utils.h"
29 
30 #include <cassert>
31 #include <iostream>
32 #include <locale>
33 #include <string>
34 #include <vector>
35 #include <boost/algorithm/string.hpp>
36 #include <boost/array.hpp>
37 #include <boost/assign.hpp>
38 #include <boost/filesystem.hpp>
39 #include <boost/lexical_cast.hpp>
40 #include <boost/tokenizer.hpp>
41 #include <boost/xpressive/xpressive_static.hpp>
42 
43 using namespace boost;
44 using namespace std;
45 using namespace boost::xpressive;
46 
47 namespace bx = boost::xpressive;
48 
49 namespace {
50 
51 using namespace tfc;
52 
53 // Inserció estàndard:
54 // std::copy(comp.begin(), comp.end(), ostream_iterator<string>(oss, "/"));
55 template<typename S, typename I> S join_r(const I & beg, const I & end, const S & sep) {
56  S ret;
57  for (I it = beg; it != end; ++it) {
58  ret.append(sep);
59  ret.append(*it);
60  }
61  return ret;
62 }
63 
68 template<typename TipusStr>
70  typedef typename TipusStr::value_type TipusChar;
71  TipusStr & desti;
72  FunctorCodificacio(TipusStr & destinacio) : desti(destinacio) {}
73  static bool cal_codificar(TipusChar c) {
74  static boost::array<char,15> NO_SEGURS = { { '{', '}', '|', '\\', '^', '~', '[', ']', '.',
75  // Reservables segons esquema:
76  '/', '?', ':', '@', '=', '&'
77  } };
78  return (c > 127) || // fora d'US-ASCII
79  (c < 0x1F) || (c == 0x7F) || // caràcters de control
80  (NO_SEGURS.end() != find(NO_SEGURS.begin(), NO_SEGURS.end(), c)) // no segurs
81  ;
82  }
83 
84  void operator()(const TipusChar & c) {
85  // Caracters no segurs (FIXME:)
86  // Caracters fora de rang US-ASCII
87  if (cal_codificar(c)) {
88  string hex = utils::to_string<int>(c, std::hex);
89  boost::to_upper(hex);
90  desti.append("%"+hex);
91  return;
92  }
93  desti.append(1, c);
94  }
95 };
96 
100 string reconstrueix(const string & esquema, const string & autoritat,
101  const string & ruta, const string & params,
102  const string & query, const string & fragment)
103 {
104  ostringstream ss;
105  if (!esquema.empty()) {
106  ss << esquema << ":";
107  }
108 
109  if (!autoritat.empty()) {
110  ss << "//" << autoritat;
111  }
112 
113  ss << ruta;
114 
115  if (!params.empty()) {
116  ss << ';' << params;
117  }
118 
119  if (!query.empty()) {
120  ss << '?' << query;
121  }
122 
123  if (!fragment.empty()) {
124  ss << '#' << fragment;
125  }
126  return ss.str();
127 }
128 
135 inline string reconstrueix(const Uri & u) {
136  return reconstrueix(u.esquema(), u.autoritat(), u.ruta(),
137  u.params(), u.query(), u.fragment());
138 }
139 
140 } // ns anònim
141 
142 // ------- Sintaxi d'URIs --------
143 //
144 // Regles extretes de la secció "Augmented BNF" de l'RFC 2068 (pàg. 15)
145 // expressades amb Boost Xpressive.
146 //
147 // RFC 2068, pg.18, 19:
148 //
149 // URI = ( absoluteURI | relativeURI ) [ "#" fragment ]
150 //
151 // absoluteURI = scheme ":" *( uchar | reserved )
152 // relativeURI = net_path | abs_path | rel_path
153 //
154 // net_path = "//" net_loc [ abs_path ]
155 // abs_path = "/" rel_path
156 // rel_path = [ path ] [ ";" params ] [ "?" query ]
157 //
158 // path = fsegment *( "/" segment )
159 // fsegment = 1*pchar
160 // segment = *pchar
161 //
162 // params = param *( ";" param )
163 // param = *( pchar | "/" )
164 //
165 // scheme = 1*( ALPHA | DIGIT | "+" | "-" | "." )
166 // net_loc = *( pchar | ";" | "?" )
167 //
168 // query = *( uchar | reserved )
169 // fragment = *( uchar | reserved )
170 //
171 // pchar = uchar | ":" | "@" | "&" | "=" | "+"
172 // uchar = unreserved | escape
173 // unreserved = ALPHA | DIGIT | safe | extra | national
174 //
175 // escape = "%" HEX HEX
176 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+"
177 // extra = "!" | "*" | "’" | "(" | ")" | ","
178 // safe = "$" | "-" | "_" | "."
179 // unsafe = CTL | SP | <"> | "#" | "%" | "<" | ">"
180 // national = <any OCTET excluding ALPHA, DIGIT,
181 // reserved, extra, safe, and unsafe>
182 //
183 // ----------------------------------------------------------------------
184 //
185 // En Xpressive els parèntesis no capturen, només agrupen.
186 // La diferència entre `set[ ]` i `( )` és que els sets es poden negar amb `~`
187 namespace RFC2068_Sintaxi_URIs {
188 
189  // unsafe = CTL | SP | <"> | "#" | "%" | "<" | ">"
190 const sregex unsafe = imbue(locale::classic())(bx::set[ cntrl | char(32) | '"'
191  | '#' | '%' | '<' | '>' ])
192  // safe = "$" | "-" | "_" | "."
193  ,safe = bx::set[ as_xpr('$') | '-' | '_' | '.' ]
194  // extra = "!" | "*" | "’" | "(" | ")" | ","
195  ,extra = bx::set[ as_xpr('!') | '*' | '\'' | '(' | ')' | ',' ]
196  // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+"
197  ,reserved = bx::set[ as_xpr(';') | '/' | '?' | ':' | '@' | '&' | '=' | '+' ]
198  ,escape = '%' >> xdigit >> xdigit // "%" HEX HEX
199  // national no compila amb referències (normals o by_ref() a reserved, etc.)
200  // national = <any OCTET excluding ALPHA, DIGIT, reserved, extra, safe, and unsafe>
201  ,national = imbue(locale::classic())(~bx::set[ alpha | digit
202  | ';' | '/' | '?' | ':' | '@' | '&' | '=' | '+' // reserved
203  | '!' | '*' | '\'' | '(' | ')' | ',' // extra
204  | '$' | '-' | '_' | '.' // safe
205  | cntrl | char(32) | '"' | '#' | '%' | '<' | '>' // unsafe
206  ])
207 ;
208 
209  // unreserved = ALPHA | DIGIT | safe | extra | national
210 const sregex unreserved = imbue(locale::classic())( alpha | digit | safe | extra | national );
211 const sregex uchar = ( unreserved | escape ); // = unreserved | escape
212  // pchar = uchar | ":" | "@" | "&" | "=" | "+"
213 const sregex pchar = ( uchar | ':' | '@' | '&' | '=' | '+' ) ;
214 const sregex query = *( uchar | reserved ) // = *( uchar | reserved )
215  ,fragment = *( uchar | reserved ) // = *( uchar | reserved )
216 ;
217 const sregex net_loc = *( pchar | ';' | '?' ) // = *( pchar | ";" | "?" )
218  // scheme = 1*( ALPHA | DIGIT | "+" | "-" | "." )
219  ,scheme = +( alpha | digit | '+' | '-' | '.' )
220 ;
221 const sregex param = *( pchar | '/' ); // *( pchar | "/" )
222 const sregex params = param >> *( ';' >> param ); // = param *( ";" param )
223 
224 const sregex segment = *pchar // = *pchar
225  ,fsegment = +pchar; // = 1*pchar
226 const sregex path = fsegment >> *( '/' >> segment ); // = fsegment *( "/" segment )
227 
228  // rel_path = [ path ] [ ";" params ] [ "?" query ]
229 const sregex rel_path = !path >> !( ';' >> params ) >> !( '?' >> query );
230 const sregex abs_path = '/' >> rel_path ; // = "/" rel_path
231 const sregex net_path = "//" >> net_loc >> !abs_path; // = "//" net_loc [ abs_path ]
232 
233  // absoluteURI = scheme ":" *( uchar | reserved )
234 const sregex absoluteURI = scheme >> ':' >> *( uchar | reserved );
235  // relativeURI = net_path | abs_path | rel_path
236 const sregex relativeURI = net_path | abs_path | rel_path ;
237 
238  // URI = ( absoluteURI | relativeURI ) [ "#" fragment ]
239 const sregex URI = ( absoluteURI | relativeURI ) >> !( '#' >> fragment ) ;
240 
241 // Extracció de components
242 const sregex
243  // La definició d'URI és massa genèrica ("greedy") per aplicar en extraccions
244  // 1: esquema, 2: URL (relativa), 3: fragment
245  ex_URI = !( (s1=scheme) >> ':' ) >> ( s2=relativeURI ) >> !( '#' >> (s3=fragment) ),
246  // 1: host, 2: ruta
247  ex_net_path = "//" >> (s1 = net_loc) >> (s2 = !abs_path),
248  // 1: ruta, 2: paràmetres, 3: query string
249  ex_rel_path = !( s1=path ) >> !( ';' >> (s2=params) ) >> !( '?' >> (s3=query) )
250 ;
251 
252 } // ns RFC2068_Sintaxi_URIs
253 
254 namespace {
259 inline bool es_parseable(const string & s) {
260  return regex_match(s, RFC2068_Sintaxi_URIs::URI);
261 }
262 } // ns anònim (continuació)
263 
264 namespace tfc {
265 
266 Uri::Uri(const string & uri) throw (ErrorAnalisiUrl) {
267  using namespace RFC2068_Sintaxi_URIs;
268 
269  if (!regex_match(uri, URI)) {
270  throw ErrorAnalisiUrl();
271  }
272  smatch m;
273  regex_match(uri, m, ex_URI);
274  const string esquema = m[1], relativa = m[2], fragment = m[3];
275  assert( regex_match(relativa, relativeURI) );
276  bool es_net_path = regex_match(relativa, net_path),
277  es_abs_path = regex_match(relativa, abs_path),
278  es_rel_path = regex_match(relativa, rel_path);
279  assert( es_net_path | es_abs_path | es_rel_path );
280  string netloc, abspath, relpath;
281  if (es_net_path) {
282  regex_match(relativa, m, ex_net_path);
283  netloc = m[1];
284  abspath = m[2];
285  }
286  else if (es_abs_path) {
287  abspath = relativa;
288  }
289  else if (es_rel_path) {
290  relpath = relativa;
291  }
292  if (!abspath.empty()) {
293  assert( abspath[0] == '/' );
294  relpath = abspath.substr(1);
295  }
296  string ruta, params, query;
297  if (!relpath.empty()) {
298  regex_match(relpath, m, ex_rel_path);
299  ruta = m[1];
300  params = m[2];
301  query = m[3];
302  }
303  if (!abspath.empty()) {
304  ruta = "/" + ruta;
305  }
306 
307  esquema_ = esquema;
308  autoritat_ = netloc;
309  ruta_ = descodifica(ruta); // throws ErrorAnalisiUrl
310  query_ = query;
311  fragment_ = fragment;
312  params_ = params;
313  // FIXME: Què més cal descodificar?
314 } // Uri::Uri()
315 
316 Uri& Uri::operator=(const Uri & u) {
317  esquema_ = u.esquema_;
318  autoritat_ = u.autoritat_;
319  ruta_ = u.ruta_;
320  params_ = u.params_;
321  query_ = u.query_;
322  fragment_ = u.fragment_;
323  return *this;
324 }
325 
326 void Uri::ruta(const string & r) throw (ErrorAnalisiUrl) {
327  ruta_ = descodifica(r);
328 }
329 
330 bool Uri::operator==(const Uri & u) const {
331  if (this == &u) {
332  return true;
333  }
334  return str() == u.str(); // Equivalència
335 }
336 
337 bool Uri::es_uri_absoluta() const {
338  return esquema_.length() != 0 && autoritat_.length() != 0;
339 }
340 
341 bool Uri::te_ruta_absoluta() const {
342  return ruta_.empty() || ruta_.at(0) == '/';
343 }
344 
345 string Uri::str() const {
346  return reconstrueix(*this);
347 }
348 
349 // FIXME: Portabilitat de path, veure http://www.boost.org/doc/libs/1_49_0/libs/filesystem/v3/doc/reference.html#Path-decomposition-table
350 
351 // static
352 Uri Uri::normalitza(const Uri & u) {
353  assert( es_parseable(u) ); // INVARIANT de Uri
354 
355  using filesystem::path;
356  string p = u.ruta();
357  // Casos trivials
358  if (p.empty() || p == "/") {
359  return u;
360  }
361  if (p == ".") {
362  Uri copia(u);
363  copia.ruta_.clear();
364  return copia;
365  }
366 
367  // Eliminació de barres repetides
368  const sregex dblbarra = as_xpr('/') >> +as_xpr('/'); // = regex("//+")
369  p = regex_replace(p, dblbarra, "/"); // , boost::match_default | boost::format_perl);
370  if (p == "/") {
371  Uri copia(u);
372  copia.ruta_.swap(p);
373  return copia;
374  }
375  // Interpretació de '.' i '..'
376  const char_separator<char> sepdir("/");
377  tokenizer< char_separator<char> > tokens(p, sepdir);
378 
379  vector<string> comp;
380  for (auto it = tokens.begin(); it != tokens.end(); ++it) {
381  const string & elem = *it;
382  if (elem == ".") {
383  // Ignorar
384  comp.push_back(string());
385  continue;
386  }
387  if (elem == "..") {
388  // Elimina el directori actual, si n'hi ha
389  if (!comp.empty()) {
390  comp.pop_back();
391  }
392  continue;
393  }
394  comp.push_back(elem);
395  }
396 
397  string nova_ruta = join_r(comp.begin(), comp.end(), string("/"));
398  // Retindre "/" a l'inici si en tenia i l'ha perdut
399  if (nova_ruta.empty() && !p.empty() && p.at(0) == '/') {
400  nova_ruta = "/";
401  }
402  const string nova_url = reconstrueix(u.esquema(), u.autoritat(),
403  nova_ruta, u.params(),
404  u.query(), u.fragment());
405  assert( es_parseable(nova_url) );
406  return Uri(nova_url);
407 }
408 
409 // static
410 string Uri::descodifica(const string & s, bool & error) throw() {
411  string ret;
412  ret.reserve(s.length());
413  error = false;
414  // Caràcters hexadecimals, expressió regular per simplicitar comprovació
415  // en xpressive "xdigit >> xdigit" (= '[[:xdigit:][:xdigit:]]')
416  using namespace boost::xpressive;
417  const sregex hex_re = xdigit >> xdigit;
418  string::size_type idx = 0, percent;
419  while (string::npos != (percent = s.find('%', idx))) {
420  assert( s.at(percent) == '%' );
421  // copiem la part de la cadena saltada [idx, percent):
422  ret.append(s, idx, percent-idx);
423  // comprovem que es tracta d'un codi hexadecimal vàlid
424  // si no fós el cas ho acceptem, però marquem l'error
425  if (s.length() < (percent+3)) {
426  error = true;
427  idx = percent+1;
428  ret.append(1, s.at(percent));
429  continue;
430  }
431  assert( s.length() >= (percent+3) );
432  const string hex(s.substr(percent+1, percent+3), 0, 2);//0,2 necessari!
433  assert( hex.length() == 2 );
434  if (!regex_match(hex, hex_re)) {
435  // Caràcters NO hexadecimals, com abans marquem error i saltem
436  error = true;
437  idx = percent+1;
438  ret.append(1, s.at(percent));
439  continue;
440  }
441  const int C = tfc::utils::from_string<int>(hex, std::hex);
442  assert( C <= 0xFF ); // dos dígits hexadecimals <-> valors d'un byte
443  const char c = static_cast<char>(C);
444  ret.append(1, c);
445  idx = percent+3;
446  }
447  // resta
448  ret.append(s.substr(idx));
449  return ret;
450 }
451 
452 // static
453 string Uri::descodifica(const string & url) throw (ErrorAnalisiUrl) {
454  bool e;
455  const string s = descodifica(url, e);
456  if (e) {
457  throw ErrorAnalisiUrl("Error descodificant URL");
458  }
459  return s;
460 }
461 
462 // static
463 string Uri::codifica(const string & s) throw () {
464  string ret;
465  ret.reserve(s.length()); // mínim
466  for_each(s.begin(), s.end(), FunctorCodificacio<string>(ret));
467  return ret;
468 }
469 
470 // friend
471 ostream& operator<<(ostream & os, const Uri & u) {
472 #if 0
473  return os << "[" << u.esquema() << ", " << u.autoritat()
474  << ", " << u.ruta() << ", " << u.query() << ", "
475  << u.fragment() << "]";
476 #endif
477  return os << string(u);
478 }
479 
480 } // ns tfc
481 
482 // vim:set ts=4 et ai: //