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