libdap  Updated for version 3.17.2
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 
37 #include <cassert>
38 #include <cstring>
39 #include <sstream>
40 
41 #include "D4Connect.h"
42 #include "HTTPConnect.h"
43 #include "Response.h"
44 #include "DMR.h"
45 #include "D4Group.h"
46 
47 #include "D4ParserSax2.h"
48 #include "chunked_stream.h"
49 #include "chunked_istream.h"
50 #include "D4StreamUnMarshaller.h"
51 
52 #include "escaping.h"
53 #include "mime_util.h"
54 #include "debug.h"
55 
56 using namespace std;
57 
58 namespace libdap {
59 
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: d_stream = " << rs << 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  parser.intern(*rs.get_cpp_stream(), &dmr, /*debug*/false);
92  }
93  catch (Error &e) {
94  cerr << "Exception: " << e.get_error_message() << endl;
95  return;
96  }
97  catch (std::exception &e) {
98  cerr << "Exception: " << e.what() << endl;
99  return;
100  }
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 
116 void D4Connect::process_data(DMR &data, Response &rs)
117 {
118  DBG(cerr << "Entering D4Connect::process_data" << endl);
119 
120  assert(rs.get_cpp_stream()); // DAP4 code uses cpp streams
121 
122  data.set_dap_version(rs.get_protocol());
123 
124  DBG(cerr << "Entering process_data: d_stream = " << rs << endl);
125  switch (rs.get_type()) {
126  case dap4_error: {
127 #if 0
128  Error e;
129  if (!e.parse(rs.get_cpp_stream()))
130  throw InternalErr(__FILE__, __LINE__, "Could not parse the Error object returned by the server!");
131  throw e;
132 #endif
133  throw InternalErr(__FILE__, __LINE__, "DAP4 errors not processed yet: FIXME!");
134  }
135 
136  case web_error:
137  // Web errors (those reported in the return document's MIME header)
138  // are processed by the WWW library.
139  throw InternalErr(__FILE__, __LINE__,
140  "An error was reported by the remote httpd; this should have been processed by HTTPConnect..");
141 
142  case dap4_data: {
143 #if BYTE_ORDER_PREFIX
144  // Read the byte-order byte; used later on
145  char byte_order;
146  *rs.get_cpp_stream() >> byte_order;
147  //if (debug) cerr << "Byte order: " << ((byte_order) ? "big endian" : "little endian") << endl;
148 #endif
149  // get a chunked input stream
150 #if BYTE_ORDER_PREFIX
151  chunked_istream cis(*rs.get_cpp_stream(), 1024, byte_order);
152 #else
153  chunked_istream cis(*(rs.get_cpp_stream()), CHUNK_SIZE);
154 #endif
155  // parse the DMR, stopping when the boundary is found.
156  try {
157  // force chunk read
158  // get chunk size
159  int chunk_size = cis.read_next_chunk();
160  if (chunk_size < 0)
161  throw Error("Found an unexpected end of input (EOF) while reading a DAP4 data response. (1)");
162 
163  // get chunk
164  char chunk[chunk_size];
165  cis.read(chunk, chunk_size);
166  // parse char * with given size
167  D4ParserSax2 parser;
168  // '-2' to discard the CRLF pair
169  parser.intern(chunk, chunk_size - 2, &data, /*debug*/false);
170  }
171  catch (Error &e) {
172  cerr << "Exception: " << e.get_error_message() << endl;
173  return;
174  }
175  catch (std::exception &e) {
176  cerr << "Exception: " << e.what() << endl;
177  return;
178  }
179  catch (...) {
180  cerr << "Exception: unknown error" << endl;
181  return;
182  }
183 
184 #if BYTE_ORDER_PREFIX
185  D4StreamUnMarshaller um(cis, byte_order);
186 #else
187  D4StreamUnMarshaller um(cis, cis.twiddle_bytes());
188 #endif
189  data.root()->deserialize(um, data);
190 
191  return;
192  }
193 
194  default:
195  throw Error("Unknown response type");
196  }
197 }
198 
207 void D4Connect::parse_mime(Response &rs)
208 {
209  rs.set_version("dods/0.0"); // initial value; for backward compatibility.
210  rs.set_protocol("2.0");
211 
212  istream &data_source = *rs.get_cpp_stream();
213  string mime = get_next_mime_header(data_source);
214  while (!mime.empty()) {
215  string header, value;
216  parse_mime_header(mime, header, value);
217 
218  // Note that this is an ordered list
219  if (header == "content-description") {
220  DBG(cout << header << ": " << value << endl);
221  rs.set_type(get_description_type(value));
222  }
223  // Use the value of xdods-server only if no other value has been read
224  else if (header == "xdods-server" && rs.get_version() == "dods/0.0") {
225  DBG(cout << header << ": " << value << endl);
226  rs.set_version(value);
227  }
228  // This trumps 'xdods-server' and 'server'
229  else if (header == "xopendap-server") {
230  DBG(cout << header << ": " << value << endl);
231  rs.set_version(value);
232  }
233  else if (header == "xdap") {
234  DBG(cout << header << ": " << value << endl);
235  rs.set_protocol(value);
236  }
237  // Only look for 'server' if no other header supplies this info.
238  else if (rs.get_version() == "dods/0.0" && header == "server") {
239  DBG(cout << header << ": " << value << endl);
240  rs.set_version(value);
241  }
242 
243  mime = get_next_mime_header(data_source);
244  }
245 }
246 
247 // public mfuncs
248 
255 D4Connect::D4Connect(const string &url, string uname, string password) :
256  d_http(0), d_local(false), d_URL(""), d_UrlQueryString(""), d_server("unknown"), d_protocol("4.0")
257 {
258  string name = prune_spaces(url);
259 
260  // Figure out if the URL starts with 'http', if so, make sure that we
261  // talk to an instance of HTTPConnect.
262  if (name.find("http") == 0) {
263  DBG(cerr << "Connect: The identifier is an http URL" << endl);
264  d_http = new HTTPConnect(RCReader::instance());
265  d_http->set_use_cpp_streams(true);
266 
267  d_URL = name;
268 
269  // Find and store any CE given with the URL.
270  string::size_type dotpos = name.find('?');
271  if (dotpos != std::string::npos) { // Found a match.
272  d_URL = name.substr(0, dotpos);
273 
274  d_UrlQueryString = name.substr(dotpos + 1);
275 
276  if(d_UrlQueryString.find(DAP4_CE_QUERY_KEY) != std::string::npos){
277  std::stringstream msg;
278  msg << endl;
279  msg << "WARNING: A DAP4 constraint expression key was found in the query string!" << endl;
280  msg << "The submitted dataset URL: " << name << endl;
281  msg << "Contains the query string: " << d_UrlQueryString << endl;
282  msg << "This will cause issues when making DAP4 requests that specify additional constraints. " << endl;
283  cerr << msg.str() << endl;
284  // throw Error(malformed_expr, msg.str());
285  }
286 
287  }
288  }
289  else {
290  DBG(cerr << "Connect: The identifier is a local data source." << endl);
291  d_local = true; // local in this case means non-DAP
292  }
293 
294  set_credentials(uname, password);
295 }
296 
297 D4Connect::~D4Connect()
298 {
299  if (d_http) delete d_http;
300 }
301 
302 
303 
304 std::string D4Connect::build_dap4_ce(const string requestSuffix, const string dap4ce){
305 
306  std::stringstream url;
307  bool needsAmpersand = false;
308 
309  url << d_URL << requestSuffix << "?";
310 
311  if(d_UrlQueryString.length()> 0){
312  url << d_UrlQueryString;
313  needsAmpersand = true;
314  }
315 
316  if(dap4ce.length()> 0){
317 
318  if(needsAmpersand)
319  url << "&";
320 
321  url << DAP4_CE_QUERY_KEY << "=" << id2www_ce(dap4ce);
322 
323  }
324 
325 #if 1
326  cerr << "D4Connect::build_dap4_ce() - Source URL: " << d_URL << endl;
327  cerr << "D4Connect::build_dap4_ce() - Source URL Query String: " << d_UrlQueryString << endl;
328  cerr << "D4Connect::build_dap4_ce() - dap4ce: " << dap4ce << endl;
329  cerr << "D4Connect::build_dap4_ce() - request URL: " << url.str() << endl;
330 
331 #endif
332 
333  return url.str();
334 
335 }
336 
337 
338 
339 void D4Connect::request_dmr(DMR &dmr, const string expr)
340 {
341  string url = build_dap4_ce(".dmr", expr);
342 
343 
344  Response *rs = 0;
345  try {
346  rs = d_http->fetch_url(url);
347 
348  d_server = rs->get_version();
349  d_protocol = rs->get_protocol();
350 
351  switch (rs->get_type()) {
352  case unknown_type: // FIXME Pure hackery!
353  cerr << "Response type unknown, assuming it's a DMR response." << endl;
354  /* no break */
355  case dap4_dmr: {
356  D4ParserSax2 parser;
357  parser.intern(*rs->get_cpp_stream(), &dmr, false /* debug */);
358  break;
359  }
360 
361  case dap4_error:
362  throw InternalErr(__FILE__, __LINE__, "DAP4 errors are not processed yet.");
363 
364  case web_error:
365  // We should never get here; a web error should be picked up read_url
366  // (called by fetch_url) and result in a thrown Error object.
367  throw InternalErr(__FILE__, __LINE__, "Web error found where it should never be.");
368  break;
369 
370  default:
371  throw InternalErr(__FILE__, __LINE__, "Response type not handled (got "
372  + long_to_string(rs->get_type()) + ").");
373  }
374  }
375  catch (...) {
376  delete rs;
377  throw;
378  }
379 
380  delete rs;
381 }
382 
383 void D4Connect::request_dap4_data(DMR &dmr, const string expr)
384 {
385  string url = build_dap4_ce(".dap", expr);
386 
387  Response *rs = 0;
388  try {
389  rs = d_http->fetch_url(url);
390 
391  d_server = rs->get_version();
392  d_protocol = rs->get_protocol();
393 
394  switch (rs->get_type()) {
395  case unknown_type: // FIXME Pure hackery!
396  cerr << "Response type unknown, assuming it's a DAP4 Data response." << endl;
397  /* no break */
398  case dap4_data: {
399  // TODO Move to a function 11/9/13 process_data()???
400 #if BYTE_ORDER_PREFIX
401  istream &in = *rs->get_cpp_stream();
402  // Read the byte-order byte; used later on
403  char byte_order;
404  in >> byte_order;
405 #endif
406 
407  // get a chunked input stream
408 #if BYTE_ORDER_PREFIX
409  chunked_istream cis(*(rs->get_cpp_stream()), 1024, byte_order);
410 #else
411  chunked_istream cis(*(rs->get_cpp_stream()), CHUNK_SIZE);
412 #endif
413 
414  // parse the DMR, stopping when the boundary is found.
415 
416  // force chunk read
417  // get chunk size
418  int chunk_size = cis.read_next_chunk();
419  if (chunk_size < 0)
420  throw Error("Found an unexpected end of input (EOF) while reading a DAP4 data response. (2)");
421 
422  // get chunk
423  char chunk[chunk_size];
424  cis.read(chunk, chunk_size);
425  // parse char * with given size
426  D4ParserSax2 parser;
427  // '-2' to discard the CRLF pair
428  parser.intern(chunk, chunk_size - 2, &dmr, false /*debug*/);
429 
430  // Read data and store in the DMR
431 #if BYTE_ORDER_PREFIX
432  D4StreamUnMarshaller um(cis, byte_order);
433 #else
434  D4StreamUnMarshaller um(cis, cis.twiddle_bytes());
435 #endif
436  dmr.root()->deserialize(um, dmr);
437 
438  break;
439  }
440 
441  case dap4_error:
442  throw InternalErr(__FILE__, __LINE__, "DAP4 errors are not processed yet.");
443 
444  case web_error:
445  // We should never get here; a web error should be picked up read_url
446  // (called by fetch_url) and result in a thrown Error object.
447  throw InternalErr(__FILE__, __LINE__, "Web error found where it should never be.");
448  break;
449 
450  default:
451  throw InternalErr(__FILE__, __LINE__, "Response type not handled (got "
452  + long_to_string(rs->get_type()) + ").");
453  }
454  }
455  catch (...) {
456  delete rs;
457  throw;
458  }
459 
460  delete rs;
461 }
462 
463 void
464 D4Connect::read_dmr(DMR &dmr, Response &rs)
465 {
466  parse_mime(rs);
467  if (rs.get_type() == unknown_type)
468  throw Error("Unknown response type.");
469 
470  read_dmr_no_mime(dmr, rs);
471 }
472 
473 void
474 D4Connect::read_dmr_no_mime(DMR &dmr, Response &rs)
475 {
476  // Assume callers know what they are doing
477  if (rs.get_type() == unknown_type)
478  rs.set_type(dap4_dmr);
479 
480  switch (rs.get_type()) {
481  case dap4_dmr:
482  process_dmr(dmr, rs);
483  d_server = rs.get_version();
484  d_protocol = dmr.dap_version();
485  break;
486  default:
487  throw Error("Expected a DAP4 DMR response.");
488  }
489 }
490 
491 void
492 D4Connect::read_data(DMR &data, Response &rs)
493 {
494  parse_mime(rs);
495  if (rs.get_type() == unknown_type)
496  throw Error("Unknown response type.");
497 
498  read_data_no_mime(data, rs);
499 }
500 
501 void D4Connect::read_data_no_mime(DMR &data, Response &rs)
502 {
503  // Assume callers know what they are doing
504  if (rs.get_type() == unknown_type)
505  rs.set_type(dap4_data);
506 
507  switch (rs.get_type()) {
508  case dap4_data:
509  process_data(data, rs);
510  d_server = rs.get_version();
511  d_protocol = data.dap_version();
512  break;
513  default:
514  throw Error("Expected a DAP4 Data response.");
515  }
516 }
517 
523 void D4Connect::set_credentials(string u, string p)
524 {
525  if (d_http)
526  d_http->set_credentials(u, p);
527 }
528 
533 {
534  if (d_http)
535  d_http->set_accept_deflate(deflate);
536 }
537 
543 void D4Connect::set_xdap_protocol(int major, int minor)
544 {
545  if (d_http)
546  d_http->set_xdap_protocol(major, minor);
547 }
548 
553 {
554  if (d_http)
555  d_http->set_cache_enabled(cache);
556 }
557 
558 bool D4Connect::is_cache_enabled()
559 {
560  if (d_http)
561  return d_http->is_cache_enabled();
562  else
563  return false;
564 }
565 
566 } // namespace libdap
string get_next_mime_header(FILE *in)
Definition: mime_util.cc:836
void set_credentials(std::string u, std::string p)
Set the credentials for responding to challenges while dereferencing URLs.
Definition: D4Connect.cc:523
string id2www_ce(string in, const string &allowable)
Definition: escaping.cc:178
string prune_spaces(const string &name)
Definition: util.cc:455
void set_credentials(const string &u, const string &p)
D4Group * root()
Definition: DMR.cc:242
Read data from the stream made by D4StreamMarshaller.
void set_xdap_protocol(int major, int minor)
Definition: D4Connect.cc:543
STL namespace.
void set_cache_enabled(bool enabled)
Definition: HTTPConnect.h:150
HTTPResponse * fetch_url(const string &url)
Definition: HTTPConnect.cc:617
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:898
void set_accept_deflate(bool deflate)
Definition: D4Connect.cc:532
ObjectType get_description_type(const string &value)
Definition: mime_util.cc:339
void set_cache_enabled(bool enabled)
Definition: D4Connect.cc:552
virtual void deserialize(D4StreamUnMarshaller &um, DMR &dmr)
Definition: D4Group.cc:537
void set_accept_deflate(bool defalte)
Definition: HTTPConnect.cc:998
void set_xdap_protocol(int major, int minor)
A class for error processing.
Definition: Error.h:90