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 00031 #ifndef WIN32 00032 // #include <unistd.h> // for getopt 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