bes  Updated for version 3.20.5
BESUtil.cc
1 // BESUtil.cc
2 
3 // This file is part of bes, A C++ back-end server implementation framework
4 // for the OPeNDAP Data Access Protocol.
5 
6 // Copyright (c) 2004-2009 University Corporation for Atmospheric Research
7 // Author: Patrick West <pwest@ucar.edu> and Jose Garcia <jgarcia@ucar.edu>
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public
11 // License as published by the Free Software Foundation; either
12 // version 2.1 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // Lesser General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 //
23 // You can contact University Corporation for Atmospheric Research at
24 // 3080 Center Green Drive, Boulder, CO 80301
25 
26 // (c) COPYRIGHT University Corporation for Atmospheric Research 2004-2005
27 // Please read the full copyright statement in the file COPYRIGHT_UCAR.
28 //
29 // Authors:
30 // pwest Patrick West <pwest@ucar.edu>
31 // jgarcia Jose Garcia <jgarcia@ucar.edu>
32 
33 #include "config.h"
34 
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 
38 #if HAVE_UNISTD_H
39 #include <unistd.h>
40 #endif
41 
42 #include <cstdio>
43 #include <cerrno>
44 #include <cstring>
45 #include <cstdlib>
46 #include <ctime>
47 #include <cassert>
48 #include <vector>
49 
50 #include <sstream>
51 #include <iostream>
52 
53 using std::stringstream;
54 using std::istringstream;
55 using std::cout;
56 using std::endl;
57 using std::vector;
58 using std::string;
59 
60 #include "TheBESKeys.h"
61 #include "BESUtil.h"
62 #include "BESDebug.h"
63 #include "BESForbiddenError.h"
64 #include "BESNotFoundError.h"
65 #include "BESInternalError.h"
66 #include "BESLog.h"
67 #include "BESCatalogList.h"
68 
69 #define CRLF "\r\n"
70 
71 #define debug_key "util"
72 #define prolog string("BESUtil::").append(__func__).append("() - ")
73 
74 const string BES_KEY_TIMEOUT_CANCEL = "BES.CancelTimeoutOnSend";
75 
80 void BESUtil::set_mime_text(ostream &strm)
81 {
82  strm << "HTTP/1.0 200 OK" << CRLF;
83  strm << "XBES-Server: " << PACKAGE_STRING << CRLF;
84 
85  const time_t t = time(0);
86  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
87  strm << "Last-Modified: " << rfc822_date(t).c_str() << CRLF;
88 
89  strm << "Content-Type: text/plain" << CRLF;
90  // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
91  strm << "Content-Description: unknown" << CRLF;
92  strm << CRLF;
93 }
94 
99 void BESUtil::set_mime_html(ostream &strm)
100 {
101  strm << "HTTP/1.0 200 OK" << CRLF;
102  strm << "XBES-Server: " << PACKAGE_STRING << CRLF;
103 
104  const time_t t = time(0);
105  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
106  strm << "Last-Modified: " << rfc822_date(t).c_str() << CRLF;
107 
108  strm << "Content-type: text/html" << CRLF;
109  // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
110  strm << "Content-Description: unknown" << CRLF;
111  strm << CRLF;
112 }
113 
114 // Return a MIME rfc-822 date. The grammar for this is:
115 // date-time = [ day "," ] date time ; dd mm yy
116 // ; hh:mm:ss zzz
117 //
118 // day = "Mon" / "Tue" / "Wed" / "Thu"
119 // / "Fri" / "Sat" / "Sun"
120 //
121 // date = 1*2DIGIT month 2DIGIT ; day month year
122 // ; e.g. 20 Jun 82
123 // NB: year is 4 digit; see RFC 1123. 11/30/99 jhrg
124 //
125 // month = "Jan" / "Feb" / "Mar" / "Apr"
126 // / "May" / "Jun" / "Jul" / "Aug"
127 // / "Sep" / "Oct" / "Nov" / "Dec"
128 //
129 // time = hour zone ; ANSI and Military
130 //
131 // hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT]
132 // ; 00:00:00 - 23:59:59
133 //
134 // zone = "UT" / "GMT" ; Universal Time
135 // ; North American : UT
136 // / "EST" / "EDT" ; Eastern: - 5/ - 4
137 // / "CST" / "CDT" ; Central: - 6/ - 5
138 // / "MST" / "MDT" ; Mountain: - 7/ - 6
139 // / "PST" / "PDT" ; Pacific: - 8/ - 7
140 // / 1ALPHA ; Military: Z = UT;
141 // ; A:-1; (J not used)
142 // ; M:-12; N:+1; Y:+12
143 // / ( ("+" / "-") 4DIGIT ) ; Local differential
144 // ; hours+min. (HHMM)
145 
146 static const char *days[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
147 static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
148 
158 string BESUtil::rfc822_date(const time_t t)
159 {
160  struct tm *stm = gmtime(&t);
161  char d[256];
162 
163  snprintf(d, 255, "%s, %02d %s %4d %02d:%02d:%02d GMT", days[stm->tm_wday], stm->tm_mday, months[stm->tm_mon], 1900 + stm->tm_year, stm->tm_hour,
164  stm->tm_min, stm->tm_sec);
165  d[255] = '\0';
166  return string(d);
167 }
168 
169 string BESUtil::unhexstring(string s)
170 {
171  int val;
172  istringstream ss(s);
173  ss >> std::hex >> val;
174  char tmp_str[2];
175  tmp_str[0] = static_cast<char>(val);
176  tmp_str[1] = '\0';
177  return string(tmp_str);
178 }
179 
180 // I modified this to mirror the version in libdap. The change allows several
181 // escape sequences to by listed in 'except'. jhrg 2/18/09
182 string BESUtil::www2id(const string &in, const string &escape, const string &except)
183 {
184  string::size_type i = 0;
185  string res = in;
186  while ((i = res.find_first_of(escape, i)) != string::npos) {
187  if (except.find(res.substr(i, 3)) != string::npos) {
188  i += 3;
189  continue;
190  }
191  res.replace(i, 3, unhexstring(res.substr(i + 1, 2)));
192  }
193 
194  return res;
195 }
196 
197 string BESUtil::lowercase(const string &s)
198 {
199  string return_string = s;
200  for (int j = 0; j < static_cast<int>(return_string.length()); j++) {
201  return_string[j] = (char) tolower(return_string[j]);
202  }
203 
204  return return_string;
205 }
206 
207 string BESUtil::unescape(const string &s)
208 {
209  bool done = false;
210  string::size_type index = 0;
211  /* string::size_type new_index = 0 ; */
212  string new_str;
213  while (!done) {
214  string::size_type bs = s.find('\\', index);
215  if (bs == string::npos) {
216  new_str += s.substr(index, s.length() - index);
217  done = true;
218  }
219  else {
220  new_str += s.substr(index, bs - index);
221  new_str += s[bs + 1];
222  index = bs + 2;
223  }
224  }
225 
226  return new_str;
227 }
228 
251 void BESUtil::check_path(const string &path, const string &root, bool follow_sym_links)
252 {
253  // if nothing is passed in path, then the path checks out since root is
254  // assumed to be valid.
255  if (path == "") return;
256 
257  // Rather than have two basically identical code paths for the two cases (follow and !follow symlinks)
258  // We evaluate the follow_sym_links switch and use a function pointer to get the correct "stat"
259  // function for the eval operation.
260  int (*ye_old_stat_function)(const char *pathname, struct stat *buf);
261  if (follow_sym_links) {
262  BESDEBUG(debug_key, "check_path() - Using 'stat' function (follow_sym_links = true)" << endl);
263  ye_old_stat_function = &stat;
264  }
265  else {
266  BESDEBUG(debug_key, "check_path() - Using 'lstat' function (follow_sym_links = false)" << endl);
267  ye_old_stat_function = &lstat;
268  }
269 
270  // make sure there are no ../ in the directory, backing up in any way is
271  // not allowed.
272  string::size_type dotdot = path.find("..");
273  if (dotdot != string::npos) {
274  string s = (string) "You are not allowed to access the node " + path;
275  throw BESForbiddenError(s, __FILE__, __LINE__);
276  }
277 
278  // What I want to do is to take each part of path and check to see if it
279  // is a symbolic link and it is accessible. If everything is ok, add the
280  // next part of the path.
281  bool done = false;
282 
283  // what is remaining to check
284  string rem = path;
285  if (rem[0] == '/') rem = rem.substr(1); // substr(1, rem.length() - 1); jhrg 3/5/18
286  if (rem[rem.length() - 1] == '/') rem = rem.substr(0, rem.length() - 1);
287 
288  // full path of the thing to check
289  string fullpath = root;
290  if (fullpath[fullpath.length() - 1] == '/') {
291  fullpath = fullpath.substr(0, fullpath.length() - 1);
292  }
293 
294  // path checked so far
295  //string checked;
296  while (!done) {
297  size_t slash = rem.find('/');
298  if (slash == string::npos) {
299  // fullpath = fullpath + "/" + rem; jhrg 3/5/18
300  fullpath.append("/").append(rem);
301  // checked = checked + "/" + rem;
302  done = true;
303  }
304  else {
305  // fullpath = fullpath + "/" + rem.substr(0, slash);
306  fullpath.append("/").append(rem.substr(0, slash));
307  // checked = checked + "/" + rem.substr(0, slash);
308  //checked.append("/").append(rem.substr(0, slash));
309  rem = rem.substr(slash + 1, rem.length() - slash);
310  }
311 
312  //checked = fullpath;
313 
314  struct stat buf;
315  int statret = ye_old_stat_function(fullpath.c_str(), &buf);
316  if (statret == -1) {
317  int errsv = errno;
318  // stat failed, so not accessible. Get the error string,
319  // store in error, and throw exception
320  char *s_err = strerror(errsv);
321  //string error = "Unable to access node " + checked + ": ";
322  string error = "Unable to access node " + fullpath + ": ";
323  if (s_err)
324  error.append(s_err);
325  else
326  error.append("unknown error");
327 
328  BESDEBUG(debug_key, "check_path() - error: "<< error << " errno: " << errno << endl);
329 
330  // ENOENT means that the node wasn't found.
331  // On some systems a file that doesn't exist returns ENOTDIR because: w.f.t?
332  // Otherwise, access is being denied for some other reason
333  if (errsv == ENOENT || errsv == ENOTDIR) {
334  // On some systems a file that doesn't exist returns ENOTDIR because: w.f.t?
335  throw BESNotFoundError(error, __FILE__, __LINE__);
336  }
337  else {
338  throw BESForbiddenError(error, __FILE__, __LINE__);
339  }
340  }
341  else {
342  // The call to (stat | lstat) was successful, now check to see if it's a symlink.
343  // Note that if follow_symlinks is true then this will never evaluate as true
344  // because we'll be using 'stat' and not 'lstat' and stat will follow the link
345  // and return information about the file/dir pointed to by the symlink
346  if (S_ISLNK(buf.st_mode)) {
347  //string error = "You do not have permission to access " + checked;
348  throw BESForbiddenError(string("You do not have permission to access ") + fullpath, __FILE__, __LINE__);
349  }
350  }
351  }
352 
353 #if 0
354  while (!done) {
355  size_t slash = rem.find('/');
356  if (slash == string::npos) {
357  fullpath = fullpath + "/" + rem;
358  checked = checked + "/" + rem;
359  done = true;
360  }
361  else {
362  fullpath = fullpath + "/" + rem.substr(0, slash);
363  checked = checked + "/" + rem.substr(0, slash);
364  rem = rem.substr(slash + 1, rem.length() - slash);
365  }
366 
367  if (!follow_sym_links) {
368  struct stat buf;
369  int statret = lstat(fullpath.c_str(), &buf);
370  if (statret == -1) {
371  int errsv = errno;
372  // stat failed, so not accessible. Get the error string,
373  // store in error, and throw exception
374  char *s_err = strerror(errsv);
375  string error = "Unable to access node " + checked + ": ";
376  if (s_err) {
377  error = error + s_err;
378  }
379  else {
380  error = error + "unknown access error";
381  }
382  // ENOENT means that the node wasn't found. Otherwise, access
383  // is denied for some reason
384  if (errsv == ENOENT) {
385  throw BESNotFoundError(error, __FILE__, __LINE__);
386  }
387  else {
388  throw BESForbiddenError(error, __FILE__, __LINE__);
389  }
390  }
391  else {
392  // lstat was successful, now check if sym link
393  if (S_ISLNK( buf.st_mode )) {
394  string error = "You do not have permission to access "
395  + checked;
396  throw BESForbiddenError(error, __FILE__, __LINE__);
397  }
398  }
399  }
400  else {
401  // just do a stat and see if we can access the thing. If we
402  // can't, get the error information and throw an exception
403  struct stat buf;
404  int statret = stat(fullpath.c_str(), &buf);
405  if (statret == -1) {
406  int errsv = errno;
407  // stat failed, so not accessible. Get the error string,
408  // store in error, and throw exception
409  char *s_err = strerror(errsv);
410  string error = "Unable to access node " + checked + ": ";
411  if (s_err) {
412  error = error + s_err;
413  }
414  else {
415  error = error + "unknown access error";
416  }
417  // ENOENT means that the node wasn't found. Otherwise, access
418  // is denied for some reason
419  if (errsv == ENOENT) {
420  throw BESNotFoundError(error, __FILE__, __LINE__);
421  }
422  else {
423  throw BESForbiddenError(error, __FILE__, __LINE__);
424  }
425  }
426  }
427  }
428 
429 #endif
430 }
431 
432 char *
433 BESUtil::fastpidconverter(char *buf, int base)
434 {
435  return fastpidconverter(getpid(), buf, base);
436 }
437 
438 char *
439 BESUtil::fastpidconverter(long val, /* value to be converted */
440 char *buf, /* output string */
441 int base) /* conversion base */
442 {
443  ldiv_t r; /* result of val / base */
444 
445  if (base > 36 || base < 2) /* no conversion if wrong base */
446  {
447  *buf = '\0';
448  return buf;
449  }
450  if (val < 0) *buf++ = '-';
451  r = ldiv(labs(val), base);
452 
453  /* output digits of val/base first */
454 
455  if (r.quot > 0) buf = fastpidconverter(r.quot, buf, base);
456  /* output last digit */
457 
458  *buf++ = "0123456789abcdefghijklmnopqrstuvwxyz"[(int) r.rem];
459  *buf = '\0';
460  return buf;
461 }
462 
464 {
465  if (!key.empty()) {
466  string::size_type first = key.find_first_not_of(" \t\n\r");
467  string::size_type last = key.find_last_not_of(" \t\n\r");
468  if (first == string::npos)
469  key = "";
470  else {
471  string::size_type num = last - first + 1;
472  string new_key = key.substr(first, num);
473  key = new_key;
474  }
475  }
476 }
477 
478 string BESUtil::entity(char c)
479 {
480  switch (c) {
481  case '>':
482  return "&gt;";
483  case '<':
484  return "&lt;";
485  case '&':
486  return "&amp;";
487  case '\'':
488  return "&apos;";
489  case '\"':
490  return "&quot;";
491  default:
492  return string(1, c); // is this proper default, just the char?
493  }
494 }
495 
502 string BESUtil::id2xml(string in, const string &not_allowed)
503 {
504  string::size_type i = 0;
505 
506  while ((i = in.find_first_of(not_allowed, i)) != string::npos) {
507  in.replace(i, 1, entity(in[i]));
508  i++;
509  }
510 
511  return in;
512 }
513 
519 string BESUtil::xml2id(string in)
520 {
521  string::size_type i = 0;
522 
523  while ((i = in.find("&gt;", i)) != string::npos)
524  in.replace(i, 4, ">");
525 
526  i = 0;
527  while ((i = in.find("&lt;", i)) != string::npos)
528  in.replace(i, 4, "<");
529 
530  i = 0;
531  while ((i = in.find("&amp;", i)) != string::npos)
532  in.replace(i, 5, "&");
533 
534  i = 0;
535  while ((i = in.find("&apos;", i)) != string::npos)
536  in.replace(i, 6, "'");
537 
538  i = 0;
539  while ((i = in.find("&quot;", i)) != string::npos)
540  in.replace(i, 6, "\"");
541 
542  return in;
543 }
544 
558 void BESUtil::explode(char delim, const string &str, list<string> &values)
559 {
560  std::string::size_type start = 0;
561  std::string::size_type qstart = 0;
562  std::string::size_type adelim = 0;
563  std::string::size_type aquote = 0;
564  bool done = false;
565  while (!done) {
566  string aval;
567  if (str[start] == '"') {
568  bool endquote = false;
569  qstart = start + 1;
570  while (!endquote) {
571  aquote = str.find('"', qstart);
572  if (aquote == string::npos) {
573  string currval = str.substr(start, str.length() - start);
574  string err = "BESUtil::explode - No end quote after value " + currval;
575  throw BESInternalError(err, __FILE__, __LINE__);
576  }
577  // could be an escaped escape character and an escaped
578  // quote, or an escaped escape character and a quote
579  if (str[aquote - 1] == '\\') {
580  if (str[aquote - 2] == '\\') {
581  endquote = true;
582  qstart = aquote + 1;
583  }
584  else {
585  qstart = aquote + 1;
586  }
587  }
588  else {
589  endquote = true;
590  qstart = aquote + 1;
591  }
592  }
593  if (str[qstart] != delim && qstart != str.length()) {
594  string currval = str.substr(start, qstart - start);
595  string err = "BESUtil::explode - No delim after end quote " + currval;
596  throw BESInternalError(err, __FILE__, __LINE__);
597  }
598  if (qstart == str.length()) {
599  adelim = string::npos;
600  }
601  else {
602  adelim = qstart;
603  }
604  }
605  else {
606  adelim = str.find(delim, start);
607  }
608  if (adelim == string::npos) {
609  aval = str.substr(start, str.length() - start);
610  done = true;
611  }
612  else {
613  aval = str.substr(start, adelim - start);
614  }
615 
616  values.push_back(aval);
617  start = adelim + 1;
618  if (start == str.length()) {
619  values.push_back("");
620  done = true;
621  }
622  }
623 }
624 
635 string BESUtil::implode(const list<string> &values, char delim)
636 {
637  string result;
638  list<string>::const_iterator i = values.begin();
639  list<string>::const_iterator e = values.end();
640  bool first = true;
641  string::size_type d; // = string::npos ;
642  for (; i != e; i++) {
643  if (!first) result += delim;
644  d = (*i).find(delim);
645  if (d != string::npos && (*i)[0] != '"') {
646  string err = (string) "BESUtil::implode - delimiter exists in value " + (*i);
647  throw BESInternalError(err, __FILE__, __LINE__);
648  }
649  //d = string::npos ;
650  result += (*i);
651  first = false;
652  }
653  return result;
654 }
655 
675 void BESUtil::url_explode(const string &url_str, BESUtil::url &url_parts)
676 {
677  string rest;
678 
679  string::size_type colon = url_str.find(":");
680  if (colon == string::npos) {
681  string err = "BESUtil::url_explode: missing colon for protocol";
682  throw BESInternalError(err, __FILE__, __LINE__);
683  }
684 
685  url_parts.protocol = url_str.substr(0, colon);
686 
687  if (url_str.substr(colon, 3) != "://") {
688  string err = "BESUtil::url_explode: no :// in the URL";
689  throw BESInternalError(err, __FILE__, __LINE__);
690  }
691 
692  colon += 3;
693  rest = url_str.substr(colon);
694 
695  string::size_type slash = rest.find("/");
696  if (slash == string::npos) slash = rest.length();
697 
698  string::size_type at = rest.find("@");
699  if ((at != string::npos) && (at < slash)) {
700  // everything before the @ is username:password
701  string up = rest.substr(0, at);
702  colon = up.find(":");
703  if (colon != string::npos) {
704  url_parts.uname = up.substr(0, colon);
705  url_parts.psswd = up.substr(colon + 1);
706  }
707  else {
708  url_parts.uname = up;
709  }
710  // everything after the @ is domain/path
711  rest = rest.substr(at + 1);
712  }
713  slash = rest.find("/");
714  if (slash == string::npos) slash = rest.length();
715  colon = rest.find(":");
716  if ((colon != string::npos) && (colon < slash)) {
717  // everything before the colon is the domain
718  url_parts.domain = rest.substr(0, colon);
719  // everything after the folon is port/path
720  rest = rest.substr(colon + 1);
721  slash = rest.find("/");
722  if (slash != string::npos) {
723  url_parts.port = rest.substr(0, slash);
724  url_parts.path = rest.substr(slash + 1);
725  }
726  else {
727  url_parts.port = rest;
728  url_parts.path = "";
729  }
730  }
731  else {
732  slash = rest.find("/");
733  if (slash != string::npos) {
734  url_parts.domain = rest.substr(0, slash);
735  url_parts.path = rest.substr(slash + 1);
736  }
737  else {
738  url_parts.domain = rest;
739  }
740  }
741 }
742 
743 string BESUtil::url_create(BESUtil::url &url_parts)
744 {
745  string url = url_parts.protocol + "://";
746  if (!url_parts.uname.empty()) {
747  url += url_parts.uname;
748  if (!url_parts.psswd.empty()) url += ":" + url_parts.psswd;
749  url += "@";
750  }
751  url += url_parts.domain;
752  if (!url_parts.port.empty()) url += ":" + url_parts.port;
753  if (!url_parts.path.empty()) url += "/" + url_parts.path;
754 
755  return url;
756 }
757 
758 
769 string BESUtil::pathConcat(const string &firstPart, const string &secondPart, char separator)
770 {
771  string first = firstPart;
772  string second = secondPart;
773  string sep(1,separator);
774 
775  // make sure there are not multiple slashes at the end of the first part...
776  // Note that this removes all of the slashes. jhrg 9/27/16
777  while (!first.empty() && *first.rbegin() == separator) {
778  // C++-11 first.pop_back();
779  first = first.substr(0, first.length() - 1);
780  }
781  // make sure second part does not BEGIN with a slash
782  while (!second.empty() && second[0] == separator) {
783  // erase is faster? second = second.substr(1);
784  second.erase(0, 1);
785  }
786  string newPath;
787  if (first.empty()) {
788  newPath = second;
789  }
790  else if (second.empty()) {
791  newPath = first;
792  }
793  else {
794  newPath = first.append(sep).append(second);
795  }
796  return newPath;
797 }
818 string BESUtil::assemblePath(const string &firstPart, const string &secondPart, bool leadingSlash, bool trailingSlash)
819 {
820 #if 0
821  assert(!firstPart.empty());
822 
823  // This version works but does not remove duplicate slashes
824  string first = firstPart;
825  string second = secondPart;
826 
827  // add a leading slash if needed
828  if (ensureLeadingSlash && first[0] != '/')
829  first = "/" + first;
830 
831  // if 'second' start with a slash, remove it
832  if (second[0] == '/')
833  second = second.substr(1);
834 
835  // glue the two parts together, adding a slash if needed
836  if (first.back() == '/')
837  return first.append(second);
838  else
839  return first.append("/").append(second);
840 #endif
841 
842 #if 1
843  BESDEBUG(debug_key, prolog << "firstPart: '" << firstPart << "'" << endl);
844  BESDEBUG(debug_key, prolog << "secondPart: '" << secondPart << "'" << endl);
845 
846 #if 0
847  // assert(!firstPart.empty()); // I dropped this because I had to ask, why? Why does it matter? ndp 2017
848 
849  string first = firstPart;
850  string second = secondPart;
851 
852  // make sure there are not multiple slashes at the end of the first part...
853  // Note that this removes all of the slashes. jhrg 9/27/16
854  while (!first.empty() && *first.rbegin() == '/') {
855  // C++-11 first.pop_back();
856  first = first.substr(0, first.length() - 1);
857  }
858 
859  // make sure second part does not BEGIN with a slash
860  while (!second.empty() && second[0] == '/') {
861  // erase is faster? second = second.substr(1);
862  second.erase(0, 1);
863  }
864 
865  string newPath;
866 
867  if (first.empty()) {
868  newPath = second;
869  }
870  else if (second.empty()) {
871  newPath = first;
872  }
873  else {
874  newPath = first.append("/").append(second);
875  }
876 #endif
877 
878  string newPath = BESUtil::pathConcat(firstPart,secondPart);
879  if (leadingSlash) {
880  if (newPath.empty()) {
881  newPath = "/";
882  }
883  else if (newPath.compare(0, 1, "/")) {
884  newPath = "/" + newPath;
885  }
886  }
887 
888  if (trailingSlash) {
889  if (newPath.compare(newPath.length(), 1, "/")) {
890  newPath = newPath.append("/");
891  }
892  }
893  else {
894  while(newPath.length()>1 && *newPath.rbegin() == '/')
895  newPath = newPath.substr(0,newPath.length()-1);
896  }
897  BESDEBUG(debug_key, prolog << "newPath: "<< newPath << endl);
898  return newPath;
899 #endif
900 
901 #if 0
902  BESDEBUG("util", "BESUtil::assemblePath() - firstPart: "<< firstPart << endl);
903  BESDEBUG("util", "BESUtil::assemblePath() - secondPart: "<< secondPart << endl);
904 
905  string first = firstPart;
906  string second = secondPart;
907 
908  if (ensureLeadingSlash) {
909  if (*first.begin() != '/') first = "/" + first;
910  }
911 
912  // make sure there are not multiple slashes at the end of the first part...
913  while (*first.rbegin() == '/' && first.length() > 0) {
914  first = first.substr(0, first.length() - 1);
915  }
916 
917  // make sure first part ends with a "/"
918  if (*first.rbegin() != '/') {
919  first += "/";
920  }
921 
922  // make sure second part does not BEGIN with a slash
923  while (*second.begin() == '/' && second.length() > 0) {
924  second = second.substr(1);
925  }
926 
927  string newPath = first + second;
928 
929  BESDEBUG("util", "BESUtil::assemblePath() - newPath: "<< newPath << endl);
930 
931  return newPath;
932 #endif
933 }
934 
939 bool BESUtil::endsWith(string const &fullString, string const &ending)
940 {
941  if (fullString.length() >= ending.length()) {
942  return (0 == fullString.compare(fullString.length() - ending.length(), ending.length(), ending));
943  }
944  else {
945  return false;
946  }
947 }
948 
965 {
966  bool cancel_timeout_on_send = false;
967  bool found = false;
968  string doset = "";
969  const string dosettrue = "true";
970  const string dosetyes = "yes";
971 
972  TheBESKeys::TheKeys()->get_value(BES_KEY_TIMEOUT_CANCEL, doset, found);
973  if (true == found) {
974  doset = BESUtil::lowercase(doset);
975  if (dosettrue == doset || dosetyes == doset) cancel_timeout_on_send = true;
976  }
977  BESDEBUG(debug_key, __func__ << "() - cancel_timeout_on_send: " <<(cancel_timeout_on_send?"true":"false") << endl);
978  if (cancel_timeout_on_send) alarm(0);
979 }
980 
986 void BESUtil::replace_all(string &s, string find_this, string replace_with_this)
987 {
988  size_t pos = s.find(find_this);
989  while (pos != string::npos) {
990  // Replace current matching substring
991  s.replace(pos, find_this.size(), replace_with_this);
992  // Get the next occurrence from current position
993  pos = s.find(find_this, pos + find_this.size());
994  }
995 }
996 
1008 string BESUtil::normalize_path(const string &raw_path, bool leading_separator, bool trailing_separator, const string separator /* = "/" */)
1009 {
1010  if (separator.length() != 1)
1011  throw BESInternalError("Path separators must be a single character. The string '" + separator + "' does not qualify.", __FILE__, __LINE__);
1012  char separator_char = separator[0];
1013  string double_separator;
1014  double_separator = double_separator.append(separator).append(separator);
1015 
1016  string path(raw_path);
1017 
1018  replace_all(path, double_separator, separator);
1019 
1020  if (path.empty()) {
1021  path = separator;
1022  }
1023  if (path == separator) {
1024  return path;
1025  }
1026  if (leading_separator) {
1027  if (path[0] != separator_char) {
1028  path = string(separator).append(path);
1029  }
1030  }
1031  else {
1032  if (path[0] == separator_char) {
1033  path = path.substr(1);
1034  }
1035  }
1036  if (trailing_separator) {
1037  if (*path.rbegin() != separator_char) {
1038  path = path.append(separator);
1039  }
1040  }
1041  else {
1042  if (*path.rbegin() == separator_char) {
1043  path = path.substr(0, path.length() - 1);
1044  }
1045  }
1046  return path;
1047 }
1048 
1054 void BESUtil::tokenize(const string& str, vector<string>& tokens, const string& delimiters /* = "/" */)
1055 {
1056  // Skip delimiters at beginning.
1057  string::size_type lastPos = str.find_first_not_of(delimiters, 0);
1058  // Find first "non-delimiter".
1059  string::size_type pos = str.find_first_of(delimiters, lastPos);
1060  while (string::npos != pos || string::npos != lastPos) {
1061  // Found a token, add it to the vector.
1062  tokens.push_back(str.substr(lastPos, pos - lastPos));
1063  // Skip delimiters. Note the "not_of"
1064  lastPos = str.find_first_not_of(delimiters, pos);
1065  // Find next "non-delimiter"
1066  pos = str.find_first_of(delimiters, lastPos);
1067  }
1068 }
1069 
1076 string BESUtil::get_time(bool use_local_time)
1077 {
1078  return get_time(time(0), use_local_time);
1079 }
1080 
1088 string BESUtil::get_time(time_t the_time, bool use_local_time)
1089 {
1090  char buf[sizeof "YYYY-MM-DDTHH:MM:SS zones"];
1091  int status = 0;
1092 
1093  // From StackOverflow:
1094  // This will work too, if your compiler doesn't support %F or %T:
1095  // strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%S%Z", gmtime(&now));
1096  //
1097  // UTC is the default. Override to local time based on the
1098  // passed parameter 'use_local_time'
1099  if (!use_local_time)
1100  status = strftime(buf, sizeof buf, "%FT%T%Z", gmtime(&the_time));
1101  else
1102  status = strftime(buf, sizeof buf, "%FT%T%Z", localtime(&the_time));
1103 
1104  if (!status) {
1105  LOG(prolog + "Error formatting time value!");
1106  return "date-format-error";
1107  }
1108 
1109  return buf;
1110 }
1111 
1122 vector<string> BESUtil::split(const string &s, char delim /* '/' */, bool skip_empty /* true */)
1123 {
1124  stringstream ss(s);
1125  string item;
1126  vector<string> tokens;
1127 
1128  while (getline(ss, item, delim)) {
1129 
1130  if (item.empty() && skip_empty)
1131  continue;
1132 
1133  tokens.push_back(item);
1134 
1135 #if 0
1136  // If skip_empty is false, item is not ever pushed, regardless of whether it's empty. jhrg 1/24/19
1137  if (skip_empty && !item.empty())
1138  tokens.push_back(item);
1139 #endif
1140 
1141  }
1142 
1143  return tokens;
1144 }
1145 
1146 BESCatalog *BESUtil::separateCatalogFromPath(std::string &ppath)
1147 {
1148  BESCatalog *catalog = 0; // pointer to a singleton; do not delete
1149  vector<string> path_tokens;
1150 
1151  // BESUtil::normalize_path() removes duplicate separators and adds leading and trailing separators as directed.
1152  string path = BESUtil::normalize_path(ppath, false, false);
1153  BESDEBUG(debug_key, prolog << "Normalized path: " << path << endl);
1154 
1155  // Because we may need to alter the container/file/resource name by removing
1156  // a catalog name from the first node in the path we use "use_container" to store
1157  // the altered container path.
1158  string use_container = ppath;
1159 
1160  // Breaks path into tokens
1161  BESUtil::tokenize(path, path_tokens);
1162  if (!path_tokens.empty()) {
1163  BESDEBUG(debug_key, "First path token: " << path_tokens[0] << endl);
1164  catalog = BESCatalogList::TheCatalogList()->find_catalog(path_tokens[0]);
1165  if (catalog) {
1166  BESDEBUG(debug_key, prolog << "Located catalog " << catalog->get_catalog_name() << " from path component" << endl);
1167  // Since the catalog name is in the path we
1168  // need to drop it this should leave container
1169  // with a leading
1170  ppath = BESUtil::normalize_path(path.substr(path_tokens[0].length()), true, false);
1171  BESDEBUG(debug_key, prolog << "Modified container/path value to: " << use_container << endl);
1172  }
1173  }
1174 
1175  return catalog;
1176 }
1177 
1178 
1179 
BESUtil::set_mime_html
static void set_mime_html(ostream &strm)
Generate an HTTP 1.0 response header for a html document.
Definition: BESUtil.cc:99
BESUtil::replace_all
static void replace_all(std::string &s, std::string find_this, std::string replace_with_this)
Operates on the string 's' to replaces every occurrence of the value of the string 'find_this' with t...
Definition: BESUtil.cc:986
BESUtil::unescape
static string unescape(const string &s)
Definition: BESUtil.cc:207
BESUtil::tokenize
static void tokenize(const std::string &str, std::vector< std::string > &tokens, const std::string &delimiters="/")
Definition: BESUtil.cc:1054
BESNotFoundError
error thrown if the resource requested cannot be found
Definition: BESNotFoundError.h:40
BESUtil::removeLeadingAndTrailingBlanks
static void removeLeadingAndTrailingBlanks(string &key)
Definition: BESUtil.cc:463
BESUtil::explode
static void explode(char delim, const string &str, list< string > &values)
Definition: BESUtil.cc:558
BESUtil::assemblePath
static string assemblePath(const string &firstPart, const string &secondPart, bool leadingSlash=false, bool trailingSlash=false)
Assemble path fragments making sure that they are separated by a single '/' character.
Definition: BESUtil.cc:818
BESUtil::url_explode
static void url_explode(const string &url_str, BESUtil::url &url_parts)
Given a url, break the url into its different parts.
Definition: BESUtil.cc:675
BESUtil::conditional_timeout_cancel
static void conditional_timeout_cancel()
Definition: BESUtil.cc:964
BESCatalog::get_catalog_name
virtual std::string get_catalog_name() const
Get the name for this catalog.
Definition: BESCatalog.h:103
BESUtil::get_time
static string get_time(bool use_local_time=false)
Definition: BESUtil.cc:1076
BESUtil::split
static std::vector< std::string > split(const string &s, char delim='/', bool skip_empty=true)
Splits the string s into the return vector of tokens using the delimiter delim and skipping empty val...
Definition: BESUtil.cc:1122
BESUtil::normalize_path
static std::string normalize_path(const std::string &path, bool leading_separator, bool trailing_separator, const string separator="/")
Removes duplicate separators and provides leading and trailing separators as directed.
Definition: BESUtil.cc:1008
BESUtil::set_mime_text
static void set_mime_text(ostream &strm)
Generate an HTTP 1.0 response header for a text document.
Definition: BESUtil.cc:80
BESCatalogList::TheCatalogList
static BESCatalogList * TheCatalogList()
Get the singleton BESCatalogList instance.
Definition: BESCatalogList.cc:81
TheBESKeys::TheKeys
static TheBESKeys * TheKeys()
Definition: TheBESKeys.cc:61
BESUtil::fastpidconverter
static char * fastpidconverter(char *buf, int base)
Definition: BESUtil.cc:433
BESUtil::lowercase
static string lowercase(const string &s)
Definition: BESUtil.cc:197
BESUtil::check_path
static void check_path(const string &path, const string &root, bool follow_sym_links)
Check if the specified path is valid.
Definition: BESUtil.cc:251
BESUtil::endsWith
static bool endsWith(std::string const &fullString, std::string const &ending)
Definition: BESUtil.cc:939
BESUtil::url
Definition: BESUtil.h:97
BESForbiddenError
error thrown if the BES is not allowed to access the resource requested
Definition: BESForbiddenError.h:40
BESInternalError
exception thrown if inernal error encountered
Definition: BESInternalError.h:43
TheBESKeys::get_value
void get_value(const std::string &s, std::string &val, bool &found)
Retrieve the value of a given key, if set.
Definition: TheBESKeys.cc:420
BESUtil::pathConcat
static string pathConcat(const string &firstPart, const string &secondPart, char separator='/')
Concatenate path fragments making sure that they are separated by a single '/' character.
Definition: BESUtil.cc:769
BESUtil::xml2id
static string xml2id(string in)
Definition: BESUtil.cc:519
BESCatalog
Catalogs provide a hierarchical organization for data.
Definition: BESCatalog.h:51
BESUtil::www2id
static string www2id(const string &in, const string &escape="%", const string &except="")
Definition: BESUtil.cc:182
BESUtil::implode
static string implode(const list< string > &values, char delim)
Definition: BESUtil.cc:635
BESUtil::id2xml
static string id2xml(string in, const string &not_allowed="><&'\"")
Definition: BESUtil.cc:502