libdap  Updated for version 3.20.11
libdap4 is an implementation of OPeNDAP's DAP protocol.
mime_util.cc
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 // Reza Nekovei <rnekovei@intcomm.net>
10 //
11 // This library is free software; you can redistribute it and/or
12 // modify it under the terms of the GNU Lesser General Public
13 // License as published by the Free Software Foundation; either
14 // version 2.1 of the License, or (at your option) any later version.
15 //
16 // This library is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 // Lesser General Public License for more details.
20 //
21 // You should have received a copy of the GNU Lesser General Public
22 // License along with this library; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24 //
25 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
26 
27 // (c) COPYRIGHT URI/MIT 1994-2001
28 // Please read the full copyright statement in the file COPYRIGHT_URI.
29 //
30 // Authors:
31 // jhrg,jimg James Gallagher <jgallagher@gso.uri.edu>
32 // reza Reza Nekovei <rnekovei@intcomm.net>
33 
34 // A few useful routines which are used in CGI programs.
35 //
36 // ReZa 9/30/94
37 
38 #include "config.h"
39 
40 #include <cstring>
41 #include <cstdio>
42 #include <ctype.h>
43 
44 #ifndef TM_IN_SYS_TIME
45 #include <time.h>
46 #else
47 #include <sys/time.h>
48 #endif
49 
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 
53 #ifndef WIN32
54 #include <unistd.h> // for access
55 #include <sys/wait.h>
56 #else
57 #include <io.h>
58 #include <fcntl.h>
59 #include <process.h>
60 // Win32 does not define this. 08/21/02 jhrg
61 #define F_OK 0
62 #endif
63 
64 #include <iostream>
65 #include <sstream>
66 #include <fstream>
67 #include <string>
68 
69 #include "mime_util.h"
70 #include "media_types.h"
71 
72 #include "Ancillary.h"
73 #include "util.h" // This supplies flush_stream for WIN32.
74 #include "debug.h"
75 
76 #ifdef WIN32
77 #define FILE_DELIMITER '\\'
78 #else // default to unix
79 #define FILE_DELIMITER '/'
80 #endif
81 
82 // ...not using a const string here to avoid global objects. jhrg 12/23/05
83 #define CRLF "\r\n" // Change here, expr-test.cc, in DODSFilter and ResponseBuilder
84 
85 using namespace std;
86 
87 namespace libdap {
88 
94 time_t
95 last_modified_time(const string &name)
96 {
97  struct stat m;
98 
99  if (stat(name.c_str(), &m) == 0 && (S_IFREG & m.st_mode))
100  return m.st_mtime;
101  else
102  return time(0);
103 }
104 // Return a MIME rfc-822 date. The grammar for this is:
105 // date-time = [ day "," ] date time ; dd mm yy
106 // ; hh:mm:ss zzz
107 //
108 // day = "Mon" / "Tue" / "Wed" / "Thu"
109 // / "Fri" / "Sat" / "Sun"
110 //
111 // date = 1*2DIGIT month 2DIGIT ; day month year
112 // ; e.g. 20 Jun 82
113 // NB: year is 4 digit; see RFC 1123. 11/30/99 jhrg
114 //
115 // month = "Jan" / "Feb" / "Mar" / "Apr"
116 // / "May" / "Jun" / "Jul" / "Aug"
117 // / "Sep" / "Oct" / "Nov" / "Dec"
118 //
119 // time = hour zone ; ANSI and Military
120 //
121 // hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT]
122 // ; 00:00:00 - 23:59:59
123 //
124 // zone = "UT" / "GMT" ; Universal Time
125 // ; North American : UT
126 // / "EST" / "EDT" ; Eastern: - 5/ - 4
127 // / "CST" / "CDT" ; Central: - 6/ - 5
128 // / "MST" / "MDT" ; Mountain: - 7/ - 6
129 // / "PST" / "PDT" ; Pacific: - 8/ - 7
130 // / 1ALPHA ; Military: Z = UT;
131 // ; A:-1; (J not used)
132 // ; M:-12; N:+1; Y:+12
133 // / ( ("+" / "-") 4DIGIT ) ; Local differential
134 // ; hours+min. (HHMM)
135 
136 static const char *days[] =
137  {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
138  };
139 static const char *months[] =
140  {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
141  "Aug", "Sep", "Oct", "Nov", "Dec"
142  };
143 
144 #ifdef _MSC_VER
145 #define snprintf sprintf_s
146 #endif
155 string
156 rfc822_date(const time_t t)
157 {
158  struct tm stm;
159  const struct tm *ret = gmtime_r(&t, &stm);
160  if (!ret)
161  return "";
162 
163  char d[256];
164 
165  snprintf(d, 255, "%s, %02d %s %4d %02d:%02d:%02d GMT", days[stm.tm_wday],
166  stm.tm_mday, months[stm.tm_mon],
167  1900 + stm.tm_year,
168  stm.tm_hour, stm.tm_min, stm.tm_sec);
169  d[255] = '\0';
170  return {d};
171 }
172 
173 static const int TimLen = 26; // length of string from asctime()
174 //static const int CLUMP_SIZE = 1024; // size of clumps to new in fmakeword()
175 
189 bool
190 do_version(const string &script_ver, const string &dataset_ver)
191 {
192  fprintf(stdout, "HTTP/1.0 200 OK%s", CRLF) ;
193  fprintf(stdout, "XDODS-Server: %s%s", DVR, CRLF) ;
194  fprintf(stdout, "XOPeNDAP-Server: %s%s", DVR, CRLF) ;
195  fprintf(stdout, "XDAP: %s%s", DAP_PROTOCOL_VERSION, CRLF) ;
196  fprintf(stdout, "Content-Type: text/plain%s", CRLF) ;
197  fprintf(stdout, CRLF) ;
198 
199  fprintf(stdout, "Core software version: %s%s", DVR, CRLF) ;
200 
201  if (script_ver != "")
202  fprintf(stdout, "Server Script Revision: %s%s", script_ver.c_str(), CRLF) ;
203 
204  if (dataset_ver != "")
205  fprintf(stdout, "Dataset version: %s%s", dataset_ver.c_str(), CRLF) ;
206 
207  fflush(stdout) ; // Not sure this is needed. jhrg 12/23/05
208 
209  return true;
210 }
211 
222 void
223 ErrMsgT(const string &Msgt)
224 {
225  time_t TimBin;
226  char TimStr[TimLen];
227 
228  if (time(&TimBin) == (time_t) - 1)
229  strncpy(TimStr, "time() error ", TimLen-1);
230  else {
231  char ctime_value[TimLen];
232  const char *ret = ctime_r(&TimBin, ctime_value);
233  if (!ret)
234  strncpy(TimStr, "Unknown", TimLen-1);
235  else {
236  strncpy(TimStr, ctime_value, TimLen-1);
237  TimStr[TimLen - 2] = '\0'; // overwrite the \n
238  }
239  }
240 
241  cerr << "[" << TimStr << "] DAP server error: " << Msgt << endl;
242 }
243 
244 // Given a pathname, return just the filename component with any extension
245 // removed. The new string resides in newly allocated memory; the caller must
246 // delete it when done using the filename.
247 // Originally from the netcdf distribution (ver 2.3.2).
248 //
249 // *** Change to string class argument and return type. jhrg
250 // *** Changed so it also removes the#path#of#the#file# from decompressed
251 // files. rph.
252 // Returns: A filename, with path and extension information removed. If
253 // memory for the new name cannot be allocated, does not return!
254 
265 string
266 name_path(const string &path)
267 {
268  if (path == "")
269  return string("");
270 
271  string::size_type delim = path.find_last_of(FILE_DELIMITER);
272  string::size_type pound = path.find_last_of("#");
273  string new_path;
274 
275  if (pound != string::npos)
276  new_path = path.substr(pound + 1);
277  else
278  new_path = path.substr(delim + 1);
279 
280  return new_path;
281 }
282 
283 // Send string to set the transfer (mime) type and server version
284 // Note that the content description filed is used to indicate whether valid
285 // information of an error message is contained in the document and the
286 // content-encoding field is used to indicate whether the data is compressed.
287 // If the data stream is to be compressed, arrange for a compression output
288 // filter so that all information sent after the header will be compressed.
289 //
290 // Returns: false if the compression output filter was to be used but could
291 // not be started, true otherwise.
292 #if 0
293 static const char *descrip[] =
294  {"unknown", "dods_das", "dods_dds", "dods_data", "dods_ddx",
295  "dods_error", "web_error", "dap4-dmr", "dap4-data", "dap4-error"
296  };
297 #endif
298 
299 static const char *descrip[] = {
300 "unknown_type",
301 "dods_das",
302 "dods_dds",
303 "dods_data",
304 "dods_ddx", // This is the old XML DDS/DAS used prior to dap4
305 "dods_data_ddx", // This is used for caching data responses
306 "dods_error",
307 "web_error",
308 
309 "dap4_dmr", // DAP4 metadata
310 "dap4_data", // The DMR with a data blob
311 "dap4_error" // The error response for DAP4
312 };
313 
314 static const char *encoding[] =
315  {"unknown", "deflate", "x-plain", "gzip", "binary"
316  };
317 
324 get_type(const string &value)
325 {
326  return get_description_type(value);
327 }
328 
329 // TODO Recode to use the constants in media_types.h. jhrg 11/12/13
330 
336 ObjectType
337 get_description_type(const string &value)
338 {
339  if ((value == DAS1) || (value == "dods-das"))
340  return dods_das;
341  else if ((value == "dods_dds") || (value == "dods-dds"))
342  return dods_dds;
343  else if ((value == "dods_data") || (value == "dods-data"))
344  return dods_data;
345  else if ((value == "dods_ddx") || (value == "dods-ddx"))
346  return dods_ddx;
347  else if ((value == "dods_data_ddx" || (value == "dods-data-ddx")))
348  return dods_data_ddx;
349  else if ((value == "dods_error") || (value == "dods-error"))
350  return dods_error;
351  else if ((value == "web_error") || (value == "web-error"))
352  return web_error;
353 
354  else if ((value == "dap4_dmr") || (value == "dap4-dmr") || (value == DMR_Content_Type))
355  return dap4_dmr;
356  else if ((value == "dap4_data") || (value == "dap4-data") || (value == DAP4_DATA_Content_Type))
357  return dap4_data;
358  else if ((value == "dap4_error") || (value == "dap4-error"))
359  return dap4_error;
360 
361  else
362  return unknown_type;
363 }
364 
378 void
379 set_mime_text(FILE *out, ObjectType type, const string &ver,
380  EncodingType enc, const time_t last_modified)
381 {
382  ostringstream oss;
383  set_mime_text(oss, type, ver, enc, last_modified);
384  fwrite(oss.str().data(), 1, oss.str().length(), out);
385 }
386 
400 void
401 set_mime_text(ostream &strm, ObjectType type, const string &ver,
402  EncodingType enc, const time_t last_modified)
403 {
404  strm << "HTTP/1.0 200 OK" << CRLF ;
405  if (ver == "") {
406  strm << "XDODS-Server: " << DVR << CRLF ;
407  strm << "XOPeNDAP-Server: " << DVR << CRLF ;
408  }
409  else {
410  strm << "XDODS-Server: " << ver.c_str() << CRLF ;
411  strm << "XOPeNDAP-Server: " << ver.c_str() << CRLF ;
412  }
413  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
414 
415  const time_t t = time(0);
416  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
417 
418  strm << "Last-Modified: " ;
419  if (last_modified > 0)
420  strm << rfc822_date(last_modified).c_str() << CRLF ;
421  else
422  strm << rfc822_date(t).c_str() << CRLF ;
423 
424  if (type == dap4_dmr)
425  strm << "Content-Type: application/vnd.org.opendap.dap4.dataset-metadata+xml" << CRLF ;
426  else
427  strm << "Content-Type: text/plain" << CRLF ;
428 
429  // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
430  // jhrg 12/23/05
431  strm << "Content-Description: " << descrip[type] << CRLF ;
432  if (type == dods_error) // don't cache our error responses.
433  strm << "Cache-Control: no-cache" << CRLF ;
434  // Don't write a Content-Encoding header for x-plain since that breaks
435  // Netscape on NT. jhrg 3/23/97
436  if (enc != x_plain)
437  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
438  strm << CRLF ;
439 }
440 
456 void set_mime_text(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
457  const string &protocol)
458 {
459  strm << "HTTP/1.0 200 OK" << CRLF;
460 
461  strm << "XDODS-Server: " << DVR << CRLF;
462  strm << "XOPeNDAP-Server: " << DVR << CRLF;
463 
464  if (protocol == "")
465  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
466  else
467  strm << "XDAP: " << protocol << CRLF;
468 
469  const time_t t = time(0);
470  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
471 
472  strm << "Last-Modified: ";
473  if (last_modified > 0)
474  strm << rfc822_date(last_modified).c_str() << CRLF;
475  else
476  strm << rfc822_date(t).c_str() << CRLF;
477 
478  if (type == dap4_dmr)
479  strm << "Content-Type: application/vnd.org.opendap.dap4.dataset-metadata+xml" << CRLF;
480  else
481  strm << "Content-Type: text/plain" << CRLF;
482 
483  // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
484  // jhrg 12/23/05
485  strm << "Content-Description: " << descrip[type] << CRLF;
486  if (type == dods_error) // don't cache our error responses.
487  strm << "Cache-Control: no-cache" << CRLF;
488  // Don't write a Content-Encoding header for x-plain since that breaks
489  // Netscape on NT. jhrg 3/23/97
490  if (enc != x_plain)
491  strm << "Content-Encoding: " << encoding[enc] << CRLF;
492  strm << CRLF;
493 }
494 
506 void
507 set_mime_html(FILE *out, ObjectType type, const string &ver,
508  EncodingType enc, const time_t last_modified)
509 {
510  ostringstream oss;
511  set_mime_html(oss, type, ver, enc, last_modified);
512  fwrite(oss.str().data(), 1, oss.str().length(), out);
513 }
514 
526 void
527 set_mime_html(ostream &strm, ObjectType type, const string &ver,
528  EncodingType enc, const time_t last_modified)
529 {
530  strm << "HTTP/1.0 200 OK" << CRLF ;
531  if (ver == "") {
532  strm << "XDODS-Server: " << DVR << CRLF ;
533  strm << "XOPeNDAP-Server: " << DVR << CRLF ;
534  }
535  else {
536  strm << "XDODS-Server: " << ver.c_str() << CRLF ;
537  strm << "XOPeNDAP-Server: " << ver.c_str() << CRLF ;
538  }
539  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
540 
541  const time_t t = time(0);
542  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
543 
544  strm << "Last-Modified: " ;
545  if (last_modified > 0)
546  strm << rfc822_date(last_modified).c_str() << CRLF ;
547  else
548  strm << rfc822_date(t).c_str() << CRLF ;
549 
550  strm << "Content-type: text/html" << CRLF ;
551  // See note above about Content-Description header. jhrg 12/23/05
552  strm << "Content-Description: " << descrip[type] << CRLF ;
553  if (type == dods_error) // don't cache our error responses.
554  strm << "Cache-Control: no-cache" << CRLF ;
555  // Don't write a Content-Encoding header for x-plain since that breaks
556  // Netscape on NT. jhrg 3/23/97
557  if (enc != x_plain)
558  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
559  strm << CRLF ;
560 }
561 
572 void set_mime_html(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
573  const string &protocol)
574 {
575  strm << "HTTP/1.0 200 OK" << CRLF;
576 
577  strm << "XDODS-Server: " << DVR<< CRLF;
578  strm << "XOPeNDAP-Server: " << DVR<< CRLF;
579 
580  if (protocol == "")
581  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
582  else
583  strm << "XDAP: " << protocol << CRLF;
584 
585  const time_t t = time(0);
586  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
587 
588  strm << "Last-Modified: ";
589  if (last_modified > 0)
590  strm << rfc822_date(last_modified).c_str() << CRLF;
591  else
592  strm << rfc822_date(t).c_str() << CRLF;
593 
594  strm << "Content-type: text/html" << CRLF;
595  // See note above about Content-Description header. jhrg 12/23/05
596  strm << "Content-Description: " << descrip[type] << CRLF;
597  if (type == dods_error) // don't cache our error responses.
598  strm << "Cache-Control: no-cache" << CRLF;
599  // Don't write a Content-Encoding header for x-plain since that breaks
600  // Netscape on NT. jhrg 3/23/97
601  if (enc != x_plain)
602  strm << "Content-Encoding: " << encoding[enc] << CRLF;
603  strm << CRLF;
604 }
605 
620 void
621 set_mime_binary(FILE *out, ObjectType type, const string &ver,
622  EncodingType enc, const time_t last_modified)
623 {
624  ostringstream oss;
625  set_mime_binary(oss, type, ver, enc, last_modified);
626  fwrite(oss.str().data(), 1, oss.str().length(), out);
627 }
628 
643 void
644 set_mime_binary(ostream &strm, ObjectType type, const string &ver,
645  EncodingType enc, const time_t last_modified)
646 {
647  strm << "HTTP/1.0 200 OK" << CRLF ;
648  if (ver == "") {
649  strm << "XDODS-Server: " << DVR << CRLF ;
650  strm << "XOPeNDAP-Server: " << DVR << CRLF ;
651  }
652  else {
653  strm << "XDODS-Server: " << ver.c_str() << CRLF ;
654  strm << "XOPeNDAP-Server: " << ver.c_str() << CRLF ;
655  }
656  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
657 
658  const time_t t = time(0);
659  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
660 
661  strm << "Last-Modified: " ;
662  if (last_modified > 0)
663  strm << rfc822_date(last_modified).c_str() << CRLF ;
664  else
665  strm << rfc822_date(t).c_str() << CRLF ;
666 
667  strm << "Content-Type: application/octet-stream" << CRLF ;
668  strm << "Content-Description: " << descrip[type] << CRLF ;
669  if (enc != x_plain)
670  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
671 
672  strm << CRLF ;
673 }
674 
688 void set_mime_binary(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
689  const string &protocol)
690 {
691  strm << "HTTP/1.0 200 OK" << CRLF;
692 
693  strm << "XDODS-Server: " << DVR << CRLF;
694  strm << "XOPeNDAP-Server: " << DVR << CRLF;
695 
696  if (protocol.empty())
697  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
698  else
699  strm << "XDAP: " << protocol << CRLF;
700 
701  const time_t t = time(0);
702  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
703 
704  strm << "Last-Modified: ";
705  if (last_modified > 0)
706  strm << rfc822_date(last_modified).c_str() << CRLF;
707  else
708  strm << rfc822_date(t).c_str() << CRLF;
709 
710  strm << "Content-Type: application/octet-stream" << CRLF;
711  strm << "Content-Description: " << descrip[type] << CRLF;
712  if (enc != x_plain)
713  strm << "Content-Encoding: " << encoding[enc] << CRLF;
714 
715  strm << CRLF;
716 }
717 
718 void set_mime_multipart(ostream &strm, const string &boundary,
719  const string &start, ObjectType type,
720  const string &version, EncodingType enc,
721  const time_t last_modified)
722 {
723  strm << "HTTP/1.0 200 OK" << CRLF ;
724  if (version == "") {
725  strm << "XDODS-Server: " << DVR << CRLF ;
726  strm << "XOPeNDAP-Server: " << DVR << CRLF ;
727  }
728  else {
729  strm << "XDODS-Server: " << version.c_str() << CRLF ;
730  strm << "XOPeNDAP-Server: " << version.c_str() << CRLF ;
731  }
732  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
733 
734  const time_t t = time(0);
735  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
736 
737  strm << "Last-Modified: " ;
738  if (last_modified > 0)
739  strm << rfc822_date(last_modified).c_str() << CRLF ;
740  else
741  strm << rfc822_date(t).c_str() << CRLF ;
742 
743  strm << "Content-Type: Multipart/Related; boundary=" << boundary
744  << "; start=\"<" << start << ">\"; type=\"Text/xml\"" << CRLF ;
745  strm << "Content-Description: " << descrip[type] << CRLF ;
746  if (enc != x_plain)
747  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
748 
749  strm << CRLF ;
750 }
751 
754 void set_mime_multipart(ostream &strm, const string &boundary, const string &start, ObjectType type, EncodingType enc,
755  const time_t last_modified, const string &protocol, const string &url)
756 {
757  strm << "HTTP/1.1 200 OK" << CRLF;
758 
759  const time_t t = time(0);
760  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
761 
762  strm << "Last-Modified: ";
763  if (last_modified > 0)
764  strm << rfc822_date(last_modified).c_str() << CRLF;
765  else
766  strm << rfc822_date(t).c_str() << CRLF;
767 
768  strm << "Content-Type: multipart/related; boundary=" << boundary << "; start=\"<" << start
769  << ">\"; type=\"text/xml\"" << CRLF;
770 
771  // data-ddx;"; removed as a result of the merge of the hyrax 1.8 release
772  // branch.
773  strm << "Content-Description: " << descrip[type] << ";";
774  if (!url.empty())
775  strm << " url=\"" << url << "\"" << CRLF;
776  else
777  strm << CRLF;
778 
779  if (enc != x_plain)
780  strm << "Content-Encoding: " << encoding[enc] << CRLF;
781 
782  if (protocol == "")
783  strm << "X-DAP: " << DAP_PROTOCOL_VERSION << CRLF;
784  else
785  strm << "X-DAP: " << protocol << CRLF;
786 
787  strm << "X-OPeNDAP-Server: " << DVR<< CRLF;
788 
789  strm << CRLF;
790 }
791 
792 void set_mime_ddx_boundary(ostream &strm, const string &boundary,
793  const string &cid, ObjectType type, EncodingType enc)
794 {
795  strm << "--" << boundary << CRLF;
796  // TODO - Bite the bullet and make the encoding UTF-8 as required by dap4. This will break a lot of tests but the baselines could be amended using a bash script and sed.
797  strm << "Content-Type: Text/xml; charset=iso-8859-1" << CRLF;
798  strm << "Content-Id: <" << cid << ">" << CRLF;
799  strm << "Content-Description: " << descrip[type] << CRLF ;
800  if (enc != x_plain)
801  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
802 
803  strm << CRLF;
804 }
805 
806 void set_mime_data_boundary(ostream &strm, const string &boundary,
807  const string &cid, ObjectType type, EncodingType enc)
808 {
809  strm << "--" << boundary << CRLF;
810  strm << "Content-Type: application/octet-stream" << CRLF;
811  strm << "Content-Id: <" << cid << ">" << CRLF;
812  strm << "Content-Description: " << descrip[type] << CRLF ;
813  if (enc != x_plain)
814  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
815 
816  strm << CRLF;
817 }
818 
819 const size_t line_length = 1024;
820 
836 string get_next_mime_header(FILE *in)
837 {
838  // Get the header line and strip \r\n. Some headers end with just \n.
839  // If a blank line is found, return an empty string.
840  char line[line_length];
841  while (!feof(in)) {
842  if (fgets(line, line_length, in)
843  && (strncmp(line, CRLF, 2) == 0 || line[0] == '\n'))
844  return "";
845  else {
846  size_t slen = min(strlen(line), line_length); // Never > line_length
847  line[slen - 1] = '\0'; // remove the newline
848  if (line[slen - 2] == '\r') // ...and the preceding carriage return
849  line[slen - 2] = '\0';
850  return string(line);
851  }
852  }
853 
854  throw Error("I expected to find a MIME header, but got EOF instead.");
855 }
856 
869 string get_next_mime_header(istream &in)
870 {
871 #if 0
872  // Get the header line and strip \r\n. Some headers end with just \n.
873  // If a blank line is found, return an empty string.
874  char line[line_length];
875  while (!in.eof()) {
876  in.getline(line, line_length);
877  if (strncmp(line, CRLF, 2) == 0 || line[0] == '\n') {
878  return "";
879  }
880  else {
881  size_t slen = min(strlen(line), line_length); // Never > line_length
882  line[slen - 1] = '\0'; // remove the newline
883  if (line[slen - 2] == '\r') // ...and the preceding carriage return
884  line[slen - 2] = '\0';
885  return string(line);
886  }
887  }
888 #endif
889  // Get the header line and strip \r\n. Some headers end with just \n.
890  // If a blank line is found, return an empty string.
891  char raw_line[line_length];
892  while (!in.eof()) {
893  in.getline(raw_line, line_length); // strips the trailing newline; terminates with null
894  string line = raw_line;
895  if (line.find('\r') != string::npos)
896  line = line.substr(0, line.size()-1);
897  return line;
898  }
899 
900  throw Error("I expected to find a MIME header, but got EOF instead.");
901 }
902 
910 void parse_mime_header(const string &header, string &name, string &value)
911 {
912  istringstream iss(header);
913 
914  size_t length = header.length() + 1;
915  vector<char> s(length);
916  //char s[line_length];
917  iss.getline(s.data(), length, ':');
918  name = s.data();
919 
920  iss.ignore(length, ' ');
921  iss.getline(s.data(), length);
922  value = s.data();
923 
924  downcase(name);
925  downcase(value);
926 }
927 
939 bool is_boundary(const char *line, const string &boundary)
940 {
941  if (strlen(line) < 2 || !(line[0] == '-' && line[1] == '-'))
942  return false;
943  else
944  return strncmp(line, boundary.c_str(), boundary.length()) == 0;
945 }
946 
957 string read_multipart_boundary(FILE *in, const string &boundary)
958 {
959  string boundary_line = get_next_mime_header(in);
960  // If the caller passed in a value for the boundary, test for that value,
961  // else just see that this line starts with '--'.
962  // The value of 'boundary_line' is returned by this function.
963  if ((!boundary.empty() && is_boundary(boundary_line.c_str(), boundary))
964  || boundary_line.find("--") != 0)
965  throw Error(internal_error, "The DAP4 data response document is broken - missing or malformed boundary.");
966 
967  return boundary_line;
968 }
969 
970 string read_multipart_boundary(istream &in, const string &boundary)
971 {
972  string boundary_line = get_next_mime_header(in);
973  // If the caller passed in a value for the boundary, test for that value,
974  // else just see that this line starts with '--'.
975  // The value of 'boundary_line' is returned by this function.
976  if ((!boundary.empty() && is_boundary(boundary_line.c_str(), boundary))
977  || boundary_line.find("--") != 0)
978  throw Error(internal_error, "The DAP4 data response document is broken - missing or malformed boundary.");
979 
980  return boundary_line;
981 }
982 
1003 void read_multipart_headers(FILE *in, const string &content_type, const ObjectType object_type, const string &cid)
1004 {
1005  bool ct = false, cd = false, ci = false;
1006 
1007  // The function get_next_mime_header() returns tainted data. Fix this
1008  // if we are going to use this code. jhrg 4/18/17
1009  string header = get_next_mime_header(in);
1010  while (!header.empty()) {
1011  string name, value;
1012  parse_mime_header(header, name, value);
1013 
1014  if (name == "content-type") {
1015  ct = true;
1016  if (value.find(content_type) == string::npos)
1017  throw Error(internal_error, "Content-Type for this part of a DAP2 data ddx response must be " + content_type + ".");
1018  }
1019  else if (name == "content-description") {
1020  cd = true;
1021  if (get_description_type(value) != object_type)
1022  throw Error(internal_error, "Content-Description for this part of a DAP2 data ddx response must be dods-ddx or dods-data-ddx");
1023  }
1024  else if (name == "content-id") {
1025  ci = true;
1026  if (!cid.empty() && value != cid)
1027  throw Error("Content-Id mismatch. Expected: " + cid + ", but got: " + value);
1028  }
1029 
1030  header = get_next_mime_header(in);
1031  }
1032 
1033  if (!(ct && cd && ci)) throw Error(internal_error, "The DAP4 data response document is broken - missing header.");
1034 }
1035 
1036 void read_multipart_headers(istream &in, const string &content_type, const ObjectType object_type, const string &cid)
1037 {
1038  bool ct = false, cd = false, ci = false;
1039 
1040  string header = get_next_mime_header(in);
1041  while (!header.empty()) {
1042  string name, value;
1043  parse_mime_header(header, name, value);
1044 
1045  if (name == "content-type") {
1046  ct = true;
1047  if (value.find(content_type) == string::npos)
1048  throw Error(internal_error, "Content-Type for this part of a DAP4 data response must be " + content_type + ".");
1049  }
1050  else if (name == "content-description") {
1051  cd = true;
1052  if (get_description_type(value) != object_type)
1053  throw Error("Content-Description '" + value + "' not the expected value (expected: " + descrip[object_type] + ").");
1054  }
1055  else if (name == "content-id") {
1056  ci = true;
1057  if (!cid.empty() && value != cid)
1058  throw Error("Content-Id mismatch. Expected: " + cid + ", but got: " + value);
1059  }
1060 
1061  header = get_next_mime_header(in);
1062  }
1063 
1064  if (!(ct && cd && ci)) throw Error(internal_error, "The DAP4 data response document is broken - missing header.");
1065 }
1066 
1075 string cid_to_header_value(const string &cid)
1076 {
1077  string::size_type offset = cid.find("cid:");
1078  if (offset != 0)
1079  throw Error(internal_error, "expected CID to start with 'cid:'");
1080 
1081  string value = "<";
1082  value.append(cid.substr(offset + 4));
1083  value.append(">");
1084  downcase(value);
1085 
1086  return value;
1087 }
1088 
1097 void
1098 set_mime_error(FILE *out, int code, const string &reason,
1099  const string &version)
1100 {
1101  ostringstream oss;
1102  set_mime_error(oss, code, reason, version);
1103  fwrite(oss.str().data(), 1, oss.str().length(), out);
1104 }
1105 
1114 void
1115 set_mime_error(ostream &strm, int code, const string &reason,
1116  const string &version)
1117 {
1118  strm << "HTTP/1.0 " << code << " " << reason.c_str() << CRLF ;
1119  if (version == "") {
1120  strm << "XDODS-Server: " << DVR << CRLF ;
1121  strm << "XOPeNDAP-Server: " << DVR << CRLF ;
1122  }
1123  else {
1124  strm << "XDODS-Server: " << version.c_str() << CRLF ;
1125  strm << "XOPeNDAP-Server: " << version.c_str() << CRLF ;
1126  }
1127  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
1128 
1129  const time_t t = time(0);
1130  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
1131  strm << "Cache-Control: no-cache" << CRLF ;
1132  strm << CRLF ;
1133 }
1134 
1142 void
1144 {
1145  ostringstream oss;
1146  set_mime_not_modified(oss);
1147  fwrite(oss.str().data(), 1, oss.str().length(), out);
1148 }
1149 
1157 void
1159 {
1160  strm << "HTTP/1.0 304 NOT MODIFIED" << CRLF ;
1161  const time_t t = time(0);
1162  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
1163  strm << CRLF ;
1164 }
1165 
1166 #if 0
1167 
1168 // This was removed because it's not being used by our server.
1169 
1179 bool
1180 found_override(string name, string &doc)
1181 {
1182  ifstream ifs((name + ".ovr").c_str());
1183  if (!ifs)
1184  return false;
1185 
1186  char tmp[256];
1187  doc = "";
1188  while (!ifs.eof()) {
1189  ifs.getline(tmp, 255);
1190  tmp[255] = '\0';
1191  strncat(tmp, "\n", sizeof(tmp) - strlen(tmp) - 1);
1192  doc += tmp;
1193  }
1194 
1195  ifs.close();
1196  return true;
1197 }
1198 #endif
1199 
1209 bool
1211 {
1212  char tmp[256];
1213  while (!feof(in)) {
1214  char *s = fgets(tmp, 255, in);
1215  if (s && strncmp(s, CRLF, 2) == 0)
1216  return true;
1217  }
1218 
1219  return false;
1220 }
1221 
1226 void
1228 {
1229  while(!get_next_mime_header(in).empty()) ;
1230 #if 0
1231  string header;
1232  do {
1233  header = get_next_mime_header(in);
1234  } while (!header.empty());
1235 #endif
1236 }
1237 
1238 } // namespace libdap
1239 
A class for error processing.
Definition: Error.h:94
top level DAP object to house generic methods
Definition: AlarmHandler.h:36
string read_multipart_boundary(FILE *in, const string &boundary)
Definition: mime_util.cc:957
void set_mime_error(FILE *out, int code, const string &reason, const string &version)
Definition: mime_util.cc:1098
void set_mime_html(FILE *out, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified)
Definition: mime_util.cc:507
ObjectType get_description_type(const string &value)
Definition: mime_util.cc:337
string cid_to_header_value(const string &cid)
Definition: mime_util.cc:1075
void parse_mime_header(const string &header, string &name, string &value)
Definition: mime_util.cc:910
time_t last_modified_time(const string &name)
Definition: mime_util.cc:95
string name_path(const string &path)
Returns the filename portion of a pathname.
Definition: mime_util.cc:266
void set_mime_not_modified(FILE *out)
Send a ‘Not Modified’ response.
Definition: mime_util.cc:1143
void set_mime_binary(FILE *out, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified)
Definition: mime_util.cc:621
bool do_version(const string &script_ver, const string &dataset_ver)
Send a version number.
Definition: mime_util.cc:190
void downcase(string &s)
Definition: util.cc:566
bool remove_mime_header(FILE *in)
Read and discard the MIME header of the stream in.
Definition: mime_util.cc:1210
void read_multipart_headers(FILE *in, const string &content_type, const ObjectType object_type, const string &cid)
Definition: mime_util.cc:1003
EncodingType
The type of encoding used on the current stream.
Definition: EncodingType.h:48
void ErrMsgT(const string &Msgt)
Logs an error message.
Definition: mime_util.cc:223
ObjectType get_type(const string &value)
Definition: mime_util.cc:324
string rfc822_date(const time_t t)
Definition: mime_util.cc:156
bool is_boundary(const char *line, const string &boundary)
Definition: mime_util.cc:939
ObjectType
The type of object in the stream coming from the data server.
Definition: ObjectType.h:58
void set_mime_text(FILE *out, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified)
Definition: mime_util.cc:379
string get_next_mime_header(FILE *in)
Definition: mime_util.cc:836