TFCweb  1.0.4 $Rev: 483 $
TFC Primavera 2012: Nucli d'un servidor web
Recurs.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 "DataHTTP.h"
28 #include "Recurs.h"
29 #include "TipusMIME.h"
30 #include "utils.h"
31 
32 #include <array>
33 #include <cassert>
34 #include <fstream>
35 #include <sstream>
36 #include <boost/asio.hpp>
37 #include <boost/tokenizer.hpp>
38 #include <boost/lexical_cast.hpp>
39 #include <boost/regex.hpp>
40 
41 using namespace boost::asio::ip;
42 using namespace boost::filesystem;
43 using namespace std;
44 
45 namespace {
46  enum {
47  MIDA_BUFFER = 8192
48  };
49 
51  template<typename T> string hexadecimal(const T & t) {
52  ostringstream oss;
53  oss << std::hex << t;
54  return oss.str();
55  }
56 } // ns anònim
57 
58 namespace tfc {
59 
60 // FIXME: rutes com /INEXISTENT/../ són vàlides en Apache i webfsd, però perquè wget les normalitza
61 // curl les envia tal qual, webfsd no les normalitza, apache sí
62 // FIXME: Url decoding Re-FIXME: Incorporar en Disseny com amb Base64
63 
64 Recurs::Recurs(const PeticioHTTP &peticio,
65  const boost::filesystem::path &arrel_servidor) throw (ErrorRutaInexistent)
66  : CosResposta(peticio), arrel_servidor_(arrel_servidor)
67 {
68  // PRECONDICIONS: arrel_servidor existeix, és un directori, és absoluta i canònica
69  assert( exists(arrel_servidor) && is_directory(arrel_servidor) );
70  assert( arrel_servidor.is_absolute() );
71  assert( canonical(arrel_servidor) == arrel_servidor );
72 
73  const string ruta_peticio = peticio.ruta().ruta();
74  const path arrel_i_peticio(arrel_servidor_ / path(ruta_peticio));
75  if (!exists(arrel_i_peticio)) {;
76  throw ErrorRutaInexistent("Ruta inexistent: " + ruta_peticio);
77  }
78  assert( exists(arrel_i_peticio) );
79  const path ruta_en_disc(canonical(arrel_i_peticio));
80 
81  // FIXME: Assegurar que està dintre del servidor
82  //path ruta_en_servidor(ruta_peticio);
83 
84  // FIXME: Representació POSIX de regex[^/] en Windows???
85 
86  fisica_ = ruta_en_disc;
87  // POSTCONDICIONS: NR.ruta_fisica és absoluta i canònica
88  assert( fisica_.is_absolute() );
89  assert( canonical(fisica_) == fisica_ );
90 }
91 
92 void Recurs::envia(tcp::socket & socket) const throw (runtime_error)
93 {
94 #if 0
95  istream_iterator<char[MIDA_BUFFER]> begin(ifs), end();
96  ostream_iterator<char[MIDA_BUFFER]> osi(gc.osi());
97 #endif
98 
99  ifstream ifs(fisica_.string().c_str(), ifstream::in | ifstream::binary);
100  //ifs.exceptions( ifstream::failbit | ifstream::badbit );
101  std::array<char, MIDA_BUFFER> buffer;
102 
103  // FIXME: Són INVARIANTS?
104  // invariant de Recurs
105  assert( canonical(fisica_) == fisica_ );
106  // => symlinks seguits
107  assert( is_regular_file(fisica_) );
108 
109  while (ifs.good() && socket.is_open()) {
110  ifs.read(buffer.data(), buffer.size());
111  const streamsize bytes = ifs.gcount();
112  const string bloc(buffer.data(), bytes);
113  this->envia(socket, bloc);
114  }
115  if (!ifs.eof()) {
116  // Error
117  throw ErrorLlegintFitxer();
118  }
119 }
120 
121 Recurs::tipus_capsaleres Recurs::capsaleres() const {
122  using namespace boost;
123 
124  assert( is_regular_file(fisica()) );
125 
126  Recurs::tipus_capsaleres capsaleres = CosResposta::capsaleres();
127  const string mida = lexical_cast<string>(file_size(fisica()));
128  const time_t mtime = filesystem::last_write_time(fisica());
129  capsaleres.push_back( make_pair("Last-Modified", data_http(mtime)) );
130  capsaleres.push_back( make_pair("ETag", this->entity_tag()) );
131  const string mime = RegistreTipusMIME::tipus(fisica());
132  capsaleres.push_back( make_pair("Content-Type", mime) );
133 
134  // RFC 2616, 4.3 "
135  // If a Content-Length header field (section 14.13) is present, its
136  // decimal value in OCTETs represents both the entity-length and the
137  // transfer-length. The Content-Length header field MUST NOT be sent
138  // if these two lengths are different
139  // "
140  if (0 == transformacio().get()) {
141  // No es transformarà <=> la mida és coneguda
142  capsaleres.push_back( make_pair("Content-Length", mida) );
143  }
144 
145  return capsaleres;
146 }
147 
148 string Recurs::entity_tag() const {
149  using utils::to_string;
150  // Elements que composen l'ETag:
151  // - marca de temps de modificació
152  // - mida
153  // - ruta física
154  const time_t mtime = last_write_time(fisica_);
155  const size_t mida = file_size(fisica_);
156  // Re-utilitzem el hasher std::hash
157  const size_t hsh = hash<string>()(fisica_.string());
158 
159  // W marca "weak" (no s'asegura unicitat)
160  // RFC 2616, pàg. 126, exemples: ETag: "xyzzy"; ETag: W/"xyzzy"; ETag: ""
161  return "W/\"" + hexadecimal(hsh) + "-" +
162  hexadecimal(mida) + "-" +
163  hexadecimal(mtime) + "\"";
164 }
165 
166 } // ns tfc
167 
168 // vim:set ts=4 et ai: //