libdap++ Updated for version 3.8.2
|
00001 00002 // -*- mode: c++; c-basic-offset:4 -*- 00003 00004 // This file is part of libdap, A C++ implementation of the OPeNDAP Data 00005 // Access Protocol. 00006 00007 // Copyright (c) 2002,2003 OPeNDAP, Inc. 00008 // Author: James Gallagher <jgallagher@opendap.org> 00009 // Dan Holloway <dholloway@gso.uri.edu> 00010 // Reza Nekovei <reza@intcomm.net> 00011 // 00012 // This library is free software; you can redistribute it and/or 00013 // modify it under the terms of the GNU Lesser General Public 00014 // License as published by the Free Software Foundation; either 00015 // version 2.1 of the License, or (at your option) any later version. 00016 // 00017 // This library is distributed in the hope that it will be useful, 00018 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00020 // Lesser General Public License for more details. 00021 // 00022 // You should have received a copy of the GNU Lesser General Public 00023 // License along with this library; if not, write to the Free Software 00024 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 00025 // 00026 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112. 00027 00028 // (c) COPYRIGHT URI/MIT 1994-2002 00029 // Please read the full copyright statement in the file COPYRIGHT_URI. 00030 // 00031 // Authors: 00032 // jhrg,jimg James Gallagher <jgallagher@gso.uri.edu> 00033 // dan Dan Holloway <dholloway@gso.uri.edu> 00034 // reza Reza Nekovei <reza@intcomm.net> 00035 00036 00037 #include "config.h" 00038 00039 //#define DODS_DEBUG 00040 00041 static char rcsid[] not_used = 00042 { "$Id: Connect.cc 19917 2008-11-25 23:47:56Z jimg $" 00043 }; 00044 00045 #include <cstring> 00046 #include <fstream> 00047 #include <algorithm> 00048 00049 #include "debug.h" 00050 #include "DataDDS.h" 00051 #include "Connect.h" 00052 #include "escaping.h" 00053 #include "RCReader.h" 00054 #include "DDXParser.h" 00055 #include "XDRFileUnMarshaller.h" 00056 00057 using std::cerr; 00058 using std::endl; 00059 using std::ifstream; 00060 using std::ofstream; 00061 using std::min; 00062 00063 namespace libdap { 00064 00067 void 00068 Connect::process_data(DataDDS &data, Response *rs) 00069 { 00070 DBG(cerr << "Entering Connect::process_data" << endl); 00071 00072 // Use the implementation and protocol versions from the Response object 00073 // since the copies in Connect might go away. Regardless, we must keep the 00074 // Response object alive until we no longer need the stream, since 00075 // destroying it will close the stream. So, might as well use it for the 00076 // version info too... 00077 data.set_version(rs->get_version()); 00078 data.set_protocol(rs->get_protocol()); 00079 00080 DBG(cerr << "Entering process_data: d_stream = " << rs << endl); 00081 switch (rs->get_type()) { 00082 case dods_error: { 00083 Error e; 00084 if (!e.parse(rs->get_stream())) 00085 throw InternalErr(__FILE__, __LINE__, 00086 "Could not parse the Error object returned by the server!"); 00087 throw e; 00088 } 00089 00090 case web_error: 00091 // Web errors (those reported in the return document's MIME header) 00092 // are processed by the WWW library. 00093 throw InternalErr(__FILE__, __LINE__, "An error was reported by the remote httpd; this should have been processed by HTTPConnect.."); 00094 00095 case dods_data: 00096 default: { 00097 // Parse the DDS; throw an exception on error. 00098 data.parse(rs->get_stream()); 00099 XDRFileUnMarshaller um( rs->get_stream() ) ; 00100 00101 // Load the DDS with data. 00102 try { 00103 for (DDS::Vars_iter i = data.var_begin(); i != data.var_end(); 00104 i++) { 00105 (*i)->deserialize(um, &data); 00106 } 00107 } 00108 catch (Error &e) { 00109 throw e; 00110 } 00111 00112 return; 00113 } 00114 00115 #if 0 00116 // According to the spec (DAP 2), servers MUST return the dods_data 00117 // Content-Type. But, many older servers do not do this, so the 00118 // default case is not to throw an error, but to treat the response 00119 // as data... See bug 706. 03/22/04 jhrg 00120 default: 00121 throw Error("The site did not return a valid response (it lacked the\nexpected content description header value of 'dods_data').\nThis may indicate that the server at the site is not correctly\nconfigured, or that the URL has changed."); 00122 #endif 00123 } 00124 } 00125 00126 // Barely a parser... This is used when reading from local sources of DODS 00127 // Data objects. It simulates the important actions of the libwww MIME header 00128 // parser. Those actions fill in certain fields in the Connect object. jhrg 00129 // 5/20/97 00130 // 00131 // Make sure that this parser reads from data_source without disturbing the 00132 // information in data_source that follows the MIME header. Since the DDS 00133 // (which follows the MIME header) is parsed by a flex/bison scanner/parser, 00134 // make sure to use I/O calls that will mesh with ANSI C I/O calls. In the 00135 // old GNU libg++, the C++ calls were synchronized with the C calls, but that 00136 // may no longer be the case. 5/31/99 jhrg 00137 00147 void 00148 Connect::parse_mime(Response *rs) 00149 { 00150 rs->set_version("dods/0.0"); // initial value; for backward compat. 00151 rs->set_protocol("2.0"); 00152 00153 // If the first line does not start with HTTP, XDODS or XDAP, assume 00154 // there's no MIME header here and return without reading anymore 00155 FILE *data_source = rs->get_stream(); 00156 00157 char line[256]; 00158 fgets(line, 255, data_source); 00159 00160 int slen = strlen(line); 00161 slen = min(slen, 256); // use min to limit slen to 256 (fortify) 00162 line[slen - 1] = '\0'; // remove the newline 00163 if (line[slen - 2] == '\r') // ...and the preceding carriage return 00164 line[slen - 2] = '\0'; 00165 00166 while (line[0] != '\0') { 00167 char h[256], v[256]; 00168 sscanf(line, "%s %s\n", h, v); 00169 string header = h; 00170 string value = v; 00171 downcase(header); 00172 downcase(value); 00173 00174 if (header == "content-description:") { 00175 DBG(cout << header << ": " << value << endl); 00176 rs->set_type(get_description_type(value)); 00177 } 00178 else if (header == "xdods-server:" 00179 && rs->get_version() == "dods/0.0") { 00180 DBG(cout << header << ": " << value << endl); 00181 rs->set_version(value); 00182 } 00183 else if (header == "xopendap-server:") { 00184 DBG(cout << header << ": " << value << endl); 00185 rs->set_version(value); 00186 } 00187 else if (header == "xdap:") { 00188 DBG(cout << header << ": " << value << endl); 00189 rs->set_protocol(value); 00190 } 00191 else if (rs->get_version() == "dods/0.0" && header == "server:") { 00192 DBG(cout << header << ": " << value << endl); 00193 rs->set_version(value); 00194 } 00195 00196 fgets(line, 255, data_source); 00197 slen = strlen(line); 00198 slen = min(slen, 256); // use min to limit slen to 256 00199 line[slen - 1] = '\0'; 00200 if (line[slen - 2] == '\r') 00201 line[slen - 2] = '\0'; 00202 } 00203 } 00204 00205 // public mfuncs 00206 00214 Connect::Connect(const string &n, string uname, string password) 00215 throw(Error, InternalErr) 00216 : d_http(0), d_version("unknown"), d_protocol("2.0") 00217 { 00218 string name = prune_spaces(n); 00219 00220 // Figure out if the URL starts with 'http', if so, make sure that we 00221 // talk to an instance of HTTPConnect. 00222 if (name.find("http") == 0) { 00223 DBG(cerr << "Connect: The identifier is an http URL" << endl); 00224 d_http = new HTTPConnect(RCReader::instance()); 00225 00226 // Find and store any CE given with the URL. 00227 string::size_type dotpos = name.find('?'); 00228 if (dotpos != name.npos) { 00229 _URL = name.substr(0, dotpos); 00230 string expr = name.substr(dotpos + 1); 00231 00232 dotpos = expr.find('&'); 00233 if (dotpos != expr.npos) { 00234 _proj = expr.substr(0, dotpos); 00235 _sel = expr.substr(dotpos); // XXX includes '&' 00236 } 00237 else { 00238 _proj = expr; 00239 _sel = ""; 00240 } 00241 } 00242 else { 00243 _URL = name; 00244 _proj = ""; 00245 _sel = ""; 00246 } 00247 00248 _local = false; 00249 } 00250 else { 00251 DBG(cerr << "Connect: The identifier is a local data source." << endl); 00252 00253 d_http = 0; 00254 _URL = ""; 00255 _local = true; // local in this case means non-DAP 00256 } 00257 00258 set_credentials(uname, password); 00259 } 00260 00261 Connect::~Connect() 00262 { 00263 DBG2(cerr << "Entering the Connect dtor" << endl); 00264 00265 if (d_http) 00266 delete d_http; d_http = 0; 00267 00268 DBG2(cerr << "Leaving the Connect dtor" << endl); 00269 } 00270 00278 string 00279 Connect::request_version() 00280 { 00281 string version_url = _URL + ".ver"; 00282 if (_proj.length() + _sel.length()) 00283 version_url = version_url + "?" + id2www_ce(_proj + _sel); 00284 00285 Response *rs = 0; 00286 try { 00287 rs = d_http->fetch_url(version_url); 00288 } 00289 catch (Error &e) { 00290 delete rs; rs = 0; 00291 throw e; 00292 } 00293 00294 d_version = rs->get_version(); 00295 d_protocol = rs->get_protocol(); 00296 delete rs; rs = 0; 00297 00298 return d_version; 00299 } 00300 00312 string 00313 Connect::request_protocol() 00314 { 00315 string version_url = _URL + ".ver"; 00316 if (_proj.length() + _sel.length()) 00317 version_url = version_url + "?" + id2www_ce(_proj + _sel); 00318 00319 Response *rs = 0; 00320 try { 00321 rs = d_http->fetch_url(version_url); 00322 } 00323 catch (Error &e) { 00324 delete rs; rs = 0; 00325 throw e; 00326 } 00327 00328 d_version = rs->get_version(); 00329 d_protocol = rs->get_protocol(); 00330 delete rs; rs = 0; 00331 00332 return d_protocol; 00333 } 00334 00342 void 00343 Connect::request_das(DAS &das) 00344 { 00345 string das_url = _URL + ".das"; 00346 if (_proj.length() + _sel.length()) 00347 das_url = das_url + "?" + id2www_ce(_proj + _sel); 00348 00349 Response *rs = 0; 00350 try { 00351 rs = d_http->fetch_url(das_url); 00352 } 00353 catch (Error &e) { 00354 delete rs; rs = 0; 00355 throw e; 00356 } 00357 00358 d_version = rs->get_version(); 00359 d_protocol = rs->get_protocol(); 00360 00361 switch (rs->get_type()) { 00362 case dods_error: { 00363 Error e; 00364 if (!e.parse(rs->get_stream())) { 00365 throw InternalErr(__FILE__, __LINE__, 00366 "Could not parse error returned from server."); 00367 break; 00368 } 00369 throw e; 00370 break; 00371 } 00372 00373 case web_error: 00374 // We should never get here; a web error should be picked up read_url 00375 // (called by fetch_url) and result in a thrown Error object. 00376 break; 00377 00378 case dods_das: 00379 default: 00380 // DAS::parse throws an exception on error. 00381 try { 00382 das.parse(rs->get_stream()); // read and parse the das from a file 00383 } 00384 catch (Error &e) { 00385 delete rs; rs = 0; 00386 throw e; 00387 } 00388 00389 break; 00390 00391 #if 0 00392 // See the comment in process_data() and bug 706. 03/22/04 jhrg 00393 default: 00394 throw Error("The site did not return a valid response (it lacked the\nexpected content description header value of 'dods_das').\nThis may indicate that the server at the site is not correctly\nconfigured, or that the URL has changed."); 00395 #endif 00396 } 00397 00398 delete rs; rs = 0; 00399 } 00400 00411 void 00412 Connect::request_das_url(DAS &das) 00413 { 00414 string use_url = _URL + "?" + _proj + _sel ; 00415 Response *rs = 0; 00416 try { 00417 rs = d_http->fetch_url(use_url); 00418 } 00419 catch (Error &e) { 00420 delete rs; rs = 0; 00421 throw e; 00422 } 00423 00424 d_version = rs->get_version(); 00425 d_protocol = rs->get_protocol(); 00426 00427 switch (rs->get_type()) { 00428 case dods_error: { 00429 Error e; 00430 if (!e.parse(rs->get_stream())) { 00431 throw InternalErr(__FILE__, __LINE__, 00432 "Could not parse error returned from server."); 00433 break; 00434 } 00435 throw e; 00436 break; 00437 } 00438 00439 case web_error: 00440 // We should never get here; a web error should be picked up read_url 00441 // (called by fetch_url) and result in a thrown Error object. 00442 break; 00443 00444 case dods_das: 00445 default: 00446 // DAS::parse throws an exception on error. 00447 try { 00448 das.parse(rs->get_stream()); // read and parse the das from a file 00449 } 00450 catch (Error &e) { 00451 delete rs; rs = 0; 00452 throw e; 00453 } 00454 00455 break; 00456 } 00457 00458 delete rs; rs = 0; 00459 } 00460 00474 void 00475 Connect::request_dds(DDS &dds, string expr) 00476 { 00477 string proj, sel; 00478 string::size_type dotpos = expr.find('&'); 00479 if (dotpos != expr.npos) { 00480 proj = expr.substr(0, dotpos); 00481 sel = expr.substr(dotpos); 00482 } 00483 else { 00484 proj = expr; 00485 sel = ""; 00486 } 00487 00488 string dds_url = _URL + ".dds" + "?" 00489 + id2www_ce(_proj + proj + _sel + sel); 00490 00491 Response *rs = 0; 00492 try { 00493 rs = d_http->fetch_url(dds_url); 00494 } 00495 catch (Error &e) { 00496 delete rs; rs = 0; 00497 throw e; 00498 } 00499 00500 d_version = rs->get_version(); 00501 d_protocol = rs->get_protocol(); 00502 00503 switch (rs->get_type()) { 00504 case dods_error: { 00505 Error e; 00506 if (!e.parse(rs->get_stream())) { 00507 throw InternalErr(__FILE__, __LINE__, 00508 "Could not parse error returned from server."); 00509 break; 00510 } 00511 throw e; 00512 break; 00513 } 00514 00515 case web_error: 00516 // We should never get here; a web error should be picked up read_url 00517 // (called by fetch_url) and result in a thrown Error object. 00518 break; 00519 00520 case dods_dds: 00521 default: 00522 // DDS::prase throws an exception on error. 00523 try { 00524 dds.parse(rs->get_stream()); // read and parse the dds from a file 00525 } 00526 catch (Error &e) { 00527 delete rs; rs = 0; 00528 throw e; 00529 } 00530 break; 00531 00532 #if 0 00533 // See the comment in process_data() and bug 706. 03/22/04 jhrg 00534 default: 00535 throw Error("The site did not return a valid response (it lacked the\nexpected content description header value of 'dods_dds').\nThis may indicate that the server at the site is not correctly\nconfigured, or that the URL has changed."); 00536 #endif 00537 } 00538 00539 delete rs; rs = 0; 00540 } 00541 00558 void 00559 Connect::request_dds_url(DDS &dds) 00560 { 00561 string use_url = _URL + "?" + _proj + _sel ; 00562 Response *rs = 0; 00563 try { 00564 rs = d_http->fetch_url(use_url); 00565 } 00566 catch (Error &e) { 00567 delete rs; rs = 0; 00568 throw e; 00569 } 00570 00571 d_version = rs->get_version(); 00572 d_protocol = rs->get_protocol(); 00573 00574 switch (rs->get_type()) { 00575 case dods_error: { 00576 Error e; 00577 if (!e.parse(rs->get_stream())) { 00578 throw InternalErr(__FILE__, __LINE__, 00579 "Could not parse error returned from server."); 00580 break; 00581 } 00582 throw e; 00583 break; 00584 } 00585 00586 case web_error: 00587 // We should never get here; a web error should be picked up read_url 00588 // (called by fetch_url) and result in a thrown Error object. 00589 break; 00590 00591 case dods_dds: 00592 default: 00593 // DDS::prase throws an exception on error. 00594 try { 00595 dds.parse(rs->get_stream()); // read and parse the dds from a file 00596 } 00597 catch (Error &e) { 00598 delete rs; rs = 0; 00599 throw e; 00600 } 00601 break; 00602 } 00603 00604 delete rs; rs = 0; 00605 } 00606 00618 void 00619 Connect::request_ddx(DDS &dds, string expr) 00620 { 00621 string proj, sel; 00622 string::size_type dotpos = expr.find('&'); 00623 if (dotpos != expr.npos) { 00624 proj = expr.substr(0, dotpos); 00625 sel = expr.substr(dotpos); 00626 } 00627 else { 00628 proj = expr; 00629 sel = ""; 00630 } 00631 00632 string ddx_url = _URL + ".ddx" + "?" 00633 + id2www_ce(_proj + proj + _sel + sel); 00634 00635 Response *rs = 0; 00636 try { 00637 rs = d_http->fetch_url(ddx_url); 00638 } 00639 catch (Error &e) { 00640 delete rs; rs = 0; 00641 throw e; 00642 } 00643 00644 d_version = rs->get_version(); 00645 d_protocol = rs->get_protocol(); 00646 00647 switch (rs->get_type()) { 00648 case dods_error: { 00649 Error e; 00650 if (!e.parse(rs->get_stream())) { 00651 throw InternalErr(__FILE__, __LINE__, 00652 "Could not parse error returned from server."); 00653 break; 00654 } 00655 throw e; 00656 break; 00657 } 00658 00659 case web_error: 00660 // We should never get here; a web error should be picked up read_url 00661 // (called by fetch_url) and result in a thrown Error object. 00662 break; 00663 00664 case dap4_ddx: 00665 default: 00666 // DDS::parse throws an exception on error. 00667 try { 00668 #if 0 00669 string blob; 00670 #endif 00671 DDXParser ddxp(dds.get_factory()); 00672 ddxp.intern_stream(rs->get_stream(), &dds/*, &blob***/); 00673 #if 0 00674 dds.parse(rs->get_stream()); // read and parse the dds from a file 00675 #endif 00676 } 00677 catch (Error &e) { 00678 delete rs; rs = 0; 00679 throw e; 00680 } 00681 break; 00682 00683 #if 0 00684 // See the comment in process_data() and bug 706. 03/22/04 jhrg 00685 default: 00686 throw Error("The site did not return a valid response (it lacked the\nexpected content description header value of 'dods_ddx').\nThis may indicate that the server at the site is not correctly\nconfigured, or that the URL has changed."); 00687 #endif 00688 } 00689 00690 delete rs; rs = 0; 00691 } 00692 00695 void 00696 Connect::request_ddx_url(DDS &dds) 00697 { 00698 string use_url = _URL + "?" + _proj + _sel ; 00699 00700 Response *rs = 0; 00701 try { 00702 rs = d_http->fetch_url(use_url); 00703 } 00704 catch (Error &e) { 00705 delete rs; rs = 0; 00706 throw e; 00707 } 00708 00709 d_version = rs->get_version(); 00710 d_protocol = rs->get_protocol(); 00711 00712 switch (rs->get_type()) { 00713 case dods_error: { 00714 Error e; 00715 if (!e.parse(rs->get_stream())) { 00716 throw InternalErr(__FILE__, __LINE__, 00717 "Could not parse error returned from server."); 00718 break; 00719 } 00720 throw e; 00721 break; 00722 } 00723 00724 case web_error: 00725 // We should never get here; a web error should be picked up read_url 00726 // (called by fetch_url) and result in a thrown Error object. 00727 break; 00728 00729 case dap4_ddx: 00730 default: 00731 // DDS::prase throws an exception on error. 00732 try { 00733 dds.parse(rs->get_stream()); // read and parse the dds from a file 00734 } 00735 catch (Error &e) { 00736 delete rs; rs = 0; 00737 throw e; 00738 } 00739 break; 00740 00741 #if 0 00742 // See the comment in process_data() and bug 706. 03/22/04 jhrg 00743 default: 00744 throw Error("The site did not return a valid response (it lacked the\nexpected content description header value of 'dods_ddx').\nThis may indicate that the server at the site is not correctly\nconfigured, or that the URL has changed."); 00745 #endif 00746 } 00747 00748 delete rs; rs = 0; 00749 } 00750 00766 void 00767 Connect::request_data(DataDDS &data, string expr) 00768 { 00769 string proj, sel; 00770 string::size_type dotpos = expr.find('&'); 00771 if (dotpos != expr.npos) { 00772 proj = expr.substr(0, dotpos); 00773 sel = expr.substr(dotpos); 00774 } 00775 else { 00776 proj = expr; 00777 sel = ""; 00778 } 00779 00780 string data_url = _URL + ".dods?" 00781 + id2www_ce(_proj + proj + _sel + sel); 00782 00783 Response *rs = 0; 00784 // We need to catch Error exceptions to ensure calling close_output. 00785 try { 00786 rs = d_http->fetch_url(data_url); 00787 d_version = rs->get_version(); 00788 d_protocol = rs->get_protocol(); 00789 00790 process_data(data, rs); 00791 delete rs; rs = 0; 00792 } 00793 catch (Error &e) { 00794 delete rs; rs = 0; 00795 throw e; 00796 } 00797 } 00798 00816 void 00817 Connect::request_data_url(DataDDS &data) 00818 { 00819 string use_url = _URL + "?" + _proj + _sel ; 00820 Response *rs = 0; 00821 // We need to catch Error exceptions to ensure calling close_output. 00822 try { 00823 rs = d_http->fetch_url(use_url); 00824 d_version = rs->get_version(); 00825 d_protocol = rs->get_protocol(); 00826 00827 process_data(data, rs); 00828 delete rs; rs = 0; 00829 } 00830 catch (Error &e) { 00831 delete rs; rs = 0; 00832 throw e; 00833 } 00834 } 00835 00836 00853 void 00854 Connect::read_data(DataDDS &data, Response *rs) 00855 { 00856 if (!rs) 00857 throw InternalErr(__FILE__, __LINE__, "Response object is null."); 00858 00859 // Read from data_source and parse the MIME headers specific to DAP2. 00860 parse_mime(rs); 00861 00862 read_data_no_mime(data, rs); 00863 } 00864 00873 void 00874 Connect::read_data_no_mime(DataDDS &data, Response *rs) 00875 { 00876 d_version = rs->get_version(); 00877 d_protocol = rs->get_protocol(); 00878 00879 process_data(data, rs); 00880 } 00881 00882 bool 00883 Connect::is_local() 00884 { 00885 return _local; 00886 } 00887 00904 string 00905 Connect::URL(bool ce) 00906 { 00907 if (_local) 00908 throw InternalErr(__FILE__, __LINE__, 00909 "URL(): This call is only valid for a DAP data source."); 00910 00911 if (ce) 00912 return _URL + "?" + _proj + _sel; 00913 else 00914 return _URL; 00915 } 00916 00925 string 00926 Connect::CE() 00927 { 00928 if (_local) 00929 throw InternalErr(__FILE__, __LINE__, 00930 "CE(): This call is only valid for a DAP data source."); 00931 00932 return _proj + _sel; 00933 } 00934 00940 void 00941 Connect::set_credentials(string u, string p) 00942 { 00943 if (d_http) 00944 d_http->set_credentials(u, p); 00945 } 00946 00950 void 00951 Connect::set_accept_deflate(bool deflate) 00952 { 00953 if (d_http) 00954 d_http->set_accept_deflate(deflate); 00955 } 00956 00962 void 00963 Connect::set_xdap_protocol(int major, int minor) 00964 { 00965 if (d_http) 00966 d_http->set_xdap_protocol(major, minor); 00967 } 00968 00972 void 00973 Connect::set_cache_enabled(bool cache) 00974 { 00975 if (d_http) 00976 d_http->set_cache_enabled(cache); 00977 } 00978 00979 bool 00980 Connect::is_cache_enabled() 00981 { 00982 bool status; 00983 DBG(cerr << "Entering is_cache_enabled (" << hex << d_http << dec 00984 << ")... "); 00985 if (d_http) 00986 status = d_http->is_cache_enabled(); 00987 else 00988 status = false; 00989 DBGN(cerr << "exiting" << endl); 00990 return status; 00991 } 00992 00993 } // namespace libdap