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 #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 "util.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 
00072 void ResponseBuilder::initialize() {
00073     // Set default values. Don't use the C++ constructor initialization so
00074     // that a subclass can have more control over this process.
00075     d_dataset = "";
00076     d_ce = "";
00077     d_timeout = 0;
00078 
00079     d_default_protocol = DAP_PROTOCOL_VERSION;
00080 #if 0   // Keyword support moved to Keywords class
00081     // Load known_keywords
00082     d_known_keywords.insert("dap2");
00083     d_known_keywords.insert("dap2.0");
00084 
00085     d_known_keywords.insert("dap3.2");
00086     d_known_keywords.insert("dap3.3");
00087 
00088     d_known_keywords.insert("dap4");
00089     d_known_keywords.insert("dap4.0");
00090 #endif
00091 #ifdef WIN32
00092     //  We want serving from win32 to behave in a manner
00093     //  similar to the UNIX way - no CR->NL terminated lines
00094     //  in files. Hence stdout goes to binary mode.
00095     _setmode(_fileno(stdout), _O_BINARY);
00096 #endif
00097 }
00098 
00099 #if 0
00100 
00104 void ResponseBuilder::add_keyword(const string &kw)
00105 {
00106     d_keywords.insert(kw);
00107 }
00108 
00115 bool ResponseBuilder::is_keyword(const string &kw) const
00116 {
00117     return d_keywords.count(kw) != 0;
00118 }
00119 
00125 list<string> ResponseBuilder::get_keywords() const
00126 {
00127     list<string> kws;
00128     set<string>::const_iterator i;
00129     for (i = d_keywords.begin(); i != d_keywords.end(); ++i)
00130     kws.push_front(*i);
00131     return kws;
00132 }
00133 
00139 bool ResponseBuilder::is_known_keyword(const string &w) const
00140 {
00141     return d_known_keywords.count(w) != 0;
00142 }
00143 #endif
00144 
00151 string ResponseBuilder::get_ce() const {
00152     return d_ce;
00153 }
00154 
00155 void ResponseBuilder::set_ce(string _ce) {
00156     d_ce = www2id(_ce, "%", "%20");
00157 
00158 #if 0
00159     // Get the whole CE
00160     string projection = www2id(_ce, "%", "%20");
00161     string selection = "";
00162 
00163     // Separate the selection part (which follows/includes the first '&')
00164     string::size_type amp = projection.find('&');
00165     if (amp != string::npos) {
00166         selection = projection.substr(amp);
00167         projection = projection.substr(0, amp);
00168     }
00169 
00170     // Extract keywords; add to the ResponseBuilder keywords. For this, scan for
00171     // a known set of keywords and assume that anything else is part of the
00172     // projection and should be left alone. Keywords must come before variables
00173     // The 'projection' string will look like: '' or 'dap4.0' or 'dap4.0,u,v'
00174     while (!projection.empty()) {
00175         string::size_type i = projection.find(',');
00176         string next_word = projection.substr(0, i);
00177         if (is_known_keyword(next_word)) {
00178             add_keyword(next_word);
00179             projection = projection.substr(i + 1);
00180         }
00181         else {
00182             break; // exit on first non-keyword
00183         }
00184     }
00185 
00186     // The CE is whatever is left after removing the keywords
00187     d_ce = projection + selection;
00188 #endif
00189 }
00190 
00199 string ResponseBuilder::get_dataset_name() const {
00200     return d_dataset;
00201 }
00202 
00203 void ResponseBuilder::set_dataset_name(const string ds) {
00204     d_dataset = www2id(ds, "%", "%20");
00205 }
00206 
00211 void ResponseBuilder::set_timeout(int t) {
00212     d_timeout = t;
00213 }
00214 
00216 int ResponseBuilder::get_timeout() const {
00217     return d_timeout;
00218 }
00219 
00230 void ResponseBuilder::establish_timeout(ostream &stream) const {
00231 #ifndef WIN32
00232     if (d_timeout > 0) {
00233         SignalHandler *sh = SignalHandler::instance();
00234         EventHandler *old_eh = sh->register_handler(SIGALRM, new AlarmHandler(stream));
00235         delete old_eh;
00236         alarm(d_timeout);
00237     }
00238 #endif
00239 }
00240 
00252 void ResponseBuilder::send_das(ostream &out, DAS &das, bool with_mime_headers) const {
00253     if (with_mime_headers)
00254         set_mime_text(out, dods_das, x_plain, last_modified_time(d_dataset), "2.0");
00255     das.print(out);
00256 
00257     out << flush;
00258 }
00259 
00276 void ResponseBuilder::send_dds(ostream &out, DDS &dds, ConstraintEvaluator &eval, bool constrained,
00277                                bool with_mime_headers) const {
00278     // If constrained, parse the constraint. Throws Error or InternalErr.
00279     if (constrained)
00280         eval.parse_constraint(d_ce, dds);
00281 
00282     if (eval.functional_expression())
00283         throw Error(
00284                 "Function calls can only be used with data requests. To see the structure of the underlying data source, reissue the URL without the function.");
00285 
00286     if (with_mime_headers)
00287         set_mime_text(out, dods_dds, x_plain, last_modified_time(d_dataset), dds.get_dap_version());
00288 
00289     if (constrained)
00290         dds.print_constrained(out);
00291     else
00292         dds.print(out);
00293 
00294     out << flush;
00295 }
00296 
00297 void ResponseBuilder::dataset_constraint(ostream &out, DDS & dds, ConstraintEvaluator & eval, bool ce_eval) const {
00298     // send constrained DDS
00299     dds.print_constrained(out);
00300     out << "Data:\n";
00301     out << flush;
00302 
00303     // Grab a stream that encodes using XDR.
00304     XDRStreamMarshaller m(out);
00305 
00306     try {
00307         // Send all variables in the current projection (send_p())
00308         for (DDS::Vars_iter i = dds.var_begin(); i != dds.var_end(); i++)
00309             if ((*i)->send_p()) {
00310                 DBG(cerr << "Sending " << (*i)->name() << endl);
00311                 (*i)->serialize(eval, dds, m, ce_eval);
00312             }
00313     } catch (Error & e) {
00314         throw;
00315     }
00316 }
00317 
00318 void ResponseBuilder::dataset_constraint_ddx(ostream &out, DDS & dds, ConstraintEvaluator & eval,
00319                                              const string &boundary, const string &start, bool ce_eval) const {
00320     // Write the MPM headers for the DDX (text/xml) part of the response
00321     set_mime_ddx_boundary(out, boundary, start, dap4_ddx);
00322 
00323     // Make cid
00324     uuid_t uu;
00325     uuid_generate(uu);
00326     char uuid[37];
00327     uuid_unparse(uu, &uuid[0]);
00328     char domain[256];
00329     if (getdomainname(domain, 255) != 0 || strlen(domain) == 0)
00330         strncpy(domain, "opendap.org", 255);
00331 
00332     string cid = string(&uuid[0]) + "@" + string(&domain[0]);
00333 
00334     // Send constrained DDX with a data blob reference
00335     dds.print_xml(out, true, cid);
00336 
00337     // Write the MPM headers for the data part of the response.
00338     set_mime_data_boundary(out, boundary, cid, dap4_data, binary);
00339 
00340     // Grab a stream that encodes using XDR.
00341     XDRStreamMarshaller m(out);
00342 
00343     try {
00344         // Send all variables in the current projection (send_p())
00345         for (DDS::Vars_iter i = dds.var_begin(); i != dds.var_end(); i++)
00346             if ((*i)->send_p()) {
00347                 DBG(cerr << "Sending " << (*i)->name() << endl);
00348                 (*i)->serialize(eval, dds, m, ce_eval);
00349             }
00350     } catch (Error & e) {
00351         throw;
00352     }
00353 }
00354 
00371 void ResponseBuilder::send_data(ostream & data_stream, DDS & dds, ConstraintEvaluator & eval, bool with_mime_headers) const {
00372     // Set up the alarm.
00373     establish_timeout(data_stream);
00374     dds.set_timeout(d_timeout);
00375 
00376     eval.parse_constraint(d_ce, dds); // Throws Error if the ce doesn't parse.
00377 
00378     dds.tag_nested_sequences(); // Tag Sequences as Parent or Leaf node.
00379 
00380     if (dds.get_response_limit() != 0 && dds.get_request_size(true) > dds.get_response_limit()) {
00381         string msg = "The Request for " + long_to_string(dds.get_request_size(true) / 1024)
00382                 + "KB is too large; requests for this user are limited to " + long_to_string(
00383                 dds.get_response_limit() / 1024) + "KB.";
00384         throw Error(msg);
00385     }
00386 
00387     // Start sending the response...
00388 
00389     // Handle *functional* constraint expressions specially
00390     if (eval.function_clauses()) {
00391         DDS *fdds = eval.eval_function_clauses(dds);
00392         if (with_mime_headers)
00393             set_mime_binary(data_stream, dods_data, x_plain, last_modified_time(d_dataset), dds.get_dap_version());
00394 
00395         dataset_constraint(data_stream, *fdds, eval, false);
00396         delete fdds;
00397     }
00398     else {
00399         if (with_mime_headers)
00400             set_mime_binary(data_stream, dods_data, x_plain, last_modified_time(d_dataset), dds.get_dap_version());
00401 
00402         dataset_constraint(data_stream, dds, eval);
00403     }
00404 
00405     data_stream << flush;
00406 }
00407 
00418 void ResponseBuilder::send_ddx(ostream &out, DDS &dds, ConstraintEvaluator &eval, bool with_mime_headers) const {
00419     // If constrained, parse the constraint. Throws Error or InternalErr.
00420     if (!d_ce.empty())
00421         eval.parse_constraint(d_ce, dds);
00422 
00423     if (eval.functional_expression())
00424         throw Error(
00425                 "Function calls can only be used with data requests. To see the structure of the underlying data source, reissue the URL without the function.");
00426 
00427     if (with_mime_headers)
00428         set_mime_text(out, dap4_ddx, x_plain, last_modified_time(d_dataset), dds.get_dap_version());
00429 
00430     dds.print_xml_writer(out, !d_ce.empty(), "");
00431 }
00432 
00449 void ResponseBuilder::send_data_ddx(ostream & data_stream, DDS & dds, ConstraintEvaluator & eval, const string &start,
00450                                     const string &boundary, bool with_mime_headers) const {
00451     // Set up the alarm.
00452     establish_timeout(data_stream);
00453     dds.set_timeout(d_timeout);
00454 
00455     eval.parse_constraint(d_ce, dds); // Throws Error if the ce doesn't parse.
00456 
00457     if (dds.get_response_limit() != 0 && dds.get_request_size(true) > dds.get_response_limit()) {
00458         string msg = "The Request for " + long_to_string(dds.get_request_size(true) / 1024)
00459                 + "KB is too large; requests for this user are limited to " + long_to_string(
00460                 dds.get_response_limit() / 1024) + "KB.";
00461         throw Error(msg);
00462     }
00463 
00464     dds.tag_nested_sequences(); // Tag Sequences as Parent or Leaf node.
00465 
00466     // Start sending the response...
00467 
00468     // Handle *functional* constraint expressions specially
00469     if (eval.function_clauses()) {
00470         DDS *fdds = eval.eval_function_clauses(dds);
00471         if (with_mime_headers)
00472             set_mime_multipart(data_stream, boundary, start, dap4_data_ddx, x_plain, last_modified_time(d_dataset));
00473         data_stream << flush;
00474         // TODO: Change this to dataset_constraint_ddx()
00475         dataset_constraint(data_stream, *fdds, eval, false);
00476         delete fdds;
00477     }
00478     else {
00479         if (with_mime_headers)
00480             set_mime_multipart(data_stream, boundary, start, dap4_data_ddx, x_plain, last_modified_time(d_dataset));
00481         data_stream << flush;
00482         dataset_constraint_ddx(data_stream, dds, eval, boundary, start);
00483     }
00484 
00485     data_stream << flush;
00486 
00487     if (with_mime_headers)
00488         data_stream << CRLF << "--" << boundary << "--" << CRLF;
00489 }
00490 
00491 static const char *descrip[] = { "unknown", "dods_das", "dods_dds", "dods_data", "dods_error", "web_error", "dap4-ddx",
00492         "dap4-data", "dap4-error", "dap4-data-ddx", "dods_ddx" };
00493 static const char *encoding[] = { "unknown", "deflate", "x-plain", "gzip", "binary" };
00494 
00507 void ResponseBuilder::set_mime_text(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
00508                                     const string &protocol) const {
00509     strm << "HTTP/1.0 200 OK" << CRLF;
00510 
00511     strm << "XDODS-Server: " << DVR << CRLF;
00512     strm << "XOPeNDAP-Server: " << DVR << CRLF;
00513 
00514     if (protocol == "")
00515         strm << "XDAP: " << d_default_protocol << CRLF;
00516     else
00517         strm << "XDAP: " << protocol << CRLF;
00518 
00519     const time_t t = time(0);
00520     strm << "Date: " << rfc822_date(t).c_str() << CRLF;
00521 
00522     strm << "Last-Modified: ";
00523     if (last_modified > 0)
00524         strm << rfc822_date(last_modified).c_str() << CRLF;
00525     else
00526         strm << rfc822_date(t).c_str() << CRLF;
00527 
00528     if (type == dap4_ddx)
00529         strm << "Content-Type: text/xml" << CRLF;
00530     else
00531         strm << "Content-Type: text/plain" << CRLF;
00532 
00533     // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
00534     // jhrg 12/23/05
00535     strm << "Content-Description: " << descrip[type] << CRLF;
00536     if (type == dods_error) // don't cache our error responses.
00537         strm << "Cache-Control: no-cache" << CRLF;
00538     // Don't write a Content-Encoding header for x-plain since that breaks
00539     // Netscape on NT. jhrg 3/23/97
00540     if (enc != x_plain)
00541         strm << "Content-Encoding: " << encoding[enc] << CRLF;
00542     strm << CRLF;
00543 }
00544 
00555 void ResponseBuilder::set_mime_html(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
00556                                     const string &protocol) const {
00557     strm << "HTTP/1.0 200 OK" << CRLF;
00558 
00559     strm << "XDODS-Server: " << DVR << CRLF;
00560     strm << "XOPeNDAP-Server: " << DVR << CRLF;
00561 
00562     if (protocol == "")
00563         strm << "XDAP: " << d_default_protocol << CRLF;
00564     else
00565         strm << "XDAP: " << protocol << CRLF;
00566 
00567     const time_t t = time(0);
00568     strm << "Date: " << rfc822_date(t).c_str() << CRLF;
00569 
00570     strm << "Last-Modified: ";
00571     if (last_modified > 0)
00572         strm << rfc822_date(last_modified).c_str() << CRLF;
00573     else
00574         strm << rfc822_date(t).c_str() << CRLF;
00575 
00576     strm << "Content-type: text/html" << CRLF;
00577     // See note above about Content-Description header. jhrg 12/23/05
00578     strm << "Content-Description: " << descrip[type] << CRLF;
00579     if (type == dods_error) // don't cache our error responses.
00580         strm << "Cache-Control: no-cache" << CRLF;
00581     // Don't write a Content-Encoding header for x-plain since that breaks
00582     // Netscape on NT. jhrg 3/23/97
00583     if (enc != x_plain)
00584         strm << "Content-Encoding: " << encoding[enc] << CRLF;
00585     strm << CRLF;
00586 }
00587 
00601 void ResponseBuilder::set_mime_binary(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
00602                                       const string &protocol) const {
00603     strm << "HTTP/1.0 200 OK" << CRLF;
00604 
00605     strm << "XDODS-Server: " << DVR << CRLF;
00606     strm << "XOPeNDAP-Server: " << DVR << CRLF;
00607 
00608     if (protocol == "")
00609         strm << "XDAP: " << d_default_protocol << CRLF;
00610     else
00611         strm << "XDAP: " << protocol << CRLF;
00612 
00613     const time_t t = time(0);
00614     strm << "Date: " << rfc822_date(t).c_str() << CRLF;
00615 
00616     strm << "Last-Modified: ";
00617     if (last_modified > 0)
00618         strm << rfc822_date(last_modified).c_str() << CRLF;
00619     else
00620         strm << rfc822_date(t).c_str() << CRLF;
00621 
00622     strm << "Content-Type: application/octet-stream" << CRLF;
00623     strm << "Content-Description: " << descrip[type] << CRLF;
00624     if (enc != x_plain)
00625         strm << "Content-Encoding: " << encoding[enc] << CRLF;
00626 
00627     strm << CRLF;
00628 }
00629 
00630 void ResponseBuilder::set_mime_multipart(ostream &strm, const string &boundary, const string &start, ObjectType type,
00631                                          EncodingType enc, const time_t last_modified, const string &protocol) const {
00632     strm << "HTTP/1.0 200 OK" << CRLF;
00633 
00634     strm << "XDODS-Server: " << DVR << CRLF;
00635     strm << "XOPeNDAP-Server: " << DVR << CRLF;
00636 
00637     if (protocol == "")
00638         strm << "XDAP: " << d_default_protocol << CRLF;
00639     else
00640         strm << "XDAP: " << protocol << CRLF;
00641 
00642     const time_t t = time(0);
00643     strm << "Date: " << rfc822_date(t).c_str() << CRLF;
00644 
00645     strm << "Last-Modified: ";
00646     if (last_modified > 0)
00647         strm << rfc822_date(last_modified).c_str() << CRLF;
00648     else
00649         strm << rfc822_date(t).c_str() << CRLF;
00650 
00651     strm << "Content-Type: Multipart/Related; boundary=" << boundary << "; start=\"<" << start
00652             << ">\"; type=\"Text/xml\"" << CRLF;
00653     strm << "Content-Description: " << descrip[type] << CRLF;
00654     if (enc != x_plain)
00655         strm << "Content-Encoding: " << encoding[enc] << CRLF;
00656 
00657     strm << CRLF;
00658 }
00659 
00660 void ResponseBuilder::set_mime_ddx_boundary(ostream &strm, const string &boundary, const string &cid, ObjectType type,
00661                                             EncodingType enc) const {
00662     strm << "--" << boundary << CRLF;
00663     strm << "Content-Type: Text/xml; charset=iso-8859-1" << CRLF;
00664     strm << "Content-Id: <" << cid << ">" << 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_data_boundary(ostream &strm, const string &boundary, const string &cid, ObjectType type,
00673                                              EncodingType enc) const {
00674     strm << "--" << boundary << CRLF;
00675     strm << "Content-Type: application/octet-stream" << 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 
00690 void ResponseBuilder::set_mime_error(ostream &strm, int code, const string &reason, const string &protocol) const {
00691     strm << "HTTP/1.0 " << code << " " << reason.c_str() << CRLF;
00692 
00693     strm << "XDODS-Server: " << DVR << CRLF;
00694     strm << "XOPeNDAP-Server: " << DVR << CRLF;
00695 
00696     if (protocol == "")
00697         strm << "XDAP: " << d_default_protocol << CRLF;
00698     else
00699         strm << "XDAP: " << protocol << CRLF;
00700 
00701     const time_t t = time(0);
00702     strm << "Date: " << rfc822_date(t).c_str() << CRLF;
00703     strm << "Cache-Control: no-cache" << CRLF;
00704     strm << CRLF;
00705 }
00706 
00707 } // namespace libdap
00708