1 
2 // -*- mode: c++; c-basic-offset:4 -*-
3 
4 // This file is part of libdap, A C++ implementation of the OPeNDAP Data
5 // Access Protocol.
6 
7 // Copyright (c) 2002,2003 OPeNDAP, Inc.
8 // Author: James Gallagher <jgallagher@opendap.org>
9 //
10 // This library is free software; you can redistribute it and/or
11 // modify it under the terms of the GNU Lesser General Public
12 // License as published by the Free Software Foundation; either
13 // version 2.1 of the License, or (at your option) any later version.
14 //
15 // This library is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 // Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public
21 // License along with this library; if not, write to the Free Software
22 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23 //
24 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
25 
26 // (c) COPYRIGHT URI/MIT 1997-1999
27 // Please read the full copyright statement in the file COPYRIGHT_URI.
28 //
29 // Authors:
30 //      jhrg,jimg       James Gallagher <jgallagher@gso.uri.edu>
31 
32 // Implementation of the DODSFilter class. This class is used to build dods
33 // filter programs which, along with a CGI program, comprise OPeNDAP servers.
34 // jhrg 8/26/97
35 
36 
37 #include "config.h"
38 
39 #include <signal.h>
40 
41 #ifndef WIN32
42 #include <unistd.h>   // for getopt
43 #include <sys/wait.h>
44 #else
45 #include <io.h>
46 #include <fcntl.h>
47 #include <process.h>
48 #endif
49 
50 #include <iostream>
51 #include <sstream>
52 #include <string>
53 #include <algorithm>
54 #include <cstdlib>
55 #include <cstring>
56 
57 #ifdef HAVE_UUID_UUID_H
58 #include <uuid/uuid.h>	// used to build CID header value for data ddx
59 #elif defined(HAVE_UUID_H)
60 #include <uuid.h>
61 #else
62 #error "Could not find UUID library header"
63 #endif
64 
65 #include <GetOpt.h>
66 
67 #include "DAS.h"
68 #include "DDS.h"
69 #include "debug.h"
70 #include "mime_util.h"
71 #include "Ancillary.h"
72 #include "util.h"
73 #include "escaping.h"
74 #include "DODSFilter.h"
75 #include "XDRStreamMarshaller.h"
76 #include "InternalErr.h"
77 
78 #ifndef WIN32
79 #include "SignalHandler.h"
80 #include "EventHandler.h"
81 #include "AlarmHandler.h"
82 #endif
83 
84 #define CRLF "\r\n"             // Change here, expr-test.cc and DODSFilter.cc
85 
86 using namespace std;
87 
88 namespace libdap {
89 
90 const string usage =
91     "Usage: <handler name> -o <response> -u <url> [options ...] [data set]\n\
92     \n\
93     options: -o <response>: DAS, DDS, DataDDS, DDX, BLOB or Version (Required)\n\
94     -u <url>: The complete URL minus the CE (required for DDX)\n\
95     -c: Compress the response using the deflate algorithm.\n\
96     -e <expr>: When returning a DataDDS, use <expr> as the constraint.\n\
97     -v <version>: Use <version> as the version number\n\
98     -d <dir>: Look for ancillary file in <dir> (deprecated).\n\
99     -f <file>: Look for ancillary data in <file> (deprecated).\n\
100     -r <dir>: Use <dir> as a cache directory\n\
101     -l <time>: Conditional request; if data source is unchanged since\n\
102     <time>, return an HTTP 304 response.\n\
103     -t <seconds>: Timeout the handler after <seconds>.\n\
104     -h: This message.";
105 
106 /** Create an instance of DODSFilter using the command line
107 arguments passed by the CGI (or other) program.  The default
108 constructor is private; this and the copy constructor (which is
109 just the default copy constructor) are the only way to create an
110 instance of DODSFilter.
111 
112 These are the valid options:
113 
114 <dl>
115 <dt><i>filename</i><dd>
116 The name of the file on which the filter is to operate.  Usually
117 this would be the file whose data has been requested. In fact, this class
118 can be specialized and <i>any meaning</i> can be associated to this
119 string. It could be the name of a database, for example.
120 
121 <dt><tt>-o</tt> <i>response</i><dd>
122 
123 Specifies the type of response desired. The \e response is a string
124 and must be one of \c DAS, \c DDS, \c DataDDS or \c Version. Note
125 that \c Version returns version information in the body of the response
126 and is useful for debugging, et cetera. Each response returns version
127 information in an HTTP header for internal use by a client.
128 
129 <dt><tt>-c</tt><dd>
130 Send compressed data. Data are compressed using the deflate program.
131 
132 <dt><tt>-e</tt> <i>expression</i><dd>
133 This option specifies a non-blank constraint expression used to
134 subsample a dataset.
135 
136 <dt><tt>-v</tt> <i>cgi-version</i><dd> Set the CGI/Server version to
137 <tt>cgi-version</tt>. This is a way for the caller to set version
138 information passed back to the client either as the response to a
139 version request of in the response headers.
140 
141 <dt><tt>-d</tt> <i>ancdir</i><dd>
142 Specifies that ancillary data be sought in the <i>ancdir</i>
143 directory. <i>ancdir</i> must end in '/'.
144 
145 <dt><tt>-f</tt> <i>ancfile</i><dd>
146 Specifies that ancillary data may be found in a file called
147 <i>ancfile</i>.
148 
149 <dt><tt>-r</tt> <i>cache directory</i><dd>
150 Specify a directory to use if/when files are to be cached. Not all
151 handlers support caching and each uses its own rules tailored to a
152 specific file or data type.
153 
154 <dt><tt>-t</tt> <i>timeout</i><dd> Specifies a a timeout value in
155 seconds. If the server runs longer than \e timeout seconds, an Error is
156 returned to the client explaining that the request has timed out.
157 
158 <dt><tt>-l</tt> <i>time</i><dd> Indicates that the request is a
159 conditional request; send a complete response if and only if the data has
160 changed since <i>time</i>. If it has not changed since <i>time</i>, then
161 send a 304 (Not Modified) response. The <i>time</i> parameter is the
162 <tt>Last-Modified</tt> time from an If-Modified-Since condition GET
163 request. It is given in seconds since the start of the Unix epoch
164 (Midnight, 1 Jan 1970).
165 
166 </dl>
167 
168 @brief DODSFilter constructor. */
169 
DODSFilter(int argc,char * argv[])170 DODSFilter::DODSFilter(int argc, char *argv[]) throw(Error)
171 {
172     initialize(argc, argv);
173 
174     DBG(cerr << "d_comp: " << d_comp << endl);
175     DBG(cerr << "d_dap2ce: " << d_dap2ce << endl);
176     DBG(cerr << "d_cgi_ver: " << d_cgi_ver << endl);
177     DBG(cerr << "d_response: " << d_response << endl);
178     DBG(cerr << "d_anc_dir: " << d_anc_dir << endl);
179     DBG(cerr << "d_anc_file: " << d_anc_file << endl);
180     DBG(cerr << "d_cache_dir: " << d_cache_dir << endl);
181     DBG(cerr << "d_conditional_request: " << d_conditional_request << endl);
182     DBG(cerr << "d_if_modified_since: " << d_if_modified_since << endl);
183     DBG(cerr << "d_url: " << d_url << endl);
184     DBG(cerr << "d_timeout: " << d_timeout << endl);
185 }
186 
~DODSFilter()187 DODSFilter::~DODSFilter()
188 {
189 }
190 
191 /** Called when initializing a DODSFilter that's not going to be passed a
192 command line arguments. */
193 void
initialize()194 DODSFilter::initialize()
195 {
196     // Set default values. Don't use the C++ constructor initialization so
197     // that a subclass can have more control over this process.
198     d_comp = false;
199     d_bad_options = false;
200     d_conditional_request = false;
201     d_dataset = "";
202     d_dap2ce = "";
203     d_cgi_ver = "";
204     d_anc_dir = "";
205     d_anc_file = "";
206     d_cache_dir = "";
207     d_response = Unknown_Response;;
208     d_anc_das_lmt = 0;
209     d_anc_dds_lmt = 0;
210     d_if_modified_since = -1;
211     d_url = "";
212     d_program_name = "Unknown";
213     d_timeout = 0;
214 
215 #ifdef WIN32
216     //  We want serving from win32 to behave in a manner
217     //  similar to the UNIX way - no CR->NL terminated lines
218     //  in files. Hence stdout goes to binary mode.
219     _setmode(_fileno(stdout), _O_BINARY);
220 #endif
221 }
222 
223 /** Initialize. Specializations can call this once an empty DODSFilter has
224 been created using the default constructor. Using a method such as this
225 provides a way to specialize the process_options() method and then have
226 that specialization called by the subclass' constructor.
227 
228 This class and any class that specializes it should call this method in
229 its constructor. Note that when this method is called, the object is \e
230 not fully constructed.
231 
232 @param argc The argument count
233 @param argv The vector of char * argument strings. */
234 void
initialize(int argc,char * argv[])235 DODSFilter::initialize(int argc, char *argv[])
236 {
237     initialize();
238 
239     d_program_name = argv[0];
240 
241     // This should be specialized by a subclass. This may throw Error.
242     int next_arg = process_options(argc, argv);
243 
244     // Look at what's left after processing the command line options. Either
245     // there MUST be a dataset name OR the caller is asking for version
246     // information. If neither is true, then the options are bad.
247     if (next_arg < argc) {
248         d_dataset = argv[next_arg];
249         d_dataset = www2id(d_dataset, "%", "%20");
250     }
251     else if (get_response() != Version_Response)
252         print_usage();   // Throws Error
253 }
254 
255 /** Processing the command line options passed to the filter is handled by
256 this method so that specializations can change the options easily.
257 
258 @param argc The argument count
259 @param argv The vector of char * argument strings.
260 @return The index of the next, unprocessed, argument. This must be the
261 identifier passed to the filter program that identifies the data source.
262 It's often a file name. */
263 int
process_options(int argc,char * argv[])264 DODSFilter::process_options(int argc, char *argv[])
265 {
266     DBG(cerr << "Entering process_options... ");
267 
268     int option_char;
269     GetOpt getopt (argc, argv, "ce: v: d: f: r: l: o: u: t: ");
270 
271     while ((option_char = getopt()) != -1) {
272         switch (option_char) {
273         case 'c': d_comp = true; break;
274         case 'e': set_ce(getopt.optarg); break;
275         case 'v': set_cgi_version(getopt.optarg); break;
276         case 'd': d_anc_dir = getopt.optarg; break;
277         case 'f': d_anc_file = getopt.optarg; break;
278         case 'r': d_cache_dir = getopt.optarg; break;
279         case 'o': set_response(getopt.optarg); break;
280         case 'u': set_URL(getopt.optarg); break;
281         case 't': d_timeout = atoi(getopt.optarg); break;
282         case 'l':
283             d_conditional_request = true;
284             d_if_modified_since
285             = static_cast<time_t>(strtol(getopt.optarg, NULL, 10));
286             break;
287         case 'h': print_usage();
288             break;
289                                  // exit(1);
290                                  // Removed 12/29/2011; exit should
291                                  // not be called by a library. NB:
292                                  // print_usage() throws Error.
293         default: print_usage();  // Throws Error
294             break;
295         }
296     }
297 
298     DBGN(cerr << "exiting." << endl);
299 
300     return getopt.optind; // return the index of the next argument
301 }
302 
303 /** @brief Is this request conditional?
304 
305 @return True if the request is conditional.
306 @see get_request_if_modified_since(). */
307 bool
is_conditional() const308 DODSFilter::is_conditional() const
309 {
310     return d_conditional_request;
311 }
312 
313 /** Set the CGI/Server version number. Servers use this when answering
314 requests for version information. The version `number' should include
315 both the name of the server (e.g., <tt>ff_dods</tt>) as well
316 as the version
317 number. Since this information is typically divined by configure,
318 it's up to the executable to poke the correct value in using this
319 method.
320 
321 Note that the -v switch that this class understands is deprecated
322 since it is usually called by Perl code. It makes more sense to have
323 the actual C++ software set the version string.
324 
325 @param version A version string for this server. */
326 void
set_cgi_version(string version)327 DODSFilter::set_cgi_version(string version)
328 {
329     d_cgi_ver = version;
330 }
331 
332 /** Return the version information passed to the instance when it was
333 created. This string is passed to the DODSFilter ctor using the -v
334 option.
335 
336 @return The version string supplied at initialization. */
337 string
get_cgi_version() const338 DODSFilter::get_cgi_version() const
339 {
340     return d_cgi_ver;
341 }
342 
343 /** Return the entire constraint expression in a string.  This
344 includes both the projection and selection clauses, but not the
345 question mark.
346 
347 @brief Get the constraint expression.
348 @return A string object that contains the constraint expression. */
349 string
get_ce() const350 DODSFilter::get_ce() const
351 {
352     return d_dap2ce;
353 }
354 
355 void
set_ce(string _ce)356 DODSFilter::set_ce(string _ce)
357 {
358     d_dap2ce = www2id(_ce, "%", "%20");
359 }
360 
361 /** The ``dataset name'' is the filename or other string that the
362 filter program will use to access the data. In some cases this
363 will indicate a disk file containing the data.  In others, it
364 may represent a database query or some other exotic data
365 access method.
366 
367 @brief Get the dataset name.
368 @return A string object that contains the name of the dataset. */
369 string
get_dataset_name() const370 DODSFilter::get_dataset_name() const
371 {
372     return d_dataset;
373 }
374 
375 void
set_dataset_name(const string ds)376 DODSFilter::set_dataset_name(const string ds)
377 {
378     d_dataset = www2id(ds, "%", "%20");
379 }
380 
381 /** Get the URL. This returns the URL, minus the constraint originally sent
382 to the server.
383 @return The URL. */
384 string
get_URL() const385 DODSFilter::get_URL() const
386 {
387     return d_url;
388 }
389 
390 /** Set the URL. Set the URL sent to the server.
391 @param url The URL, minus the constraint. */
392 void
set_URL(const string & url)393 DODSFilter::set_URL(const string &url)
394 {
395     if (url.find('?') != url.npos)
396         print_usage();  // Throws Error
397 
398     d_url = url;
399 }
400 
401 /** To read version information that is specific to a certain
402 dataset, override this method with an implementation that does
403 what you want. By default, this returns an empty string.
404 
405 @brief Get the version information for the dataset.
406 @return A string object that contains the dataset version
407 information.  */
408 string
get_dataset_version() const409 DODSFilter::get_dataset_version() const
410 {
411     return "";
412 }
413 
414 /** Set the response to be returned. Valid response names are "DAS", "DDS",
415 "DataDDS, "Version".
416 
417 @param r The name of the object.
418 @exception InternalErr Thrown if the response is not one of the valid
419 names. */
set_response(const string & r)420 void DODSFilter::set_response(const string &r)
421 {
422     if (r == "DAS" || r == "das") {
423 	d_response = DAS_Response;
424 	d_action = "das" ;
425     }
426     else if (r == "DDS" || r == "dds") {
427 	d_response = DDS_Response;
428 	d_action = "dds" ;
429     }
430     else if (r == "DataDDS" || r == "dods") {
431 	d_response = DataDDS_Response;
432 	d_action = "dods" ;
433     }
434     else if (r == "DDX" || r == "ddx") {
435 	d_response = DDX_Response;
436 	d_action = "ddx" ;
437     }
438     else if (r == "DataDDX" || r == "dataddx") {
439 	d_response = DataDDX_Response;
440 	d_action = "dataddx" ;
441     }
442     else if (r == "Version") {
443 	d_response = Version_Response;
444 	d_action = "version" ;
445     }
446     else
447 	print_usage();   // Throws Error
448 }
449 
450 /** Get the enum name of the response to be returned. */
451 DODSFilter::Response
get_response() const452 DODSFilter::get_response() const
453 {
454     return d_response;
455 }
456 
457 /** Get the string name of the response to be returned. */
get_action() const458 string DODSFilter::get_action() const
459 {
460     return d_action;
461 }
462 
463 /** Get the dataset's last modified time. This returns the time at which
464     the dataset was last modified as defined by UNIX's notion of
465     modification. This does not take into account the modification of an
466     ancillary DAS or DDS. Time is given in seconds since the epoch (1 Jan
467     1970 00:00:00 GMT).
468 
469     This method perform a simple check on the file named by the dataset
470     given when the DODSFilter instance was created. If the dataset is not
471     a filter, this method returns the current time. Servers which provide
472     access to non-file-based data should subclass DODSFilter and supply a
473     more suitable version of this method.
474 
475     From the stat(2) man page: ``Traditionally, <tt>st_mtime</tt>
476     is changed by mknod(2), utime(2), and write(2). The
477     <tt>st_mtime</tt> is not changed for
478     changes in owner, group, hard link count, or mode.''
479 
480     @return Time of the last modification in seconds since the epoch.
481     @see get_das_last_modified_time()
482     @see get_dds_last_modified_time() */
483 time_t
get_dataset_last_modified_time() const484 DODSFilter::get_dataset_last_modified_time() const
485 {
486     return last_modified_time(d_dataset);
487 }
488 
489 /** Get the last modified time for the dataset's DAS. This time, given in
490     seconds since the epoch (1 Jan 1970 00:00:00 GMT), is the greater of
491     the datasets's and any ancillary DAS' last modified time.
492 
493     @param anc_location A directory to search for ancillary files (in
494     addition to the CWD).
495     @return Time of last modification of the DAS.
496     @see get_dataset_last_modified_time()
497     @see get_dds_last_modified_time() */
498 time_t
get_das_last_modified_time(const string & anc_location) const499 DODSFilter::get_das_last_modified_time(const string &anc_location) const
500 {
501     DBG(cerr << "DODSFilter::get_das_last_modified_time(anc_location="
502         << anc_location << "call faf(das) d_dataset=" << d_dataset
503         << " d_anc_file=" << d_anc_file << endl);
504 
505     string name
506     = Ancillary::find_ancillary_file(d_dataset, "das",
507                           (anc_location == "") ? d_anc_dir : anc_location,
508                           d_anc_file);
509 
510     return max((name != "") ? last_modified_time(name) : 0,
511                get_dataset_last_modified_time());
512 }
513 
514 /** Get the last modified time for the dataset's DDS. This time, given in
515     seconds since the epoch (1 Jan 1970 00:00:00 GMT), is the greater of
516     the datasets's and any ancillary DDS' last modified time.
517 
518     @return Time of last modification of the DDS.
519     @see get_dataset_last_modified_time()
520     @see get_dds_last_modified_time() */
521 time_t
get_dds_last_modified_time(const string & anc_location) const522 DODSFilter::get_dds_last_modified_time(const string &anc_location) const
523 {
524     DBG(cerr << "DODSFilter::get_das_last_modified_time(anc_location="
525         << anc_location << "call faf(dds) d_dataset=" << d_dataset
526         << " d_anc_file=" << d_anc_file << endl);
527 
528     string name
529     = Ancillary::find_ancillary_file(d_dataset, "dds",
530                           (anc_location == "") ? d_anc_dir : anc_location,
531                           d_anc_file);
532 
533     return max((name != "") ? last_modified_time(name) : 0,
534                get_dataset_last_modified_time());
535 }
536 
537 /** Get the last modified time to be used for a particular data request.
538     This method should look at both the constraint expression and any
539     ancillary files for this dataset. The implementation provided here
540     returns the latest time returned by the <tt>get_dataset</tt>...(),
541     <tt>get_das</tt>...() and <tt>get_dds</tt>...() methods and
542     does not currently check the CE.
543 
544     @param anc_location A directory to search for ancillary files (in
545     addition to the CWD).
546     @return Time of last modification of the data.
547     @see get_dataset_last_modified_time()
548     @see get_das_last_modified_time()
549     @see get_dds_last_modified_time() */
550 time_t
get_data_last_modified_time(const string & anc_location) const551 DODSFilter::get_data_last_modified_time(const string &anc_location) const
552 {
553     DBG(cerr << "DODSFilter::get_das_last_modified_time(anc_location="
554         << anc_location << "call faf(both) d_dataset=" << d_dataset
555         << " d_anc_file=" << d_anc_file << endl);
556 
557     string dds_name
558     = Ancillary::find_ancillary_file(d_dataset, "dds",
559                           (anc_location == "") ? d_anc_dir : anc_location,
560                           d_anc_file);
561     string das_name
562     = Ancillary::find_ancillary_file(d_dataset, "das",
563                           (anc_location == "") ? d_anc_dir : anc_location,
564                           d_anc_file);
565 
566     time_t m = max((das_name != "") ? last_modified_time(das_name) : (time_t)0,
567                    (dds_name != "") ? last_modified_time(dds_name) : (time_t)0);
568     // Note that this is a call to get_dataset_... not get_data_...
569     time_t n = get_dataset_last_modified_time();
570 
571     return max(m, n);
572 }
573 
574 /** Get the value of a conditional request's If-Modified-Since header.
575     This value is used to determine if the request should get a full
576     response or a Not Modified (304) response. The time is given in
577     seconds since the Unix epoch (midnight, 1 Jan 1970). If no time was
578     given with the request, this methods returns -1.
579 
580     @return If-Modified-Since time from a condition GET request. */
581 time_t
get_request_if_modified_since() const582 DODSFilter::get_request_if_modified_since() const
583 {
584     return d_if_modified_since;
585 }
586 
587 /** The <tt>cache_dir</tt> is used to hold the cached .dds and .das files.
588     By default, this returns an empty string (store cache files in
589     current directory.
590 
591     @brief Get the cache directory.
592     @return A string object that contains the cache file directory.  */
593 string
get_cache_dir() const594 DODSFilter::get_cache_dir() const
595 {
596     return d_cache_dir;
597 }
598 
599 /** Set the server's timeout value. A value of zero (the default) means no
600     timeout.
601 
602     @param t Server timeout in seconds. Default is zero (no timeout). */
603 void
set_timeout(int t)604 DODSFilter::set_timeout(int t)
605 {
606     d_timeout = t;
607 }
608 
609 /** Get the server's timeout value. */
610 int
get_timeout() const611 DODSFilter::get_timeout() const
612 {
613     return d_timeout;
614 }
615 
616 /** Use values of this instance to establish a timeout alarm for the server.
617     If the timeout value is zero, do nothing.
618 
619     @todo When the alarm handler is called, two CRLF pairs are dumped to the
620     stream and then an Error object is sent. No attempt is made to write the
621     'correct' MIME headers for an Error object. Instead, a savvy client will
622     know that when an exception is thrown during a deserialize operation, it
623     should scan ahead in the input stream for an Error object. Add this, or a
624     sensible variant once libdap++ supports reliable error delivery. Dumb
625     clients will never get the Error object... */
626 
627 void
establish_timeout(FILE * stream) const628 DODSFilter::establish_timeout(FILE *stream) const
629 {
630 #ifndef WIN32
631     if (d_timeout > 0) {
632         SignalHandler *sh = SignalHandler::instance();
633         EventHandler *old_eh = sh->register_handler(SIGALRM, new AlarmHandler(stream));
634         delete old_eh;
635         alarm(d_timeout);
636     }
637 #endif
638 }
639 
640 void
establish_timeout(ostream & stream) const641 DODSFilter::establish_timeout(ostream &stream) const
642 {
643 #ifndef WIN32
644     if (d_timeout > 0) {
645         SignalHandler *sh = SignalHandler::instance();
646         EventHandler *old_eh = sh->register_handler(SIGALRM, new AlarmHandler(stream));
647         delete old_eh;
648         alarm(d_timeout);
649     }
650 #endif
651 }
652 
653 static const char *emessage = "DODS internal server error; usage error. Please report this to the dataset maintainer, or to the opendap-tech@opendap.org mailing list.";
654 
655 /** This message is printed when the filter program is incorrectly
656     invoked by the dispatch CGI.  This is an error in the server
657     installation or the CGI implementation, so the error message is
658     written to stderr instead of stdout.  A server's stderr messages
659     show up in the httpd log file. In addition, an error object is
660     sent back to the client program telling them that the server is
661     broken.
662 
663     @brief Print usage information for a filter program. */
664 void
print_usage() const665 DODSFilter::print_usage() const
666 {
667     // Write a message to the WWW server error log file.
668     ErrMsgT(usage.c_str());
669 
670     throw Error(emessage);
671 }
672 
673 /** This function formats and sends to stdout version
674     information from the httpd server, the server dispatch scripts,
675     the DODS core software, and (optionally) the dataset.
676 
677     @brief Send version information back to the client program. */
678 void
send_version_info() const679 DODSFilter::send_version_info() const
680 {
681     do_version(d_cgi_ver, get_dataset_version());
682 }
683 
684 /** This function formats and prints an ASCII representation of a
685     DAS on stdout.  This has the effect of sending the DAS object
686     back to the client program.
687 
688     @brief Transmit a DAS.
689     @param out The output FILE to which the DAS is to be sent.
690     @param das The DAS object to be sent.
691     @param anc_location The directory in which the external DAS file resides.
692     @param with_mime_headers If true (the default) send MIME headers.
693     @return void
694     @see DAS */
695 void
send_das(FILE * out,DAS & das,const string & anc_location,bool with_mime_headers) const696 DODSFilter::send_das(FILE *out, DAS &das, const string &anc_location,
697                      bool with_mime_headers) const
698 {
699     ostringstream oss;
700     send_das(oss, das, anc_location, with_mime_headers);
701     fwrite(oss.str().data(), sizeof(char), oss.str().length(), out);
702 }
703 
704 /** This function formats and prints an ASCII representation of a
705     DAS on stdout.  This has the effect of sending the DAS object
706     back to the client program.
707 
708     @brief Transmit a DAS.
709     @param out The output stream to which the DAS is to be sent.
710     @param das The DAS object to be sent.
711     @param anc_location The directory in which the external DAS file resides.
712     @param with_mime_headers If true (the default) send MIME headers.
713     @return void
714     @see DAS */
715 void
send_das(ostream & out,DAS & das,const string & anc_location,bool with_mime_headers) const716 DODSFilter::send_das(ostream &out, DAS &das, const string &anc_location,
717                      bool with_mime_headers) const
718 {
719     time_t das_lmt = get_das_last_modified_time(anc_location);
720     if (is_conditional()
721         && das_lmt <= get_request_if_modified_since()
722         && with_mime_headers) {
723         set_mime_not_modified(out);
724     }
725     else {
726         if (with_mime_headers)
727             set_mime_text(out, dods_das, d_cgi_ver, x_plain, das_lmt);
728         das.print(out);
729     }
730     out << flush ;
731 }
732 
733 void
send_das(DAS & das,const string & anc_location,bool with_mime_headers) const734 DODSFilter::send_das(DAS &das, const string &anc_location,
735                      bool with_mime_headers) const
736 {
737     send_das(cout, das, anc_location, with_mime_headers);
738 }
739 
740 /** This function formats and prints an ASCII representation of a
741     DDS on stdout.  When called by a CGI program, this has the
742     effect of sending a DDS object back to the client
743     program. Either an entire DDS or a constrained DDS may be sent.
744 
745     @brief Transmit a DDS.
746     @param out The output FILE to which the DAS is to be sent.
747     @param dds The DDS to send back to a client.
748     @param eval A reference to the ConstraintEvaluator to use.
749     @param constrained If this argument is true, evaluate the
750     current constraint expression and send the `constrained DDS'
751     back to the client.
752     @param anc_location The directory in which the external DAS file resides.
753     @param with_mime_headers If true (the default) send MIME headers.
754     @return void
755     @see DDS */
756 void
send_dds(FILE * out,DDS & dds,ConstraintEvaluator & eval,bool constrained,const string & anc_location,bool with_mime_headers) const757 DODSFilter::send_dds(FILE *out, DDS &dds, ConstraintEvaluator &eval,
758                      bool constrained,
759                      const string &anc_location,
760                      bool with_mime_headers) const
761 {
762     ostringstream oss;
763     send_dds(oss, dds, eval, constrained, anc_location, with_mime_headers);
764     fwrite(oss.str().data(), sizeof(char), oss.str().length(), out);
765 }
766 
767 /** This function formats and prints an ASCII representation of a
768     DDS on stdout.  When called by a CGI program, this has the
769     effect of sending a DDS object back to the client
770     program. Either an entire DDS or a constrained DDS may be sent.
771 
772     @brief Transmit a DDS.
773     @param out The output stream to which the DAS is to be sent.
774     @param dds The DDS to send back to a client.
775     @param eval A reference to the ConstraintEvaluator to use.
776     @param constrained If this argument is true, evaluate the
777     current constraint expression and send the `constrained DDS'
778     back to the client.
779     @param anc_location The directory in which the external DAS file resides.
780     @param with_mime_headers If true (the default) send MIME headers.
781     @return void
782     @see DDS */
783 void
send_dds(ostream & out,DDS & dds,ConstraintEvaluator & eval,bool constrained,const string & anc_location,bool with_mime_headers) const784 DODSFilter::send_dds(ostream &out, DDS &dds, ConstraintEvaluator &eval,
785                      bool constrained,
786                      const string &anc_location,
787                      bool with_mime_headers) const
788 {
789     // If constrained, parse the constraint. Throws Error or InternalErr.
790     if (constrained)
791         eval.parse_constraint(d_dap2ce, dds);
792 
793     if (eval.functional_expression())
794         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.");
795 
796     time_t dds_lmt = get_dds_last_modified_time(anc_location);
797     if (is_conditional()
798         && dds_lmt <= get_request_if_modified_since()
799         && with_mime_headers) {
800         set_mime_not_modified(out);
801     }
802     else {
803         if (with_mime_headers)
804             set_mime_text(out, dods_dds, d_cgi_ver, x_plain, dds_lmt);
805         if (constrained)
806             dds.print_constrained(out);
807         else
808             dds.print(out);
809     }
810 
811     out << flush ;
812 }
813 
814 void
send_dds(DDS & dds,ConstraintEvaluator & eval,bool constrained,const string & anc_location,bool with_mime_headers) const815 DODSFilter::send_dds(DDS &dds, ConstraintEvaluator &eval,
816                      bool constrained, const string &anc_location,
817                      bool with_mime_headers) const
818 {
819     send_dds(cout, dds, eval, constrained, anc_location, with_mime_headers);
820 }
821 
822 // 'lmt' unused. Should it be used to supply a LMT or removed from the
823 // method? jhrg 8/9/05
824 void
functional_constraint(BaseType & var,DDS & dds,ConstraintEvaluator & eval,FILE * out) const825 DODSFilter::functional_constraint(BaseType &var, DDS &dds,
826                                   ConstraintEvaluator &eval, FILE *out) const
827 {
828     ostringstream oss;
829     functional_constraint(var, dds, eval, oss);
830     fwrite(oss.str().data(), sizeof(char), oss.str().length(), out);
831 }
832 
833 // 'lmt' unused. Should it be used to supply a LMT or removed from the
834 // method? jhrg 8/9/05
835 void
functional_constraint(BaseType & var,DDS & dds,ConstraintEvaluator & eval,ostream & out) const836 DODSFilter::functional_constraint(BaseType &var, DDS &dds,
837                                   ConstraintEvaluator &eval, ostream &out) const
838 {
839     out << "Dataset {\n" ;
840     var.print_decl(out, "    ", true, false, true);
841     out << "} function_value;\n" ;
842     out << "Data:\n" ;
843 
844     out << flush ;
845 
846     // Grab a stream that encodes using XDR.
847     XDRStreamMarshaller m( out ) ;
848 
849     try {
850         // In the following call to serialize, suppress CE evaluation.
851         var.serialize(eval, dds, m, false);
852     }
853     catch (Error &e) {
854         throw;
855     }
856 }
857 
858 void
dataset_constraint(DDS & dds,ConstraintEvaluator & eval,FILE * out,bool ce_eval) const859 DODSFilter::dataset_constraint(DDS & dds, ConstraintEvaluator & eval,
860                                FILE * out, bool ce_eval) const
861 {
862     ostringstream oss;
863     dataset_constraint(dds, eval, oss, ce_eval);
864     fwrite(oss.str().data(), sizeof(char), oss.str().length(), out);
865 }
866 
867 void
dataset_constraint(DDS & dds,ConstraintEvaluator & eval,ostream & out,bool ce_eval) const868 DODSFilter::dataset_constraint(DDS & dds, ConstraintEvaluator & eval,
869                                ostream &out, bool ce_eval) const
870 {
871     // send constrained DDS
872     dds.print_constrained(out);
873     out << "Data:\n" ;
874     out << flush ;
875 
876     // Grab a stream that encodes using XDR.
877     XDRStreamMarshaller m( out ) ;
878 
879     try {
880         // Send all variables in the current projection (send_p())
881         for (DDS::Vars_iter i = dds.var_begin(); i != dds.var_end(); i++)
882             if ((*i)->send_p()) {
883                 DBG(cerr << "Sending " << (*i)->name() << endl);
884                 (*i)->serialize(eval, dds, m, ce_eval);
885             }
886     }
887     catch (Error & e) {
888         throw;
889     }
890 }
891 
892 void
dataset_constraint_ddx(DDS & dds,ConstraintEvaluator & eval,ostream & out,const string & boundary,const string & start,bool ce_eval) const893 DODSFilter::dataset_constraint_ddx(DDS & dds, ConstraintEvaluator & eval,
894                                ostream &out, const string &boundary,
895                                const string &start, bool ce_eval) const
896 {
897     // Write the MPM headers for the DDX (text/xml) part of the response
898     set_mime_ddx_boundary(out, boundary, start, dods_ddx);
899 
900     // Make cid
901     uuid_t uu;
902     uuid_generate(uu);
903     char uuid[37];
904     uuid_unparse(uu, &uuid[0]);
905     char domain[256];
906     if (getdomainname(domain, 255) != 0 || strlen(domain) == 0)
907 	strncpy(domain, "opendap.org", 255);
908 
909     string cid = string(&uuid[0]) + "@" + string(&domain[0]);
910 
911     // Send constrained DDX with a data blob reference
912     dds.print_xml_writer(out, true, cid);
913 
914     // Write the MPM headers for the data part of the response.
915     set_mime_data_boundary(out, boundary, cid, dap4_data, binary);
916 
917     // Grab a stream that encodes using XDR.
918     XDRStreamMarshaller m( out ) ;
919 
920     try {
921         // Send all variables in the current projection (send_p())
922         for (DDS::Vars_iter i = dds.var_begin(); i != dds.var_end(); i++)
923             if ((*i)->send_p()) {
924                 DBG(cerr << "Sending " << (*i)->name() << endl);
925                 (*i)->serialize(eval, dds, m, ce_eval);
926             }
927     }
928     catch (Error & e) {
929         throw;
930     }
931 }
932 
933 /** Send the data in the DDS object back to the client program. The data is
934     encoded using a Marshaller, and enclosed in a MIME document which is all sent
935     to \c data_stream. If this is being called from a CGI, \c data_stream is
936     probably \c stdout and writing to it has the effect of sending the
937     response back to the client.
938 
939     @brief Transmit data.
940     @param dds A DDS object containing the data to be sent.
941     @param eval A reference to the ConstraintEvaluator to use.
942     @param data_stream Write the response to this FILE.
943     @param anc_location A directory to search for ancillary files (in
944     addition to the CWD).  This is used in a call to
945     get_data_last_modified_time().
946     @param with_mime_headers If true, include the MIME headers in the response.
947     Defaults to true.
948     @return void */
949 void
send_data(DDS & dds,ConstraintEvaluator & eval,FILE * data_stream,const string & anc_location,bool with_mime_headers) const950 DODSFilter::send_data(DDS & dds, ConstraintEvaluator & eval,
951                       FILE * data_stream, const string & anc_location,
952                       bool with_mime_headers) const
953 {
954     ostringstream oss;
955     send_data(dds, eval, oss, anc_location, with_mime_headers);
956     fwrite(oss.str().data(), sizeof(char), oss.str().length(), data_stream);
957 }
958 
959 /** Send the data in the DDS object back to the client program. The data is
960     encoded using a Marshaller, and enclosed in a MIME document which is all sent
961     to \c data_stream. If this is being called from a CGI, \c data_stream is
962     probably \c stdout and writing to it has the effect of sending the
963     response back to the client.
964 
965     @brief Transmit data.
966     @param dds A DDS object containing the data to be sent.
967     @param eval A reference to the ConstraintEvaluator to use.
968     @param data_stream Write the response to this stream.
969     @param anc_location A directory to search for ancillary files (in
970     addition to the CWD).  This is used in a call to
971     get_data_last_modified_time().
972     @param with_mime_headers If true, include the MIME headers in the response.
973     Defaults to true.
974     @return void */
975 void
send_data(DDS & dds,ConstraintEvaluator & eval,ostream & data_stream,const string & anc_location,bool with_mime_headers) const976 DODSFilter::send_data(DDS & dds, ConstraintEvaluator & eval,
977                       ostream & data_stream, const string & anc_location,
978                       bool with_mime_headers) const
979 {
980     // If this is a conditional request and the server should send a 304
981     // response, do that and exit. Otherwise, continue on and send the full
982     // response.
983     time_t data_lmt = get_data_last_modified_time(anc_location);
984     if (is_conditional()
985         && data_lmt <= get_request_if_modified_since()
986         && with_mime_headers) {
987         set_mime_not_modified(data_stream);
988         return;
989     }
990     // Set up the alarm.
991     establish_timeout(data_stream);
992     dds.set_timeout(d_timeout);
993 
994     eval.parse_constraint(d_dap2ce, dds);   // Throws Error if the ce doesn't
995 					// parse.
996 
997     dds.tag_nested_sequences(); // Tag Sequences as Parent or Leaf node.
998 
999     // Start sending the response...
1000 
1001     // Handle *functional* constraint expressions specially
1002 #if 0
1003     if (eval.functional_expression()) {
1004         // Get the result and then start sending the headers. This provides a
1005         // way to send errors back to the client w/o colliding with the
1006         // normal response headers. There's some duplication of code with this
1007         // and the else-clause.
1008         BaseType *var = eval.eval_function(dds, d_dataset);
1009         if (!var)
1010             throw Error(unknown_error, "Error calling the CE function.");
1011 
1012        if (with_mime_headers)
1013             set_mime_binary(data_stream, dods_data, d_cgi_ver, x_plain, data_lmt);
1014 
1015 	data_stream << flush ;
1016 
1017         functional_constraint(*var, dds, eval, data_stream);
1018         delete var;
1019         var = 0;
1020     }
1021 #endif
1022     if (eval.function_clauses()) {
1023 	DDS *fdds = eval.eval_function_clauses(dds);
1024         if (with_mime_headers)
1025             set_mime_binary(data_stream, dods_data, d_cgi_ver, x_plain, data_lmt);
1026 
1027         dataset_constraint(*fdds, eval, data_stream, false);
1028 	delete fdds;
1029     }
1030     else {
1031         if (with_mime_headers)
1032             set_mime_binary(data_stream, dods_data, d_cgi_ver, x_plain, data_lmt);
1033 
1034         dataset_constraint(dds, eval, data_stream);
1035     }
1036 
1037     data_stream << flush ;
1038 }
1039 
1040 /** Send the DDX response. The DDX never contains data, instead it holds a
1041     reference to a Blob response which is used to get the data values. The
1042     DDS and DAS objects are built using code that already exists in the
1043     servers.
1044 
1045     @param dds The dataset's DDS \e with attributes in the variables.
1046     @param eval A reference to the ConstraintEvaluator to use.
1047     @param out Destination
1048     @param with_mime_headers If true, include the MIME headers in the response.
1049     Defaults to true. */
1050 void
send_ddx(DDS & dds,ConstraintEvaluator & eval,FILE * out,bool with_mime_headers) const1051 DODSFilter::send_ddx(DDS &dds, ConstraintEvaluator &eval, FILE *out,
1052                      bool with_mime_headers) const
1053 {
1054     ostringstream oss;
1055     send_ddx(dds, eval, oss, with_mime_headers);
1056     fwrite(oss.str().data(), sizeof(char), oss.str().length(), out);
1057 }
1058 
1059 /** Send the DDX response. The DDX never contains data, instead it holds a
1060     reference to a Blob response which is used to get the data values. The
1061     DDS and DAS objects are built using code that already exists in the
1062     servers.
1063 
1064     @param dds The dataset's DDS \e with attributes in the variables.
1065     @param eval A reference to the ConstraintEvaluator to use.
1066     @param out Destination
1067     @param with_mime_headers If true, include the MIME headers in the response.
1068     Defaults to true. */
1069 void
send_ddx(DDS & dds,ConstraintEvaluator & eval,ostream & out,bool with_mime_headers) const1070 DODSFilter::send_ddx(DDS &dds, ConstraintEvaluator &eval, ostream &out,
1071                      bool with_mime_headers) const
1072 {
1073     // If constrained, parse the constraint. Throws Error or InternalErr.
1074     if (!d_dap2ce.empty())
1075         eval.parse_constraint(d_dap2ce, dds);
1076 
1077     if (eval.functional_expression())
1078         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.");
1079 
1080     time_t dds_lmt = get_dds_last_modified_time(d_anc_dir);
1081 
1082     // If this is a conditional request and the server should send a 304
1083     // response, do that and exit. Otherwise, continue on and send the full
1084     // response.
1085     if (is_conditional() && dds_lmt <= get_request_if_modified_since()
1086         && with_mime_headers) {
1087         set_mime_not_modified(out);
1088         return;
1089     }
1090     else {
1091         if (with_mime_headers)
1092             set_mime_text(out, dods_ddx, d_cgi_ver, x_plain, dds_lmt);
1093         dds.print_xml_writer(out, !d_dap2ce.empty(), "");
1094     }
1095 }
1096 
1097 /** Send the data in the DDS object back to the client program. The data is
1098     encoded using a Marshaller, and enclosed in a MIME document which is all sent
1099     to \c data_stream. If this is being called from a CGI, \c data_stream is
1100     probably \c stdout and writing to it has the effect of sending the
1101     response back to the client.
1102 
1103     @brief Transmit data.
1104 
1105     @param dds A DDS object containing the data to be sent.
1106     @param eval A reference to the ConstraintEvaluator to use.
1107     @param data_stream Write the response to this stream.
1108     @param start
1109     @param boundary
1110     @param anc_location A directory to search for ancillary files (in
1111     addition to the CWD).  This is used in a call to
1112     get_data_last_modified_time().
1113     @param with_mime_headers If true, include the MIME headers in the response.
1114     Defaults to true.
1115 
1116     @return void */
1117 void
send_data_ddx(DDS & dds,ConstraintEvaluator & eval,ostream & data_stream,const string & start,const string & boundary,const string & anc_location,bool with_mime_headers) const1118 DODSFilter::send_data_ddx(DDS & dds, ConstraintEvaluator & eval,
1119                       ostream & data_stream, const string &start,
1120                       const string &boundary, const string & anc_location,
1121                       bool with_mime_headers) const
1122 {
1123     // If this is a conditional request and the server should send a 304
1124     // response, do that and exit. Otherwise, continue on and send the full
1125     // response.
1126     time_t data_lmt = get_data_last_modified_time(anc_location);
1127     if (is_conditional()
1128         && data_lmt <= get_request_if_modified_since()
1129         && with_mime_headers) {
1130         set_mime_not_modified(data_stream);
1131         return;
1132     }
1133     // Set up the alarm.
1134     establish_timeout(data_stream);
1135     dds.set_timeout(d_timeout);
1136 
1137     eval.parse_constraint(d_dap2ce, dds);   // Throws Error if the ce doesn't
1138 					// parse.
1139 
1140     dds.tag_nested_sequences(); // Tag Sequences as Parent or Leaf node.
1141 
1142     // Start sending the response...
1143 
1144     // Handle *functional* constraint expressions specially
1145 #if 0
1146     if (eval.functional_expression()) {
1147         BaseType *var = eval.eval_function(dds, d_dataset);
1148         if (!var)
1149             throw Error(unknown_error, "Error calling the CE function.");
1150 
1151         if (with_mime_headers)
1152             set_mime_multipart(data_stream, boundary, start, dods_data_ddx,
1153 		d_cgi_ver, x_plain, data_lmt);
1154 	data_stream << flush ;
1155 	BaseTypeFactory btf;
1156 	DDS var_dds(&btf, var->name());
1157 	var->set_send_p(true);
1158 	var_dds.add_var(var);
1159         serialize_dap2_data_ddx(var_dds, eval, data_stream, boundary, start);
1160 
1161         // functional_constraint_ddx(*var, dds, eval, data_stream, boundary);
1162         delete var;
1163         var = 0;
1164     }
1165 #endif
1166     if (eval.function_clauses()) {
1167     	DDS *fdds = eval.eval_function_clauses(dds);
1168         if (with_mime_headers)
1169             set_mime_multipart(data_stream, boundary, start, dods_data_ddx,
1170         	    d_cgi_ver, x_plain, data_lmt);
1171         data_stream << flush ;
1172         dataset_constraint(*fdds, eval, data_stream, false);
1173     	delete fdds;
1174     }
1175     else {
1176         if (with_mime_headers)
1177             set_mime_multipart(data_stream, boundary, start, dods_data_ddx,
1178         	    d_cgi_ver, x_plain, data_lmt);
1179         data_stream << flush ;
1180         dataset_constraint_ddx(dds, eval, data_stream, boundary, start);
1181     }
1182 
1183     data_stream << flush ;
1184 
1185     if (with_mime_headers)
1186 	data_stream << CRLF << "--" << boundary << "--" << CRLF;
1187 }
1188 
1189 } // namespace libdap
1190 
1191