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