libdap++  Updated for version 3.8.2
ResponseBuilder.cc
Go to the documentation of this file.
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) 2011 OPeNDAP, Inc.
7 // Author: James Gallagher <jgallagher@opendap.org>
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public
11 // License as published by the Free Software Foundation; either
12 // version 2.1 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // Lesser General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 //
23 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
24 
25 #include "config.h"
26 
27 static char rcsid[] not_used = { "$Id: ResponseBuilder.cc 23477 2010-09-02 21:02:59Z jimg $" };
28 
29 #include <signal.h>
30 
31 #ifndef WIN32
32 #include <unistd.h> // for getopt, and alarm()
33 #include <sys/wait.h>
34 #else
35 #include <io.h>
36 #include <fcntl.h>
37 #include <process.h>
38 #endif
39 
40 #include <iostream>
41 #include <string>
42 #include <sstream>
43 #include <cstring>
44 
45 #include <uuid/uuid.h> // used to build CID header value for data ddx
46 
47 #include "DAS.h"
48 #include "DDS.h"
49 #include "debug.h"
50 #include "mime_util.h" // for last_modified_time() and rfc_822_date()
51 #include "escaping.h"
52 #include "ResponseBuilder.h"
53 #include "XDRStreamMarshaller.h"
54 
55 #ifndef WIN32
56 #include "SignalHandler.h"
57 #include "EventHandler.h"
58 #include "AlarmHandler.h"
59 #endif
60 
61 #define CRLF "\r\n" // Change here, expr-test.cc
62 using namespace std;
63 
64 namespace libdap {
65 
66 ResponseBuilder::~ResponseBuilder()
67 {
68 }
69 
72 void ResponseBuilder::initialize()
73 {
74  // Set default values. Don't use the C++ constructor initialization so
75  // that a subclass can have more control over this process.
76  d_dataset = "";
77  d_ce = "";
78  d_timeout = 0;
79 
80  d_default_protocol = DAP_PROTOCOL_VERSION;
81 #if 0 // Keyword support moved to Keywords class
82  // Load known_keywords
83  d_known_keywords.insert("dap2");
84  d_known_keywords.insert("dap2.0");
85 
86  d_known_keywords.insert("dap3.2");
87  d_known_keywords.insert("dap3.3");
88 
89  d_known_keywords.insert("dap4");
90  d_known_keywords.insert("dap4.0");
91 #endif
92 #ifdef WIN32
93  // We want serving from win32 to behave in a manner
94  // similar to the UNIX way - no CR->NL terminated lines
95  // in files. Hence stdout goes to binary mode.
96  _setmode(_fileno(stdout), _O_BINARY);
97 #endif
98 }
99 
100 #if 0
101 
105 void ResponseBuilder::add_keyword(const string &kw)
106 {
107  d_keywords.insert(kw);
108 }
109 
116 bool ResponseBuilder::is_keyword(const string &kw) const
117 {
118  return d_keywords.count(kw) != 0;
119 }
120 
126 list<string> ResponseBuilder::get_keywords() const
127 {
128  list<string> kws;
129  set<string>::const_iterator i;
130  for (i = d_keywords.begin(); i != d_keywords.end(); ++i)
131  kws.push_front(*i);
132  return kws;
133 }
134 
140 bool ResponseBuilder::is_known_keyword(const string &w) const
141 {
142  return d_known_keywords.count(w) != 0;
143 }
144 #endif
145 
152 string ResponseBuilder::get_ce() const
153 {
154  return d_ce;
155 }
156 
157 void ResponseBuilder::set_ce(string _ce)
158 {
159  d_ce = www2id(_ce, "%", "%20");
160 
161 #if 0
162  // Get the whole CE
163  string projection = www2id(_ce, "%", "%20");
164  string selection = "";
165 
166  // Separate the selection part (which follows/includes the first '&')
167  string::size_type amp = projection.find('&');
168  if (amp != string::npos) {
169  selection = projection.substr(amp);
170  projection = projection.substr(0, amp);
171  }
172 
173  // Extract keywords; add to the ResponseBuilder keywords. For this, scan for
174  // a known set of keywords and assume that anything else is part of the
175  // projection and should be left alone. Keywords must come before variables
176  // The 'projection' string will look like: '' or 'dap4.0' or 'dap4.0,u,v'
177  while (!projection.empty()) {
178  string::size_type i = projection.find(',');
179  string next_word = projection.substr(0, i);
180  if (is_known_keyword(next_word)) {
181  add_keyword(next_word);
182  projection = projection.substr(i + 1);
183  }
184  else {
185  break; // exit on first non-keyword
186  }
187  }
188 
189  // The CE is whatever is left after removing the keywords
190  d_ce = projection + selection;
191 #endif
192 }
193 
202 string ResponseBuilder::get_dataset_name() const
203 {
204  return d_dataset;
205 }
206 
207 void ResponseBuilder::set_dataset_name(const string ds)
208 {
209  d_dataset = www2id(ds, "%", "%20");
210 }
211 
216 void ResponseBuilder::set_timeout(int t)
217 {
218  d_timeout = t;
219 }
220 
222 int ResponseBuilder::get_timeout() const
223 {
224  return d_timeout;
225 }
226 
237 void ResponseBuilder::establish_timeout(ostream &stream) const
238 {
239 #ifndef WIN32
240  if (d_timeout > 0) {
241  SignalHandler *sh = SignalHandler::instance();
242  EventHandler *old_eh = sh->register_handler(SIGALRM, new AlarmHandler(stream));
243  delete old_eh;
244  alarm(d_timeout);
245  }
246 #endif
247 }
248 
260 void ResponseBuilder::send_das(ostream &out, DAS &das, bool with_mime_headers) const
261 {
262  if (with_mime_headers)
263  set_mime_text(out, dods_das, x_plain, last_modified_time(d_dataset), "2.0");
264  das.print(out);
265 
266  out << flush;
267 }
268 
285 void ResponseBuilder::send_dds(ostream &out, DDS &dds, ConstraintEvaluator &eval, bool constrained,
286  bool with_mime_headers) const
287 {
288  // If constrained, parse the constraint. Throws Error or InternalErr.
289  if (constrained)
290  eval.parse_constraint(d_ce, dds);
291 
292  if (eval.functional_expression())
293  throw Error("Function calls can only be used with data requests. To see the structure of the underlying data source, reissue the URL without the function.");
294 
295  if (with_mime_headers)
297 
298  if (constrained)
299  dds.print_constrained(out);
300  else
301  dds.print(out);
302 
303  out << flush;
304 }
305 
306 void ResponseBuilder::dataset_constraint(ostream &out, DDS & dds, ConstraintEvaluator & eval, bool ce_eval) const
307 {
308  // send constrained DDS
309  dds.print_constrained(out);
310  out << "Data:\n";
311  out << flush;
312 
313  // Grab a stream that encodes using XDR.
314  XDRStreamMarshaller m(out);
315 
316  try {
317  // Send all variables in the current projection (send_p())
318  for (DDS::Vars_iter i = dds.var_begin(); i != dds.var_end(); i++)
319  if ((*i)->send_p()) {
320  DBG(cerr << "Sending " << (*i)->name() << endl);
321  (*i)->serialize(eval, dds, m, ce_eval);
322  }
323  }
324  catch (Error & e) {
325  throw;
326  }
327 }
328 
329 void ResponseBuilder::dataset_constraint_ddx( ostream &out, DDS & dds, ConstraintEvaluator & eval,
330  const string &boundary, const string &start, bool ce_eval) const
331 {
332  // Write the MPM headers for the DDX (text/xml) part of the response
333  set_mime_ddx_boundary(out, boundary, start, dap4_ddx);
334 
335  // Make cid
336  uuid_t uu;
337  uuid_generate(uu);
338  char uuid[37];
339  uuid_unparse(uu, &uuid[0]);
340  char domain[256];
341  if (getdomainname(domain, 255) != 0 || strlen(domain) == 0)
342  strncpy(domain, "opendap.org", 255);
343 
344  string cid = string(&uuid[0]) + "@" + string(&domain[0]);
345 
346  // Send constrained DDX with a data blob reference
347  dds.print_xml(out, true, cid);
348 
349  // Write the MPM headers for the data part of the response.
350  set_mime_data_boundary(out, boundary, cid, dap4_data, binary);
351 
352  // Grab a stream that encodes using XDR.
353  XDRStreamMarshaller m(out);
354 
355  try {
356  // Send all variables in the current projection (send_p())
357  for (DDS::Vars_iter i = dds.var_begin(); i != dds.var_end(); i++)
358  if ((*i)->send_p()) {
359  DBG(cerr << "Sending " << (*i)->name() << endl);
360  (*i)->serialize(eval, dds, m, ce_eval);
361  }
362  }
363  catch (Error & e) {
364  throw;
365  }
366 }
367 
384 void ResponseBuilder::send_data(ostream & data_stream, DDS & dds, ConstraintEvaluator & eval, bool with_mime_headers) const
385 {
386  // Set up the alarm.
387  establish_timeout(data_stream);
388  dds.set_timeout(d_timeout);
389 
390  eval.parse_constraint(d_ce, dds); // Throws Error if the ce doesn't
391  // parse.
392 
393  dds.tag_nested_sequences(); // Tag Sequences as Parent or Leaf node.
394 
395  // Start sending the response...
396 
397  // Handle *functional* constraint expressions specially
398  if (eval.function_clauses()) {
399  DDS *fdds = eval.eval_function_clauses(dds);
400  if (with_mime_headers)
401  set_mime_binary(data_stream, dods_data, x_plain, last_modified_time(d_dataset), dds.get_dap_version());
402 
403  dataset_constraint(data_stream, *fdds, eval, false);
404  delete fdds;
405  }
406  else {
407  if (with_mime_headers)
408  set_mime_binary(data_stream, dods_data, x_plain, last_modified_time(d_dataset), dds.get_dap_version());
409 
410  dataset_constraint(data_stream, dds, eval);
411  }
412 
413  data_stream << flush;
414 }
415 
426 void ResponseBuilder::send_ddx(ostream &out, DDS &dds, ConstraintEvaluator &eval, bool with_mime_headers) const
427 {
428  // If constrained, parse the constraint. Throws Error or InternalErr.
429  if (!d_ce.empty())
430  eval.parse_constraint(d_ce, dds);
431 
432  if (eval.functional_expression())
433  throw Error(
434  "Function calls can only be used with data requests. To see the structure of the underlying data source, reissue the URL without the function.");
435 
436  if (with_mime_headers)
438  dds.print_xml(out, !d_ce.empty(), "");
439 }
440 
457 void ResponseBuilder::send_data_ddx(ostream & data_stream, DDS & dds, ConstraintEvaluator & eval, const string &start,
458  const string &boundary, bool with_mime_headers) const
459 {
460  // Set up the alarm.
461  establish_timeout(data_stream);
462  dds.set_timeout(d_timeout);
463 
464  eval.parse_constraint(d_ce, dds); // Throws Error if the ce doesn't
465  // parse.
466 
467  dds.tag_nested_sequences(); // Tag Sequences as Parent or Leaf node.
468 
469  // Start sending the response...
470 
471  // Handle *functional* constraint expressions specially
472  if (eval.function_clauses()) {
473  DDS *fdds = eval.eval_function_clauses(dds);
474  if (with_mime_headers)
475  set_mime_multipart(data_stream, boundary, start, dap4_data_ddx, x_plain, last_modified_time(d_dataset));
476  data_stream << flush;
477  // TODO: Change this to dataset_constraint_ddx()
478  dataset_constraint(data_stream, *fdds, eval, false);
479  delete fdds;
480  }
481  else {
482  if (with_mime_headers)
483  set_mime_multipart(data_stream, boundary, start, dap4_data_ddx, x_plain, last_modified_time(d_dataset));
484  data_stream << flush;
485  dataset_constraint_ddx(data_stream, dds, eval, boundary, start);
486  }
487 
488  data_stream << flush;
489 
490  if (with_mime_headers)
491  data_stream << CRLF << "--" << boundary << "--" << CRLF;
492 }
493 
494 static const char *descrip[] = { "unknown", "dods_das", "dods_dds", "dods_data", "dods_error", "web_error", "dap4-ddx",
495  "dap4-data", "dap4-error", "dap4-data-ddx", "dods_ddx" };
496 static const char *encoding[] = { "unknown", "deflate", "x-plain", "gzip", "binary" };
497 
511  EncodingType enc, const time_t last_modified,
512  const string &protocol) const
513 {
514  strm << "HTTP/1.0 200 OK" << CRLF;
515 
516  strm << "XDODS-Server: " << DVR << CRLF;
517  strm << "XOPeNDAP-Server: " << DVR << CRLF;
518 
519  if (protocol == "")
520  strm << "XDAP: " << d_default_protocol << CRLF;
521  else
522  strm << "XDAP: " << protocol << CRLF;
523 
524  const time_t t = time(0);
525  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
526 
527  strm << "Last-Modified: ";
528  if (last_modified > 0)
529  strm << rfc822_date(last_modified).c_str() << CRLF;
530  else
531  strm << rfc822_date(t).c_str() << CRLF;
532 
533  if (type == dap4_ddx)
534  strm << "Content-Type: text/xml" << CRLF;
535  else
536  strm << "Content-Type: text/plain" << CRLF;
537 
538  // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
539  // jhrg 12/23/05
540  strm << "Content-Description: " << descrip[type] << CRLF;
541  if (type == dods_error) // don't cache our error responses.
542  strm << "Cache-Control: no-cache" << CRLF;
543  // Don't write a Content-Encoding header for x-plain since that breaks
544  // Netscape on NT. jhrg 3/23/97
545  if (enc != x_plain)
546  strm << "Content-Encoding: " << encoding[enc] << CRLF;
547  strm << CRLF;
548 }
549 
561  EncodingType enc, const time_t last_modified,
562  const string &protocol) const
563 {
564  strm << "HTTP/1.0 200 OK" << CRLF;
565 
566  strm << "XDODS-Server: " << DVR << CRLF;
567  strm << "XOPeNDAP-Server: " << DVR << CRLF;
568 
569  if (protocol == "")
570  strm << "XDAP: " << d_default_protocol << CRLF;
571  else
572  strm << "XDAP: " << protocol << CRLF;
573 
574  const time_t t = time(0);
575  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
576 
577  strm << "Last-Modified: ";
578  if (last_modified > 0)
579  strm << rfc822_date(last_modified).c_str() << CRLF;
580  else
581  strm << rfc822_date(t).c_str() << CRLF;
582 
583  strm << "Content-type: text/html" << CRLF;
584  // See note above about Content-Description header. jhrg 12/23/05
585  strm << "Content-Description: " << descrip[type] << CRLF;
586  if (type == dods_error) // don't cache our error responses.
587  strm << "Cache-Control: no-cache" << CRLF;
588  // Don't write a Content-Encoding header for x-plain since that breaks
589  // Netscape on NT. jhrg 3/23/97
590  if (enc != x_plain)
591  strm << "Content-Encoding: " << encoding[enc] << CRLF;
592  strm << CRLF;
593 }
594 
609  EncodingType enc, const time_t last_modified,
610  const string &protocol) const
611 {
612  strm << "HTTP/1.0 200 OK" << CRLF;
613 
614  strm << "XDODS-Server: " << DVR << CRLF;
615  strm << "XOPeNDAP-Server: " << DVR << CRLF;
616 
617  if (protocol == "")
618  strm << "XDAP: " << d_default_protocol << CRLF;
619  else
620  strm << "XDAP: " << protocol << CRLF;
621 
622  const time_t t = time(0);
623  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
624 
625  strm << "Last-Modified: ";
626  if (last_modified > 0)
627  strm << rfc822_date(last_modified).c_str() << CRLF;
628  else
629  strm << rfc822_date(t).c_str() << CRLF;
630 
631  strm << "Content-Type: application/octet-stream" << CRLF;
632  strm << "Content-Description: " << descrip[type] << CRLF;
633  if (enc != x_plain)
634  strm << "Content-Encoding: " << encoding[enc] << CRLF;
635 
636  strm << CRLF;
637 }
638 
639 void ResponseBuilder::set_mime_multipart(ostream &strm, const string &boundary,
640  const string &start, ObjectType type, EncodingType enc,
641  const time_t last_modified, const string &protocol) const
642 {
643  strm << "HTTP/1.0 200 OK" << CRLF;
644 
645  strm << "XDODS-Server: " << DVR << CRLF;
646  strm << "XOPeNDAP-Server: " << DVR << CRLF;
647 
648  if (protocol == "")
649  strm << "XDAP: " << d_default_protocol << CRLF;
650  else
651  strm << "XDAP: " << protocol << CRLF;
652 
653  const time_t t = time(0);
654  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
655 
656  strm << "Last-Modified: ";
657  if (last_modified > 0)
658  strm << rfc822_date(last_modified).c_str() << CRLF;
659  else
660  strm << rfc822_date(t).c_str() << CRLF;
661 
662  strm << "Content-Type: Multipart/Related; boundary=" << boundary << "; start=\"<" << start
663  << ">\"; type=\"Text/xml\"" << CRLF;
664  strm << "Content-Description: " << descrip[type] << CRLF;
665  if (enc != x_plain)
666  strm << "Content-Encoding: " << encoding[enc] << CRLF;
667 
668  strm << CRLF;
669 }
670 
671 void ResponseBuilder::set_mime_ddx_boundary(ostream &strm, const string &boundary,
672  const string &cid, ObjectType type, EncodingType enc) const
673 {
674  strm << "--" << boundary << CRLF;
675  strm << "Content-Type: Text/xml; charset=iso-8859-1" << CRLF;
676  strm << "Content-Id: <" << cid << ">" << CRLF;
677  strm << "Content-Description: " << descrip[type] << CRLF;
678  if (enc != x_plain)
679  strm << "Content-Encoding: " << encoding[enc] << CRLF;
680 
681  strm << CRLF;
682 }
683 
684 void ResponseBuilder::set_mime_data_boundary(ostream &strm, const string &boundary,
685  const string &cid, ObjectType type, EncodingType enc) const
686 {
687  strm << "--" << boundary << CRLF;
688  strm << "Content-Type: application/octet-stream" << CRLF;
689  strm << "Content-Id: <" << cid << ">" << CRLF;
690  strm << "Content-Description: " << descrip[type] << CRLF;
691  if (enc != x_plain)
692  strm << "Content-Encoding: " << encoding[enc] << CRLF;
693 
694  strm << CRLF;
695 }
696 
703 void ResponseBuilder::set_mime_error(ostream &strm, int code, const string &reason,
704  const string &protocol) const
705 {
706  strm << "HTTP/1.0 " << code << " " << reason.c_str() << CRLF;
707 
708  strm << "XDODS-Server: " << DVR << CRLF;
709  strm << "XOPeNDAP-Server: " << DVR << CRLF;
710 
711  if (protocol == "")
712  strm << "XDAP: " << d_default_protocol << CRLF;
713  else
714  strm << "XDAP: " << protocol << CRLF;
715 
716  const time_t t = time(0);
717  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
718  strm << "Cache-Control: no-cache" << CRLF;
719  strm << CRLF;
720 }
721 
722 } // namespace libdap
723