TFCweb  1.0.4 $Rev: 483 $
TFC Primavera 2012: Nucli d'un servidor web
CGI.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 "CGI.h"
28 #include "Excepcions.h"
29 #include "Log.h"
30 #include "portabilitat.h"
31 #include "Transformacions.h"
32 
33 #include <array>
34 #include <cassert>
35 #include <boost/algorithm/string.hpp>
36 #include <boost/bind.hpp>
37 #include <boost/date_time.hpp>
38 #include <boost/lexical_cast.hpp>
39 #include <boost/process.hpp>
40 #include <boost/regex.hpp>
41 #include <boost/thread.hpp>
42 #include <boost/tokenizer.hpp>
43 
44 using namespace std;
45 using namespace boost;
46 using namespace boost::filesystem;
47 using boost::algorithm::to_lower_copy;
48 namespace proc = boost::process;
49 
50 namespace {
51 
53 class ErrorCodificacio : public ErrorTFC {
54 public:
55  ErrorCodificacio(const string & w = "Error en codificació de valor")
56  : ErrorTFC(w)
57  {
58  // buit
59  }
60 };
61 
62 } // ns anònim
63 
64 namespace tfc {
65 
66 ProgramaCGI::ProgramaCGI(const PeticioHTTP & peticio, const boost::filesystem::path &arrel_servidor)
67  throw (ErrorRutaInexistent)
68  : RecursDinamic(peticio), arrel_servidor_(arrel_servidor)
69 {
70  // Ruta del CGI:
71  ruta_ = arrel_servidor_ / peticio.ruta().ruta();
72  assert( exists(ruta_) && is_regular_file(ruta_) ); // PRECONDICIÓ
73  assert( portabilitat::permis_execucio(ruta_) ); // PRECONDICIÓ
74  // Salvaguardes, per si es violèssin les PREs amb asserts desactivats;
75  // tenint en compte que executarem l'arxiu millor assegurar el tret
76  if (!exists(ruta_) || !is_regular_file(ruta_)) {
77  throw ErrorRutaInexistent(peticio.ruta().ruta()+" no és un fitxer vàlid");
78  }
79  if (!portabilitat::permis_execucio(ruta_)) {
80  throw ErrorRutaInexistent("Configuració de permisos incorrecta");
81  }
82 }
83 
84 void ProgramaCGI::envia(asio::ip::tcp::socket & socket) const throw (std::runtime_error) {
85  std::array<char, MIDA_BUFFER> buf;
86 
87  // Referència: http://www.highscore.de/boost/gsoc2010/
88 
89  proc::context ctx;
90  // Comportament d'e/s estàndard. Per defecte s'hereten.
91  // null() descarta, però no tanca
92  if (peticio().te_cos()) {
93  ctx.streams[proc::stdin_id] = proc::behavior::pipe();
94  }
95  else {
96  ctx.streams[proc::stdin_id] = proc::behavior::null();
97  }
98  ctx.streams[proc::stdout_id] = proc::behavior::pipe();
99  ctx.streams[proc::stderr_id] = proc::behavior::pipe();
100  // L'entorn està en ctx.env. Per defecte s'hereta l'entorn del procés
101  // pare
102  // Variables estàndard:
103  // http://www.perlfect.com/articles/cgi_env.shtml
104  // http://www.tutorialspoint.com/perl/perl_cgi.htm
105  ctx.env = proc::environment(); // buidem
106  if (peticio().te_cos()) {
107  ctx.env["CONTENT_LENGTH"] = lexical_cast<string>(peticio().cos().lock()->mida());
108  }
109  else {
110  ctx.env["CONTENT_LENGTH"] = "0";
111  }
112  ctx.env["HTTP_COOKIE"] = peticio().capsalera("Cookie");
113  ctx.env["CONTENT_TYPE"] = peticio().capsalera("Content-Type");
114  ctx.env["DOCUMENT_ROOT"] = arrel_servidor_.string();
115  ctx.env["HTTP_REFERER"] = peticio().capsalera("Referer");
116  ctx.env["HTTP_USER_AGENT"] = peticio().capsalera("User-Agent");
117  // XXX: En realitat no s'implementa perquè script/args no es resol a script amb arguments /args
118  ctx.env["PATH_INFO"] = "";
119  ctx.env["PATH_TRANSLATED"] = "";
120  ctx.env["QUERY_STRING"] = peticio().ruta().query();
121  ctx.env["REMOTE_ADDR"] = socket.remote_endpoint().address().to_string();
122  ctx.env["REMOTE_HOST"] = socket.remote_endpoint().address().to_string(); // TODO: resoldre
123  ctx.env["REQUEST_METHOD"] = NomTipusPeticioHTTP::nom(peticio().tipus());
124  ctx.env["SCRIPT_NAME"] = peticio().ruta().ruta(); // Ruta "virtual"
125  ctx.env["SERVER_NAME"] = socket.local_endpoint().address().to_string();
126  ctx.env["SERVER_PORT"] = lexical_cast<string>(socket.local_endpoint().port());
127 
128  // http://www.w3.org/TR/html401/interact/forms.html#adef-enctype
129  // Els formularis en POST s'envien amb Content-Encoding
130  // application/x-www-form-urlencoded en condicions normals (nom=valor&nom=valor...,
131  // url encode, amb espais canviats a '+')
132  // multipart/form-data per a `input type="file"`
133  // De totes formes és tasca del CGI descodificar els valors
134  // http://www.tutorialspoint.com/perl/perl_cgi.htm
135 
136  // fork & exec
137  proc::child cgi = proc::create_child(ruta_.string(), vector<string>(), ctx);
138  proc::pistream is(cgi.get_handle(proc::stdout_id));
139  proc::pistream is_err(cgi.get_handle(proc::stderr_id));
140  if (peticio().te_cos()) {
141  // Síncron:
142  proc::postream os(cgi.get_handle(proc::stdin_id));
143  const string & cos = peticio().cos().lock()->contingut();
144  os.write(cos.c_str(), cos.size()); // Li passem el cos del missatge...
145  os.close(); // ...i tanquem l'entrada de dades del procés (important!)
146  }
147 
148  // Exhaureix les capçaleres generades pel CGI
149  stringstream capss;
150  // XXX: En les proves en Windows de CGIs un \r\n s'ha vist com a \r\r (???)
151  // Acceptem qualsevol combinació de CR i LF per terminar la línia
152  const regex re_final_caps("^[\r\n]*$");
153  while (!is.eof() && socket.is_open()) {
154  string linia;
155  getline(is, linia, '\n');
156  if (linia.empty() || regex_match(linia, re_final_caps)) {
157  // Final de les capçaleres
158  break;
159  }
160  capss << linia << '\n';
161  }
162  capss.write("\r\n", 2); // El final de les capçaleres ben marcat encara
163  // que el CGI no ho faci
164  this->envia_literal(socket, capss.str());
165 
166  // I a continuació, segueix enviant les dades generades pel CGI (el cos
167  // del missatge), codificades
168  while (!is.eof() && socket.is_open()) {
169  is.read(buf.data(), buf.size());
170  const streamsize bytes = is.gcount();
171  cerr << "Enviant: " << string(buf.data(), bytes) << endl;
172  this->envia(socket, string(buf.data(), bytes));
173  }
174  int ret = cgi.wait();
175  if (0 != ret) {
176  log() << NivellError
177  << "Error d'execució de CGI " << ruta_ << ": " << is_err.rdbuf() << commit;
178 
179  }
180 
181 #if 0
182  const bool cal_transformar = (0 != transformacio_.get());
183  while (ifs.good() && socket.is_open()) {
184  ifs.read(buffer.data(), buffer.size());
185  const streamsize bytes = ifs.gcount();
186 
187  string bloc(buffer.data(), bytes);
188  if (cal_transformar) {
189  bloc = transformacio_->transforma(bloc);
190  }
191  this->envia(socket, bloc);
192  }
193  if (!ifs.eof()) {
194  // Error
195  throw ErrorLlegintFitxer();
196  }
197  if (cal_transformar && socket.is_open()) {
198  this->envia(socket, transformacio_->finalitza());
199  }
200 #endif
201 }
202 
203 } // ns tfc
204 
205 // vim:set ts=4 et ai: //