1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
|
// -*- 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 "HTTPConnect.h"
#include "Response.h"
#include "DMR.h"
#include "D4Group.h"
#include "D4ParserSax2.h"
#include "chunked_stream.h"
#include "chunked_istream.h"
#include "D4StreamUnMarshaller.h"
#include "escaping.h"
#include "mime_util.h"
#include "debug.h"
using namespace std;
namespace libdap {
/** This private method process data from both local and remote sources. It
exists to eliminate duplication of code. */
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");
}
}
/** This private method process data from both local and remote sources. It
exists to eliminate duplication of code. */
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");
}
}
/** Use when you cannot use libcurl.
@note This method tests for MIME headers with lines terminated by CRLF
(\r\n) and Newlines (\n). In either case, the line terminators are removed
before each header is processed.
@param rs Value/Result parameter. Dump version and type information here.
*/
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
/** The D4Connect constructor requires a URL or local file.
@param n The URL for the virtual connection.
@param uname Use this username for authentication. Null by default.
@param password Password to use for authentication. Null by default.
@brief Create an instance of Connect. */
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.");
}
}
/** @brief Set the credentials for responding to challenges while dereferencing
URLs.
@param u The username.
@param p The password.
@see extract_auth_info() */
void D4Connect::set_credentials(string u, string p)
{
if (d_http) d_http->set_credentials(u, p);
}
/** Set the \e accept deflate property.
@param deflate True if the client can accept compressed responses, False
otherwise. */
void D4Connect::set_accept_deflate(bool deflate)
{
if (d_http) d_http->set_accept_deflate(deflate);
}
/** Set the \e XDAP-Accept property/header. This is used to send to a server
the (highest) DAP protocol version number that this client understands.
@param major The client dap protocol major version
@param minor The client dap protocol minor version */
void D4Connect::set_xdap_protocol(int major, int minor)
{
if (d_http) d_http->set_xdap_protocol(major, minor);
}
/** Disable any further use of the client-side cache. In a future version
of this software, this should be handled so that the www library is
not initialized with the cache running by default. */
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
|