libdap Updated for version 3.20.11
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 "HTTPConnect.h"
44#include "Response.h"
45#include "DMR.h"
46#include "D4Group.h"
47
48#include "D4ParserSax2.h"
49#include "chunked_stream.h"
50#include "chunked_istream.h"
51#include "D4StreamUnMarshaller.h"
52
53#include "escaping.h"
54#include "mime_util.h"
55#include "debug.h"
56
57using namespace std;
58
59namespace libdap {
60
63void D4Connect::process_dmr(DMR &dmr, Response &rs)
64{
65 DBG(cerr << "Entering D4Connect::process_dmr" << endl);
66
67 dmr.set_dap_version(rs.get_protocol());
68
69 DBG(cerr << "Entering process_data. Response.getVersion() = " << rs.get_version() << endl);
70 switch (rs.get_type()) {
71 case dap4_error: {
72#if 0
73 Error e;
74 if (!e.parse(rs.get_stream()))
75 throw InternalErr(__FILE__, __LINE__, "Could not parse the Error object returned by the server!");
76 throw e;
77#endif
78 throw InternalErr(__FILE__, __LINE__, "DAP4 errors not processed yet: FIXME!");
79 }
80
81 case web_error:
82 // Web errors (those reported in the return document's MIME header)
83 // are processed by the WWW library.
84 throw InternalErr(__FILE__, __LINE__,
85 "An error was reported by the remote httpd; this should have been processed by HTTPConnect.");
86
87 case dap4_dmr: {
88 // parse the DMR
89 try {
90 D4ParserSax2 parser;
91 // When parsing a data response, we use the permissive mode of the DMR parser
92 // (which allows Map elements to reference Arrays that are not in the DMR).
93 // Do not use that mode when parsing the DMR response - assume the DMR is
94 // valid. jhrg 4/13/16
95 parser.intern(*rs.get_cpp_stream(), &dmr);
96 }
97 catch (Error &e) {
98 cerr << "Exception: " << e.get_error_message() << endl;
99 return;
100 }
101 catch (std::exception &e) {
102 cerr << "Exception: " << e.what() << endl;
103 return;
104 }
105 catch (...) {
106 cerr << "Exception: unknown error" << endl;
107 return;
108 }
109
110 return;
111 }
112
113 default:
114 throw Error("Unknown response type");
115 }
116}
117
120void D4Connect::process_data(DMR &data, Response &rs)
121{
122 DBG(cerr << "Entering D4Connect::process_data" << endl);
123
124 assert(rs.get_cpp_stream()); // DAP4 code uses cpp streams
125
126 data.set_dap_version(rs.get_protocol());
127
128 DBG(cerr << "Entering process_data. Response.getVersion() = " << rs.get_version() << endl);
129 switch (rs.get_type()) {
130 case dap4_error: {
131 throw InternalErr(__FILE__, __LINE__, "DAP4 errors not processed yet: FIXME!");
132 }
133
134 case web_error:
135 // Web errors (those reported in the return document's MIME header)
136 // are processed by the WWW library.
137 throw InternalErr(__FILE__, __LINE__,
138 "An error was reported by the remote httpd; this should have been processed by HTTPConnect..");
139
140 case dap4_data: {
141 chunked_istream cis(*(rs.get_cpp_stream()), CHUNK_SIZE);
142 // parse the DMR, stopping when the boundary is found.
143 try {
144 // force chunk read
145 // get chunk size
146 int chunk_size = cis.read_next_chunk();
147 if (chunk_size < 0)
148 throw Error("Found an unexpected end of input (EOF) while reading a DAP4 data response. (1)");
149
150 // get chunk
151 char chunk[chunk_size];
152 cis.read(chunk, chunk_size);
153 // parse char * with given size
154 D4ParserSax2 parser;
155 // permissive mode allows references to Maps that are not in the response.
156 // Use this mode when parsing a data response (but not the DMR). jhrg 4/13/16
157 parser.set_strict(false);
158
159 // '-2' to discard the CRLF pair
160 parser.intern(chunk, chunk_size - 2, &data);
161 }
162 catch (Error &e) {
163 cerr << "Exception: " << e.get_error_message() << endl;
164 return;
165 }
166 catch (std::exception &e) {
167 cerr << "Exception: " << e.what() << endl;
168 return;
169 }
170 catch (...) {
171 cerr << "Exception: unknown error" << endl;
172 return;
173 }
174
175 D4StreamUnMarshaller um(cis, cis.twiddle_bytes());
176 data.root()->deserialize(um, data);
177
178 return;
179 }
180
181 default:
182 throw Error("Unknown response type");
183 }
184}
185
194void D4Connect::parse_mime(Response &rs)
195{
196 rs.set_version("dods/0.0"); // initial value; for backward compatibility.
197 rs.set_protocol("2.0");
198
199 istream &data_source = *rs.get_cpp_stream();
200 string mime = get_next_mime_header(data_source);
201 while (!mime.empty()) {
202 string header, value;
203 parse_mime_header(mime, header, value);
204
205 // Note that this is an ordered list
206 if (header == "content-description") {
207 DBG(cout << header << ": " << value << endl);
208 rs.set_type(get_description_type(value));
209 }
210 // Use the value of xdods-server only if no other value has been read
211 else if (header == "xdods-server" && rs.get_version() == "dods/0.0") {
212 DBG(cout << header << ": " << value << endl);
213 rs.set_version(value);
214 }
215 // This trumps 'xdods-server' and 'server'
216 else if (header == "xopendap-server") {
217 DBG(cout << header << ": " << value << endl);
218 rs.set_version(value);
219 }
220 else if (header == "xdap") {
221 DBG(cout << header << ": " << value << endl);
222 rs.set_protocol(value);
223 }
224 // Only look for 'server' if no other header supplies this info.
225 else if (rs.get_version() == "dods/0.0" && header == "server") {
226 DBG(cout << header << ": " << value << endl);
227 rs.set_version(value);
228 }
229
230 mime = get_next_mime_header(data_source);
231 }
232}
233
234// public mfuncs
235
242D4Connect::D4Connect(const string &url, string uname, string password) :
243 d_http(0), d_local(false), d_URL(""), d_UrlQueryString(""), d_server("unknown"), d_protocol("4.0")
244{
245 string name = prune_spaces(url);
246
247 // Figure out if the URL starts with 'http', if so, make sure that we
248 // talk to an instance of HTTPConnect.
249 if (name.find("http") == 0) {
250 DBG(cerr << "Connect: The identifier is an http URL" << endl);
251 d_http = new HTTPConnect(RCReader::instance());
252 d_http->set_use_cpp_streams(true);
253
254 d_URL = name;
255
256 // Find and store any CE given with the URL.
257 string::size_type dotpos = name.find('?');
258 if (dotpos != std::string::npos) { // Found a match.
259 d_URL = name.substr(0, dotpos);
260
261 d_UrlQueryString = name.substr(dotpos + 1);
262
263 if (d_UrlQueryString.find(DAP4_CE_QUERY_KEY) != std::string::npos) {
264 std::stringstream msg;
265 msg << endl;
266 msg << "WARNING: A DAP4 constraint expression key was found in the query string!" << endl;
267 msg << "The submitted dataset URL: " << name << endl;
268 msg << "Contains the query string: " << d_UrlQueryString << endl;
269 msg << "This will cause issues when making DAP4 requests that specify additional constraints. " << endl;
270 cerr << msg.str() << endl;
271 // throw Error(malformed_expr, msg.str());
272 }
273
274 }
275 }
276 else {
277 DBG(cerr << "Connect: The identifier is a local data source." << endl);
278 d_local = true; // local in this case means non-DAP
279 }
280
281 set_credentials(uname, password);
282}
283
284D4Connect::~D4Connect()
285{
286 if (d_http) delete d_http;
287}
288
289std::string D4Connect::build_dap4_ce(const string requestSuffix, const string dap4ce)
290{
291 std::stringstream url;
292 bool needsAmpersand = false;
293
294 url << d_URL << requestSuffix << "?";
295
296 if (d_UrlQueryString.length() > 0) {
297 url << d_UrlQueryString;
298 needsAmpersand = true;
299 }
300
301 if (dap4ce.length() > 0) {
302 if (needsAmpersand) url << "&";
303
304 url << DAP4_CE_QUERY_KEY << "=" << id2www_ce(dap4ce);
305 }
306
307 DBG(cerr << "D4Connect::build_dap4_ce() - Source URL: " << d_URL << endl);
308 DBG(cerr << "D4Connect::build_dap4_ce() - Source URL Query String: " << d_UrlQueryString << endl);
309 DBG(cerr << "D4Connect::build_dap4_ce() - dap4ce: " << dap4ce << endl);
310 DBG(cerr << "D4Connect::build_dap4_ce() - request URL: " << url.str() << endl);
311
312 return url.str();
313}
314
315void D4Connect::request_dmr(DMR &dmr, const string expr)
316{
317 string url = build_dap4_ce(".dmr", expr);
318
319 Response *rs = 0;
320 try {
321 rs = d_http->fetch_url(url);
322
323 d_server = rs->get_version();
324 d_protocol = rs->get_protocol();
325
326 switch (rs->get_type()) {
327 case unknown_type:
328 DBG(cerr << "Response type unknown, assuming it's a DMR response." << endl);
329 /* no break */
330 case dap4_dmr: {
331 D4ParserSax2 parser;
332 parser.intern(*rs->get_cpp_stream(), &dmr);
333 break;
334 }
335
336 case dap4_error:
337 throw InternalErr(__FILE__, __LINE__, "DAP4 errors are not processed yet.");
338
339 case web_error:
340 // We should never get here; a web error should be picked up read_url
341 // (called by fetch_url) and result in a thrown Error object.
342 throw InternalErr(__FILE__, __LINE__, "Web error found where it should never be.");
343
344 default:
345 throw InternalErr(__FILE__, __LINE__,
346 "Response type not handled (got " + long_to_string(rs->get_type()) + ").");
347 }
348 }
349 catch (...) {
350 delete rs;
351 throw;
352 }
353
354 delete rs;
355}
356
357void D4Connect::request_dap4_data(DMR &dmr, const string expr)
358{
359 string url = build_dap4_ce(".dap", expr);
360
361 Response *rs = 0;
362 try {
363 rs = d_http->fetch_url(url);
364
365 d_server = rs->get_version();
366 d_protocol = rs->get_protocol();
367
368 switch (rs->get_type()) {
369 case unknown_type:
370 DBG(cerr << "Response type unknown, assuming it's a DAP4 Data response." << endl);
371 /* no break */
372 case dap4_data: {
373 // get a chunked input stream
374 chunked_istream cis(*(rs->get_cpp_stream()), CHUNK_SIZE);
375
376 // parse the DMR, stopping when the boundary is found.
377
378 // force chunk read
379 // get chunk size
380 int chunk_size = cis.read_next_chunk();
381 if (chunk_size < 0)
382 throw Error("Found an unexpected end of input (EOF) while reading a DAP4 data response. (2)");
383
384 // get chunk
385 char chunk[chunk_size];
386 cis.read(chunk, chunk_size);
387 // parse char * with given size
388 D4ParserSax2 parser;
389 // permissive mode allows references to Maps that are not in the response.
390 parser.set_strict(false);
391 // '-2' to discard the CRLF pair
392 parser.intern(chunk, chunk_size - 2, &dmr, false /*debug*/);
393
394 // Read data and store in the DMR
395 D4StreamUnMarshaller um(cis, cis.twiddle_bytes());
396 dmr.root()->deserialize(um, dmr);
397
398 break;
399 }
400
401 case dap4_error:
402 throw InternalErr(__FILE__, __LINE__, "DAP4 errors are not processed yet.");
403
404 case web_error:
405 // We should never get here; a web error should be picked up read_url
406 // (called by fetch_url) and result in a thrown Error object.
407 throw InternalErr(__FILE__, __LINE__, "Web error found where it should never be.");
408
409 default:
410 throw InternalErr(__FILE__, __LINE__,
411 "Response type not handled (got " + long_to_string(rs->get_type()) + ").");
412 }
413 }
414 catch (...) {
415 delete rs;
416 throw;
417 }
418
419 delete rs;
420}
421
422void D4Connect::read_dmr(DMR &dmr, Response &rs)
423{
424 parse_mime(rs);
425 if (rs.get_type() == unknown_type) throw Error("Unknown response type.");
426
427 read_dmr_no_mime(dmr, rs);
428}
429
430void D4Connect::read_dmr_no_mime(DMR &dmr, Response &rs)
431{
432 // Assume callers know what they are doing
433 if (rs.get_type() == unknown_type) rs.set_type(dap4_dmr);
434
435 switch (rs.get_type()) {
436 case dap4_dmr:
437 process_dmr(dmr, rs);
438 d_server = rs.get_version();
439 d_protocol = dmr.dap_version();
440 break;
441 default:
442 throw Error("Expected a DAP4 DMR response.");
443 }
444}
445
446void D4Connect::read_data(DMR &data, Response &rs)
447{
448 parse_mime(rs);
449 if (rs.get_type() == unknown_type) throw Error("Unknown response type.");
450
451 read_data_no_mime(data, rs);
452}
453
454void D4Connect::read_data_no_mime(DMR &data, Response &rs)
455{
456 // Assume callers know what they are doing
457 if (rs.get_type() == unknown_type) rs.set_type(dap4_data);
458
459 switch (rs.get_type()) {
460 case dap4_data:
461 process_data(data, rs);
462 d_server = rs.get_version();
463 d_protocol = data.dap_version();
464 break;
465 default:
466 throw Error("Expected a DAP4 Data response.");
467 }
468}
469
475void D4Connect::set_credentials(string u, string p)
476{
477 if (d_http) d_http->set_credentials(u, p);
478}
479
484{
485 if (d_http) d_http->set_accept_deflate(deflate);
486}
487
493void D4Connect::set_xdap_protocol(int major, int minor)
494{
495 if (d_http) d_http->set_xdap_protocol(major, minor);
496}
497
502{
503 if (d_http) d_http->set_cache_enabled(cache);
504}
505
506bool D4Connect::is_cache_enabled()
507{
508 if (d_http)
509 return d_http->is_cache_enabled();
510 else
511 return false;
512}
513
514} // namespace libdap
void set_accept_deflate(bool deflate)
Definition: D4Connect.cc:483
void set_cache_enabled(bool enabled)
Definition: D4Connect.cc:501
void set_xdap_protocol(int major, int minor)
Definition: D4Connect.cc:493
void set_credentials(std::string u, std::string p)
Set the credentials for responding to challenges while dereferencing URLs.
Definition: D4Connect.cc:475
void set_accept_deflate(bool defalte)
HTTPResponse * fetch_url(const string &url)
Definition: HTTPConnect.cc:635
void set_credentials(const string &u, const string &p)
void set_cache_enabled(bool enabled)
Definition: HTTPConnect.h:151
void set_xdap_protocol(int major, int minor)
top level DAP object to house generic methods
Definition: AlarmHandler.h:36
ObjectType get_description_type(const string &value)
Definition: mime_util.cc:337
void parse_mime_header(const string &header, string &name, string &value)
Definition: mime_util.cc:910
string prune_spaces(const string &name)
Definition: util.cc:459
string id2www_ce(string in, const string &allowable)
Definition: escaping.cc:178
string get_next_mime_header(FILE *in)
Definition: mime_util.cc:836