37#include <sys/resource.h>
63#include "TheBESKeys.h"
66#include "BESForbiddenError.h"
67#include "BESNotFoundError.h"
68#include "BESInternalError.h"
70#include "BESCatalogList.h"
72#include "BESInternalFatalError.h"
73#include "RequestServiceTimer.h"
80#define prolog string("BESUtil::").append(__func__).append("() - ")
82const string BES_KEY_TIMEOUT_CANCEL =
"BES.CancelTimeoutOnSend";
92 if (getrusage(RUSAGE_SELF, &usage) == 0) {
96 return usage.ru_maxrss / 1024;
98 return usage.ru_maxrss;
115 if (!value.empty() && value.back() ==
'/')
128 if (!value.empty() && value[0] ==
'"')
130 if (!value.empty() && value.back() ==
'"')
141 strm <<
"HTTP/1.0 200 OK" << CRLF;
142 strm <<
"XBES-Server: " << PACKAGE_STRING << CRLF;
144 const time_t t = time(0);
145 strm <<
"Date: " << rfc822_date(t).c_str() << CRLF;
146 strm <<
"Last-Modified: " << rfc822_date(t).c_str() << CRLF;
148 strm <<
"Content-Type: text/plain" << CRLF;
150 strm <<
"Content-Description: unknown" << CRLF;
160 strm <<
"HTTP/1.0 200 OK" << CRLF;
161 strm <<
"XBES-Server: " << PACKAGE_STRING << CRLF;
163 const time_t t = time(0);
164 strm <<
"Date: " << rfc822_date(t).c_str() << CRLF;
165 strm <<
"Last-Modified: " << rfc822_date(t).c_str() << CRLF;
167 strm <<
"Content-type: text/html" << CRLF;
169 strm <<
"Content-Description: unknown" << CRLF;
205static const char *days[] = {
"Sun",
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat" };
206static const char *months[] = {
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec" };
217string BESUtil::rfc822_date(
const time_t t)
223 snprintf(d, 255,
"%s, %02d %s %4d %02d:%02d:%02d GMT", days[stm.tm_wday], stm.tm_mday,
224 months[stm.tm_mon], 1900 + stm.tm_year, stm.tm_hour, stm.tm_min, stm.tm_sec);
229string BESUtil::unhexstring(
const string& s)
233 ss >> std::hex >> val;
235 tmp_str[0] =
static_cast<char>(val);
244 string::size_type i = 0;
246 while ((i = res.find_first_of(escape, i)) != string::npos) {
247 if (except.find(res.substr(i, 3)) != string::npos) {
251 res.replace(i, 3, unhexstring(res.substr(i + 1, 2)));
259 string return_string = s;
260 for (
int j = 0; j < static_cast<int>(return_string.size()); j++) {
261 return_string[j] = (char) tolower(return_string[j]);
264 return return_string;
270 string::size_type index = 0;
274 string::size_type bs = s.find(
'\\', index);
275 if (bs == string::npos) {
276 new_str += s.substr(index, s.size() - index);
280 new_str += s.substr(index, bs - index);
281 new_str += s[bs + 1];
294static void throw_access_error(
const string &pathname,
long error_number)
296 switch(error_number) {
299 string message = string(
"Failed to locate '").append(pathname).append(
"'\n");
305 string message = string(
"Not allowed to access '").append(pathname).append(
"'\n");
318bool pathname_contains_symlink(
const string &path,
int search_limit)
324 string pathname = path;
325 if (!pathname.empty() && pathname.back() ==
'/') {
329 bool is_link =
false;
335 int status = lstat(pathname.c_str(), &buf);
337 is_link = S_ISLNK(buf.st_mode);
340 string msg =
"Could not resolve path when testing for symbolic links: ";
341 msg.append(strerror(errno));
342 BESDEBUG(MODULE, prolog << msg << endl);
347 pos = pathname.find_last_of(
'/');
348 if (pos != string::npos)
350 }
while (++i < search_limit && !is_link && pos != string::npos && !pathname.empty());
358 ssize_t len = readlinkat(AT_FDCWD, pathname.c_str(),
nullptr, 0);
365 string msg =
"Could not resolve path when testing for symbolic links: ";
366 msg.append(strerror(errno));
387 if (path ==
"")
return;
389 if (path.find(
"..") != string::npos) {
390 throw_access_error(path, EACCES);
397 string pathname = root;
399 if (pathname.back() !=
'/' && path.front() !=
'/')
400 pathname.append(
"/");
402 pathname.append(path);
403 if (access(pathname.c_str(), R_OK) != 0) {
404 throw_access_error(pathname, errno);
407 if (follow_sym_links ==
false) {
408 auto n = count(path.begin(), path.end(),
'/');
411 if (pathname_contains_symlink(pathname, n)) {
412 throw_access_error(pathname, EACCES);
430 if (base > 36 || base < 2)
435 if (val < 0) *buf++ =
'-';
436 r = ldiv(labs(val), base);
443 *buf++ =
"0123456789abcdefghijklmnopqrstuvwxyz"[(int) r.rem];
451 string::size_type first = key.find_first_not_of(
" \t\n\r");
452 string::size_type last = key.find_last_not_of(
" \t\n\r");
453 if (first == string::npos)
456 string::size_type num = last - first + 1;
457 string new_key = key.substr(first, num);
463string BESUtil::entity(
char c)
489 string::size_type i = 0;
491 while ((i = in.find_first_of(not_allowed, i)) != string::npos) {
492 in.replace(i, 1, entity(in[i]));
506 string::size_type i = 0;
508 while ((i = in.find(
">", i)) != string::npos)
509 in.replace(i, 4,
">");
512 while ((i = in.find(
"<", i)) != string::npos)
513 in.replace(i, 4,
"<");
516 while ((i = in.find(
"&", i)) != string::npos)
517 in.replace(i, 5,
"&");
520 while ((i = in.find(
"'", i)) != string::npos)
521 in.replace(i, 6,
"'");
524 while ((i = in.find(
""", i)) != string::npos)
525 in.replace(i, 6,
"\"");
545 std::string::size_type start = 0;
546 std::string::size_type qstart = 0;
547 std::string::size_type adelim = 0;
548 std::string::size_type aquote = 0;
552 if (str[start] ==
'"') {
553 bool endquote =
false;
556 aquote = str.find(
'"', qstart);
557 if (aquote == string::npos) {
558 string currval = str.substr(start, str.size() - start);
559 string err =
"BESUtil::explode - No end quote after value " + currval;
564 if (str[aquote - 1] ==
'\\') {
565 if (str[aquote - 2] ==
'\\') {
578 if (str[qstart] != delim && qstart != str.size()) {
579 string currval = str.substr(start, qstart - start);
580 string err =
"BESUtil::explode - No delim after end quote " + currval;
583 if (qstart == str.size()) {
584 adelim = string::npos;
591 adelim = str.find(delim, start);
593 if (adelim == string::npos) {
594 aval = str.substr(start, str.size() - start);
598 aval = str.substr(start, adelim - start);
601 values.push_back(aval);
603 if (start == str.size()) {
604 values.push_back(
"");
627 for (; i != e; i++) {
628 if (!first) result += delim;
629 d = (*i).find(delim);
630 if (d != string::npos && (*i)[0] !=
'"') {
631 string err = (string)
"BESUtil::implode - delimiter exists in value " + (*i);
664 string::size_type colon = url_str.find(
":");
665 if (colon == string::npos) {
666 string err =
"BESUtil::url_explode: missing colon for protocol";
670 url_parts.protocol = url_str.substr(0, colon);
672 if (url_str.substr(colon, 3) !=
"://") {
673 string err =
"BESUtil::url_explode: no :// in the URL";
678 rest = url_str.substr(colon);
680 string::size_type slash = rest.find(
"/");
681 if (slash == string::npos) slash = rest.size();
683 string::size_type at = rest.find(
"@");
684 if ((at != string::npos) && (at < slash)) {
686 string up = rest.substr(0, at);
687 colon = up.find(
":");
688 if (colon != string::npos) {
689 url_parts.uname = up.substr(0, colon);
690 url_parts.psswd = up.substr(colon + 1);
693 url_parts.uname = up;
696 rest = rest.substr(at + 1);
698 slash = rest.find(
"/");
699 if (slash == string::npos) slash = rest.size();
700 colon = rest.find(
":");
701 if ((colon != string::npos) && (colon < slash)) {
703 url_parts.domain = rest.substr(0, colon);
705 rest = rest.substr(colon + 1);
706 slash = rest.find(
"/");
707 if (slash != string::npos) {
708 url_parts.port = rest.substr(0, slash);
709 url_parts.path = rest.substr(slash + 1);
712 url_parts.port = rest;
717 slash = rest.find(
"/");
718 if (slash != string::npos) {
719 url_parts.domain = rest.substr(0, slash);
720 url_parts.path = rest.substr(slash + 1);
723 url_parts.domain = rest;
730 string url = url_parts.protocol +
"://";
731 if (!url_parts.uname.empty()) {
732 url += url_parts.uname;
733 if (!url_parts.psswd.empty()) url +=
":" + url_parts.psswd;
736 url += url_parts.domain;
737 if (!url_parts.port.empty())
url +=
":" + url_parts.port;
738 if (!url_parts.path.empty())
url +=
"/" + url_parts.path;
756 string first = firstPart;
757 string second = secondPart;
758 string sep(1,separator);
762 while (!first.empty() && *first.rbegin() == separator) {
764 first = first.substr(0, first.size() - 1);
767 while (!second.empty() && second[0] == separator) {
775 else if (second.empty()) {
779 newPath = first.append(sep).append(second);
804string BESUtil::assemblePath(
const string &firstPart,
const string &secondPart,
bool leadingSlash,
bool trailingSlash)
806 BESDEBUG(MODULE, prolog <<
"firstPart: '" << firstPart <<
"'" << endl);
807 BESDEBUG(MODULE, prolog <<
"secondPart: '" << secondPart <<
"'" << endl);
811 if (newPath.empty()) {
814 else if (newPath.front() !=
'/') {
815 newPath =
"/" + newPath;
820 if (newPath.empty() || newPath.back() !=
'/') {
825 while (!newPath.empty() && newPath.back() ==
'/')
826 newPath.erase(newPath.size()-1);
829 BESDEBUG(MODULE, prolog <<
"newPath: " << newPath << endl);
839 if (fullString.size() >= ending.size()) {
840 return (0 == fullString.compare(fullString.size() - ending.size(), ending.size(), ending));
872 msg <<
"The submitted request took too long to service.";
900 const string false_str =
"false";
901 const string no_str =
"no";
903 bool cancel_timeout_on_send =
true;
910 if ( value == false_str || value == no_str) cancel_timeout_on_send =
false;
912 BESDEBUG(MODULE, __func__ <<
"() - cancel_timeout_on_send: " << (cancel_timeout_on_send ?
"true" :
"false") << endl);
913 if (cancel_timeout_on_send) {
926 unsigned int replace_count = 0;
927 size_t pos = s.find(find_this);
928 while (pos != string::npos) {
930 s.replace(pos, find_this.size(), replace_with_this);
932 pos = s.find(find_this, pos + replace_with_this.size());
935 return replace_count;
949string BESUtil::normalize_path(
const string &raw_path,
bool leading_separator,
bool trailing_separator,
const string separator )
951 if (separator.size() != 1)
952 throw BESInternalError(
"Path separators must be a single character. The string '" + separator +
"' does not qualify.", __FILE__, __LINE__);
953 char separator_char = separator[0];
954 string double_separator;
955 double_separator = double_separator.append(separator).append(separator);
957 string path(raw_path);
964 if (path == separator) {
967 if (leading_separator) {
968 if (path[0] != separator_char) {
969 path = string(separator).append(path);
973 if (path[0] == separator_char) {
974 path = path.substr(1);
977 if (trailing_separator) {
978 if (*path.rbegin() != separator_char) {
979 path = path.append(separator);
983 if (*path.rbegin() == separator_char) {
984 path = path.substr(0, path.size() - 1);
998 string::size_type lastPos = str.find_first_not_of(delimiters, 0);
1000 string::size_type pos = str.find_first_of(delimiters, lastPos);
1001 while (string::npos != pos || string::npos != lastPos) {
1003 tokens.push_back(str.substr(lastPos, pos - lastPos));
1005 lastPos = str.find_first_not_of(delimiters, pos);
1007 pos = str.find_first_of(delimiters, lastPos);
1019 return get_time(time(0), use_local_time);
1031 char buf[
sizeof "YYYY-MM-DDTHH:MM:SS zones"];
1041 if (!use_local_time) {
1042 gmtime_r(&the_time, &result);
1043 status = strftime(buf,
sizeof buf,
"%FT%T%Z", &result);
1046 localtime_r(&the_time, &result);
1047 status = strftime(buf,
sizeof buf,
"%FT%T%Z", &result);
1051 ERROR_LOG(prolog +
"Error formatting time value!");
1052 return "date-format-error";
1072 vector<string> tokens;
1074 while (getline(ss, item, delim)) {
1076 if (item.empty() && skip_empty)
1079 tokens.push_back(item);
1085BESCatalog *BESUtil::separateCatalogFromPath(std::string &ppath)
1088 vector<string> path_tokens;
1092 BESDEBUG(MODULE, prolog <<
"Normalized path: " << path << endl);
1097 string use_container = ppath;
1101 if (!path_tokens.empty()) {
1102 BESDEBUG(MODULE,
"First path token: " << path_tokens[0] << endl);
1103 catalog = BESCatalogList::TheCatalogList()->find_catalog(path_tokens[0]);
1105 BESDEBUG(MODULE, prolog <<
"Located catalog " << catalog->
get_catalog_name() <<
" from path component" << endl);
1110 BESDEBUG(MODULE, prolog <<
"Modified container/path value to: " << use_container << endl);
1117void ios_state_msg(std::ios &ios_ref, std::stringstream &msg) {
1118 msg <<
" {ios.good()=" << (ios_ref.good() ?
"true" :
"false") <<
"}";
1119 msg <<
" {ios.eof()=" << (ios_ref.eof()?
"true":
"false") <<
"}";
1120 msg <<
" {ios.fail()=" << (ios_ref.fail()?
"true":
"false") <<
"}";
1121 msg <<
" {ios.bad()=" << (ios_ref.bad()?
"true":
"false") <<
"}";
1126#define OUTPUT_FILE_BLOCK_SIZE 4096
1140 msg << prolog <<
"Using ostream: " << (
void *) &o_strm <<
" cout: " << (
void *) &cout << endl;
1141 BESDEBUG(MODULE, msg.str());
1142 INFO_LOG( msg.str());
1145 vector<char> rbuffer(OUTPUT_FILE_BLOCK_SIZE);
1146 std::ifstream i_stream(file_name, std::ios_base::in | std::ios_base::binary);
1149 if(!i_stream.good()){
1151 msg << prolog <<
"Failed to open file " << file_name;
1152 ios_state_msg(i_stream, msg);
1153 BESDEBUG(MODULE, msg.str() << endl);
1160 msg << prolog <<
"Problem with ostream. " << file_name;
1161 ios_state_msg(i_stream, msg);
1162 BESDEBUG(MODULE, msg.str() << endl);
1166 i_stream.seekg(read_start_position);
1170 uint64_t tcount = 0;
1171 while (i_stream.good() && o_strm.good()){
1172 i_stream.read(rbuffer.data(), OUTPUT_FILE_BLOCK_SIZE);
1173 o_strm.write(rbuffer.data(), i_stream.gcount());
1174 tcount += i_stream.gcount();
1179 if(i_stream.fail() && !i_stream.eof()){
1181 msg << prolog <<
"There was an ifstream error when reading from: " << file_name;
1182 ios_state_msg(i_stream, msg);
1183 msg <<
" last_lap: " << i_stream.gcount() <<
" bytes";
1184 msg <<
" total_read: " << tcount <<
" bytes";
1185 BESDEBUG(MODULE, msg.str() << endl);
1190 if (!i_stream.eof()){
1192 msg << prolog <<
"Failed to reach EOF on source file: " << file_name;
1193 ios_state_msg(i_stream, msg);
1194 msg <<
" last_lap: " << i_stream.gcount() <<
" bytes";
1195 msg <<
" total_read: " << tcount <<
" bytes";
1196 BESDEBUG(MODULE, msg.str() << endl);
1203 msg << prolog <<
"There was an ostream error during transmit. Transmitted " << tcount <<
" bytes.";
1204 ios_state_msg(o_strm, msg);
1205 auto crntpos = o_strm.tellp();
1206 msg <<
" current_position: " << crntpos << endl;
1207 BESDEBUG(MODULE, msg.str());
1208 ERROR_LOG(msg.str());
1213 msg << prolog <<
"Sent "<< tcount <<
" bytes from file '" << file_name<<
"'. " << endl;
1214 BESDEBUG(MODULE,msg.str());
1215 INFO_LOG(msg.str());
1224 if (stat(p.c_str(), &st) == 0) {
1225 return S_ISDIR(st.st_mode);
1236 size_t pos = p.find_last_of(
'/');
1237 if (pos == string::npos) {
1240 else if (pos == 0) {
1244 return p.substr(0, pos);
1260 if (p[p.size() - 1] ==
'/') {
1261 p = p.substr(0, p.size() - 1);
1274 rc = mkdir(p.c_str(), mode);
1280string BESUtil::file_to_string(
const string &filename) {
1281 std::ifstream t(filename);
1283 throw BESInternalError(
"Could not open file: " + filename, __FILE__, __LINE__);
1285 std::stringstream buffer;
1286 buffer << t.rdbuf();
1287 return buffer.str();
1306 int fd = mkstemp(&temp_file_name[0]);
1308 throw BESInternalError(
string(
"mkstemp() for ") + temp_file_name +
" failed (" + strerror(errno) +
").",
1309 __FILE__, __LINE__);
1322 std::ofstream t(filename, std::ios::out | std::ios::trunc);
1324 throw BESInternalError(
"Could not open file: " + filename, __FILE__, __LINE__);
1335 const auto the_bad_things =
"\r\n";
1337 while ((pos = str.find_first_of(the_bad_things, pos)) != std::string::npos) {
1344std::string BESUtil::uuid() {
1346 uuid_generate_random(raw_uuid);
1348 uuid_unparse_lower(raw_uuid, uuid_str);
Catalogs provide a hierarchical organization for data.
virtual std::string get_catalog_name() const
Get the name for this catalog.
error thrown if the BES is not allowed to access the resource requested
exception thrown if internal error encountered
exception thrown if an internal error is found and is fatal to the BES
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...
static void explode(char delim, const std::string &str, std::list< std::string > &values)
static long get_current_memory_usage() noexcept
Get the Resident Set Size in KB.
static void url_explode(const std::string &url_str, BESUtil::url &url_parts)
Given a url, break the url into its different parts.
static bool endsWith(std::string const &fullString, std::string const &ending)
static void tokenize(const std::string &str, std::vector< std::string > &tokens, const std::string &delimiters="/")
static std::string get_dir_name(const std::string &p)
static void set_mime_text(std::ostream &strm)
Generate an HTTP 1.0 response header for a text document.
static std::string id2xml(std::string in, const std::string ¬_allowed="><&'\"")
static void conditional_timeout_cancel()
Checks if the timeout alarm should be canceled based on the value of the BES key BES....
static void check_path(const std::string &path, const std::string &root, bool follow_sym_links)
Is the combination of root + path a pathname the BES can/should access?
static int mkdir_p(const std::string &path, mode_t mode)
static void exit_on_request_timeout()
Checks if the timeout alarm should be canceled based on the value of the BES key BES....
static unsigned int 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...
static void set_mime_html(std::ostream &strm)
Generate an HTTP 1.0 response header for a html document.
static std::string lowercase(const std::string &s)
static bool is_directory(const std::string &p)
Is the given path a directory?
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.
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.
static std::string www2id(const std::string &in, const std::string &escape="%", const std::string &except="")
static std::string implode(const std::list< std::string > &values, char delim)
static void trim_if_surrounding_quotes(std::string &value)
Remove double quotes around a string This function will remove a leading and/or trailing double quote...
static std::string normalize_path(const std::string &path, bool leading_separator, bool trailing_separator, std::string separator="/")
Removes duplicate separators and provides leading and trailing separators as directed.
static std::string xml2id(std::string in)
static uint64_t file_to_stream(const std::string &file_name, std::ostream &o_strm, uint64_t read_start_position=0)
Copies the contents of the file identified by file_name to the stream o_strm.
static void trim_if_trailing_slash(std::string &value)
If the string ends in a slash, remove it This function works for empty strings (doing nothing)....
static std::string unescape(const std::string &s)
static void string_to_file(const std::string &filename, const std::string &content)
Write a string to a file.
static char * fastpidconverter(char *buf, int base)
static void removeLeadingAndTrailingBlanks(std::string &key)
static std::string & remove_crlf(std::string &str)
"Sanitizes" the string by replacing any 0x0A (new line) or 0x0D (carriage return) characters with 0x2...
static int make_temp_file(const std::string &temp_file_dir, std::string &temp_file_name)
Make and open a temporary file. The file is opened such that we know it is unique and not in use by a...
static std::string get_time(bool use_local_time=false)
static RequestServiceTimer * TheTimer()
Return a pointer to a singleton timer instance. If an instance does not exist it will create and init...
void disable_timeout()
Set the time_out is disabled.
void get_value(const std::string &s, std::string &val, bool &found)
Retrieve the value of a given key, if set.
static TheBESKeys * TheKeys()
Access to the singleton.