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 #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