libdap  Updated for version 3.20.6
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 
57 using namespace std;
58 
59 namespace libdap {
60 
63 void 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 
120 void 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 
194 void 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 
242 D4Connect::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 
284 D4Connect::~D4Connect()
285 {
286  if (d_http) delete d_http;
287 }
288 
289 std::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 
315 void 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 
357 void 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 
422 void 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 
430 void 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 
446 void 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 
454 void 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 
475 void 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 
493 void 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 
506 bool 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
string get_next_mime_header(FILE *in)
Definition: mime_util.cc:838
void set_credentials(std::string u, std::string p)
Set the credentials for responding to challenges while dereferencing URLs.
Definition: D4Connect.cc:475
string id2www_ce(string in, const string &allowable)
Definition: escaping.cc:178
string prune_spaces(const string &name)
Definition: util.cc:459
void set_credentials(const string &u, const string &p)
D4Group * root()
Definition: DMR.cc:407
Read data from the stream made by D4StreamMarshaller.
void set_xdap_protocol(int major, int minor)
Definition: D4Connect.cc:493
STL namespace.
void set_cache_enabled(bool enabled)
Definition: HTTPConnect.h:151
void set_strict(bool s)
Set the &#39;strict&#39; mode to true or false.
Definition: D4ParserSax2.h:302
HTTPResponse * fetch_url(const string &url)
Definition: HTTPConnect.cc:627
top level DAP object to house generic methods
Definition: AISConnect.cc:30
A class for software fault reporting.
Definition: InternalErr.h:64
void parse_mime_header(const string &header, string &name, string &value)
Definition: mime_util.cc:912
void set_accept_deflate(bool deflate)
Definition: D4Connect.cc:483
ObjectType get_description_type(const string &value)
Definition: mime_util.cc:339
void set_cache_enabled(bool enabled)
Definition: D4Connect.cc:501
virtual void deserialize(D4StreamUnMarshaller &um, DMR &dmr)
Definition: D4Group.cc:546
void set_accept_deflate(bool defalte)
void set_xdap_protocol(int major, int minor)
A class for error processing.
Definition: Error.h:92