Connect.cc

Go to the documentation of this file.
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 18315 2008-03-03 20:14:44Z 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(/*FILE *data_source, */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_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 
00308 string
00309 Connect::request_protocol()
00310 {
00311     string version_url = _URL + ".ver";
00312     if (_proj.length() + _sel.length())
00313         version_url = version_url + "?" + id2www_ce(_proj + _sel);
00314 
00315     Response *rs = 0;
00316     try {
00317         rs = d_http->fetch_url(version_url);
00318     }
00319     catch (Error &e) {
00320         delete rs; rs = 0;
00321         throw e;
00322     }
00323 
00324     d_version = rs->get_version();
00325     d_protocol = rs->get_protocol();
00326     delete rs; rs = 0;
00327 
00328     return d_protocol;
00329 }
00330 
00338 void
00339 Connect::request_das(DAS &das)
00340 {
00341     string das_url = _URL + ".das";
00342     if (_proj.length() + _sel.length())
00343         das_url = das_url + "?" + id2www_ce(_proj + _sel);
00344 
00345     Response *rs = 0;
00346     try {
00347         rs = d_http->fetch_url(das_url);
00348     }
00349     catch (Error &e) {
00350         delete rs; rs = 0;
00351         throw e;
00352     }
00353 
00354     d_version = rs->get_version();
00355     d_protocol = rs->get_protocol();
00356 
00357     switch (rs->get_type()) {
00358     case dods_error: {
00359             Error e;
00360             if (!e.parse(rs->get_stream())) {
00361                 throw InternalErr(__FILE__, __LINE__,
00362                                   "Could not parse error returned from server.");
00363                 break;
00364             }
00365             throw e;
00366             break;
00367         }
00368 
00369     case web_error:
00370         // We should never get here; a web error should be picked up read_url
00371         // (called by fetch_url) and result in a thrown Error object.
00372         break;
00373 
00374     case dods_das:
00375     default:
00376         // DAS::parse throws an exception on error.
00377         try {
00378             das.parse(rs->get_stream()); // read and parse the das from a file
00379         }
00380         catch (Error &e) {
00381             delete rs; rs = 0;
00382             throw e;
00383         }
00384 
00385         break;
00386 
00387 #if 0
00388         // See the comment in process_data() and bug 706. 03/22/04 jhrg
00389     default:
00390         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.");
00391 #endif
00392     }
00393 
00394     delete rs; rs = 0;
00395 }
00396 
00407 void
00408 Connect::request_das_url(DAS &das)
00409 {
00410     string use_url = _URL + "?" + _proj + _sel ;
00411     Response *rs = 0;
00412     try {
00413         rs = d_http->fetch_url(use_url);
00414     }
00415     catch (Error &e) {
00416         delete rs; rs = 0;
00417         throw e;
00418     }
00419 
00420     d_version = rs->get_version();
00421     d_protocol = rs->get_protocol();
00422 
00423     switch (rs->get_type()) {
00424     case dods_error: {
00425             Error e;
00426             if (!e.parse(rs->get_stream())) {
00427                 throw InternalErr(__FILE__, __LINE__,
00428                                   "Could not parse error returned from server.");
00429                 break;
00430             }
00431             throw e;
00432             break;
00433         }
00434 
00435     case web_error:
00436         // We should never get here; a web error should be picked up read_url
00437         // (called by fetch_url) and result in a thrown Error object.
00438         break;
00439 
00440     case dods_das:
00441     default:
00442         // DAS::parse throws an exception on error.
00443         try {
00444             das.parse(rs->get_stream()); // read and parse the das from a file
00445         }
00446         catch (Error &e) {
00447             delete rs; rs = 0;
00448             throw e;
00449         }
00450 
00451         break;
00452     }
00453 
00454     delete rs; rs = 0;
00455 }
00456 
00470 void
00471 Connect::request_dds(DDS &dds, string expr)
00472 {
00473     string proj, sel;
00474     string::size_type dotpos = expr.find('&');
00475     if (dotpos != expr.npos) {
00476         proj = expr.substr(0, dotpos);
00477         sel = expr.substr(dotpos);
00478     }
00479     else {
00480         proj = expr;
00481         sel = "";
00482     }
00483 
00484     string dds_url = _URL + ".dds" + "?"
00485                      + id2www_ce(_proj + proj + _sel + sel);
00486 
00487     Response *rs = 0;
00488     try {
00489         rs = d_http->fetch_url(dds_url);
00490     }
00491     catch (Error &e) {
00492         delete rs; rs = 0;
00493         throw e;
00494     }
00495 
00496     d_version = rs->get_version();
00497     d_protocol = rs->get_protocol();
00498 
00499     switch (rs->get_type()) {
00500     case dods_error: {
00501             Error e;
00502             if (!e.parse(rs->get_stream())) {
00503                 throw InternalErr(__FILE__, __LINE__,
00504                                   "Could not parse error returned from server.");
00505                 break;
00506             }
00507             throw e;
00508             break;
00509         }
00510 
00511     case web_error:
00512         // We should never get here; a web error should be picked up read_url
00513         // (called by fetch_url) and result in a thrown Error object.
00514         break;
00515 
00516     case dods_dds:
00517     default:
00518         // DDS::prase throws an exception on error.
00519         try {
00520             dds.parse(rs->get_stream()); // read and parse the dds from a file
00521         }
00522         catch (Error &e) {
00523             delete rs; rs = 0;
00524             throw e;
00525         }
00526         break;
00527 
00528 #if 0
00529         // See the comment in process_data() and bug 706. 03/22/04 jhrg
00530     default:
00531         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.");
00532 #endif
00533     }
00534 
00535     delete rs; rs = 0;
00536 }
00537 
00554 void
00555 Connect::request_dds_url(DDS &dds)
00556 {
00557     string use_url = _URL + "?" + _proj + _sel ;
00558     Response *rs = 0;
00559     try {
00560         rs = d_http->fetch_url(use_url);
00561     }
00562     catch (Error &e) {
00563         delete rs; rs = 0;
00564         throw e;
00565     }
00566 
00567     d_version = rs->get_version();
00568     d_protocol = rs->get_protocol();
00569 
00570     switch (rs->get_type()) {
00571     case dods_error: {
00572             Error e;
00573             if (!e.parse(rs->get_stream())) {
00574                 throw InternalErr(__FILE__, __LINE__,
00575                                   "Could not parse error returned from server.");
00576                 break;
00577             }
00578             throw e;
00579             break;
00580         }
00581 
00582     case web_error:
00583         // We should never get here; a web error should be picked up read_url
00584         // (called by fetch_url) and result in a thrown Error object.
00585         break;
00586 
00587     case dods_dds:
00588     default:
00589         // DDS::prase throws an exception on error.
00590         try {
00591             dds.parse(rs->get_stream()); // read and parse the dds from a file
00592         }
00593         catch (Error &e) {
00594             delete rs; rs = 0;
00595             throw e;
00596         }
00597         break;
00598     }
00599 
00600     delete rs; rs = 0;
00601 }
00602 
00614 void
00615 Connect::request_ddx(DDS &dds, string expr)
00616 {
00617     string proj, sel;
00618     string::size_type dotpos = expr.find('&');
00619     if (dotpos != expr.npos) {
00620         proj = expr.substr(0, dotpos);
00621         sel = expr.substr(dotpos);
00622     }
00623     else {
00624         proj = expr;
00625         sel = "";
00626     }
00627 
00628     string ddx_url = _URL + ".ddx" + "?"
00629                      + id2www_ce(_proj + proj + _sel + sel);
00630 
00631     Response *rs = 0;
00632     try {
00633         rs = d_http->fetch_url(ddx_url);
00634     }
00635     catch (Error &e) {
00636         delete rs; rs = 0;
00637         throw e;
00638     }
00639 
00640     d_version = rs->get_version();
00641     d_protocol = rs->get_protocol();
00642 
00643     switch (rs->get_type()) {
00644     case dods_error: {
00645             Error e;
00646             if (!e.parse(rs->get_stream())) {
00647                 throw InternalErr(__FILE__, __LINE__,
00648                                   "Could not parse error returned from server.");
00649                 break;
00650             }
00651             throw e;
00652             break;
00653         }
00654 
00655     case web_error:
00656         // We should never get here; a web error should be picked up read_url
00657         // (called by fetch_url) and result in a thrown Error object.
00658         break;
00659 
00660     case dap4_ddx:
00661     default:
00662         // DDS::prase throws an exception on error.
00663         try {
00664             string blob;
00665             DDXParser ddxp(dds.get_factory());
00666             ddxp.intern_stream(rs->get_stream(), &dds, &blob);
00667 #if 0
00668             dds.parse(rs->get_stream()); // read and parse the dds from a file
00669 #endif
00670         }
00671         catch (Error &e) {
00672             delete rs; rs = 0;
00673             throw e;
00674         }
00675         break;
00676 
00677 #if 0
00678         // See the comment in process_data() and bug 706. 03/22/04 jhrg
00679     default:
00680         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.");
00681 #endif
00682     }
00683 
00684     delete rs; rs = 0;
00685 }
00686 
00689 void
00690 Connect::request_ddx_url(DDS &dds)
00691 {
00692     string use_url = _URL + "?" + _proj + _sel ;
00693 
00694     Response *rs = 0;
00695     try {
00696         rs = d_http->fetch_url(use_url);
00697     }
00698     catch (Error &e) {
00699         delete rs; rs = 0;
00700         throw e;
00701     }
00702 
00703     d_version = rs->get_version();
00704     d_protocol = rs->get_protocol();
00705 
00706     switch (rs->get_type()) {
00707     case dods_error: {
00708             Error e;
00709             if (!e.parse(rs->get_stream())) {
00710                 throw InternalErr(__FILE__, __LINE__,
00711                                   "Could not parse error returned from server.");
00712                 break;
00713             }
00714             throw e;
00715             break;
00716         }
00717 
00718     case web_error:
00719         // We should never get here; a web error should be picked up read_url
00720         // (called by fetch_url) and result in a thrown Error object.
00721         break;
00722 
00723     case dap4_ddx:
00724     default:
00725         // DDS::prase throws an exception on error.
00726         try {
00727             dds.parse(rs->get_stream()); // read and parse the dds from a file
00728         }
00729         catch (Error &e) {
00730             delete rs; rs = 0;
00731             throw e;
00732         }
00733         break;
00734 
00735 #if 0
00736         // See the comment in process_data() and bug 706. 03/22/04 jhrg
00737     default:
00738         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.");
00739 #endif
00740     }
00741 
00742     delete rs; rs = 0;
00743 }
00744 
00760 void
00761 Connect::request_data(DataDDS &data, string expr)
00762 {
00763     string proj, sel;
00764     string::size_type dotpos = expr.find('&');
00765     if (dotpos != expr.npos) {
00766         proj = expr.substr(0, dotpos);
00767         sel = expr.substr(dotpos);
00768     }
00769     else {
00770         proj = expr;
00771         sel = "";
00772     }
00773 
00774     string data_url = _URL + ".dods?"
00775                       + id2www_ce(_proj + proj + _sel + sel);
00776 
00777     Response *rs = 0;
00778     // We need to catch Error exceptions to ensure calling close_output.
00779     try {
00780         rs = d_http->fetch_url(data_url);
00781         d_version = rs->get_version();
00782         d_protocol = rs->get_protocol();
00783 
00784         process_data(data, rs);
00785         delete rs; rs = 0;
00786     }
00787     catch (Error &e) {
00788         delete rs; rs = 0;
00789         throw e;
00790     }
00791 }
00792 
00810 void
00811 Connect::request_data_url(DataDDS &data)
00812 {
00813     string use_url = _URL + "?" + _proj + _sel ;
00814     Response *rs = 0;
00815     // We need to catch Error exceptions to ensure calling close_output.
00816     try {
00817         rs = d_http->fetch_url(use_url);
00818         d_version = rs->get_version();
00819         d_protocol = rs->get_protocol();
00820 
00821         process_data(data, rs);
00822         delete rs; rs = 0;
00823     }
00824     catch (Error &e) {
00825         delete rs; rs = 0;
00826         throw e;
00827     }
00828 }
00829 
00830 
00847 void
00848 Connect::read_data(DataDDS &data, Response *rs)
00849 {
00850     if (!rs)
00851         throw InternalErr(__FILE__, __LINE__, "Response object is null.");
00852 
00853     // Read from data_source and parse the MIME headers specific to DAP2.
00854     parse_mime(rs);
00855 
00856     read_data_no_mime(data, rs);
00857 }
00858 
00867 void
00868 Connect::read_data_no_mime(DataDDS &data, Response *rs)
00869 {
00870     d_version = rs->get_version();
00871     d_protocol = rs->get_protocol();
00872 
00873     process_data(data, rs);
00874 }
00875 
00876 bool
00877 Connect::is_local()
00878 {
00879     return _local;
00880 }
00881 
00898 string
00899 Connect::URL(bool ce)
00900 {
00901     if (_local)
00902         throw InternalErr(__FILE__, __LINE__,
00903                           "URL(): This call is only valid for a DAP2 data source.");
00904 
00905     if (ce)
00906         return _URL + "?" + _proj + _sel;
00907     else
00908         return _URL;
00909 }
00910 
00919 string
00920 Connect::CE()
00921 {
00922     if (_local)
00923         throw InternalErr(__FILE__, __LINE__,
00924                           "CE(): This call is only valid for a DAP2 data source.");
00925 
00926     return _proj + _sel;
00927 }
00928 
00934 void
00935 Connect::set_credentials(string u, string p)
00936 {
00937     if (d_http)
00938         d_http->set_credentials(u, p);
00939 }
00940 
00944 void
00945 Connect::set_accept_deflate(bool deflate)
00946 {
00947     if (d_http)
00948         d_http->set_accept_deflate(deflate);
00949 }
00950 
00954 void
00955 Connect::set_cache_enabled(bool cache)
00956 {
00957     if (d_http)
00958         d_http->set_cache_enabled(cache);
00959 }
00960 
00961 bool
00962 Connect::is_cache_enabled()
00963 {
00964     bool status;
00965     DBG(cerr << "Entering is_cache_enabled (" << hex << d_http << dec
00966         << ")... ");
00967     if (d_http)
00968         status = d_http->is_cache_enabled();
00969     else
00970         status = false;
00971     DBGN(cerr << "exiting" << endl);
00972     return status;
00973 }
00974 
00975 } // namespace libdap

Generated on Tue Mar 4 18:01:54 2008 for libdap++ by  doxygen 1.5.1