66#define VERBOSE_RUNTIME(x) \
68 if (d_verbose_runtime) \
72#define VERBOSE_RUNTIME(x)
89#define CLIENT_ERR_MIN 400
90#define CLIENT_ERR_MAX 417
93 "Unauthorized: Contact the server administrator.",
95 "Forbidden: Contact the server administrator.",
96 "Not Found: The data source or server could not be found.\n\
97 Often this means that the OPeNDAP server is missing or needs attention.\n\
98 Please contact the server administrator.",
99 "Method Not Allowed.",
101 "Proxy Authentication Required.",
106 "Precondition Failed.",
107 "Request Entity Too Large.",
108 "Request URI Too Large.",
109 "Unsupported Media Type.",
110 "Requested Range Not Satisfiable.",
111 "Expectation Failed."};
113#define SERVER_ERR_MIN 500
114#define SERVER_ERR_MAX 505
116 "Internal Server Error.",
"Not Implemented.",
"Bad Gateway.",
117 "Service Unavailable.",
"Gateway Time-out.",
"HTTP Version Not Supported."};
121static string http_status_to_string(
int status) {
127 return {
"Unknown Error: This indicates a problem with libdap++.\nPlease report this to support@opendap.org."};
130static ObjectType determine_object_type(
const string &header_value) {
131 string::size_type plus = header_value.find(
'+');
133 string type_extension;
134 if (plus != string::npos) {
135 base_type = header_value.substr(0, plus);
136 type_extension = header_value.substr(plus + 1);
138 base_type = header_value;
140 if (base_type == DMR_Content_Type ||
141 (base_type.find(
"application/") != string::npos && base_type.find(
"dap4.dataset-metadata") != string::npos)) {
142 if (type_extension ==
"xml")
146 }
else if (base_type == DAP4_DATA_Content_Type ||
147 (base_type.find(
"application/") != string::npos && base_type.find(
"dap4.data") != string::npos)) {
149 }
else if (header_value.find(
"text/html") != string::npos) {
159class ParseHeader :
public unary_function<const string &, void> {
161 string server =
"dods/0.0";
162 string protocol =
"2.0";
166 ParseHeader() =
default;
168 void operator()(
const string &line) {
172 DBG2(cerr << name <<
": " << value << endl);
178 type = determine_object_type(value);
186 else if (name ==
"xdods-server" && server ==
"dods/0.0") {
188 }
else if (name ==
"xopendap-server") {
190 }
else if (name ==
"xdap") {
192 }
else if (server ==
"dods/0.0" && name ==
"server") {
194 }
else if (name ==
"location") {
199 ObjectType get_object_type()
const {
return type; }
201 string get_server()
const {
return server; }
203 string get_protocol()
const {
return protocol; }
205 string get_location()
const {
return location; }
223static size_t save_raw_http_headers(
void *ptr,
size_t size,
size_t nmemb,
void *resp_hdrs) {
224 DBG2(cerr <<
"Inside the header parser." << endl);
225 auto hdrs =
static_cast<vector<string> *
>(resp_hdrs);
228 string complete_line;
229 if (nmemb > 1 && *(
static_cast<char *
>(ptr) + size * (nmemb - 2)) ==
'\r')
230 complete_line.assign(
static_cast<char *
>(ptr), size * (nmemb - 2));
232 complete_line.assign(
static_cast<char *
>(ptr), size * (nmemb - 1));
235 if (!complete_line.empty() && complete_line.find(
"HTTP") == string::npos) {
236 DBG(cerr <<
"Header line: " << complete_line << endl);
237 hdrs->push_back(complete_line);
244static int curl_debug(CURL *, curl_infotype info,
char *msg,
size_t size,
void *) {
245 string message(msg, size);
249 cerr <<
"Text: " << message;
251 case CURLINFO_HEADER_IN:
252 cerr <<
"Header in: " << message;
254 case CURLINFO_HEADER_OUT:
255 cerr <<
"Header out: " << message;
257 case CURLINFO_DATA_IN:
259 cerr <<
"Data in: " << message;
261 case CURLINFO_DATA_OUT:
263 cerr <<
"Data out: " << message;
266 cerr <<
"End: " << message;
268#ifdef CURLINFO_SSL_DATA_IN
269 case CURLINFO_SSL_DATA_IN:
270 cerr <<
"SSL Data in: " << message;
273#ifdef CURLINFO_SSL_DATA_OUT
274 case CURLINFO_SSL_DATA_OUT:
275 cerr <<
"SSL Data out: " << message;
280 cerr <<
"Curl info: " << message;
289void HTTPConnect::www_lib_init() {
290 curl_global_init(CURL_GLOBAL_DEFAULT);
292 d_curl = curl_easy_init();
294 throw InternalErr(__FILE__, __LINE__,
"Could not initialize libcurl.");
296 curl_easy_setopt(d_curl, CURLOPT_ERRORBUFFER, d_error_buffer);
298 curl_easy_setopt(d_curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
304 if (!d_rcr->get_proxy_server_host().empty()) {
305 DBG(cerr <<
"Setting up a proxy server." << endl);
306 DBG(cerr <<
"Proxy host: " << d_rcr->get_proxy_server_host() << endl);
307 DBG(cerr <<
"Proxy port: " << d_rcr->get_proxy_server_port() << endl);
308 DBG(cerr <<
"Proxy pwd : " << d_rcr->get_proxy_server_userpw() << endl);
309 curl_easy_setopt(d_curl, CURLOPT_PROXY, d_rcr->get_proxy_server_host().c_str());
310 curl_easy_setopt(d_curl, CURLOPT_PROXYPORT, d_rcr->get_proxy_server_port());
313#ifdef CURLOPT_PROXYAUTH
314 curl_easy_setopt(d_curl, CURLOPT_PROXYAUTH, (
long)CURLAUTH_ANY);
318 if (!d_rcr->get_proxy_server_userpw().empty())
319 curl_easy_setopt(d_curl, CURLOPT_PROXYUSERPWD, d_rcr->get_proxy_server_userpw().c_str());
324 curl_easy_setopt(d_curl, CURLOPT_FAILONERROR, 0);
329 curl_easy_setopt(d_curl, CURLOPT_HTTPAUTH, (
long)CURLAUTH_ANY);
331 curl_easy_setopt(d_curl, CURLOPT_NOPROGRESS, 1);
332 curl_easy_setopt(d_curl, CURLOPT_NOSIGNAL, 1);
333 curl_easy_setopt(d_curl, CURLOPT_HEADERFUNCTION, save_raw_http_headers);
338 curl_easy_setopt(d_curl, CURLOPT_FOLLOWLOCATION, 1);
339 curl_easy_setopt(d_curl, CURLOPT_MAXREDIRS, 5);
342 if (d_rcr->get_validate_ssl() == 0) {
343 curl_easy_setopt(d_curl, CURLOPT_SSL_VERIFYPEER, 0);
344 curl_easy_setopt(d_curl, CURLOPT_SSL_VERIFYHOST, 0);
349 curl_easy_setopt(d_curl, CURLOPT_NETRC, 1);
355 if (!d_cookie_jar.empty()) {
356 DBG(cerr <<
"Setting the cookie jar to: " << d_cookie_jar << endl);
357 curl_easy_setopt(d_curl, CURLOPT_COOKIEJAR, d_cookie_jar.c_str());
358 curl_easy_setopt(d_curl, CURLOPT_COOKIESESSION, 1);
362 cerr <<
"Curl version: " << curl_version() << endl;
363 curl_easy_setopt(d_curl, CURLOPT_VERBOSE, 1);
364 curl_easy_setopt(d_curl, CURLOPT_DEBUGFUNCTION, curl_debug);
371class BuildHeaders :
public unary_function<const string &, void> {
372 struct curl_slist *d_cl =
nullptr;
375 BuildHeaders() =
default;
377 void operator()(
const string &header) {
378 DBG(cerr <<
"Adding '" << header.c_str() <<
"' to the header list." << endl);
379 d_cl = curl_slist_append(d_cl, header.c_str());
382 struct curl_slist *get_headers() {
return d_cl; }
399long HTTPConnect::read_url(
const string &url, FILE *stream, vector<string> &resp_hdrs) {
400 return read_url(url, stream, resp_hdrs, vector<string>());
417long HTTPConnect::read_url(
const string &url, FILE *stream, vector<string> &resp_hdrs,
const vector<string> &headers) {
418 curl_easy_setopt(d_curl, CURLOPT_URL, url.c_str());
420 curl_easy_setopt(d_curl, CURLOPT_WRITEDATA, stream);
422 DBG(copy(d_request_headers.begin(), d_request_headers.end(), ostream_iterator<string>(cerr,
"\n")));
424 BuildHeaders req_hdrs;
425 req_hdrs = for_each(d_request_headers.begin(), d_request_headers.end(), req_hdrs);
426 req_hdrs = for_each(headers.begin(), headers.end(), req_hdrs);
428 curl_easy_setopt(d_curl, CURLOPT_HTTPHEADER, req_hdrs.get_headers());
431 if (url_uses_no_proxy_for(url)) {
432 DBG(cerr <<
"Suppress proxy for url: " << url << endl);
433 curl_easy_setopt(d_curl, CURLOPT_PROXY, 0);
436 string::size_type at_sign = url.find(
'@');
440 if (at_sign != string::npos)
441 d_upstring = url.substr(7, at_sign - 7);
443 if (!d_upstring.empty())
444 curl_easy_setopt(d_curl, CURLOPT_USERPWD, d_upstring.c_str());
449 curl_easy_setopt(d_curl, CURLOPT_WRITEHEADER, &resp_hdrs);
454 CURLcode res = curl_easy_perform(d_curl);
457 curl_slist_free_all(req_hdrs.get_headers());
458 curl_easy_setopt(d_curl, CURLOPT_HTTPHEADER, 0);
461 if (url_uses_no_proxy_for(url) && !d_rcr->get_proxy_server_host().empty())
462 curl_easy_setopt(d_curl, CURLOPT_PROXY, d_rcr->get_proxy_server_host().c_str());
465 throw Error(d_error_buffer);
468 res = curl_easy_getinfo(d_curl, CURLINFO_HTTP_CODE, &status);
470 throw Error(d_error_buffer);
473 res = curl_easy_getinfo(d_curl, CURLINFO_CONTENT_TYPE, &ct_ptr);
474 if (res == CURLE_OK && ct_ptr)
475 d_content_type = ct_ptr;
485bool HTTPConnect::url_uses_proxy_for(
const string &url) {
486 if (d_rcr->is_proxy_for_used()) {
490 Regex host_regex(d_rcr->get_proxy_for_regexp().c_str());
491 int index = 0, matchlen;
492 return host_regex.search(url.c_str(), url.size(), matchlen, index) != -1;
501bool HTTPConnect::url_uses_no_proxy_for(
const string &url)
noexcept {
502 return d_rcr->is_no_proxy_for_used() && url.find(d_rcr->get_no_proxy_for_host()) != string::npos;
517 : d_rcr(rcr), d_accept_deflate(rcr->get_deflate()), d_use_cpp_streams(use_cpp) {
522 d_request_headers.emplace_back(
"Pragma:");
523 d_request_headers.emplace_back(
string(
"User-Agent: ") +
CNAME +
"/" +
CVER);
524 if (d_accept_deflate)
525 d_request_headers.emplace_back(
"Accept-Encoding: deflate, gzip, compress");
528 if (d_rcr->get_use_cache())
531 d_http_cache =
nullptr;
534 d_http_cache->set_cache_enabled(d_rcr->get_use_cache());
535 d_http_cache->set_expire_ignored(d_rcr->get_ignore_expires() != 0);
536 d_http_cache->set_max_size(d_rcr->get_max_cache_size());
537 d_http_cache->set_max_entry_size(d_rcr->get_max_cached_obj());
538 d_http_cache->set_default_expiration(d_rcr->get_default_expires());
539 d_http_cache->set_always_validate(d_rcr->get_always_validate() != 0);
550class HeaderMatch :
public unary_function<const string &, bool> {
551 const string &d_header;
554 HeaderMatch(
const string &header) : d_header(header) {}
555 bool operator()(
const string &arg) {
return arg.find(d_header) == 0; }
574 stream = caching_fetch_url(url);
576 stream = plain_fetch_url(url);
581 ss <<
"HTTP/1.0 " << stream->
get_status() <<
" -" << endl;
582 for (
size_t i = 0; i < stream->
get_headers()->size(); i++) {
595 HeaderMatch(
"Content-Type:")) == stream->
get_headers().end())
596 stream->
get_headers().emplace_back(
"Content-Type: " + d_content_type);
601 if (!parser.get_location().empty() &&
602 (url.substr(0, url.find(
'?')) == parser.get_location().substr(0, url.find(
'?')))) {
607 stream->
set_type(parser.get_object_type());
612 if (d_use_cpp_streams) {
629static string get_tempfile_template(
const string &file_template) {
635 Regex directory(
"[-a-zA-Z0-9_:\\]*");
640 if (c && directory.match(c.c_str(), c.length()) && (access(c.c_str(), 6) == 0))
641 goto valid_temp_directory;
644 if (c && directory.match(c.c_str(), c.length()) && (access(c.c_str(), 6) == 0))
645 goto valid_temp_directory;
650 if (c && directory.match(c.c_str(), c.length()) && (access(c.c_str(), 6) == 0))
651 goto valid_temp_directory;
655 const Regex directory(
"[-a-zA-Z0-9_/]*");
657 c = getenv(
"TMPDIR");
658 if (directory.match(c.c_str(), c.length()) && (access(c.c_str(), W_OK | R_OK) == 0))
659 goto valid_temp_directory;
664 if (access(P_tmpdir, W_OK | R_OK) == 0) {
666 goto valid_temp_directory;
672 if (directory.match(c.c_str(), c.length()) && (access(c.c_str(), W_OK | R_OK) == 0))
673 goto valid_temp_directory;
683 c +=
"\\" + file_template;
685 c +=
"/" + file_template;
709static string get_temp_file(FILE *&stream) {
710 string dods_temp = get_tempfile_template((
string)
"dodsXXXXXX");
712 vector<char> pathname(dods_temp.length() + 1);
714 strncpy(pathname.data(), dods_temp.c_str(), dods_temp.length());
718 mode_t mask = umask(077);
719 int fd = mkstemp(pathname.data());
721 throw Error(
"Could not create a temporary file to store the response: " +
string(strerror(errno)));
723 stream = fdopen(fd,
"w+");
727 throw Error(
"Failed to open a temporary file for the data values (" + dods_temp +
")");
729 return {pathname.data()};
737static void close_temp(FILE *s,
const string &name) {
742 res = unlink(name.c_str());
768HTTPResponse *HTTPConnect::caching_fetch_url(
const string &url) {
771 static recursive_mutex m;
772 lock_guard<recursive_mutex> lock(m);
774 VERBOSE_RUNTIME(cerr <<
"Is this URL (" << url <<
") in the cache?... ");
776 vector<string> headers;
778 FILE *s = d_http_cache->get_cached_response(url, headers, file_name);
782 time_t now = time(
nullptr);
783 HTTPResponse *rs = plain_fetch_url(url);
784 d_http_cache->cache_response(url, now, rs->get_headers(), rs->get_stream());
790 if (d_http_cache->is_url_valid(url)) {
791 VERBOSE_RUNTIME(cerr <<
"and it's valid; using cached response." << endl);
792 d_cached_response =
true;
793 auto crs =
new HTTPCacheResponse(s, 200, headers, file_name, d_http_cache);
798 d_http_cache->release_cached_response(s);
800 vector<string> cond_hdrs = d_http_cache->get_conditional_request_headers(url);
802 string dods_temp = get_temp_file(body);
803 time_t now = time(0);
807 http_status = read_url(url, body, headers, cond_hdrs);
809 }
catch (
const Error &) {
810 close_temp(body, dods_temp);
814 switch (http_status) {
818 d_http_cache->cache_response(url, now, headers, body);
819 auto rs =
new HTTPResponse(body, http_status, headers, dods_temp);
827 close_temp(body, dods_temp);
828 d_cached_response =
true;
829 d_http_cache->update_response(url, now, headers);
831 FILE *hs = d_http_cache->get_cached_response(url, headers, file_name);
832 auto crs =
new HTTPCacheResponse(hs, 304, headers, file_name, d_http_cache);
837 close_temp(body, dods_temp);
838 if (http_status >= 400) {
839 string msg =
"Error while reading the URL: ";
841 msg +=
".\nThe OPeNDAP server returned the following message:\n";
842 msg += http_status_to_string(http_status);
845 throw InternalErr(__FILE__, __LINE__,
846 "Bad response from the HTTP server: " +
long_to_string(http_status));
853 throw InternalErr(__FILE__, __LINE__,
"Should never get here");
867HTTPResponse *HTTPConnect::plain_fetch_url(
const string &url) {
868 DBG(cerr <<
"Getting URL: " << url << endl);
869 FILE *stream =
nullptr;
870 string dods_temp = get_temp_file(stream);
871 vector<string> resp_hdrs;
875 status = read_url(url, stream, resp_hdrs);
877 string msg =
"Error while reading the URL: ";
879 msg +=
".\nThe OPeNDAP server returned the following message:\n";
880 msg += http_status_to_string(status);
883 }
catch (
const Error &) {
884 close_temp(stream, dods_temp);
889 return new HTTPResponse(stream, status, resp_hdrs, dods_temp);
904 lock_guard<mutex> lock(d_connect_mutex);
908 if (d_accept_deflate) {
909 if (find(d_request_headers.begin(), d_request_headers.end(),
"Accept-Encoding: deflate, gzip, compress") ==
910 d_request_headers.end())
911 d_request_headers.emplace_back(
"Accept-Encoding: deflate, gzip, compress");
914 i = remove_if(d_request_headers.begin(), d_request_headers.end(),
915 [](
const string &header) { return header ==
"Accept-Encoding: deflate, gzip, compress"; });
916 d_request_headers.erase(i, d_request_headers.end());
929 lock_guard<mutex> lock(d_connect_mutex);
933 i = find_if(d_request_headers.begin(), d_request_headers.end(), HeaderMatch(
"XDAP-Accept:"));
934 if (i != d_request_headers.end())
935 d_request_headers.erase(i);
938 d_dap_client_protocol_major = major;
939 d_dap_client_protocol_minor = minor;
940 ostringstream xdap_accept;
941 xdap_accept <<
"XDAP-Accept: " << major <<
"." << minor;
943 d_request_headers.push_back(xdap_accept.str());
945 DBG(copy(d_request_headers.begin(), d_request_headers.end(), ostream_iterator<string>(cerr,
"\n")));
964 lock_guard<mutex> lock(d_connect_mutex);
973 d_upstring = u +
":" + p;
#define VERBOSE_RUNTIME(x)
A class for error processing.
static HTTPCache * instance(const std::string &cache_root="")
bool is_cache_enabled() const
void set_accept_deflate(bool deflate)
void set_xdap_protocol(int major, int minor)
void set_credentials(const std::string &u, const std::string &p)
HTTPResponse * fetch_url(const std::string &url)
virtual std::vector< std::string > & get_headers()
A class for software fault reporting.
string get_cookie_jar() const
Regular expression matching.
virtual void set_version(const std::string &v)
virtual int get_status() const
virtual void set_type(ObjectType o)
virtual void set_protocol(const std::string &p)
top level DAP object to house generic methods
ObjectType get_description_type(const string &value)
string long_to_string(long val, int base)
void parse_mime_header(const string &header, string &name, string &value)
ObjectType
The type of object in the stream coming from the data server.