bes  Updated for version 3.20.8
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  ERROR_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 
static BESCatalogList * TheCatalogList()
Get the singleton BESCatalogList instance.
Catalogs provide a hierarchical organization for data.
Definition: BESCatalog.h:51
virtual std::string get_catalog_name() const
Get the name for this catalog.
Definition: BESCatalog.h:103
error thrown if the BES is not allowed to access the resource requested
exception thrown if internal error encountered
error thrown if the resource requested cannot be found
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
static void explode(char delim, const std::string &str, std::list< std::string > &values)
Definition: BESUtil.cc:561
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
static bool endsWith(std::string const &fullString, std::string const &ending)
Definition: BESUtil.cc:942
static void tokenize(const std::string &str, std::vector< std::string > &tokens, const std::string &delimiters="/")
Definition: BESUtil.cc:1057
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
static void set_mime_text(std::ostream &strm)
Generate an HTTP 1.0 response header for a text document.
Definition: BESUtil.cc:83
static std::string id2xml(std::string in, const std::string &not_allowed="><&'\"")
Definition: BESUtil.cc:505
static void conditional_timeout_cancel()
Definition: BESUtil.cc:967
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
static void set_mime_html(std::ostream &strm)
Generate an HTTP 1.0 response header for a html document.
Definition: BESUtil.cc:102
static std::string lowercase(const std::string &s)
Definition: BESUtil.cc:200
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
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
static std::string www2id(const std::string &in, const std::string &escape="%", const std::string &except="")
Definition: BESUtil.cc:185
static std::string implode(const std::list< std::string > &values, char delim)
Definition: BESUtil.cc:638
static std::string xml2id(std::string in)
Definition: BESUtil.cc:522
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
static std::string unescape(const std::string &s)
Definition: BESUtil.cc:210
static char * fastpidconverter(char *buf, int base)
Definition: BESUtil.cc:436
static void removeLeadingAndTrailingBlanks(std::string &key)
Definition: BESUtil.cc:466
static std::string get_time(bool use_local_time=false)
Definition: BESUtil.cc:1079
void get_value(const std::string &s, std::string &val, bool &found)
Retrieve the value of a given key, if set.
Definition: TheBESKeys.cc:339
static TheBESKeys * TheKeys()
Definition: TheBESKeys.cc:71