TFCweb  1.0.4 $Rev: 483 $
TFC Primavera 2012: Nucli d'un servidor web
portabilitat.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 "portabilitat.h"
28 
29 #include <cstdlib>
30 #include <iostream>
31 #include <string>
32 #include <boost/algorithm/string.hpp>
33 #include <boost/scoped_array.hpp>
34 #include <boost/thread.hpp>
35 
36 using boost::algorithm::to_lower_copy;
37 namespace fs = boost::filesystem;
38 
39 /*
40  * Sortida UTF-8 a la consola a Windows
41  *
42  * No ho he aconseguit. Mètodes provats:
43  * - Canviar la pàgina de codis de la consola
44  * Codis de pàgina: http://msdn.microsoft.com/en-us/library/dd317756(VS.85).aspx
45  * GetConsoleOutputCP: http://msdn.microsoft.com/en-us/library/ms683169(VS.85).aspx
46  * SetConsoleOutputCP: http://msdn.microsoft.com/en-us/library/ms686036(VS.85).aspx
47  * - Canvi de mode de la consolaa UTF-8 o UTF-16
48  * http://blogs.msdn.com/b/michkap/archive/2008/03/18/8306597.aspx
49  * _setmode: http://msdn.microsoft.com/library/tw4k6df8(VS.80).aspx
50  */
51 
52 #ifdef _WIN32
53 # include <windows.h>
54 # include <fcntl.h>
55 # include <io.h>
56 # include <stdio.h>
65 # define _CRT_SECURE_NO_WARNINGS // Sembla que no funciona
66 # ifdef _MSC_VER
67 # pragma warning( disable: 4996 ) // Warning associat a funcions insegures
68 # endif
69 #endif
70 
71 #undef TFC_CANVI_DE_PAGINA
72 #undef TFC_CANVI_DE_MODE
73 
74 namespace {
75 #ifdef _WIN32
76 
77 # ifdef TFC_CANVI_DE_PAGINA
78 UINT codepage_inicial;
79 # endif
80 
81 #if 0
82 // Conversió UTF-8 a wstr. A prop, però no funciona bé
83 std::wstring utf_a_wstr(const std::string& s)
84 {
85  const int slength = (int)s.length() + 1;
86  const int len = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), slength, 0, 0);
87  wchar_t * buf = 0;
88  try {
89  buf = new wchar_t[len];
90  MultiByteToWideChar(CP_UTF8, 0, s.c_str(), slength, buf, len);
91  std::wstring r(buf);
92  delete[] buf;
93  return r;
94  }
95  catch (...) {
96  delete [] buf;
97  }
98 }
99 #endif
100 
101 inline void canvia_consola_a_utf8() {
102 # ifdef TFC_CANVI_DE_PAGINA
103  codepage_inicial = ::GetConsoleOutputCP();
104  ::SetConsoleOutputCP(CP_UTF8);
105 # endif
106 # ifdef TFC_CANVI_DE_MODE
107  _setmode(_fileno(stdout), _O_U8TEXT);
108  _setmode(_fileno(stderr), _O_U8TEXT);
109 # endif
110 }
111 
112 void restaura_consola() {
113 # ifdef TFC_CANVI_DE_PAGINA
114  // Restaura el codi de pàgina
115  ::SetConsoleOutputCP(codepage_inicial);
116 # endif
117 }
118 #else /* ! WIN32 */
119 # define canvia_consola_a_utf8() /* res */
120 # define restaura_consola() /* res */
121 #endif
122 
125 }
126 
127 } // ns anònim
128 
129 // Només glibc >= 2.1 garantitza que es puguin fer servir
130 // getenv i familia en programes multi-fil de forma segura
131 // http://www.delorie.com/gnu/docs/glibc/libc_554.html
133 boost::mutex env_mtx;
134 
135 namespace tfc {
136 
137 namespace portabilitat {
138 
139 using namespace std;
140 
141 void init() {
142  std::atexit(exit_callback);
144  ::setlocale(LC_TIME, "C"); // XXX: Necessari?
145 }
146 
147 // https://www.securecoding.cert.org/confluence/plugins/viewsource/viewpagesrc.action?pageId=1703960
148 // "
149 // The C standard says getenv() has the following behavior [ISO/IEC 9899:2011]:
150 // The getenv function returns a pointer to a string associated with the matched
151 // list member. The string pointed to shall not be modified by the program but may
152 // be overwritten by a subsequent call to the getenv function.
153 // "
154 
155 //
156 
157 string getenv(const string & var, bool * existeix) {
158  boost::unique_lock<boost::mutex> lock(env_mtx);
159 #if 0
160  // Codi (teòricament) més robust per a Windows
161  // getenv_s només està definida en versions recents d'stdlib.h.
162  //
163  // Amb MSVC 2010/2011 no hi ha problema, però amb Mingw sí ja que només
164  // fa servir msvcrt.dll per defecte.
165  // És possible enllaçar a versions que incloguin les versions *_s (p.e. -lmsvcr80)
166  // però he considerat que no val la pena perquè, estrictament parlant, la seguretat
167  // que aporta getenv_s no és gaire; només evita que crides consecutives
168  // re-escriguin el buffer, però no és "thread-safe" i igualment cal serialitzar
169  // les crides, per això n'hi ha prou amb blocar i copiar el buffer immediatament.
170  //
171  // http://msdn.microsoft.com/en-us/library/tehxacec.aspx
172  // http://msdn.microsoft.com/en-us/library/tb2sfw2z.aspx
173  size_t bufsize;
174  ::getenv_s(&bufsize, NULL, 0, var.c_str());
175  if (0 == bufsize) { // No existeix
176  if (0 != existeix) {
177  *existeix = false;
178  }
179  return string();
180  }
181  if (0 != existeix) {
182  *existeix = true;
183  }
184 
185  boost::scoped_array<char> val(new char[bufsize]);
186  ::getenv_s(&bufsize, val.get(), bufsize, val.get());
187  return string(val.get());
188 #endif // Codi mort: getenv_s() (Windows)
189  const char * val = std::getenv(var.c_str());
190  if (0 != existeix) {
191  *existeix = ( 0 != val );
192  }
193  if (0 == val) {
194  return string();
195  }
196  return string(val);
197 }
198 
199 bool setenv(const string & var, const string & valor) {
200  boost::unique_lock<boost::mutex> lock(env_mtx);
201 #if defined(_WIN32)
202  // Vegeu el bloc intern de comentaris de getenv() a sobre
203  // http://msdn.microsoft.com/en-us/library/83zh4e6k.aspx
204  // http://msdn.microsoft.com/en-us/library/eyw7eyfw.aspx
205  //const errno_t ret = ::_putenv_s(var.c_str(), valor.c_str());
206  // _putenv() espera VARIABLE=VALOR concatenades
207  const string cat(var.c_str()+string("=")+valor.c_str());
208  const int ret = ::_putenv(cat.c_str());
209 #else
210 # ifdef HAVE_SETENV
211  const int ret = ::setenv(var.c_str(), valor.c_str(), 1);
212 // elif defined(HAVE_SETENV)
213  // !! putenv associa l'string a l'entorn de manera permanent, per
214  // tant s'ha de mantenir en memòria fins què es canvii.
215  // Per simplicitat no s'implementa el seu ús ja que les plataformes
216  // principals tenen setenv.
217 # else
218 # error Cap forma suportada de modificar l`entorn
219 # endif // setenv/putenv
220 #endif // Windows / POSIX
221  return 0 == ret;
222 }
223 
224 bool unsetenv(const string & var) {
225 #if defined(_WIN32)
226  // En Windows es fa servir la cadena buida
227  // http://msdn.microsoft.com/en-us/library/eyw7eyfw.aspx
228  return portabilitat::setenv(var, "");
229 #else
230 # ifdef HAVE_UNSETENV
231  boost::unique_lock<boost::mutex> lock(env_mtx);
232  const int ret = ::unsetenv(var.c_str());
233  return 0 == ret;
234 # else
235 # error Cap forma suportada de modificar l`entorn
236 # endif
237 #endif
238 }
239 
240 string ruta_temporal() throw (runtime_error) {
241  string d;
242 #ifdef _WIN32
243  static const string MISSATGE_ERROR("Error obtenint el directori temporal");
244  // http://msdn.microsoft.com/en-us/library/aa364992(v=vs.85).aspx
245  // Mingw rebutja passar "" directament a GetTempPath (com a l'exemple de l'MSDN)
246  DWORD buflen = ::GetTempPath(0, const_cast<LPSTR>(""));
247  if (0 == buflen) {
248  throw runtime_error(MISSATGE_ERROR);
249  }
250  vector<char> buf(buflen);
251  buflen = ::GetTempPath(static_cast<DWORD>(buf.size()), &buf[0]);
252  if (0 == buflen || buf.size() < buflen) {
253  throw runtime_error(MISSATGE_ERROR);
254  }
255  d.assign(buf.begin(), buf.begin()+buflen);
256 #else
257  // tempnam() (desaconsellat)
258  // fa servir, en ordre: TMPDIR, P_tmpdir, "/tmp"
259  // http://www.delorie.com/gnu/docs/glibc/libc_295.html#IDX1612
260  bool existeix;
261  d = getenv("TMPDIR", &existeix);
262  if (!existeix) {
263 # ifdef P_tmpdir
264  d = P_tmpdir;
265 # endif
266  if (d.empty()) {
267  d = "/tmp";
268  }
269  }
270 #endif
271  return d;
272 }
273 
274 shared_ptr<ofstream> fitxer_temporal(const string & patro, string & nom, const string & dir)
275  throw (runtime_error) {
276  static const string MISSATGE_ERROR("Error en crear fitxer temporal");
277 #ifdef _WIN32
278  // http://msdn.microsoft.com/en-us/library/aa363875%28VS.85%29.aspx
279  // Nom de directori temporal (race condition?)
280  // MSDN: "This buffer should be MAX_PATH characters to accommodate the path
281  // plus the terminating null character."
282  // Una alternativa passaria per utilitzar UuidCreate()
283  char buffer[MAX_PATH];
284  UINT ret = ::GetTempFileName(dir.c_str(), patro.c_str(), 0, buffer);
285  if (ret == 0) {
286  throw runtime_error(MISSATGE_ERROR);
287  }
288  nom.assign(buffer, buffer+ret);
289  shared_ptr<ofstream> pofs(new ofstream(nom.c_str(), ios::out|ios::binary));
290 #else
291 # ifdef HAVE_MKSTEMP
292  vector<char> buf(dir.begin(), dir.end());
293  buf.push_back('/');
294  buf.insert(buf.end(), patro.begin(), patro.end());
295  // patro ha d'acabar amb 'XXXXXX'
296  buf.insert(buf.end(), 6, 'X');
297  buf.push_back(0);
298  int fd = ::mkstemp(&buf[0]);
299  if (-1 == fd) {
300  throw runtime_error(&buf[0]);
301  throw runtime_error(MISSATGE_ERROR+";1");
302  }
303  nom.assign(buf.begin(), buf.end());
304  // L'obrim com a ofstream...
305  shared_ptr<ofstream> pofs(new ofstream(nom.c_str(), ios::out|ios::binary));
306  if (pofs->fail()) {
307  throw runtime_error(MISSATGE_ERROR+";2");
308  }
309  // I tanquem el handle tipus C
310  ::close(fd);
311 # else
312 # error Cap forma suportada de crear fitxers temporals
313 # endif
314 #endif
315  return pofs;
316 }
317 
318 } // ns portabilitat
319 
320 } // ns tfc
321 
322 #ifdef _WIN32
323 #else
324 # ifdef HAVE_UNISTD_H
325 # include <unistd.h>
326 # endif
327 # ifdef HAVE_SYS_TYPES_H
328 # include <sys/types.h>
329 # endif
330 # ifdef HAVE_SYS_STAT_H
331 # include <sys/stat.h>
332 # endif
333 #endif
334 
335 namespace tfc {
336 
337 namespace portabilitat {
338 
339 // boost::filesystem no exposa els permisos per tenir definicions molt diferents
340 // segons la plataforma (de fet sí que els exposa però no proveu mètodes per
341 // comprovar-los contra les credencials de l'usuari)
342 bool permis_execucio(const fs::path & f) {
343  assert( fs::is_regular_file(f) ); // PRECONDICIÓ
344 #ifdef _WIN32
345  // En Windows no hi ha el concepte de permís d'execució però
346  // es fa servir l'extensió per definir si es pot executar directament
347  if (!f.has_extension()) {
348  return false;
349  }
350  const string ext = to_lower_copy(f.extension().string());
351  // Seria millor fer una comprovació més "profunda", per exemple en el
352  // cas dels CGIs no és extrany que estiguin escrits en Perl (extensió .pl)
353  return (".exe" == ext || ".com" == ext ); // No executables directament: || ".bat" == ext || ".cmd" == ext);
354 #else
355  struct stat st;
356  // TODO: acls
357  ::stat(f.string().c_str(), &st); // lstat() no segueix symlinks
358  if (0 == (st.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))) {
359  // Cap permís d'execució, no cal continuar
360  return false;
361  }
362  const uid_t euid = ::geteuid();
363  const gid_t egid = ::getegid();
364  bool permis;
365  if (euid == st.st_uid) { // Propietari
366  permis = (0 != (S_IXUSR|st.st_mode));
367  }
368  else if (egid == st.st_gid) { // Grup
369  permis = (0 != (S_IXGRP|st.st_mode));
370  }
371  else { // Ni propietari ni grup
372  permis = (0 != (S_IXOTH|st.st_mode));
373  }
374  return permis;
375 #endif
376 }
377 
378 } // ns portabilitat
379 
380 } // ns tfc
381 
382 // vim:set ts=4 et ai: //