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 #include <unistd.h>
31 
32 #ifndef WIN32
33 // #include <unistd.h> // for getopt
34 #include <sys/wait.h>
35 #else
36 #include <io.h>
37 #include <fcntl.h>
38 #include <process.h>
39 #endif
40 
41 #include <iostream>
42 #include <string>
43 #include <sstream>
44 #include <cstring>
45 
46 #include <uuid/uuid.h> // used to build CID header value for data ddx
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 "util.h"
53 #include "ResponseBuilder.h"
54 #include "XDRStreamMarshaller.h"
55 
56 #ifndef WIN32
57 #include "SignalHandler.h"
58 #include "EventHandler.h"
59 #include "AlarmHandler.h"
60 #endif
61 
62 #define CRLF "\r\n" // Change here, expr-test.cc
63 using namespace std;
64 
65 namespace libdap {
66 
67 ResponseBuilder::~ResponseBuilder() {
68 }
69 
72 void ResponseBuilder::initialize() {
73  // Set default values. Don't use the C++ constructor initialization so
74  // that a subclass can have more control over this process.
75  d_dataset = "";
76  d_ce = "";
77  d_timeout = 0;
78 
79  d_default_protocol = DAP_PROTOCOL_VERSION;
80 #if 0 // Keyword support moved to Keywords class
81  // Load known_keywords
82  d_known_keywords.insert("dap2");
83  d_known_keywords.insert("dap2.0");
84 
85  d_known_keywords.insert("dap3.2");
86  d_known_keywords.insert("dap3.3");
87 
88  d_known_keywords.insert("dap4");
89  d_known_keywords.insert("dap4.0");
90 #endif
91 #ifdef WIN32
92  // We want serving from win32 to behave in a manner
93  // similar to the UNIX way - no CR->NL terminated lines
94  // in files. Hence stdout goes to binary mode.
95  _setmode(_fileno(stdout), _O_BINARY);
96 #endif
97 }
98 
99 #if 0
100 
104 void ResponseBuilder::add_keyword(const string &kw)
105 {
106  d_keywords.insert(kw);
107 }
108 
115 bool ResponseBuilder::is_keyword(const string &kw) const
116 {
117  return d_keywords.count(kw) != 0;
118 }
119 
125 list<string> ResponseBuilder::get_keywords() const
126 {
127  list<string> kws;
128  set<string>::const_iterator i;
129  for (i = d_keywords.begin(); i != d_keywords.end(); ++i)
130  kws.push_front(*i);
131  return kws;
132 }
133 
139 bool ResponseBuilder::is_known_keyword(const string &w) const
140 {
141  return d_known_keywords.count(w) != 0;
142 }
143 #endif
144 
151 string ResponseBuilder::get_ce() const {
152  return d_ce;
153 }
154 
155 void ResponseBuilder::set_ce(string _ce) {
156  d_ce = www2id(_ce, "%", "%20");
157 
158 #if 0
159  // Get the whole CE
160  string projection = www2id(_ce, "%", "%20");
161  string selection = "";
162 
163  // Separate the selection part (which follows/includes the first '&')
164  string::size_type amp = projection.find('&');
165  if (amp != string::npos) {
166  selection = projection.substr(amp);
167  projection = projection.substr(0, amp);
168  }
169 
170  // Extract keywords; add to the ResponseBuilder keywords. For this, scan for
171  // a known set of keywords and assume that anything else is part of the
172  // projection and should be left alone. Keywords must come before variables
173  // The 'projection' string will look like: '' or 'dap4.0' or 'dap4.0,u,v'
174  while (!projection.empty()) {
175  string::size_type i = projection.find(',');
176  string next_word = projection.substr(0, i);
177  if (is_known_keyword(next_word)) {
178  add_keyword(next_word);
179  projection = projection.substr(i + 1);
180  }
181  else {
182  break; // exit on first non-keyword
183  }
184  }
185 
186  // The CE is whatever is left after removing the keywords
187  d_ce = projection + selection;
188 #endif
189 }
190 
199 string ResponseBuilder::get_dataset_name() const {
200  return d_dataset;
201 }
202 
203 void ResponseBuilder::set_dataset_name(const string ds) {
204  d_dataset = www2id(ds, "%", "%20");
205 }
206 
211 void ResponseBuilder::set_timeout(int t) {
212  d_timeout = t;
213 }
214 
216 int ResponseBuilder::get_timeout() const {
217  return d_timeout;
218 }
219 
230 void ResponseBuilder::establish_timeout(ostream &stream) const {
231 #ifndef WIN32
232  if (d_timeout > 0) {
233  SignalHandler *sh = SignalHandler::instance();
234  EventHandler *old_eh = sh->register_handler(SIGALRM, new AlarmHandler(stream));
235  delete old_eh;
236  alarm(d_timeout);
237  }
238 #endif
239 }
240 
252 void ResponseBuilder::send_das(ostream &out, DAS &das, bool with_mime_headers) const {
253  if (with_mime_headers)
254  set_mime_text(out, dods_das, x_plain, last_modified_time(d_dataset), "2.0");
255  das.print(out);
256 
257  out << flush;
258 }
259 
276 void ResponseBuilder::send_dds(ostream &out, DDS &dds, ConstraintEvaluator &eval, bool constrained,
277  bool with_mime_headers) const {
278  // If constrained, parse the constraint. Throws Error or InternalErr.
279  if (constrained)
280  eval.parse_constraint(d_ce, dds);
281 
282  if (eval.functional_expression())
283  throw Error(
284  "Function calls can only be used with data requests. To see the structure of the underlying data source, reissue the URL without the function.");
285 
286  if (with_mime_headers)
288 
289  if (constrained)
290  dds.print_constrained(out);
291  else
292  dds.print(out);
293 
294  out << flush;
295 }
296 
297 void ResponseBuilder::dataset_constraint(ostream &out, DDS & dds, ConstraintEvaluator & eval, bool ce_eval) const {
298  // send constrained DDS
299  dds.print_constrained(out);
300  out << "Data:\n";
301  out << flush;
302 
303  // Grab a stream that encodes using XDR.
304  XDRStreamMarshaller m(out);
305 
306  try {
307  // Send all variables in the current projection (send_p())
308  for (DDS::Vars_iter i = dds.var_begin(); i != dds.var_end(); i++)
309  if ((*i)->send_p()) {
310  DBG(cerr << "Sending " << (*i)->name() << endl);
311  (*i)->serialize(eval, dds, m, ce_eval);
312  }
313  } catch (Error & e) {
314  throw;
315  }
316 }
317 
318 void ResponseBuilder::dataset_constraint_ddx(ostream &out, DDS & dds, ConstraintEvaluator & eval,
319  const string &boundary, const string &start, bool ce_eval) const {
320  // Write the MPM headers for the DDX (text/xml) part of the response
321  set_mime_ddx_boundary(out, boundary, start, dap4_ddx);
322 
323  // Make cid
324  uuid_t uu;
325  uuid_generate(uu);
326  char uuid[37];
327  uuid_unparse(uu, &uuid[0]);
328  char domain[256];
329  if (getdomainname(domain, 255) != 0 || strlen(domain) == 0)
330  strncpy(domain, "opendap.org", 255);
331 
332  string cid = string(&uuid[0]) + "@" + string(&domain[0]);
333 
334  // Send constrained DDX with a data blob reference
335  dds.print_xml(out, true, cid);
336 
337  // Write the MPM headers for the data part of the response.
338  set_mime_data_boundary(out, boundary, cid, dap4_data, binary);
339 
340  // Grab a stream that encodes using XDR.
341  XDRStreamMarshaller m(out);
342 
343  try {
344  // Send all variables in the current projection (send_p())
345  for (DDS::Vars_iter i = dds.var_begin(); i != dds.var_end(); i++)
346  if ((*i)->send_p()) {
347  DBG(cerr << "Sending " << (*i)->name() << endl);
348  (*i)->serialize(eval, dds, m, ce_eval);
349  }
350  } catch (Error & e) {
351  throw;
352  }
353 }
354 
371 void ResponseBuilder::send_data(ostream & data_stream, DDS & dds, ConstraintEvaluator & eval, bool with_mime_headers) const {
372  // Set up the alarm.
373  establish_timeout(data_stream);
374  dds.set_timeout(d_timeout);
375 
376  eval.parse_constraint(d_ce, dds); // Throws Error if the ce doesn't parse.
377 
378  dds.tag_nested_sequences(); // Tag Sequences as Parent or Leaf node.
379 
380  if (dds.get_response_limit() != 0 && dds.get_request_size(true) > dds.get_response_limit()) {
381  string msg = "The Request for " + long_to_string(dds.get_request_size(true) / 1024)
382  + "KB is too large; requests for this user are limited to " + long_to_string(
383  dds.get_response_limit() / 1024) + "KB.";
384  throw Error(msg);
385  }
386 
387  // Start sending the response...
388 
389  // Handle *functional* constraint expressions specially
390  if (eval.function_clauses()) {
391  DDS *fdds = eval.eval_function_clauses(dds);
392  if (with_mime_headers)
393  set_mime_binary(data_stream, dods_data, x_plain, last_modified_time(d_dataset), dds.get_dap_version());
394 
395  dataset_constraint(data_stream, *fdds, eval, false);
396  delete fdds;
397  }
398  else {
399  if (with_mime_headers)
400  set_mime_binary(data_stream, dods_data, x_plain, last_modified_time(d_dataset), dds.get_dap_version());
401 
402  dataset_constraint(data_stream, dds, eval);
403  }
404 
405  data_stream << flush;
406 }
407 
418 void ResponseBuilder::send_ddx(ostream &out, DDS &dds, ConstraintEvaluator &eval, bool with_mime_headers) const {
419  // If constrained, parse the constraint. Throws Error or InternalErr.
420  if (!d_ce.empty())
421  eval.parse_constraint(d_ce, dds);
422 
423  if (eval.functional_expression())
424  throw Error(
425  "Function calls can only be used with data requests. To see the structure of the underlying data source, reissue the URL without the function.");
426 
427  if (with_mime_headers)
429 
430  dds.print_xml_writer(out, !d_ce.empty(), "");
431 }
432 
449 void ResponseBuilder::send_data_ddx(ostream & data_stream, DDS & dds, ConstraintEvaluator & eval, const string &start,
450  const string &boundary, bool with_mime_headers) const {
451  // Set up the alarm.
452  establish_timeout(data_stream);
453  dds.set_timeout(d_timeout);
454 
455  eval.parse_constraint(d_ce, dds); // Throws Error if the ce doesn't parse.
456 
457  if (dds.get_response_limit() != 0 && dds.get_request_size(true) > dds.get_response_limit()) {
458  string msg = "The Request for " + long_to_string(dds.get_request_size(true) / 1024)
459  + "KB is too large; requests for this user are limited to " + long_to_string(
460  dds.get_response_limit() / 1024) + "KB.";
461  throw Error(msg);
462  }
463 
464  dds.tag_nested_sequences(); // Tag Sequences as Parent or Leaf node.
465 
466  // Start sending the response...
467 
468  // Handle *functional* constraint expressions specially
469  if (eval.function_clauses()) {
470  DDS *fdds = eval.eval_function_clauses(dds);
471  if (with_mime_headers)
472  set_mime_multipart(data_stream, boundary, start, dap4_data_ddx, x_plain, last_modified_time(d_dataset));
473  data_stream << flush;
474  // TODO: Change this to dataset_constraint_ddx()
475  dataset_constraint(data_stream, *fdds, eval, false);
476  delete fdds;
477  }
478  else {
479  if (with_mime_headers)
480  set_mime_multipart(data_stream, boundary, start, dap4_data_ddx, x_plain, last_modified_time(d_dataset));
481  data_stream << flush;
482  dataset_constraint_ddx(data_stream, dds, eval, boundary, start);
483  }
484 
485  data_stream << flush;
486 
487  if (with_mime_headers)
488  data_stream << CRLF << "--" << boundary << "--" << CRLF;
489 }
490 
491 static const char *descrip[] = { "unknown", "dods_das", "dods_dds", "dods_data", "dods_error", "web_error", "dap4-ddx",
492  "dap4-data", "dap4-error", "dap4-data-ddx", "dods_ddx" };
493 static const char *encoding[] = { "unknown", "deflate", "x-plain", "gzip", "binary" };
494 
507 void ResponseBuilder::set_mime_text(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
508  const string &protocol) const {
509  strm << "HTTP/1.0 200 OK" << CRLF;
510 
511  strm << "XDODS-Server: " << DVR << CRLF;
512  strm << "XOPeNDAP-Server: " << DVR << CRLF;
513 
514  if (protocol == "")
515  strm << "XDAP: " << d_default_protocol << CRLF;
516  else
517  strm << "XDAP: " << protocol << CRLF;
518 
519  const time_t t = time(0);
520  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
521 
522  strm << "Last-Modified: ";
523  if (last_modified > 0)
524  strm << rfc822_date(last_modified).c_str() << CRLF;
525  else
526  strm << rfc822_date(t).c_str() << CRLF;
527 
528  if (type == dap4_ddx)
529  strm << "Content-Type: text/xml" << CRLF;
530  else
531  strm << "Content-Type: text/plain" << CRLF;
532 
533  // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
534  // jhrg 12/23/05
535  strm << "Content-Description: " << descrip[type] << CRLF;
536  if (type == dods_error) // don't cache our error responses.
537  strm << "Cache-Control: no-cache" << CRLF;
538  // Don't write a Content-Encoding header for x-plain since that breaks
539  // Netscape on NT. jhrg 3/23/97
540  if (enc != x_plain)
541  strm << "Content-Encoding: " << encoding[enc] << CRLF;
542  strm << CRLF;
543 }
544 
555 void ResponseBuilder::set_mime_html(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
556  const string &protocol) const {
557  strm << "HTTP/1.0 200 OK" << CRLF;
558 
559  strm << "XDODS-Server: " << DVR << CRLF;
560  strm << "XOPeNDAP-Server: " << DVR << CRLF;
561 
562  if (protocol == "")
563  strm << "XDAP: " << d_default_protocol << CRLF;
564  else
565  strm << "XDAP: " << protocol << CRLF;
566 
567  const time_t t = time(0);
568  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
569 
570  strm << "Last-Modified: ";
571  if (last_modified > 0)
572  strm << rfc822_date(last_modified).c_str() << CRLF;
573  else
574  strm << rfc822_date(t).c_str() << CRLF;
575 
576  strm << "Content-type: text/html" << CRLF;
577  // See note above about Content-Description header. jhrg 12/23/05
578  strm << "Content-Description: " << descrip[type] << CRLF;
579  if (type == dods_error) // don't cache our error responses.
580  strm << "Cache-Control: no-cache" << CRLF;
581  // Don't write a Content-Encoding header for x-plain since that breaks
582  // Netscape on NT. jhrg 3/23/97
583  if (enc != x_plain)
584  strm << "Content-Encoding: " << encoding[enc] << CRLF;
585  strm << CRLF;
586 }
587 
601 void ResponseBuilder::set_mime_binary(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
602  const string &protocol) const {
603  strm << "HTTP/1.0 200 OK" << CRLF;
604 
605  strm << "XDODS-Server: " << DVR << CRLF;
606  strm << "XOPeNDAP-Server: " << DVR << CRLF;
607 
608  if (protocol == "")
609  strm << "XDAP: " << d_default_protocol << CRLF;
610  else
611  strm << "XDAP: " << protocol << CRLF;
612 
613  const time_t t = time(0);
614  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
615 
616  strm << "Last-Modified: ";
617  if (last_modified > 0)
618  strm << rfc822_date(last_modified).c_str() << CRLF;
619  else
620  strm << rfc822_date(t).c_str() << CRLF;
621 
622  strm << "Content-Type: application/octet-stream" << CRLF;
623  strm << "Content-Description: " << descrip[type] << CRLF;
624  if (enc != x_plain)
625  strm << "Content-Encoding: " << encoding[enc] << CRLF;
626 
627  strm << CRLF;
628 }
629 
630 void ResponseBuilder::set_mime_multipart(ostream &strm, const string &boundary, const string &start, ObjectType type,
631  EncodingType enc, const time_t last_modified, const string &protocol) const {
632  strm << "HTTP/1.0 200 OK" << CRLF;
633 
634  strm << "XDODS-Server: " << DVR << CRLF;
635  strm << "XOPeNDAP-Server: " << DVR << CRLF;
636 
637  if (protocol == "")
638  strm << "XDAP: " << d_default_protocol << CRLF;
639  else
640  strm << "XDAP: " << protocol << CRLF;
641 
642  const time_t t = time(0);
643  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
644 
645  strm << "Last-Modified: ";
646  if (last_modified > 0)
647  strm << rfc822_date(last_modified).c_str() << CRLF;
648  else
649  strm << rfc822_date(t).c_str() << CRLF;
650 
651  strm << "Content-Type: Multipart/Related; boundary=" << boundary << "; start=\"<" << start
652  << ">\"; type=\"Text/xml\"" << CRLF;
653  strm << "Content-Description: " << descrip[type] << CRLF;
654  if (enc != x_plain)
655  strm << "Content-Encoding: " << encoding[enc] << CRLF;
656 
657  strm << CRLF;
658 }
659 
660 void ResponseBuilder::set_mime_ddx_boundary(ostream &strm, const string &boundary, const string &cid, ObjectType type,
661  EncodingType enc) const {
662  strm << "--" << boundary << CRLF;
663  strm << "Content-Type: Text/xml; charset=iso-8859-1" << CRLF;
664  strm << "Content-Id: <" << cid << ">" << CRLF;
665  strm << "Content-Description: " << descrip[type] << CRLF;
666  if (enc != x_plain)
667  strm << "Content-Encoding: " << encoding[enc] << CRLF;
668 
669  strm << CRLF;
670 }
671 
672 void ResponseBuilder::set_mime_data_boundary(ostream &strm, const string &boundary, const string &cid, ObjectType type,
673  EncodingType enc) const {
674  strm << "--" << boundary << CRLF;
675  strm << "Content-Type: application/octet-stream" << 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 
690 void ResponseBuilder::set_mime_error(ostream &strm, int code, const string &reason, const string &protocol) const {
691  strm << "HTTP/1.0 " << code << " " << reason.c_str() << CRLF;
692 
693  strm << "XDODS-Server: " << DVR << CRLF;
694  strm << "XOPeNDAP-Server: " << DVR << CRLF;
695 
696  if (protocol == "")
697  strm << "XDAP: " << d_default_protocol << CRLF;
698  else
699  strm << "XDAP: " << protocol << CRLF;
700 
701  const time_t t = time(0);
702  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
703  strm << "Cache-Control: no-cache" << CRLF;
704  strm << CRLF;
705 }
706 
707 } // namespace libdap
708