libdap++  Updated for version 3.8.2
ResponseBuilder.cc
Go to the documentation of this file.
00001 // -*- mode: c++; c-basic-offset:4 -*-
00002 
00003 // This file is part of libdap, A C++ implementation of the OPeNDAP Data
00004 // Access Protocol.
00005 
00006 // Copyright (c) 2011 OPeNDAP, Inc.
00007 // Author: James Gallagher <jgallagher@opendap.org>
00008 //
00009 // This library is free software; you can redistribute it and/or
00010 // modify it under the terms of the GNU Lesser General Public
00011 // License as published by the Free Software Foundation; either
00012 // version 2.1 of the License, or (at your option) any later version.
00013 //
00014 // This library is distributed in the hope that it will be useful,
00015 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00017 // Lesser General Public License for more details.
00018 //
00019 // You should have received a copy of the GNU Lesser General Public
00020 // License along with this library; if not, write to the Free Software
00021 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00022 //
00023 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
00024 
00025 #include "config.h"
00026 
00027 static char rcsid[] not_used = { "$Id: ResponseBuilder.cc 23477 2010-09-02 21:02:59Z jimg $" };
00028 
00029 #include <signal.h>
00030 #include <unistd.h>
00031 
00032 #ifndef WIN32
00033 // #include <unistd.h>   // for getopt
00034 #include <sys/wait.h>
00035 #else
00036 #include <io.h>
00037 #include <fcntl.h>
00038 #include <process.h>
00039 #endif
00040 
00041 #include <iostream>
00042 #include <string>
00043 #include <sstream>
00044 #include <cstring>
00045 
00046 #include <uuid/uuid.h>  // used to build CID header value for data ddx
00047 
00048 #include "DAS.h"
00049 #include "DDS.h"
00050 #include "debug.h"
00051 #include "mime_util.h"  // for last_modified_time() and rfc_822_date()
00052 #include "escaping.h"
00053 #include "ResponseBuilder.h"
00054 #include "XDRStreamMarshaller.h"
00055 
00056 #ifndef WIN32
00057 #include "SignalHandler.h"
00058 #include "EventHandler.h"
00059 #include "AlarmHandler.h"
00060 #endif
00061 
00062 #define CRLF "\r\n"             // Change here, expr-test.cc
00063 using namespace std;
00064 
00065 namespace libdap {
00066 
00067 ResponseBuilder::~ResponseBuilder()
00068 {
00069 }
00070 
00073 void ResponseBuilder::initialize()
00074 {
00075     // Set default values. Don't use the C++ constructor initialization so
00076     // that a subclass can have more control over this process.
00077     d_dataset = "";
00078     d_ce = "";
00079     d_timeout = 0;
00080 
00081     d_default_protocol = DAP_PROTOCOL_VERSION;
00082 #if 0   // Keyword support moved to Keywords class
00083     // Load known_keywords
00084     d_known_keywords.insert("dap2");
00085     d_known_keywords.insert("dap2.0");
00086 
00087     d_known_keywords.insert("dap3.2");
00088     d_known_keywords.insert("dap3.3");
00089 
00090     d_known_keywords.insert("dap4");
00091     d_known_keywords.insert("dap4.0");
00092 #endif
00093 #ifdef WIN32
00094     //  We want serving from win32 to behave in a manner
00095     //  similar to the UNIX way - no CR->NL terminated lines
00096     //  in files. Hence stdout goes to binary mode.
00097     _setmode(_fileno(stdout), _O_BINARY);
00098 #endif
00099 }
00100 
00101 #if 0
00102 
00106 void ResponseBuilder::add_keyword(const string &kw)
00107 {
00108     d_keywords.insert(kw);
00109 }
00110 
00117 bool ResponseBuilder::is_keyword(const string &kw) const
00118 {
00119     return d_keywords.count(kw) != 0;
00120 }
00121 
00127 list<string> ResponseBuilder::get_keywords() const
00128 {
00129     list<string> kws;
00130     set<string>::const_iterator i;
00131     for (i = d_keywords.begin(); i != d_keywords.end(); ++i)
00132         kws.push_front(*i);
00133     return kws;
00134 }
00135 
00141 bool ResponseBuilder::is_known_keyword(const string &w) const
00142 {
00143     return d_known_keywords.count(w) != 0;
00144 }
00145 #endif
00146 
00153 string ResponseBuilder::get_ce() const
00154 {
00155     return d_ce;
00156 }
00157 
00158 void ResponseBuilder::set_ce(string _ce)
00159 {
00160     d_ce = www2id(_ce, "%", "%20");
00161 
00162 #if 0
00163     // Get the whole CE
00164     string projection = www2id(_ce, "%", "%20");
00165     string selection = "";
00166 
00167     // Separate the selection part (which follows/includes the first '&')
00168     string::size_type amp = projection.find('&');
00169     if (amp != string::npos) {
00170         selection = projection.substr(amp);
00171         projection = projection.substr(0, amp);
00172     }
00173 
00174     // Extract keywords; add to the ResponseBuilder keywords. For this, scan for
00175     // a known set of keywords and assume that anything else is part of the
00176     // projection and should be left alone. Keywords must come before variables
00177     // The 'projection' string will look like: '' or 'dap4.0' or 'dap4.0,u,v'
00178     while (!projection.empty()) {
00179         string::size_type i = projection.find(',');
00180         string next_word = projection.substr(0, i);
00181         if (is_known_keyword(next_word)) {
00182             add_keyword(next_word);
00183             projection = projection.substr(i + 1);
00184         }
00185         else {
00186             break; // exit on first non-keyword
00187         }
00188     }
00189 
00190     // The CE is whatever is left after removing the keywords
00191     d_ce = projection + selection;
00192 #endif
00193 }
00194 
00203 string ResponseBuilder::get_dataset_name() const
00204 {
00205     return d_dataset;
00206 }
00207 
00208 void ResponseBuilder::set_dataset_name(const string ds)
00209 {
00210     d_dataset = www2id(ds, "%", "%20");
00211 }
00212 
00217 void ResponseBuilder::set_timeout(int t)
00218 {
00219     d_timeout = t;
00220 }
00221 
00223 int ResponseBuilder::get_timeout() const
00224 {
00225     return d_timeout;
00226 }
00227 
00238 void ResponseBuilder::establish_timeout(ostream &stream) const
00239 {
00240 #ifndef WIN32
00241     if (d_timeout > 0) {
00242         SignalHandler *sh = SignalHandler::instance();
00243         EventHandler *old_eh = sh->register_handler(SIGALRM, new AlarmHandler(stream));
00244         delete old_eh;
00245         alarm(d_timeout);
00246     }
00247 #endif
00248 }
00249 
00261 void ResponseBuilder::send_das(ostream &out, DAS &das, bool with_mime_headers) const
00262 {
00263     if (with_mime_headers)
00264         set_mime_text(out, dods_das, x_plain, last_modified_time(d_dataset), "2.0");
00265     das.print(out);
00266 
00267     out << flush;
00268 }
00269 
00286 void ResponseBuilder::send_dds(ostream &out, DDS &dds, ConstraintEvaluator &eval, bool constrained,
00287         bool with_mime_headers) const
00288 {
00289     // If constrained, parse the constraint. Throws Error or InternalErr.
00290     if (constrained)
00291         eval.parse_constraint(d_ce, dds);
00292 
00293     if (eval.functional_expression())
00294         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.");
00295 
00296     if (with_mime_headers)
00297         set_mime_text(out, dods_dds, x_plain, last_modified_time(d_dataset), dds.get_dap_version());
00298 
00299     if (constrained)
00300         dds.print_constrained(out);
00301     else
00302         dds.print(out);
00303 
00304     out << flush;
00305 }
00306 
00307 void ResponseBuilder::dataset_constraint(ostream &out, DDS & dds, ConstraintEvaluator & eval, bool ce_eval) const
00308 {
00309     // send constrained DDS
00310     dds.print_constrained(out);
00311     out << "Data:\n";
00312     out << flush;
00313 
00314     // Grab a stream that encodes using XDR.
00315     XDRStreamMarshaller m(out);
00316 
00317     try {
00318         // Send all variables in the current projection (send_p())
00319         for (DDS::Vars_iter i = dds.var_begin(); i != dds.var_end(); i++)
00320             if ((*i)->send_p()) {
00321                 DBG(cerr << "Sending " << (*i)->name() << endl);
00322                 (*i)->serialize(eval, dds, m, ce_eval);
00323             }
00324     }
00325     catch (Error & e) {
00326         throw;
00327     }
00328 }
00329 
00330 void ResponseBuilder::dataset_constraint_ddx( ostream &out, DDS & dds, ConstraintEvaluator & eval,
00331         const string &boundary, const string &start, bool ce_eval) const
00332 {
00333     // Write the MPM headers for the DDX (text/xml) part of the response
00334     set_mime_ddx_boundary(out, boundary, start, dap4_ddx);
00335 
00336     // Make cid
00337     uuid_t uu;
00338     uuid_generate(uu);
00339     char uuid[37];
00340     uuid_unparse(uu, &uuid[0]);
00341     char domain[256];
00342     if (getdomainname(domain, 255) != 0 || strlen(domain) == 0)
00343         strncpy(domain, "opendap.org", 255);
00344 
00345     string cid = string(&uuid[0]) + "@" + string(&domain[0]);
00346 
00347     // Send constrained DDX with a data blob reference
00348     dds.print_xml(out, true, cid);
00349 
00350     // Write the MPM headers for the data part of the response.
00351     set_mime_data_boundary(out, boundary, cid, dap4_data, binary);
00352 
00353     // Grab a stream that encodes using XDR.
00354     XDRStreamMarshaller m(out);
00355 
00356     try {
00357         // Send all variables in the current projection (send_p())
00358         for (DDS::Vars_iter i = dds.var_begin(); i != dds.var_end(); i++)
00359             if ((*i)->send_p()) {
00360                 DBG(cerr << "Sending " << (*i)->name() << endl);
00361                 (*i)->serialize(eval, dds, m, ce_eval);
00362             }
00363     }
00364     catch (Error & e) {
00365         throw;
00366     }
00367 }
00368 
00385 void ResponseBuilder::send_data(ostream & data_stream, DDS & dds, ConstraintEvaluator & eval, bool with_mime_headers) const
00386 {
00387     // Set up the alarm.
00388     establish_timeout(data_stream);
00389     dds.set_timeout(d_timeout);
00390 
00391     eval.parse_constraint(d_ce, dds); // Throws Error if the ce doesn't
00392     // parse.
00393 
00394     dds.tag_nested_sequences(); // Tag Sequences as Parent or Leaf node.
00395 
00396     // Start sending the response...
00397 
00398     // Handle *functional* constraint expressions specially
00399     if (eval.function_clauses()) {
00400         DDS *fdds = eval.eval_function_clauses(dds);
00401         if (with_mime_headers)
00402             set_mime_binary(data_stream, dods_data, x_plain, last_modified_time(d_dataset), dds.get_dap_version());
00403 
00404         dataset_constraint(data_stream, *fdds, eval, false);
00405         delete fdds;
00406     }
00407     else {
00408         if (with_mime_headers)
00409             set_mime_binary(data_stream, dods_data, x_plain, last_modified_time(d_dataset), dds.get_dap_version());
00410 
00411         dataset_constraint(data_stream, dds, eval);
00412     }
00413 
00414     data_stream << flush;
00415 }
00416 
00427 void ResponseBuilder::send_ddx(ostream &out, DDS &dds, ConstraintEvaluator &eval, bool with_mime_headers) const
00428 {
00429     // If constrained, parse the constraint. Throws Error or InternalErr.
00430     if (!d_ce.empty())
00431         eval.parse_constraint(d_ce, dds);
00432 
00433     if (eval.functional_expression())
00434         throw Error(
00435                 "Function calls can only be used with data requests. To see the structure of the underlying data source, reissue the URL without the function.");
00436 
00437     if (with_mime_headers)
00438         set_mime_text(out, dap4_ddx, x_plain, last_modified_time(d_dataset), dds.get_dap_version());
00439     dds.print_xml(out, !d_ce.empty(), "");
00440 }
00441 
00458 void ResponseBuilder::send_data_ddx(ostream & data_stream, DDS & dds, ConstraintEvaluator & eval, const string &start,
00459         const string &boundary, bool with_mime_headers) const
00460 {
00461     // Set up the alarm.
00462     establish_timeout(data_stream);
00463     dds.set_timeout(d_timeout);
00464 
00465     eval.parse_constraint(d_ce, dds); // Throws Error if the ce doesn't
00466     // parse.
00467 
00468     dds.tag_nested_sequences(); // Tag Sequences as Parent or Leaf node.
00469 
00470     // Start sending the response...
00471 
00472     // Handle *functional* constraint expressions specially
00473     if (eval.function_clauses()) {
00474         DDS *fdds = eval.eval_function_clauses(dds);
00475         if (with_mime_headers)
00476             set_mime_multipart(data_stream, boundary, start, dap4_data_ddx, x_plain, last_modified_time(d_dataset));
00477         data_stream << flush;
00478         // TODO: Change this to dataset_constraint_ddx()
00479         dataset_constraint(data_stream, *fdds, eval, false);
00480         delete fdds;
00481     }
00482     else {
00483         if (with_mime_headers)
00484             set_mime_multipart(data_stream, boundary, start, dap4_data_ddx, x_plain, last_modified_time(d_dataset));
00485         data_stream << flush;
00486         dataset_constraint_ddx(data_stream, dds, eval, boundary, start);
00487     }
00488 
00489     data_stream << flush;
00490 
00491     if (with_mime_headers)
00492         data_stream << CRLF << "--" << boundary << "--" << CRLF;
00493 }
00494 
00495 static const char *descrip[] = { "unknown", "dods_das", "dods_dds", "dods_data", "dods_error", "web_error", "dap4-ddx",
00496         "dap4-data", "dap4-error", "dap4-data-ddx", "dods_ddx" };
00497 static const char *encoding[] = { "unknown", "deflate", "x-plain", "gzip", "binary" };
00498 
00511 void ResponseBuilder::set_mime_text(ostream &strm, ObjectType type,
00512         EncodingType enc, const time_t last_modified,
00513         const string &protocol) const
00514 {
00515     strm << "HTTP/1.0 200 OK" << CRLF;
00516 
00517     strm << "XDODS-Server: " << DVR << CRLF;
00518     strm << "XOPeNDAP-Server: " << DVR << CRLF;
00519 
00520     if (protocol == "")
00521         strm << "XDAP: " << d_default_protocol << CRLF;
00522     else
00523         strm << "XDAP: " << protocol  << CRLF;
00524 
00525     const time_t t = time(0);
00526     strm << "Date: " << rfc822_date(t).c_str() << CRLF;
00527 
00528     strm << "Last-Modified: ";
00529     if (last_modified > 0)
00530         strm << rfc822_date(last_modified).c_str() << CRLF;
00531     else
00532         strm << rfc822_date(t).c_str() << CRLF;
00533 
00534     if (type == dap4_ddx)
00535         strm << "Content-Type: text/xml" << CRLF;
00536     else
00537         strm << "Content-Type: text/plain" << CRLF;
00538 
00539     // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
00540     // jhrg 12/23/05
00541     strm << "Content-Description: " << descrip[type] << CRLF;
00542     if (type == dods_error) // don't cache our error responses.
00543         strm << "Cache-Control: no-cache" << CRLF;
00544     // Don't write a Content-Encoding header for x-plain since that breaks
00545     // Netscape on NT. jhrg 3/23/97
00546     if (enc != x_plain)
00547         strm << "Content-Encoding: " << encoding[enc] << CRLF;
00548     strm << CRLF;
00549 }
00550 
00561 void ResponseBuilder::set_mime_html(ostream &strm, ObjectType type,
00562         EncodingType enc, const time_t last_modified,
00563         const string &protocol) const
00564 {
00565     strm << "HTTP/1.0 200 OK" << CRLF;
00566 
00567     strm << "XDODS-Server: " << DVR << CRLF;
00568     strm << "XOPeNDAP-Server: " << DVR << CRLF;
00569 
00570     if (protocol == "")
00571         strm << "XDAP: " << d_default_protocol << CRLF;
00572     else
00573         strm << "XDAP: " << protocol  << CRLF;
00574 
00575     const time_t t = time(0);
00576     strm << "Date: " << rfc822_date(t).c_str() << CRLF;
00577 
00578     strm << "Last-Modified: ";
00579     if (last_modified > 0)
00580         strm << rfc822_date(last_modified).c_str() << CRLF;
00581     else
00582         strm << rfc822_date(t).c_str() << CRLF;
00583 
00584     strm << "Content-type: text/html" << CRLF;
00585     // See note above about Content-Description header. jhrg 12/23/05
00586     strm << "Content-Description: " << descrip[type] << CRLF;
00587     if (type == dods_error) // don't cache our error responses.
00588         strm << "Cache-Control: no-cache" << CRLF;
00589     // Don't write a Content-Encoding header for x-plain since that breaks
00590     // Netscape on NT. jhrg 3/23/97
00591     if (enc != x_plain)
00592         strm << "Content-Encoding: " << encoding[enc] << CRLF;
00593     strm << CRLF;
00594 }
00595 
00609 void ResponseBuilder::set_mime_binary(ostream &strm, ObjectType type,
00610         EncodingType enc, const time_t last_modified,
00611         const string &protocol) const
00612 {
00613     strm << "HTTP/1.0 200 OK" << CRLF;
00614 
00615     strm << "XDODS-Server: " << DVR << CRLF;
00616     strm << "XOPeNDAP-Server: " << DVR << CRLF;
00617 
00618     if (protocol == "")
00619         strm << "XDAP: " << d_default_protocol << CRLF;
00620     else
00621         strm << "XDAP: " << protocol  << CRLF;
00622 
00623     const time_t t = time(0);
00624     strm << "Date: " << rfc822_date(t).c_str() << CRLF;
00625 
00626     strm << "Last-Modified: ";
00627     if (last_modified > 0)
00628         strm << rfc822_date(last_modified).c_str() << CRLF;
00629     else
00630         strm << rfc822_date(t).c_str() << CRLF;
00631 
00632     strm << "Content-Type: application/octet-stream" << CRLF;
00633     strm << "Content-Description: " << descrip[type] << CRLF;
00634     if (enc != x_plain)
00635         strm << "Content-Encoding: " << encoding[enc] << CRLF;
00636 
00637     strm << CRLF;
00638 }
00639 
00640 void ResponseBuilder::set_mime_multipart(ostream &strm, const string &boundary,
00641         const string &start, ObjectType type, EncodingType enc,
00642         const time_t last_modified, const string &protocol) const
00643 {
00644     strm << "HTTP/1.0 200 OK" << CRLF;
00645 
00646     strm << "XDODS-Server: " << DVR << CRLF;
00647     strm << "XOPeNDAP-Server: " << DVR << CRLF;
00648 
00649     if (protocol == "")
00650         strm << "XDAP: " << d_default_protocol << CRLF;
00651     else
00652         strm << "XDAP: " << protocol  << CRLF;
00653 
00654     const time_t t = time(0);
00655     strm << "Date: " << rfc822_date(t).c_str() << CRLF;
00656 
00657     strm << "Last-Modified: ";
00658     if (last_modified > 0)
00659         strm << rfc822_date(last_modified).c_str() << CRLF;
00660     else
00661         strm << rfc822_date(t).c_str() << CRLF;
00662 
00663     strm << "Content-Type: Multipart/Related; boundary=" << boundary << "; start=\"<" << start
00664             << ">\"; type=\"Text/xml\"" << CRLF;
00665     strm << "Content-Description: " << descrip[type] << CRLF;
00666     if (enc != x_plain)
00667         strm << "Content-Encoding: " << encoding[enc] << CRLF;
00668 
00669     strm << CRLF;
00670 }
00671 
00672 void ResponseBuilder::set_mime_ddx_boundary(ostream &strm, const string &boundary,
00673         const string &cid, ObjectType type, EncodingType enc) const
00674 {
00675     strm << "--" << boundary << CRLF;
00676     strm << "Content-Type: Text/xml; charset=iso-8859-1" << CRLF;
00677     strm << "Content-Id: <" << cid << ">" << CRLF;
00678     strm << "Content-Description: " << descrip[type] << CRLF;
00679     if (enc != x_plain)
00680         strm << "Content-Encoding: " << encoding[enc] << CRLF;
00681 
00682     strm << CRLF;
00683 }
00684 
00685 void ResponseBuilder::set_mime_data_boundary(ostream &strm, const string &boundary,
00686         const string &cid, ObjectType type, EncodingType enc) const
00687 {
00688     strm << "--" << boundary << CRLF;
00689     strm << "Content-Type: application/octet-stream" << CRLF;
00690     strm << "Content-Id: <" << cid << ">" << CRLF;
00691     strm << "Content-Description: " << descrip[type] << CRLF;
00692     if (enc != x_plain)
00693         strm << "Content-Encoding: " << encoding[enc] << CRLF;
00694 
00695     strm << CRLF;
00696 }
00697 
00704 void ResponseBuilder::set_mime_error(ostream &strm, int code, const string &reason,
00705         const string &protocol) const
00706 {
00707     strm << "HTTP/1.0 " << code << " " << reason.c_str() << CRLF;
00708 
00709     strm << "XDODS-Server: " << DVR << CRLF;
00710     strm << "XOPeNDAP-Server: " << DVR << CRLF;
00711 
00712     if (protocol == "")
00713         strm << "XDAP: " << d_default_protocol << CRLF;
00714     else
00715         strm << "XDAP: " << protocol  << CRLF;
00716 
00717     const time_t t = time(0);
00718     strm << "Date: " << rfc822_date(t).c_str() << CRLF;
00719     strm << "Cache-Control: no-cache" << CRLF;
00720     strm << CRLF;
00721 }
00722 
00723 } // namespace libdap
00724