libdap  Updated for version 3.17.0
D4Connect.cc
00001 // -*- mode: c++; c-basic-offset:4 -*-
00002 
00003 // This file is part of libdap, A C++ implementation of the OPeNDAP Data
00004 // Access Protocol.
00005 
00006 // Copyright (c) 2002,2003 OPeNDAP, Inc.
00007 // Author: James Gallagher <jgallagher@opendap.org>
00008 //         Dan Holloway <dholloway@gso.uri.edu>
00009 //         Reza Nekovei <reza@intcomm.net>
00010 //
00011 // This library is free software; you can redistribute it and/or
00012 // modify it under the terms of the GNU Lesser General Public
00013 // License as published by the Free Software Foundation; either
00014 // version 2.1 of the License, or (at your option) any later version.
00015 //
00016 // This library is distributed in the hope that it will be useful,
00017 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00018 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00019 // Lesser General Public License for more details.
00020 //
00021 // You should have received a copy of the GNU Lesser General Public
00022 // License along with this library; if not, write to the Free Software
00023 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00024 //
00025 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
00026 
00027 // (c) COPYRIGHT URI/MIT 1994-2002
00028 // Please read the full copyright statement in the file COPYRIGHT_URI.
00029 //
00030 // Authors:
00031 //      jhrg,jimg       James Gallagher <jgallagher@gso.uri.edu>
00032 //      dan             Dan Holloway <dholloway@gso.uri.edu>
00033 //      reza            Reza Nekovei <reza@intcomm.net>
00034 
00035 #include "config.h"
00036 
00037 #include <cassert>
00038 #include <cstring>
00039 
00040 #include "D4Connect.h"
00041 #include "HTTPConnect.h"
00042 #include "Response.h"
00043 #include "DMR.h"
00044 #include "D4Group.h"
00045 
00046 #include "D4ParserSax2.h"
00047 #include "chunked_stream.h"
00048 #include "chunked_istream.h"
00049 #include "D4StreamUnMarshaller.h"
00050 
00051 #include "escaping.h"
00052 #include "mime_util.h"
00053 #include "debug.h"
00054 
00055 using namespace std;
00056 
00057 namespace libdap {
00058 
00059 
00062 void D4Connect::process_dmr(DMR &dmr, Response &rs)
00063 {
00064         DBG(cerr << "Entering D4Connect::process_dmr" << endl);
00065 
00066         dmr.set_dap_version(rs.get_protocol());
00067 
00068         DBG(cerr << "Entering process_data: d_stream = " << rs << endl);
00069         switch (rs.get_type()) {
00070         case dap4_error: {
00071 #if 0
00072                 Error e;
00073                 if (!e.parse(rs.get_stream()))
00074                         throw InternalErr(__FILE__, __LINE__, "Could not parse the Error object returned by the server!");
00075                 throw e;
00076 #endif
00077                 throw InternalErr(__FILE__, __LINE__, "DAP4 errors not processed yet: FIXME!");
00078         }
00079 
00080         case web_error:
00081                 // Web errors (those reported in the return document's MIME header)
00082                 // are processed by the WWW library.
00083                 throw InternalErr(__FILE__, __LINE__,
00084                                 "An error was reported by the remote httpd; this should have been processed by HTTPConnect..");
00085 
00086         case dap4_dmr: {
00087                 // parse the DMR
00088                 try {
00089                         D4ParserSax2 parser;
00090                         parser.intern(*rs.get_cpp_stream(), &dmr, /*debug*/false);
00091                 }
00092                 catch (Error &e) {
00093                         cerr << "Exception: " << e.get_error_message() << endl;
00094                         return;
00095                 }
00096                 catch (std::exception &e) {
00097                         cerr << "Exception: " << e.what() << endl;
00098                         return;
00099                 }
00100                 catch (...) {
00101                         cerr << "Exception: unknown error" << endl;
00102                         return;
00103                 }
00104 
00105                 return;
00106         }
00107 
00108         default:
00109                 throw Error("Unknown response type");
00110         }
00111 }
00112 
00115 void D4Connect::process_data(DMR &data, Response &rs)
00116 {
00117         DBG(cerr << "Entering D4Connect::process_data" << endl);
00118 
00119         assert(rs.get_cpp_stream());    // DAP4 code uses cpp streams
00120 
00121         data.set_dap_version(rs.get_protocol());
00122 
00123         DBG(cerr << "Entering process_data: d_stream = " << rs << endl);
00124         switch (rs.get_type()) {
00125         case dap4_error: {
00126 #if 0
00127                 Error e;
00128                 if (!e.parse(rs.get_cpp_stream()))
00129                         throw InternalErr(__FILE__, __LINE__, "Could not parse the Error object returned by the server!");
00130                 throw e;
00131 #endif
00132                 throw InternalErr(__FILE__, __LINE__, "DAP4 errors not processed yet: FIXME!");
00133         }
00134 
00135         case web_error:
00136                 // Web errors (those reported in the return document's MIME header)
00137                 // are processed by the WWW library.
00138                 throw InternalErr(__FILE__, __LINE__,
00139                                 "An error was reported by the remote httpd; this should have been processed by HTTPConnect..");
00140 
00141         case dap4_data: {
00142 #if BYTE_ORDER_PREFIX
00143                 // Read the byte-order byte; used later on
00144                 char byte_order;
00145                 *rs.get_cpp_stream() >> byte_order;
00146                 //if (debug) cerr << "Byte order: " << ((byte_order) ? "big endian" : "little endian") << endl;
00147 #endif
00148                 // get a chunked input stream
00149 #if BYTE_ORDER_PREFIX
00150                 chunked_istream cis(*rs.get_cpp_stream(), 1024, byte_order);
00151 #else
00152                 chunked_istream cis(*(rs.get_cpp_stream()), CHUNK_SIZE);
00153 #endif
00154                 // parse the DMR, stopping when the boundary is found.
00155                 try {
00156                         // force chunk read
00157                         // get chunk size
00158                         int chunk_size = cis.read_next_chunk();
00159                         if (chunk_size < 0)
00160                 throw Error("Found an unexpected end of input (EOF) while reading a DAP4 data response. (1)");
00161 
00162                         // get chunk
00163                         char chunk[chunk_size];
00164                         cis.read(chunk, chunk_size);
00165                         // parse char * with given size
00166                         D4ParserSax2 parser;
00167                         // '-2' to discard the CRLF pair
00168                         parser.intern(chunk, chunk_size - 2, &data, /*debug*/false);
00169                 }
00170                 catch (Error &e) {
00171                         cerr << "Exception: " << e.get_error_message() << endl;
00172                         return;
00173                 }
00174                 catch (std::exception &e) {
00175                         cerr << "Exception: " << e.what() << endl;
00176                         return;
00177                 }
00178                 catch (...) {
00179                         cerr << "Exception: unknown error" << endl;
00180                         return;
00181                 }
00182 
00183 #if BYTE_ORDER_PREFIX
00184                 D4StreamUnMarshaller um(cis, byte_order);
00185 #else
00186                 D4StreamUnMarshaller um(cis, cis.twiddle_bytes());
00187 #endif
00188                 data.root()->deserialize(um, data);
00189 
00190                 return;
00191         }
00192 
00193         default:
00194                 throw Error("Unknown response type");
00195         }
00196 }
00197 
00206 void D4Connect::parse_mime(Response &rs)
00207 {
00208     rs.set_version("dods/0.0"); // initial value; for backward compatibility.
00209     rs.set_protocol("2.0");
00210 
00211     istream &data_source = *rs.get_cpp_stream();
00212     string mime = get_next_mime_header(data_source);
00213     while (!mime.empty()) {
00214         string header, value;
00215         parse_mime_header(mime, header, value);
00216 
00217         // Note that this is an ordered list
00218         if (header == "content-description") {
00219             DBG(cout << header << ": " << value << endl);
00220             rs.set_type(get_description_type(value));
00221         }
00222         // Use the value of xdods-server only if no other value has been read
00223         else if (header == "xdods-server" && rs.get_version() == "dods/0.0") {
00224             DBG(cout << header << ": " << value << endl);
00225             rs.set_version(value);
00226         }
00227         // This trumps 'xdods-server' and 'server'
00228         else if (header == "xopendap-server") {
00229             DBG(cout << header << ": " << value << endl);
00230             rs.set_version(value);
00231         }
00232         else if (header == "xdap") {
00233             DBG(cout << header << ": " << value << endl);
00234             rs.set_protocol(value);
00235         }
00236         // Only look for 'server' if no other header supplies this info.
00237         else if (rs.get_version() == "dods/0.0" && header == "server") {
00238             DBG(cout << header << ": " << value << endl);
00239             rs.set_version(value);
00240         }
00241 
00242         mime = get_next_mime_header(data_source);
00243     }
00244 }
00245 
00246 // public mfuncs
00247 
00254 D4Connect::D4Connect(const string &url, string uname, string password) :
00255         d_http(0), d_local(false), d_URL(""), d_dap4ce(""), d_server("unknown"), d_protocol("4.0")
00256 {
00257     string name = prune_spaces(url);
00258 
00259     // Figure out if the URL starts with 'http', if so, make sure that we
00260     // talk to an instance of HTTPConnect.
00261     if (name.find("http") == 0) {
00262         DBG(cerr << "Connect: The identifier is an http URL" << endl);
00263         d_http = new HTTPConnect(RCReader::instance());
00264         d_http->set_use_cpp_streams(true);
00265 
00266         // Find and store any CE given with the URL.
00267         string::size_type dotpos = name.find('?');
00268         if (dotpos != name.npos) {
00269             d_URL = name.substr(0, dotpos);
00270             d_dap4ce = name.substr(dotpos + 1);
00271         }
00272         else {
00273             d_URL = name;
00274         }
00275     }
00276     else {
00277         DBG(cerr << "Connect: The identifier is a local data source." << endl);
00278         d_local = true; // local in this case means non-DAP
00279     }
00280 
00281     set_credentials(uname, password);
00282 }
00283 
00284 D4Connect::~D4Connect()
00285 {
00286         if (d_http) delete d_http;
00287 }
00288 
00289 void D4Connect::request_dmr(DMR &dmr, const string expr)
00290 {
00291         string url = d_URL + ".dmr" + "?" + id2www_ce(d_dap4ce + expr);
00292 
00293         Response *rs = 0;
00294         try {
00295                 rs = d_http->fetch_url(url);
00296 
00297                 d_server = rs->get_version();
00298                 d_protocol = rs->get_protocol();
00299 
00300                 switch (rs->get_type()) {
00301                 case unknown_type:                      // FIXME Pure hackery!
00302                     cerr << "Response type unknown, assuming it's a DMR response." << endl;
00303                     /* no break */
00304                 case dap4_dmr: {
00305                         D4ParserSax2 parser;
00306                         parser.intern(*rs->get_cpp_stream(), &dmr, false /* debug */);
00307                         break;
00308                 }
00309 
00310                 case dap4_error:
00311                         throw InternalErr(__FILE__, __LINE__, "DAP4 errors are not processed yet.");
00312 
00313                 case web_error:
00314                         // We should never get here; a web error should be picked up read_url
00315                         // (called by fetch_url) and result in a thrown Error object.
00316                         throw InternalErr(__FILE__, __LINE__, "Web error found where it should never be.");
00317                         break;
00318 
00319                 default:
00320                         throw InternalErr(__FILE__, __LINE__, "Response type not handled (got "
00321                                         + long_to_string(rs->get_type()) + ").");
00322                 }
00323         }
00324         catch (...) {
00325                 delete rs;
00326                 throw;
00327         }
00328 
00329         delete rs;
00330 }
00331 
00332 void D4Connect::request_dap4_data(DMR &dmr, const string expr)
00333 {
00334     string url = d_URL + ".dap" + "?" + id2www_ce(d_dap4ce + expr);
00335 
00336     Response *rs = 0;
00337     try {
00338         rs = d_http->fetch_url(url);
00339 
00340         d_server = rs->get_version();
00341         d_protocol = rs->get_protocol();
00342 
00343         switch (rs->get_type()) {
00344         case unknown_type:          // FIXME Pure hackery!
00345             cerr << "Response type unknown, assuming it's a DAP4 Data response." << endl;
00346             /* no break */
00347         case dap4_data: {
00348             // TODO Move to a function 11/9/13 process_data()???
00349 #if BYTE_ORDER_PREFIX
00350             istream &in = *rs->get_cpp_stream();
00351             // Read the byte-order byte; used later on
00352             char byte_order;
00353             in >> byte_order;
00354 #endif
00355 
00356             // get a chunked input stream
00357 #if BYTE_ORDER_PREFIX
00358                 chunked_istream cis(*(rs->get_cpp_stream()), 1024, byte_order);
00359 #else
00360                 chunked_istream cis(*(rs->get_cpp_stream()), CHUNK_SIZE);
00361 #endif
00362 
00363             // parse the DMR, stopping when the boundary is found.
00364 
00365             // force chunk read
00366             // get chunk size
00367             int chunk_size = cis.read_next_chunk();
00368             if (chunk_size < 0)
00369                 throw Error("Found an unexpected end of input (EOF) while reading a DAP4 data response. (2)");
00370 
00371             // get chunk
00372             char chunk[chunk_size];
00373             cis.read(chunk, chunk_size);
00374             // parse char * with given size
00375             D4ParserSax2 parser;
00376             // '-2' to discard the CRLF pair
00377             parser.intern(chunk, chunk_size - 2, &dmr, false /*debug*/);
00378 
00379             // Read data and store in the DMR
00380 #if BYTE_ORDER_PREFIX
00381             D4StreamUnMarshaller um(cis, byte_order);
00382 #else
00383             D4StreamUnMarshaller um(cis, cis.twiddle_bytes());
00384 #endif
00385             dmr.root()->deserialize(um, dmr);
00386 
00387             break;
00388         }
00389 
00390         case dap4_error:
00391             throw InternalErr(__FILE__, __LINE__, "DAP4 errors are not processed yet.");
00392 
00393         case web_error:
00394             // We should never get here; a web error should be picked up read_url
00395             // (called by fetch_url) and result in a thrown Error object.
00396             throw InternalErr(__FILE__, __LINE__, "Web error found where it should never be.");
00397             break;
00398 
00399         default:
00400             throw InternalErr(__FILE__, __LINE__, "Response type not handled (got "
00401                     + long_to_string(rs->get_type()) + ").");
00402         }
00403     }
00404     catch (...) {
00405         delete rs;
00406         throw;
00407     }
00408 
00409     delete rs;
00410 }
00411 
00412 void
00413 D4Connect::read_dmr(DMR &dmr, Response &rs)
00414 {
00415     parse_mime(rs);
00416     if (rs.get_type() == unknown_type)
00417         throw Error("Unknown response type.");
00418 
00419     read_dmr_no_mime(dmr, rs);
00420 }
00421 
00422 void
00423 D4Connect::read_dmr_no_mime(DMR &dmr, Response &rs)
00424 {
00425         // Assume callers know what they are doing
00426     if (rs.get_type() == unknown_type)
00427         rs.set_type(dap4_dmr);
00428 
00429     switch (rs.get_type()) {
00430     case dap4_dmr:
00431         process_dmr(dmr, rs);
00432         d_server = rs.get_version();
00433         d_protocol = dmr.dap_version();
00434         break;
00435     default:
00436         throw Error("Expected a DAP4 DMR response.");
00437     }
00438 }
00439 
00440 void
00441 D4Connect::read_data(DMR &data, Response &rs)
00442 {
00443     parse_mime(rs);
00444     if (rs.get_type() == unknown_type)
00445         throw Error("Unknown response type.");
00446 
00447     read_data_no_mime(data, rs);
00448 }
00449 
00450 void D4Connect::read_data_no_mime(DMR &data, Response &rs)
00451 {
00452         // Assume callers know what they are doing
00453     if (rs.get_type() == unknown_type)
00454         rs.set_type(dap4_data);
00455 
00456     switch (rs.get_type()) {
00457     case dap4_data:
00458         process_data(data, rs);
00459         d_server = rs.get_version();
00460         d_protocol = data.dap_version();
00461         break;
00462     default:
00463         throw Error("Expected a DAP4 Data response.");
00464     }
00465 }
00466 
00472 void D4Connect::set_credentials(string u, string p)
00473 {
00474     if (d_http)
00475         d_http->set_credentials(u, p);
00476 }
00477 
00481 void D4Connect::set_accept_deflate(bool deflate)
00482 {
00483     if (d_http)
00484         d_http->set_accept_deflate(deflate);
00485 }
00486 
00492 void D4Connect::set_xdap_protocol(int major, int minor)
00493 {
00494     if (d_http)
00495         d_http->set_xdap_protocol(major, minor);
00496 }
00497 
00501 void D4Connect::set_cache_enabled(bool cache)
00502 {
00503     if (d_http)
00504         d_http->set_cache_enabled(cache);
00505 }
00506 
00507 bool D4Connect::is_cache_enabled()
00508 {
00509     if (d_http)
00510         return d_http->is_cache_enabled();
00511     else
00512         return false;
00513 }
00514 
00515 } // namespace libdap