libdap Updated for version 3.21.0
libdap4 is an implementation of OPeNDAP's DAP protocol.
D4Connect.cc
1// -*- mode: c++; c-basic-offset:4 -*-
2
3// This file is part of libdap, A C++ implementation of the OPeNDAP Data
4// Access Protocol.
5
6// Copyright (c) 2002,2003 OPeNDAP, Inc.
7// Author: James Gallagher <jgallagher@opendap.org>
8// Dan Holloway <dholloway@gso.uri.edu>
9// Reza Nekovei <reza@intcomm.net>
10//
11// This library is free software; you can redistribute it and/or
12// modify it under the terms of the GNU Lesser General Public
13// License as published by the Free Software Foundation; either
14// version 2.1 of the License, or (at your option) any later version.
15//
16// This library is distributed in the hope that it will be useful,
17// but WITHOUT ANY WARRANTY; without even the implied warranty of
18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19// Lesser General Public License for more details.
20//
21// You should have received a copy of the GNU Lesser General Public
22// License along with this library; if not, write to the Free Software
23// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24//
25// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
26
27// (c) COPYRIGHT URI/MIT 1994-2002
28// Please read the full copyright statement in the file COPYRIGHT_URI.
29//
30// Authors:
31// jhrg,jimg James Gallagher <jgallagher@gso.uri.edu>
32// dan Dan Holloway <dholloway@gso.uri.edu>
33// reza Reza Nekovei <reza@intcomm.net>
34
35#include "config.h"
36// #define DODS_DEBUG 1
37
38#include <cassert>
39
40#include <sstream>
41
42#include "D4Connect.h"
43#include "D4Group.h"
44#include "DMR.h"
45#include "HTTPConnect.h"
46#include "Response.h"
47
48#include "D4ParserSax2.h"
49#include "D4StreamUnMarshaller.h"
50#include "chunked_istream.h"
51#include "chunked_stream.h"
52
53#include "debug.h"
54#include "escaping.h"
55#include "mime_util.h"
56
57using namespace std;
58
59namespace libdap {
60
63void D4Connect::process_dmr(DMR &dmr, Response &rs) {
64 DBG(cerr << "Entering D4Connect::process_dmr" << endl);
65
66 dmr.set_dap_version(rs.get_protocol());
67
68 DBG(cerr << "Entering process_data. Response.getVersion() = " << rs.get_version() << endl);
69 switch (rs.get_type()) {
70 case dap4_error: {
71#if 0
72 Error e;
73 if (!e.parse(rs.get_stream()))
74 throw InternalErr(__FILE__, __LINE__, "Could not parse the Error object returned by the server!");
75 throw e;
76#endif
77 throw InternalErr(__FILE__, __LINE__, "DAP4 errors not processed yet: FIXME!");
78 }
79
80 case web_error:
81 // Web errors (those reported in the return document's MIME header)
82 // are processed by the WWW library.
83 throw InternalErr(__FILE__, __LINE__,
84 "An error was reported by the remote httpd; this should have been processed by HTTPConnect.");
85
86 case dap4_dmr: {
87 // parse the DMR
88 try {
89 D4ParserSax2 parser;
90 // When parsing a data response, we use the permissive mode of the DMR parser
91 // (which allows Map elements to reference Arrays that are not in the DMR).
92 // Do not use that mode when parsing the DMR response - assume the DMR is
93 // valid. jhrg 4/13/16
94 parser.intern(*rs.get_cpp_stream(), &dmr);
95 } catch (Error &e) {
96 cerr << "Exception: " << e.get_error_message() << endl;
97 return;
98 } catch (std::exception &e) {
99 cerr << "Exception: " << e.what() << endl;
100 return;
101 } catch (...) {
102 cerr << "Exception: unknown error" << endl;
103 return;
104 }
105
106 return;
107 }
108
109 default:
110 throw Error("Unknown response type");
111 }
112}
113
116void D4Connect::process_data(DMR &data, Response &rs) {
117 DBG(cerr << "Entering D4Connect::process_data" << endl);
118
119 assert(rs.get_cpp_stream()); // DAP4 code uses cpp streams
120
121 data.set_dap_version(rs.get_protocol());
122
123 DBG(cerr << "Entering process_data. Response.getVersion() = " << rs.get_version() << endl);
124 switch (rs.get_type()) {
125 case dap4_error: {
126 throw InternalErr(__FILE__, __LINE__, "DAP4 errors not processed yet: FIXME!");
127 }
128
129 case web_error:
130 // Web errors (those reported in the return document's MIME header)
131 // are processed by the WWW library.
132 throw InternalErr(
133 __FILE__, __LINE__,
134 "An error was reported by the remote httpd; this should have been processed by HTTPConnect..");
135
136 case dap4_data: {
137 chunked_istream cis(*(rs.get_cpp_stream()), CHUNK_SIZE);
138 // parse the DMR, stopping when the boundary is found.
139 try {
140 // force chunk read
141 // get chunk size
142 int chunk_size = cis.read_next_chunk();
143 if (chunk_size < 0)
144 throw Error("Found an unexpected end of input (EOF) while reading a DAP4 data response. (1)");
145
146 // get chunk
147 char chunk[chunk_size];
148 cis.read(chunk, chunk_size);
149 // parse char * with given size
150 D4ParserSax2 parser;
151 // permissive mode allows references to Maps that are not in the response.
152 // Use this mode when parsing a data response (but not the DMR). jhrg 4/13/16
153 parser.set_strict(false);
154
155 // '-2' to discard the CRLF pair
156 parser.intern(chunk, chunk_size - 2, &data);
157 } catch (Error &e) {
158 cerr << "Exception: " << e.get_error_message() << endl;
159 return;
160 } catch (std::exception &e) {
161 cerr << "Exception: " << e.what() << endl;
162 return;
163 } catch (...) {
164 cerr << "Exception: unknown error" << endl;
165 return;
166 }
167
168 D4StreamUnMarshaller um(cis, cis.twiddle_bytes());
169 data.root()->deserialize(um, data);
170
171 return;
172 }
173
174 default:
175 throw Error("Unknown response type");
176 }
177}
178
187void D4Connect::parse_mime(Response &rs) {
188 rs.set_version("dods/0.0"); // initial value; for backward compatibility.
189 rs.set_protocol("2.0");
190
191 istream &data_source = *rs.get_cpp_stream();
192 string mime = get_next_mime_header(data_source);
193 while (!mime.empty()) {
194 string header, value;
195 parse_mime_header(mime, header, value);
196
197 // Note that this is an ordered list
198 if (header == "content-description") {
199 DBG(cout << header << ": " << value << endl);
200 rs.set_type(get_description_type(value));
201 }
202 // Use the value of xdods-server only if no other value has been read
203 else if (header == "xdods-server" && rs.get_version() == "dods/0.0") {
204 DBG(cout << header << ": " << value << endl);
205 rs.set_version(value);
206 }
207 // This trumps 'xdods-server' and 'server'
208 else if (header == "xopendap-server") {
209 DBG(cout << header << ": " << value << endl);
210 rs.set_version(value);
211 } else if (header == "xdap") {
212 DBG(cout << header << ": " << value << endl);
213 rs.set_protocol(value);
214 }
215 // Only look for 'server' if no other header supplies this info.
216 else if (rs.get_version() == "dods/0.0" && header == "server") {
217 DBG(cout << header << ": " << value << endl);
218 rs.set_version(value);
219 }
220
221 mime = get_next_mime_header(data_source);
222 }
223}
224
225// public mfuncs
226
233D4Connect::D4Connect(const string &url, string uname, string password)
234 : d_http(0), d_local(false), d_URL(""), d_UrlQueryString(""), d_server("unknown"), d_protocol("4.0") {
235 string name = prune_spaces(url);
236
237 // Figure out if the URL starts with 'http', if so, make sure that we
238 // talk to an instance of HTTPConnect.
239 if (name.find("http") == 0) {
240 DBG(cerr << "Connect: The identifier is an http URL" << endl);
241 d_http = new HTTPConnect(RCReader::instance());
242 d_http->set_use_cpp_streams(true);
243
244 d_URL = name;
245
246 // Find and store any CE given with the URL.
247 string::size_type dotpos = name.find('?');
248 if (dotpos != std::string::npos) { // Found a match.
249 d_URL = name.substr(0, dotpos);
250
251 d_UrlQueryString = name.substr(dotpos + 1);
252
253 if (d_UrlQueryString.find(DAP4_CE_QUERY_KEY) != std::string::npos) {
254 std::stringstream msg;
255 msg << endl;
256 msg << "WARNING: A DAP4 constraint expression key was found in the query string!" << endl;
257 msg << "The submitted dataset URL: " << name << endl;
258 msg << "Contains the query string: " << d_UrlQueryString << endl;
259 msg << "This will cause issues when making DAP4 requests that specify additional constraints. " << endl;
260 cerr << msg.str() << endl;
261 // throw Error(malformed_expr, msg.str());
262 }
263 }
264 } else {
265 DBG(cerr << "Connect: The identifier is a local data source." << endl);
266 d_local = true; // local in this case means non-DAP
267 }
268
269 set_credentials(uname, password);
270}
271
272D4Connect::~D4Connect() {
273 if (d_http)
274 delete d_http;
275}
276
277std::string D4Connect::build_dap4_ce(const string requestSuffix, const string dap4ce) {
278 std::stringstream url;
279 bool needsAmpersand = false;
280
281 url << d_URL << requestSuffix << "?";
282
283 if (d_UrlQueryString.length() > 0) {
284 url << d_UrlQueryString;
285 needsAmpersand = true;
286 }
287
288 if (dap4ce.length() > 0) {
289 if (needsAmpersand)
290 url << "&";
291
292 url << DAP4_CE_QUERY_KEY << "=" << id2www_ce(dap4ce);
293 }
294
295 DBG(cerr << "D4Connect::build_dap4_ce() - Source URL: " << d_URL << endl);
296 DBG(cerr << "D4Connect::build_dap4_ce() - Source URL Query String: " << d_UrlQueryString << endl);
297 DBG(cerr << "D4Connect::build_dap4_ce() - dap4ce: " << dap4ce << endl);
298 DBG(cerr << "D4Connect::build_dap4_ce() - request URL: " << url.str() << endl);
299
300 return url.str();
301}
302
303void D4Connect::request_dmr(DMR &dmr, const string expr) {
304 string url = build_dap4_ce(".dmr", expr);
305
306 Response *rs = 0;
307 try {
308 rs = d_http->fetch_url(url);
309
310 d_server = rs->get_version();
311 d_protocol = rs->get_protocol();
312
313 switch (rs->get_type()) {
314 case unknown_type:
315 DBG(cerr << "Response type unknown, assuming it's a DMR response." << endl);
316 /* no break */
317 case dap4_dmr: {
318 D4ParserSax2 parser;
319 parser.intern(*rs->get_cpp_stream(), &dmr);
320 break;
321 }
322
323 case dap4_error:
324 throw InternalErr(__FILE__, __LINE__, "DAP4 errors are not processed yet.");
325
326 case web_error:
327 // We should never get here; a web error should be picked up read_url
328 // (called by fetch_url) and result in a thrown Error object.
329 throw InternalErr(__FILE__, __LINE__, "Web error found where it should never be.");
330
331 default:
332 throw InternalErr(__FILE__, __LINE__,
333 "Response type not handled (got " + long_to_string(rs->get_type()) + ").");
334 }
335 } catch (...) {
336 delete rs;
337 throw;
338 }
339
340 delete rs;
341}
342
343void D4Connect::request_dap4_data(DMR &dmr, const string expr) {
344 string url = build_dap4_ce(".dap", expr);
345
346 Response *rs = 0;
347 try {
348 rs = d_http->fetch_url(url);
349
350 d_server = rs->get_version();
351 d_protocol = rs->get_protocol();
352
353 switch (rs->get_type()) {
354 case unknown_type:
355 DBG(cerr << "Response type unknown, assuming it's a DAP4 Data response." << endl);
356 /* no break */
357 case dap4_data: {
358 // get a chunked input stream
359 chunked_istream cis(*(rs->get_cpp_stream()), CHUNK_SIZE);
360
361 // parse the DMR, stopping when the boundary is found.
362
363 // force chunk read
364 // get chunk size
365 int chunk_size = cis.read_next_chunk();
366 if (chunk_size < 0)
367 throw Error("Found an unexpected end of input (EOF) while reading a DAP4 data response. (2)");
368
369 // get chunk
370 char chunk[chunk_size];
371 cis.read(chunk, chunk_size);
372 // parse char * with given size
373 D4ParserSax2 parser;
374 // permissive mode allows references to Maps that are not in the response.
375 parser.set_strict(false);
376 // '-2' to discard the CRLF pair
377 parser.intern(chunk, chunk_size - 2, &dmr, false /*debug*/);
378
379 // Read data and store in the DMR
380 D4StreamUnMarshaller um(cis, cis.twiddle_bytes());
381 dmr.root()->deserialize(um, dmr);
382
383 break;
384 }
385
386 case dap4_error:
387 throw InternalErr(__FILE__, __LINE__, "DAP4 errors are not processed yet.");
388
389 case web_error:
390 // We should never get here; a web error should be picked up read_url
391 // (called by fetch_url) and result in a thrown Error object.
392 throw InternalErr(__FILE__, __LINE__, "Web error found where it should never be.");
393
394 default:
395 throw InternalErr(__FILE__, __LINE__,
396 "Response type not handled (got " + long_to_string(rs->get_type()) + ").");
397 }
398 } catch (...) {
399 delete rs;
400 throw;
401 }
402
403 delete rs;
404}
405
406void D4Connect::read_dmr(DMR &dmr, Response &rs) {
407 parse_mime(rs);
408 if (rs.get_type() == unknown_type)
409 throw Error("Unknown response type.");
410
411 read_dmr_no_mime(dmr, rs);
412}
413
414void D4Connect::read_dmr_no_mime(DMR &dmr, Response &rs) {
415 // Assume callers know what they are doing
416 if (rs.get_type() == unknown_type)
417 rs.set_type(dap4_dmr);
418
419 switch (rs.get_type()) {
420 case dap4_dmr:
421 process_dmr(dmr, rs);
422 d_server = rs.get_version();
423 d_protocol = dmr.dap_version();
424 break;
425 default:
426 throw Error("Expected a DAP4 DMR response.");
427 }
428}
429
430void D4Connect::read_data(DMR &data, Response &rs) {
431 parse_mime(rs);
432 if (rs.get_type() == unknown_type)
433 throw Error("Unknown response type.");
434
435 read_data_no_mime(data, rs);
436}
437
438void D4Connect::read_data_no_mime(DMR &data, Response &rs) {
439 // Assume callers know what they are doing
440 if (rs.get_type() == unknown_type)
441 rs.set_type(dap4_data);
442
443 switch (rs.get_type()) {
444 case dap4_data:
445 process_data(data, rs);
446 d_server = rs.get_version();
447 d_protocol = data.dap_version();
448 break;
449 default:
450 throw Error("Expected a DAP4 Data response.");
451 }
452}
453
459void D4Connect::set_credentials(string u, string p) {
460 if (d_http)
461 d_http->set_credentials(u, p);
462}
463
468 if (d_http)
469 d_http->set_accept_deflate(deflate);
470}
471
477void D4Connect::set_xdap_protocol(int major, int minor) {
478 if (d_http)
479 d_http->set_xdap_protocol(major, minor);
480}
481
486 if (d_http)
487 d_http->set_cache_enabled(cache);
488}
489
490bool D4Connect::is_cache_enabled() {
491 if (d_http)
492 return d_http->is_cache_enabled();
493 else
494 return false;
495}
496
497} // namespace libdap
void set_accept_deflate(bool deflate)
Definition D4Connect.cc:467
void set_cache_enabled(bool enabled)
Definition D4Connect.cc:485
void set_xdap_protocol(int major, int minor)
Definition D4Connect.cc:477
void set_credentials(std::string u, std::string p)
Set the credentials for responding to challenges while dereferencing URLs.
Definition D4Connect.cc:459
top level DAP object to house generic methods
Definition AISConnect.cc:30
ObjectType get_description_type(const string &value)
Definition mime_util.cc:309
void parse_mime_header(const string &header, string &name, string &value)
Definition mime_util.cc:848
string prune_spaces(const string &name)
Definition util.cc:451
string id2www_ce(string in, const string &allowable)
Definition escaping.cc:166
string get_next_mime_header(FILE *in)
Definition mime_util.cc:777