49 #include <boost/algorithm/string.hpp>
50 #include <boost/filesystem.hpp>
51 #include <boost/lexical_cast.hpp>
52 #include <boost/regex.hpp>
54 using namespace boost;
55 using namespace boost::asio::ip;
58 namespace fs = boost::filesystem;
62 vector<string>
split(
const string & str,
char delim =
' ') {
66 while (getline(ss, ostr, delim)) {
97 const string & realm =
"")
118 const regex
regex_capsalera(
"^([\\w-]+): (.*)$", boost::regbase::perl);
123 explicit ErrorTimeout(
const string & w =
"Temps exhaurit esperant dades")
153 asio::mutable_buffers_1 &
buf;
154 system::error_code &
ec;
156 asio::mutable_buffers_1 & b,
157 system::error_code & e)
158 : socket(sock), buf(b), ec(e)
161 return socket.read_some(buf, ec);
169 Servidor::~Servidor() {
170 if (socket_->is_open()) {
178 const string& Servidor::signatura() {
190 while (buffer_.tellg() < buffer_.tellp() &&
191 ( pet ==
"" || pet ==
"\r" ) )
193 getline(buffer_, pet,
'\n');
196 const vector<string> tokens =
split(pet,
' ');
197 if (tokens.size() != 3) {
201 const string & verb = tokens[0];
202 string path = tokens[1];
204 if (
'\r' != * tokens[2].rbegin()) {
208 const string versio_txt = tokens[2].substr(0, tokens[2].length()-1);
211 if (
"HTTP/1.0" == versio_txt) {
214 else if (
"HTTP/1.1" == versio_txt) {
218 regex re(
"HTTP/(\\d+)\\.(\\d+)");
220 if (regex_match(versio_txt, m, re)) {
222 const int major = lexical_cast<
int>(m[1]);
231 default: assert( major > 1 );
236 catch (
const bad_lexical_cast &) {
261 if (path ==
"*" &&
OPTIONS != tipus) {
262 throw ErrorRutaRelativaEnPeticio();
267 assert(
OPTIONS == tipus || path !=
"*" );
277 throw ErrorHTTP(e.what(),
CODI_501);
279 assert( !
"Error intern, linia inabastable" );
282 size_t Servidor::llegeix_socket(stringstream & buffer,
285 tcp::socket & socket = *socket_;
286 if (!socket.is_open()) {
287 throw ErrorConnexioTancada();
289 boost::array<char, MIDA_BUFFER_LECTURA> tmp;
290 asio::mutable_buffers_1 buf(asio::buffer(tmp));
291 system::error_code ec;
298 packaged_task<size_t> pt(FunctorReadSome(socket,buf,ec));
299 unique_future<size_t> fut_llegits = pt.get_future();
301 boost::thread task(boost::move(pt));
305 fut_llegits.timed_wait(posix_time::millisec(timeout_ms));
307 if (!fut_llegits.is_ready()) {
308 throw ErrorTimeout();
310 if (fut_llegits.has_exception()) {
313 const size_t llegits = fut_llegits.get();
316 buffer.write(tmp.cbegin(), llegits);
318 if (ec == asio::error::eof) {
319 throw ErrorConnexioTancada();
328 if (buffer_.tellg() < buffer_.tellp()) {
331 return llegeix_socket(timeout_ms);
337 bool final_capsaleres =
false;
339 final_capsaleres = ( buffer_.tellg() == buffer_.tellp() );
344 while (!final_capsaleres && buffer_.tellg() < buffer_.tellp()) {
345 getline(buffer_, linia,
'\n');
349 <<
"Capçalera mal formada (sense \\r) [" << linia <<
"]" <<
commit;
352 if (1 == linia.size()) {
353 assert(
"\r" == linia );
356 final_capsaleres =
true;
359 linia.erase( linia.size() - 1 );
369 const string nom = m[1],
371 peticio.afegeix_capsalera(nom, valor);
375 <<
"Capçalera mal formada [" << linia <<
"]" <<
commit;
379 if (!final_capsaleres) {
382 }
while(!final_capsaleres);
383 assert( final_capsaleres );
387 : cfg(c), socket_(s), buffer_()
401 struct PeticioEnError {
402 PeticioEnError(std::unique_ptr<PeticioHTTP> & peticio) : peticio_(peticio) {}
404 if (0 != peticio_.get()) {
407 return peticio_erronia;
409 const std::unique_ptr<PeticioHTTP> & peticio_;
413 std::unique_ptr<RespostaHTTP> resposta;
414 std::unique_ptr<PeticioHTTP> peticio;
415 assert( 0 == resposta.get() && 0 == peticio.get());
419 PeticioEnError e_peticio(peticio);
422 const Uri host(
"http://" + lexical_cast<string>(socket_->local_endpoint()));
427 peticio.reset(
new PeticioHTTP(analitza_peticio()) );
429 tipus_peticio = peticio->
tipus();
434 <<
" (" << peticio->
versio() <<
")"
436 << socket_->remote_endpoint().address() <<
commit;
438 Uri url_completa(host);
441 this->extreu_capsaleres(*peticio);
443 this->extreu_cos(*peticio);
455 if (fs::exists(ruta_virtual)) {
456 ruta_virtual = fs::canonical(ruta_virtual);
469 const string auth = peticio->
capsalera(
"Authorization");
471 throw ErrorAutenticacio(
"Cal autenticació", cfg.realme());
476 if (!regex_match(auth, m, regex(
"^Basic\\s+(.*)", regbase::perl))) {
483 if (!regex_match(credencials, m, regex(
"^([^:]*):(.*)$"))) {
486 const string & usuari = m[1], & pass = m[2];
487 if (usuari != cfg.usuari() || pass != cfg.contrasenya()) {
488 throw ErrorAutenticacio(
"Credencials incorrectes", cfg.realme());
497 resposta = aten_peticio_escriptura(url_completa, *peticio);
500 resposta = aten_peticio_informacio(url_completa, *peticio);
506 resposta = aten_peticio_lectura(url_completa, *peticio);
509 catch (
const ErrorHTTP & e) {
511 std::shared_ptr<CosResposta> spcr(
new CosPaginaError(e_peticio(), e.
codi(), e.what()));
514 catch (
const ErrorAutenticacio & e) {
515 assert( 0 != peticio.get() );
521 catch (
const ErrorTimeout & e) {
522 if (0 == peticio.get()) {
525 <<
"Temps exhaurit esperant petició, es tanca la connexió" <<
commit;
529 assert( 0 != peticio.get() );
535 if (0 == peticio.get()) {
538 <<
"Socket tancat esperant petició" <<
commit;
542 assert( 0 != peticio.get() );
543 if (!socket_->is_open()) {
551 catch (
const runtime_error & e) {
556 const char * msg =
"Error intern del servidor";
557 std::shared_ptr<CosResposta> spcr(
new CosPaginaError(e_peticio(), resposta->
codi(), msg));
563 const char * msg =
"Error intern del servidor";
564 std::shared_ptr<CosResposta> spcr(
new CosPaginaError(e_peticio(), resposta->
codi(), msg));
568 assert( 0 != resposta.get() );
577 const bool peticio_rebuda = (0 != peticio.get());
578 bool pipeline = peticio_rebuda;
580 if (peticio_rebuda) {
583 const string connection = peticio->
capsalera(
"Connection");
584 if (
"close" == to_lower_copy(connection)) {
602 resposta->
afegeix_capsalera(
"X-Thread-Id", lexical_cast<string>(this_thread::get_id()));
606 const auto capsaleres_cos = resposta->
cos().lock()->capsaleres();
615 string allow =
"GET, HEAD, POST, TRACE, OPTIONS";
636 this->envia_resposta(tipus_peticio, *resposta.get());
644 if (pipeline && socket_->is_open()) {
665 const bool te_cos =
te_capsalera(peticio,
"Content-Length") ||
672 const string expect = peticio.capsalera(
"Expect");
674 bool envia_100 =
false;
682 if (!envia_100 && !expect.empty()) {
683 envia_100 = (expect ==
"100-continue");
687 envia_resposta(peticio.tipus(), continua);
691 stringstream buffer_cos;
692 std::array<char, MIDA_BUFFER_LECTURA> tempbuf;
694 while (buffer_.tellp() > buffer_.tellg()) {
695 const size_t llegits = buffer_.readsome(tempbuf.data(), tempbuf.size());
696 buffer_cos.write(tempbuf.data(), llegits);
729 const string te = to_lower_copy(peticio.capsalera(
"Transfer-Encoding"));
731 if (
"chunked" != te) {
734 assert(
"chunked" == te );
743 string descodificat = chunked.
destransforma(buffer_cos.str(), estat);
750 const size_t llegits = llegeix_socket(buffer_cos);
752 throw ErrorTimeout();
754 descodificat = chunked.
destransforma(buffer_cos.str(), estat);
759 cerr <<
"Final de transformació" << endl;
763 << descodificat.length() <<
commit;
764 buffer_cos.str(descodificat);
768 const string content_length = peticio.capsalera(
"Content-Length");
770 if (!regex_match(content_length, regex(
"^\\d+$"))) {
774 length = boost::lexical_cast<
size_t>(content_length);
781 while (length > buffer_cos.str().length()) {
782 llegeix_socket(buffer_cos);
784 const size_t rebuts = buffer_cos.str().length();
785 if (rebuts != length) {
787 lexical_cast<string>(rebuts)+
788 ") difereix de la indicada ("+
789 lexical_cast<string>(length)+
")",
794 const string & cos = buffer_cos.str();
795 std::shared_ptr<CosPeticio> cos_peticio(
new CosPeticio(cos.length()));
796 cos_peticio->afegeix(cos);
797 assert( cos_peticio->mida() == cos.length() );
798 peticio.defineix_cos(cos_peticio);
806 assert(
HEAD == peticio.tipus()
807 ||
GET == peticio.tipus()
808 ||
POST == peticio.tipus() );
812 cerr <<
"- Petició " << string(62,
'-') <<
"\n"
814 << string(72,
'-') << endl;
820 throw ErrorHTTP11SenseHost(
"Rebuda petició HTTP/1.1 sense capçalera host",
825 Recurs r(peticio, cfg.arrel());
826 FlagsPermis perms = cfg.cerber().permis(r.fisica());
827 assert( 0 != (perms &
LECTURA));
833 if (is_directory(r.fisica())) {
837 if (exists(index_path) && is_regular_file(index_path)) {
839 Uri nova_ruta(peticio.ruta());
840 nova_ruta.
ruta(nova_ruta.ruta()+
"/"+cfg.index());
841 peticio =
PeticioHTTP(peticio.tipus(), nova_ruta, peticio.versio());
842 r =
Recurs(peticio, cfg.arrel());
844 perms = cfg.cerber().permis(r.fisica());
849 if (is_directory(r.fisica())) {
850 assert( canonical(r.fisica()) == r.fisica() );
854 resposta->defineix_cos(std::shared_ptr<CosResposta>(ai));
856 else if (is_regular_file(r.fisica())) {
861 const bool ex = (0 != (perms &
EXECUCIO));
874 copia =
new Recurs(peticio, cfg.arrel());
876 resposta->defineix_cos(std::shared_ptr<CosResposta>(copia));
885 throw ErrorHTTP(
"No s'ha trobat en el servidor: "+peticio.ruta().ruta(),
CODI_404);
887 assert( 0 != resposta.get() );
894 using namespace boost::filesystem;
895 using boost::system::error_code;
903 " només està implementat en HTTP/1.1",
908 const path ruta_real(cfg.arrel() /
path(p.ruta().ruta()));
910 const bool existeix = exists(ruta_real);
913 if (existeix && !is_regular_file(ruta_real)) {
917 if (p.tipus() ==
PUT) {
931 size_t content_length = 0;
932 for (
auto it = p.capsaleres().begin(); it != p.capsaleres().end(); ++it) {
933 if (
"content-length" == to_lower_copy(it->nom())) {
935 content_length = lexical_cast<
size_t>(it->valor());
938 catch (
const boost::bad_lexical_cast & e) {
944 if (content_length == 0) {
951 ofstream ofs(ruta_real.string().c_str(), ios::out|ios::binary|ios::trunc);
953 throw ErrorHTTPIntern(
"Error en obrir `"+p.ruta().ruta()+
"` per escriure'n",
956 const auto pCos = p.cos().lock();
957 ofs.write(pCos->contingut().c_str(), pCos->mida());
980 remove(ruta_real.string(), ec);
982 throw ErrorHTTP(
"Error esborrant el recurs "+p.ruta().ruta()+
": "+
983 ec.category().message(ec.value())
998 if (
TRACE == p.tipus()) {
1007 resposta->afegeix_capsalera(
"Content-Type",
"text/html");
1008 resposta->afegeix_capsalera(
"Content-Length",
1009 lexical_cast<string>(buffer_.str().length()));
1011 std::shared_ptr<CosResposta> sptrCos(pCos);
1012 resposta->defineix_cos(sptrCos);
1014 else { assert(
OPTIONS == p.tipus() );
1028 resposta->afegeix_capsalera(
"Content-Length",
"0");
1042 r.elimina_capsalera(
"Content-Length");
1048 ostringstream oss_resposta;
1052 oss_resposta <<
"HTTP/1.1 " << lexical_cast<
string>(r.codi())
1053 <<
" " << frase <<
"\r\n";
1054 r.imprimeix_capsaleres(oss_resposta,
"\r\n");
1061 if (!r.cos().lock()->pot_generar_capsaleres()) {
1062 oss_resposta <<
"\r\n";
1066 oss_resposta <<
"\r\n";
1069 asio::write(*socket_, asio::buffer(oss_resposta.str()));
1071 catch (
const boost::system::system_error & e) {
1079 assert( !r.cos().expired() );
1080 auto cos = r.cos().lock();
1082 cos->envia(*socket_);
1083 cos->envia_eom(*socket_);
1088 catch (
const std::runtime_error & e) {
1099 void Servidor::tancar_sessio() {