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