libdap++
Updated for version 3.8.2
|
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