D4Connect.cc
// -*- mode: c++; c-basic-offset:4 -*-
// This file is part of libdap, A C++ implementation of the OPeNDAP Data
// Access Protocol.
// Copyright (c) 2002,2003 OPeNDAP, Inc.
// Author: James Gallagher <jgallagher@opendap.org>
// Dan Holloway <dholloway@gso.uri.edu>
// Reza Nekovei <reza@intcomm.net>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
// (c) COPYRIGHT URI/MIT 1994-2002
// Please read the full copyright statement in the file COPYRIGHT_URI.
//
// Authors:
// jhrg,jimg James Gallagher <jgallagher@gso.uri.edu>
// dan Dan Holloway <dholloway@gso.uri.edu>
// reza Reza Nekovei <reza@intcomm.net>
#include "config.h"
// #define DODS_DEBUG 1
#include <cassert>
#include <sstream>
#include "D4Connect.h"
#include "D4Group.h"
#include "DMR.h"
#include "HTTPConnect.h"
#include "Response.h"
#include "D4ParserSax2.h"
#include "D4StreamUnMarshaller.h"
#include "chunked_istream.h"
#include "chunked_stream.h"
#include "debug.h"
#include "escaping.h"
#include "mime_util.h"
using namespace std;
namespace libdap {
void D4Connect::process_dmr(DMR &dmr, Response &rs) {
DBG(cerr << "Entering D4Connect::process_dmr" << endl);
dmr.set_dap_version(rs.get_protocol());
DBG(cerr << "Entering process_data. Response.getVersion() = " << rs.get_version() << endl);
switch (rs.get_type()) {
case dap4_error: {
#if 0
Error e;
if (!e.parse(rs.get_stream()))
throw InternalErr(__FILE__, __LINE__, "Could not parse the Error object returned by the server!");
throw e;
#endif
throw InternalErr(__FILE__, __LINE__, "DAP4 errors not processed yet: FIXME!");
}
case web_error:
// Web errors (those reported in the return document's MIME header)
// are processed by the WWW library.
throw InternalErr(__FILE__, __LINE__,
"An error was reported by the remote httpd; this should have been processed by HTTPConnect.");
case dap4_dmr: {
// parse the DMR
try {
D4ParserSax2 parser;
// When parsing a data response, we use the permissive mode of the DMR parser
// (which allows Map elements to reference Arrays that are not in the DMR).
// Do not use that mode when parsing the DMR response - assume the DMR is
// valid. jhrg 4/13/16
parser.intern(*rs.get_cpp_stream(), &dmr);
} catch (Error &e) {
cerr << "Exception: " << e.get_error_message() << endl;
return;
} catch (std::exception &e) {
cerr << "Exception: " << e.what() << endl;
return;
} catch (...) {
cerr << "Exception: unknown error" << endl;
return;
}
return;
}
default:
throw Error("Unknown response type");
}
}
void D4Connect::process_data(DMR &data, Response &rs) {
DBG(cerr << "Entering D4Connect::process_data" << endl);
assert(rs.get_cpp_stream()); // DAP4 code uses cpp streams
data.set_dap_version(rs.get_protocol());
DBG(cerr << "Entering process_data. Response.getVersion() = " << rs.get_version() << endl);
switch (rs.get_type()) {
case dap4_error: {
throw InternalErr(__FILE__, __LINE__, "DAP4 errors not processed yet: FIXME!");
}
case web_error:
// Web errors (those reported in the return document's MIME header)
// are processed by the WWW library.
throw InternalErr(
__FILE__, __LINE__,
"An error was reported by the remote httpd; this should have been processed by HTTPConnect..");
case dap4_data: {
chunked_istream cis(*(rs.get_cpp_stream()), CHUNK_SIZE);
// parse the DMR, stopping when the boundary is found.
try {
// force chunk read
// get chunk size
int chunk_size = cis.read_next_chunk();
if (chunk_size < 0)
throw Error("Found an unexpected end of input (EOF) while reading a DAP4 data response. (1)");
// get chunk
char chunk[chunk_size];
cis.read(chunk, chunk_size);
// parse char * with given size
D4ParserSax2 parser;
// permissive mode allows references to Maps that are not in the response.
// Use this mode when parsing a data response (but not the DMR). jhrg 4/13/16
parser.set_strict(false);
// '-2' to discard the CRLF pair
parser.intern(chunk, chunk_size - 2, &data);
} catch (Error &e) {
cerr << "Exception: " << e.get_error_message() << endl;
return;
} catch (std::exception &e) {
cerr << "Exception: " << e.what() << endl;
return;
} catch (...) {
cerr << "Exception: unknown error" << endl;
return;
}
D4StreamUnMarshaller um(cis, cis.twiddle_bytes());
data.root()->deserialize(um, data);
return;
}
default:
throw Error("Unknown response type");
}
}
void D4Connect::parse_mime(Response &rs) {
rs.set_version("dods/0.0"); // initial value; for backward compatibility.
rs.set_protocol("2.0");
istream &data_source = *rs.get_cpp_stream();
string mime = get_next_mime_header(data_source);
while (!mime.empty()) {
string header, value;
parse_mime_header(mime, header, value);
// Note that this is an ordered list
if (header == "content-description") {
DBG(cout << header << ": " << value << endl);
rs.set_type(get_description_type(value));
}
// Use the value of xdods-server only if no other value has been read
else if (header == "xdods-server" && rs.get_version() == "dods/0.0") {
DBG(cout << header << ": " << value << endl);
rs.set_version(value);
}
// This trumps 'xdods-server' and 'server'
else if (header == "xopendap-server") {
DBG(cout << header << ": " << value << endl);
rs.set_version(value);
} else if (header == "xdap") {
DBG(cout << header << ": " << value << endl);
rs.set_protocol(value);
}
// Only look for 'server' if no other header supplies this info.
else if (rs.get_version() == "dods/0.0" && header == "server") {
DBG(cout << header << ": " << value << endl);
rs.set_version(value);
}
mime = get_next_mime_header(data_source);
}
}
// public mfuncs
D4Connect::D4Connect(const string &url, string uname, string password)
: d_http(0), d_local(false), d_URL(""), d_UrlQueryString(""), d_server("unknown"), d_protocol("4.0") {
string name = prune_spaces(url);
// Figure out if the URL starts with 'http', if so, make sure that we
// talk to an instance of HTTPConnect.
if (name.find("http") == 0) {
DBG(cerr << "Connect: The identifier is an http URL" << endl);
d_http = new HTTPConnect(RCReader::instance());
d_http->set_use_cpp_streams(true);
d_URL = name;
// Find and store any CE given with the URL.
string::size_type dotpos = name.find('?');
if (dotpos != std::string::npos) { // Found a match.
d_URL = name.substr(0, dotpos);
d_UrlQueryString = name.substr(dotpos + 1);
if (d_UrlQueryString.find(DAP4_CE_QUERY_KEY) != std::string::npos) {
std::stringstream msg;
msg << endl;
msg << "WARNING: A DAP4 constraint expression key was found in the query string!" << endl;
msg << "The submitted dataset URL: " << name << endl;
msg << "Contains the query string: " << d_UrlQueryString << endl;
msg << "This will cause issues when making DAP4 requests that specify additional constraints. " << endl;
cerr << msg.str() << endl;
// throw Error(malformed_expr, msg.str());
}
}
} else {
DBG(cerr << "Connect: The identifier is a local data source." << endl);
d_local = true; // local in this case means non-DAP
}
set_credentials(uname, password);
}
D4Connect::~D4Connect() {
if (d_http)
delete d_http;
}
std::string D4Connect::build_dap4_ce(const string requestSuffix, const string dap4ce) {
std::stringstream url;
bool needsAmpersand = false;
url << d_URL << requestSuffix << "?";
if (d_UrlQueryString.length() > 0) {
url << d_UrlQueryString;
needsAmpersand = true;
}
if (dap4ce.length() > 0) {
if (needsAmpersand)
url << "&";
url << DAP4_CE_QUERY_KEY << "=" << id2www_ce(dap4ce);
}
DBG(cerr << "D4Connect::build_dap4_ce() - Source URL: " << d_URL << endl);
DBG(cerr << "D4Connect::build_dap4_ce() - Source URL Query String: " << d_UrlQueryString << endl);
DBG(cerr << "D4Connect::build_dap4_ce() - dap4ce: " << dap4ce << endl);
DBG(cerr << "D4Connect::build_dap4_ce() - request URL: " << url.str() << endl);
return url.str();
}
void D4Connect::request_dmr(DMR &dmr, const string expr) {
string url = build_dap4_ce(".dmr", expr);
Response *rs = 0;
try {
rs = d_http->fetch_url(url);
d_server = rs->get_version();
d_protocol = rs->get_protocol();
switch (rs->get_type()) {
case unknown_type:
DBG(cerr << "Response type unknown, assuming it's a DMR response." << endl);
/* no break */
case dap4_dmr: {
D4ParserSax2 parser;
parser.intern(*rs->get_cpp_stream(), &dmr);
break;
}
case dap4_error:
throw InternalErr(__FILE__, __LINE__, "DAP4 errors are not processed yet.");
case web_error:
// We should never get here; a web error should be picked up read_url
// (called by fetch_url) and result in a thrown Error object.
throw InternalErr(__FILE__, __LINE__, "Web error found where it should never be.");
default:
throw InternalErr(__FILE__, __LINE__,
"Response type not handled (got " + long_to_string(rs->get_type()) + ").");
}
} catch (...) {
delete rs;
throw;
}
delete rs;
}
void D4Connect::request_dap4_data(DMR &dmr, const string expr) {
string url = build_dap4_ce(".dap", expr);
Response *rs = 0;
try {
rs = d_http->fetch_url(url);
d_server = rs->get_version();
d_protocol = rs->get_protocol();
switch (rs->get_type()) {
case unknown_type:
DBG(cerr << "Response type unknown, assuming it's a DAP4 Data response." << endl);
/* no break */
case dap4_data: {
// get a chunked input stream
chunked_istream cis(*(rs->get_cpp_stream()), CHUNK_SIZE);
// parse the DMR, stopping when the boundary is found.
// force chunk read
// get chunk size
int chunk_size = cis.read_next_chunk();
if (chunk_size < 0)
throw Error("Found an unexpected end of input (EOF) while reading a DAP4 data response. (2)");
// get chunk
char chunk[chunk_size];
cis.read(chunk, chunk_size);
// parse char * with given size
D4ParserSax2 parser;
// permissive mode allows references to Maps that are not in the response.
parser.set_strict(false);
// '-2' to discard the CRLF pair
parser.intern(chunk, chunk_size - 2, &dmr, false /*debug*/);
// Read data and store in the DMR
D4StreamUnMarshaller um(cis, cis.twiddle_bytes());
dmr.root()->deserialize(um, dmr);
break;
}
case dap4_error:
throw InternalErr(__FILE__, __LINE__, "DAP4 errors are not processed yet.");
case web_error:
// We should never get here; a web error should be picked up read_url
// (called by fetch_url) and result in a thrown Error object.
throw InternalErr(__FILE__, __LINE__, "Web error found where it should never be.");
default:
throw InternalErr(__FILE__, __LINE__,
"Response type not handled (got " + long_to_string(rs->get_type()) + ").");
}
} catch (...) {
delete rs;
throw;
}
delete rs;
}
void D4Connect::read_dmr(DMR &dmr, Response &rs) {
parse_mime(rs);
if (rs.get_type() == unknown_type)
throw Error("Unknown response type.");
read_dmr_no_mime(dmr, rs);
}
void D4Connect::read_dmr_no_mime(DMR &dmr, Response &rs) {
// Assume callers know what they are doing
if (rs.get_type() == unknown_type)
rs.set_type(dap4_dmr);
switch (rs.get_type()) {
case dap4_dmr:
process_dmr(dmr, rs);
d_server = rs.get_version();
d_protocol = dmr.dap_version();
break;
default:
throw Error("Expected a DAP4 DMR response.");
}
}
void D4Connect::read_data(DMR &data, Response &rs) {
parse_mime(rs);
if (rs.get_type() == unknown_type)
throw Error("Unknown response type.");
read_data_no_mime(data, rs);
}
void D4Connect::read_data_no_mime(DMR &data, Response &rs) {
// Assume callers know what they are doing
if (rs.get_type() == unknown_type)
rs.set_type(dap4_data);
switch (rs.get_type()) {
case dap4_data:
process_data(data, rs);
d_server = rs.get_version();
d_protocol = data.dap_version();
break;
default:
throw Error("Expected a DAP4 Data response.");
}
}
void D4Connect::set_credentials(string u, string p) {
if (d_http)
d_http->set_credentials(u, p);
}
void D4Connect::set_accept_deflate(bool deflate) {
if (d_http)
d_http->set_accept_deflate(deflate);
}
void D4Connect::set_xdap_protocol(int major, int minor) {
if (d_http)
d_http->set_xdap_protocol(major, minor);
}
void D4Connect::set_cache_enabled(bool cache) {
if (d_http)
d_http->set_cache_enabled(cache);
}
bool D4Connect::is_cache_enabled() {
if (d_http)
return d_http->is_cache_enabled();
else
return false;
}
} // namespace libdap
libdap::D4Connect::set_accept_deflate
void set_accept_deflate(bool deflate)
Definition D4Connect.cc:467
libdap::D4Connect::set_cache_enabled
void set_cache_enabled(bool enabled)
Definition D4Connect.cc:485
libdap::D4Connect::set_xdap_protocol
void set_xdap_protocol(int major, int minor)
Definition D4Connect.cc:477
libdap::D4Connect::set_credentials
void set_credentials(std::string u, std::string p)
Set the credentials for responding to challenges while dereferencing URLs.
Definition D4Connect.cc:459
libdap::HTTPConnect
Definition HTTPConnect.h:71
libdap::HTTPConnect::set_accept_deflate
void set_accept_deflate(bool defalte)
Definition HTTPConnect.cc:941
libdap::HTTPConnect::is_cache_enabled
bool is_cache_enabled()
Definition HTTPConnect.h:152
libdap::HTTPConnect::fetch_url
HTTPResponse * fetch_url(const string &url)
Definition HTTPConnect.cc:576
libdap::HTTPConnect::set_credentials
void set_credentials(const string &u, const string &p)
Definition HTTPConnect.cc:998
libdap::HTTPConnect::set_cache_enabled
void set_cache_enabled(bool enabled)
Definition HTTPConnect.h:146
libdap::HTTPConnect::set_xdap_protocol
void set_xdap_protocol(int major, int minor)
Definition HTTPConnect.cc:965
libdap
top level DAP object to house generic methods
Definition AlarmHandler.h:35
libdap::get_description_type
ObjectType get_description_type(const string &value)
Definition mime_util.cc:309
libdap::parse_mime_header
void parse_mime_header(const string &header, string &name, string &value)
Definition mime_util.cc:848
libdap::prune_spaces
string prune_spaces(const string &name)
Definition util.cc:451
libdap::id2www_ce
string id2www_ce(string in, const string &allowable)
Definition escaping.cc:166
libdap::get_next_mime_header
string get_next_mime_header(FILE *in)
Definition mime_util.cc:777