libdap++  Updated for version 3.13.1
mime_util.cc
Go to the documentation of this file.
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 "Ancillary.h"
71 #include "util.h" // This supplies flush_stream for WIN32.
72 #include "debug.h"
73 
74 #ifdef WIN32
75 #define FILE_DELIMITER '\\'
76 #else // default to unix
77 #define FILE_DELIMITER '/'
78 #endif
79 
80 // ...not using a const string here to avoid global objects. jhrg 12/23/05
81 #define CRLF "\r\n" // Change here, expr-test.cc, in DODSFilter and ResponseBuilder
82 
83 using namespace std;
84 
85 namespace libdap {
86 
92 time_t
93 last_modified_time(const string &name)
94 {
95  struct stat m;
96 
97  if (stat(name.c_str(), &m) == 0 && (S_IFREG & m.st_mode))
98  return m.st_mtime;
99  else
100  return time(0);
101 }
102 // Return a MIME rfc-822 date. The grammar for this is:
103 // date-time = [ day "," ] date time ; dd mm yy
104 // ; hh:mm:ss zzz
105 //
106 // day = "Mon" / "Tue" / "Wed" / "Thu"
107 // / "Fri" / "Sat" / "Sun"
108 //
109 // date = 1*2DIGIT month 2DIGIT ; day month year
110 // ; e.g. 20 Jun 82
111 // NB: year is 4 digit; see RFC 1123. 11/30/99 jhrg
112 //
113 // month = "Jan" / "Feb" / "Mar" / "Apr"
114 // / "May" / "Jun" / "Jul" / "Aug"
115 // / "Sep" / "Oct" / "Nov" / "Dec"
116 //
117 // time = hour zone ; ANSI and Military
118 //
119 // hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT]
120 // ; 00:00:00 - 23:59:59
121 //
122 // zone = "UT" / "GMT" ; Universal Time
123 // ; North American : UT
124 // / "EST" / "EDT" ; Eastern: - 5/ - 4
125 // / "CST" / "CDT" ; Central: - 6/ - 5
126 // / "MST" / "MDT" ; Mountain: - 7/ - 6
127 // / "PST" / "PDT" ; Pacific: - 8/ - 7
128 // / 1ALPHA ; Military: Z = UT;
129 // ; A:-1; (J not used)
130 // ; M:-12; N:+1; Y:+12
131 // / ( ("+" / "-") 4DIGIT ) ; Local differential
132 // ; hours+min. (HHMM)
133 
134 static const char *days[] =
135  {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
136  };
137 static const char *months[] =
138  {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
139  "Aug", "Sep", "Oct", "Nov", "Dec"
140  };
141 
142 #ifdef _MSC_VER
143 #define snprintf sprintf_s
144 #endif
145 
153 string
154 rfc822_date(const time_t t)
155 {
156  struct tm *stm = gmtime(&t);
157  if (!stm)
158  return "";
159 
160  char d[256];
161 
162  snprintf(d, 255, "%s, %02d %s %4d %02d:%02d:%02d GMT", days[stm->tm_wday],
163  stm->tm_mday, months[stm->tm_mon],
164  1900 + stm->tm_year,
165  stm->tm_hour, stm->tm_min, stm->tm_sec);
166  d[255] = '\0';
167  return string(d);
168 }
169 
170 static const int TimLen = 26; // length of string from asctime()
171 //static const int CLUMP_SIZE = 1024; // size of clumps to new in fmakeword()
172 
186 bool
187 do_version(const string &script_ver, const string &dataset_ver)
188 {
189  fprintf(stdout, "HTTP/1.0 200 OK%s", CRLF) ;
190  fprintf(stdout, "XDODS-Server: %s%s", DVR, CRLF) ;
191  fprintf(stdout, "XOPeNDAP-Server: %s%s", DVR, CRLF) ;
192  fprintf(stdout, "XDAP: %s%s", DAP_PROTOCOL_VERSION, CRLF) ;
193  fprintf(stdout, "Content-Type: text/plain%s", CRLF) ;
194  fprintf(stdout, CRLF) ;
195 
196  fprintf(stdout, "Core software version: %s%s", DVR, CRLF) ;
197 
198  if (script_ver != "")
199  fprintf(stdout, "Server Script Revision: %s%s", script_ver.c_str(), CRLF) ;
200 
201  if (dataset_ver != "")
202  fprintf(stdout, "Dataset version: %s%s", dataset_ver.c_str(), CRLF) ;
203 
204  fflush(stdout) ; // Not sure this is needed. jhrg 12/23/05
205 
206  return true;
207 }
208 
219 void
220 ErrMsgT(const string &Msgt)
221 {
222  time_t TimBin;
223  char TimStr[TimLen];
224 
225  if (time(&TimBin) == (time_t) - 1)
226  strncpy(TimStr, "time() error ", TimLen-1);
227  else {
228  char *ctime_value = ctime(&TimBin);
229  if (!ctime_value)
230  strncpy(TimStr, "Unknown", TimLen-1);
231  else {
232  strncpy(TimStr, ctime_value, TimLen-1);
233  TimStr[TimLen - 2] = '\0'; // overwrite the \n
234  }
235 #if 0
236  strncpy(TimStr, ctime(&TimBin), TimLen-1);
237  TimStr[TimLen - 2] = '\0'; // overwrite the \n
238 #endif
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 
293 static const char *descrip[] =
294  {"unknown", "dods_das", "dods_dds", "dods_data",
295  "dods_error", "web_error", "dap4-ddx", "dap4-data", "dap4-error",
296  "dap4-data-ddx", "dods_ddx"
297  };
298 static const char *encoding[] =
299  {"unknown", "deflate", "x-plain", "gzip", "binary"
300  };
301 
308 get_type(const string &value)
309 {
310  if ((value == "dods_das") | (value == "dods-das"))
311  return dods_das;
312  else if ((value == "dods_dds") | (value == "dods-dds"))
313  return dods_dds;
314  else if ((value == "dods_data") | (value == "dods-data"))
315  return dods_data;
316  else if ((value == "dods_error") | (value == "dods-error"))
317  return dods_error;
318  else if ((value == "web_error") | (value == "web-error"))
319  return web_error;
320  else if ((value == "dap4_ddx") | (value == "dap4-ddx"))
321  return dap4_ddx;
322  else if ((value == "dap4_data") | (value == "dap4-data"))
323  return dap4_data;
324  else if ((value == "dap4_error") | (value == "dap4-error"))
325  return dap4_error;
326  else if ((value == "dap4_data_ddx") | (value == "dap4-data-ddx"))
327  return dap4_data_ddx;
328  else if ((value == "dods_ddx") | (value == "dods-ddx"))
329  return dods_ddx;
330  else
331  return unknown_type;
332 }
333 
340 get_description_type(const string &value)
341 {
342  if ((value == "dods_das") | (value == "dods-das"))
343  return dods_das;
344  else if ((value == "dods_dds") | (value == "dods-dds"))
345  return dods_dds;
346  else if ((value == "dods_data") | (value == "dods-data"))
347  return dods_data;
348  else if ((value == "dods_error") | (value == "dods-error"))
349  return dods_error;
350  else if ((value == "web_error") | (value == "web-error"))
351  return web_error;
352  else if ((value == "dods_ddx") | (value == "dods-ddx"))
353  return dods_ddx;
354  else if ((value == "dap4_ddx") | (value == "dap4-ddx"))
355  return dap4_ddx;
356  else if ((value == "dap4_data") | (value == "dap4-data"))
357  return dap4_data;
358  else if ((value == "dap4_error") | (value == "dap4-error"))
359  return dap4_error;
360  else if ((value == "dap4_data_ddx") | (value == "dap4-data-ddx"))
361  return dap4_data_ddx;
362  else if ((value == "dods_ddx") | (value == "dods-ddx"))
363  return dods_ddx;
364  else
365  return unknown_type;
366 }
367 
381 void
382 set_mime_text(FILE *out, ObjectType type, const string &ver,
383  EncodingType enc, const time_t last_modified)
384 {
385  ostringstream oss;
386  set_mime_text(oss, type, ver, enc, last_modified);
387  fwrite(oss.str().data(), 1, oss.str().length(), out);
388 }
389 
403 void
404 set_mime_text(ostream &strm, ObjectType type, const string &ver,
405  EncodingType enc, const time_t last_modified)
406 {
407  strm << "HTTP/1.0 200 OK" << CRLF ;
408  if (ver == "") {
409  strm << "XDODS-Server: " << DVR << CRLF ;
410  strm << "XOPeNDAP-Server: " << DVR << CRLF ;
411  }
412  else {
413  strm << "XDODS-Server: " << ver.c_str() << CRLF ;
414  strm << "XOPeNDAP-Server: " << ver.c_str() << CRLF ;
415  }
416  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
417 
418  const time_t t = time(0);
419  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
420 
421  strm << "Last-Modified: " ;
422  if (last_modified > 0)
423  strm << rfc822_date(last_modified).c_str() << CRLF ;
424  else
425  strm << rfc822_date(t).c_str() << CRLF ;
426 
427  if (type == dap4_ddx)
428  strm << "Content-Type: text/xml" << CRLF ;
429  else
430  strm << "Content-Type: text/plain" << CRLF ;
431 
432  // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
433  // jhrg 12/23/05
434  strm << "Content-Description: " << descrip[type] << CRLF ;
435  if (type == dods_error) // don't cache our error responses.
436  strm << "Cache-Control: no-cache" << CRLF ;
437  // Don't write a Content-Encoding header for x-plain since that breaks
438  // Netscape on NT. jhrg 3/23/97
439  if (enc != x_plain)
440  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
441  strm << CRLF ;
442 }
443 
459 void set_mime_text(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
460  const string &protocol)
461 {
462  strm << "HTTP/1.0 200 OK" << CRLF;
463 
464  strm << "XDODS-Server: " << DVR<< CRLF;
465  strm << "XOPeNDAP-Server: " << DVR<< CRLF;
466 
467  if (protocol == "")
468  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
469  else
470  strm << "XDAP: " << protocol << CRLF;
471 
472  const time_t t = time(0);
473  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
474 
475  strm << "Last-Modified: ";
476  if (last_modified > 0)
477  strm << rfc822_date(last_modified).c_str() << CRLF;
478  else
479  strm << rfc822_date(t).c_str() << CRLF;
480 
481  if (type == dap4_ddx)
482  strm << "Content-Type: text/xml" << CRLF;
483  else
484  strm << "Content-Type: text/plain" << CRLF;
485 
486  // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
487  // jhrg 12/23/05
488  strm << "Content-Description: " << descrip[type] << CRLF;
489  if (type == dods_error) // don't cache our error responses.
490  strm << "Cache-Control: no-cache" << CRLF;
491  // Don't write a Content-Encoding header for x-plain since that breaks
492  // Netscape on NT. jhrg 3/23/97
493  if (enc != x_plain)
494  strm << "Content-Encoding: " << encoding[enc] << CRLF;
495  strm << CRLF;
496 }
497 
509 void
510 set_mime_html(FILE *out, ObjectType type, const string &ver,
511  EncodingType enc, const time_t last_modified)
512 {
513  ostringstream oss;
514  set_mime_html(oss, type, ver, enc, last_modified);
515  fwrite(oss.str().data(), 1, oss.str().length(), out);
516 }
517 
529 void
530 set_mime_html(ostream &strm, ObjectType type, const string &ver,
531  EncodingType enc, const time_t last_modified)
532 {
533  strm << "HTTP/1.0 200 OK" << CRLF ;
534  if (ver == "") {
535  strm << "XDODS-Server: " << DVR << CRLF ;
536  strm << "XOPeNDAP-Server: " << DVR << CRLF ;
537  }
538  else {
539  strm << "XDODS-Server: " << ver.c_str() << CRLF ;
540  strm << "XOPeNDAP-Server: " << ver.c_str() << CRLF ;
541  }
542  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
543 
544  const time_t t = time(0);
545  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
546 
547  strm << "Last-Modified: " ;
548  if (last_modified > 0)
549  strm << rfc822_date(last_modified).c_str() << CRLF ;
550  else
551  strm << rfc822_date(t).c_str() << CRLF ;
552 
553  strm << "Content-type: text/html" << CRLF ;
554  // See note above about Content-Description header. jhrg 12/23/05
555  strm << "Content-Description: " << descrip[type] << CRLF ;
556  if (type == dods_error) // don't cache our error responses.
557  strm << "Cache-Control: no-cache" << CRLF ;
558  // Don't write a Content-Encoding header for x-plain since that breaks
559  // Netscape on NT. jhrg 3/23/97
560  if (enc != x_plain)
561  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
562  strm << CRLF ;
563 }
564 
575 void set_mime_html(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
576  const string &protocol)
577 {
578  strm << "HTTP/1.0 200 OK" << CRLF;
579 
580  strm << "XDODS-Server: " << DVR<< CRLF;
581  strm << "XOPeNDAP-Server: " << DVR<< CRLF;
582 
583  if (protocol == "")
584  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
585  else
586  strm << "XDAP: " << protocol << CRLF;
587 
588  const time_t t = time(0);
589  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
590 
591  strm << "Last-Modified: ";
592  if (last_modified > 0)
593  strm << rfc822_date(last_modified).c_str() << CRLF;
594  else
595  strm << rfc822_date(t).c_str() << CRLF;
596 
597  strm << "Content-type: text/html" << CRLF;
598  // See note above about Content-Description header. jhrg 12/23/05
599  strm << "Content-Description: " << descrip[type] << CRLF;
600  if (type == dods_error) // don't cache our error responses.
601  strm << "Cache-Control: no-cache" << CRLF;
602  // Don't write a Content-Encoding header for x-plain since that breaks
603  // Netscape on NT. jhrg 3/23/97
604  if (enc != x_plain)
605  strm << "Content-Encoding: " << encoding[enc] << CRLF;
606  strm << CRLF;
607 }
608 
623 void
624 set_mime_binary(FILE *out, ObjectType type, const string &ver,
625  EncodingType enc, const time_t last_modified)
626 {
627  ostringstream oss;
628  set_mime_binary(oss, type, ver, enc, last_modified);
629  fwrite(oss.str().data(), 1, oss.str().length(), out);
630 }
631 
646 void
647 set_mime_binary(ostream &strm, ObjectType type, const string &ver,
648  EncodingType enc, const time_t last_modified)
649 {
650  strm << "HTTP/1.0 200 OK" << CRLF ;
651  if (ver == "") {
652  strm << "XDODS-Server: " << DVR << CRLF ;
653  strm << "XOPeNDAP-Server: " << DVR << CRLF ;
654  }
655  else {
656  strm << "XDODS-Server: " << ver.c_str() << CRLF ;
657  strm << "XOPeNDAP-Server: " << ver.c_str() << CRLF ;
658  }
659  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
660 
661  const time_t t = time(0);
662  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
663 
664  strm << "Last-Modified: " ;
665  if (last_modified > 0)
666  strm << rfc822_date(last_modified).c_str() << CRLF ;
667  else
668  strm << rfc822_date(t).c_str() << CRLF ;
669 
670  strm << "Content-Type: application/octet-stream" << CRLF ;
671  strm << "Content-Description: " << descrip[type] << CRLF ;
672  if (enc != x_plain)
673  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
674 
675  strm << CRLF ;
676 }
677 
691 void set_mime_binary(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
692  const string &protocol)
693 {
694  strm << "HTTP/1.0 200 OK" << CRLF;
695 
696  strm << "XDODS-Server: " << DVR << CRLF;
697  strm << "XOPeNDAP-Server: " << DVR << CRLF;
698 
699  if (protocol == "")
700  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
701  else
702  strm << "XDAP: " << protocol << CRLF;
703 
704  const time_t t = time(0);
705  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
706 
707  strm << "Last-Modified: ";
708  if (last_modified > 0)
709  strm << rfc822_date(last_modified).c_str() << CRLF;
710  else
711  strm << rfc822_date(t).c_str() << CRLF;
712 
713  strm << "Content-Type: application/octet-stream" << CRLF;
714  strm << "Content-Description: " << descrip[type] << CRLF;
715  if (enc != x_plain)
716  strm << "Content-Encoding: " << encoding[enc] << CRLF;
717 
718  strm << CRLF;
719 }
720 
721 void set_mime_multipart(ostream &strm, const string &boundary,
722  const string &start, ObjectType type,
723  const string &version, EncodingType enc,
724  const time_t last_modified)
725 {
726  strm << "HTTP/1.0 200 OK" << CRLF ;
727  if (version == "") {
728  strm << "XDODS-Server: " << DVR << CRLF ;
729  strm << "XOPeNDAP-Server: " << DVR << CRLF ;
730  }
731  else {
732  strm << "XDODS-Server: " << version.c_str() << CRLF ;
733  strm << "XOPeNDAP-Server: " << version.c_str() << CRLF ;
734  }
735  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
736 
737  const time_t t = time(0);
738  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
739 
740  strm << "Last-Modified: " ;
741  if (last_modified > 0)
742  strm << rfc822_date(last_modified).c_str() << CRLF ;
743  else
744  strm << rfc822_date(t).c_str() << CRLF ;
745 
746  strm << "Content-Type: Multipart/Related; boundary=" << boundary
747  << "; start=\"<" << start << ">\"; type=\"Text/xml\"" << CRLF ;
748  strm << "Content-Description: " << descrip[type] << CRLF ;
749  if (enc != x_plain)
750  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
751 
752  strm << CRLF ;
753 }
754 
757 void set_mime_multipart(ostream &strm, const string &boundary, const string &start, ObjectType type, EncodingType enc,
758  const time_t last_modified, const string &protocol, const string &url)
759 {
760  strm << "HTTP/1.1 200 OK" << CRLF;
761 
762  const time_t t = time(0);
763  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
764 
765  strm << "Last-Modified: ";
766  if (last_modified > 0)
767  strm << rfc822_date(last_modified).c_str() << CRLF;
768  else
769  strm << rfc822_date(t).c_str() << CRLF;
770 
771  strm << "Content-Type: multipart/related; boundary=" << boundary << "; start=\"<" << start
772  << ">\"; type=\"text/xml\"" << CRLF;
773 
774  // data-ddx;"; removed as a result of the merge of the hyrax 1.8 release
775  // branch.
776  strm << "Content-Description: " << descrip[type] << ";";
777  if (!url.empty())
778  strm << " url=\"" << url << "\"" << CRLF;
779  else
780  strm << CRLF;
781 
782  if (enc != x_plain)
783  strm << "Content-Encoding: " << encoding[enc] << CRLF;
784 
785  if (protocol == "")
786  strm << "X-DAP: " << DAP_PROTOCOL_VERSION << CRLF;
787  else
788  strm << "X-DAP: " << protocol << CRLF;
789 
790  strm << "X-OPeNDAP-Server: " << DVR<< CRLF;
791 
792  strm << CRLF;
793 }
794 
795 void set_mime_ddx_boundary(ostream &strm, const string &boundary,
796  const string &cid, ObjectType type, EncodingType enc)
797 {
798  strm << "--" << boundary << CRLF;
799  strm << "Content-Type: Text/xml; charset=iso-8859-1" << CRLF;
800  strm << "Content-Id: <" << cid << ">" << CRLF;
801  strm << "Content-Description: " << descrip[type] << CRLF ;
802  if (enc != x_plain)
803  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
804 
805  strm << CRLF;
806 }
807 
808 void set_mime_data_boundary(ostream &strm, const string &boundary,
809  const string &cid, ObjectType type, EncodingType enc)
810 {
811  strm << "--" << boundary << CRLF;
812  strm << "Content-Type: application/octet-stream" << CRLF;
813  strm << "Content-Id: <" << cid << ">" << CRLF;
814  strm << "Content-Description: " << descrip[type] << CRLF ;
815  if (enc != x_plain)
816  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
817 
818  strm << CRLF;
819 }
820 
821 const size_t line_length = 1024;
822 
837 string get_next_mime_header(FILE *in)
838 {
839  // Get the header line and strip \r\n. Some headers end with just \n.
840  // If a blank line is found, return an empty string.
841  char line[line_length];
842  while (!feof(in)) {
843  if (fgets(line, line_length, in)
844  && (strncmp(line, CRLF, 2) == 0 || line[0] == '\n'))
845  return "";
846  else {
847  size_t slen = min(strlen(line), line_length); // Never > line_length
848  line[slen - 1] = '\0'; // remove the newline
849  if (line[slen - 2] == '\r') // ...and the preceding carriage return
850  line[slen - 2] = '\0';
851  return string(line);
852  }
853  }
854 
855  throw Error("I expected to find a MIME header, but got EOF instead.");
856 }
857 
858 string get_next_mime_header(istream &in)
859 {
860 #if 0
861  // Get the header line and strip \r\n. Some headers end with just \n.
862  // If a blank line is found, return an empty string.
863  char line[line_length];
864  while (!in.eof()) {
865  in.getline(line, line_length);
866  if (strncmp(line, CRLF, 2) == 0 || line[0] == '\n') {
867  return "";
868  }
869  else {
870  size_t slen = min(strlen(line), line_length); // Never > line_length
871  line[slen - 1] = '\0'; // remove the newline
872  if (line[slen - 2] == '\r') // ...and the preceding carriage return
873  line[slen - 2] = '\0';
874  return string(line);
875  }
876  }
877 #endif
878  // Get the header line and strip \r\n. Some headers end with just \n.
879  // If a blank line is found, return an empty string.
880  char raw_line[line_length];
881  while (!in.eof()) {
882  in.getline(raw_line, line_length); // strips the trailing newline; terminates with null
883  string line = raw_line;
884  if (line.find('\r') != string::npos)
885  line = line.substr(0, line.size()-1);
886  return line;
887  }
888 
889  throw Error("I expected to find a MIME header, but got EOF instead.");
890 }
891 
899 void parse_mime_header(const string &header, string &name, string &value)
900 {
901  istringstream iss(header);
902  // Set downcase
903  char s[line_length];
904  iss.getline(s, 1023, ':');
905  name = s;
906 
907  iss.ignore(1023, ' ');
908  iss.getline(s, 1023);
909  value = s;
910 
911  downcase(name);
912  downcase(value);
913 }
914 
926 bool is_boundary(const char *line, const string &boundary)
927 {
928  if (strlen(line) < 2 || !(line[0] == '-' && line[1] == '-'))
929  return false;
930  else
931  return strncmp(line, boundary.c_str(), boundary.length()) == 0;
932 }
933 
944 string read_multipart_boundary(FILE *in, const string &boundary)
945 {
946  string boundary_line = get_next_mime_header(in);
947  // If the caller passed in a value for the boundary, test for that value,
948  // else just see that this line starts with '--'.
949  // The value of 'boundary_line' is returned by this function.
950  if ((!boundary.empty() && is_boundary(boundary_line.c_str(), boundary))
951  || boundary_line.find("--") != 0)
952  throw Error(
953  "The DAP4 data response document is broken - missing or malformed boundary.");
954 
955  return boundary_line;
956 }
957 
958 string read_multipart_boundary(istream &in, const string &boundary)
959 {
960  string boundary_line = get_next_mime_header(in);
961  // If the caller passed in a value for the boundary, test for that value,
962  // else just see that this line starts with '--'.
963  // The value of 'boundary_line' is returned by this function.
964  if ((!boundary.empty() && is_boundary(boundary_line.c_str(), boundary))
965  || boundary_line.find("--") != 0)
966  throw Error(
967  "The DAP4 data response document is broken - missing or malformed boundary.");
968 
969  return boundary_line;
970 }
971 
992 void read_multipart_headers(FILE *in, const string &content_type, const ObjectType object_type, const string &cid)
993 {
994  bool ct = false, cd = false, ci = false;
995 
996  string header = get_next_mime_header(in);
997  while (!header.empty()) {
998  string name, value;
999  parse_mime_header(header, name, value);
1000 
1001  if (name == "content-type") {
1002  ct = true;
1003  if (value.find(content_type) == string::npos)
1004  throw Error("Content-Type for this part of a DAP4 data response must be " + content_type + ".");
1005  }
1006  else if (name == "content-description") {
1007  cd = true;
1008  if (get_description_type(value) != object_type)
1009  throw Error(
1010  "Content-Description for this part of a DAP4 data response must be dap4-ddx or dap4-data-ddx");
1011  }
1012  else if (name == "content-id") {
1013  ci = true;
1014  if (!cid.empty() && value != cid)
1015  throw Error("Content-Id mismatch. Expected: " + cid + ", but got: " + value);
1016  }
1017 
1018  header = get_next_mime_header(in);
1019  }
1020 
1021  if (!(ct && cd && ci)) throw Error("The DAP4 data response document is broken - missing header.");
1022 }
1023 
1024 void read_multipart_headers(istream &in, const string &content_type, const ObjectType object_type, const string &cid)
1025 {
1026  bool ct = false, cd = false, ci = false;
1027 
1028  string header = get_next_mime_header(in);
1029  while (!header.empty()) {
1030  string name, value;
1031  parse_mime_header(header, name, value);
1032 
1033  if (name == "content-type") {
1034  ct = true;
1035  if (value.find(content_type) == string::npos)
1036  throw Error("Content-Type for this part of a DAP4 data response must be " + content_type + ".");
1037  }
1038  else if (name == "content-description") {
1039  cd = true;
1040  if (get_description_type(value) != object_type)
1041  throw Error(
1042  "Content-Description for this part of a DAP4 data response must be dap4-ddx or dap4-data-ddx");
1043  }
1044  else if (name == "content-id") {
1045  ci = true;
1046  if (!cid.empty() && value != cid)
1047  throw Error("Content-Id mismatch. Expected: " + cid + ", but got: " + value);
1048  }
1049 
1050  header = get_next_mime_header(in);
1051  }
1052 
1053  if (!(ct && cd && ci)) throw Error("The DAP4 data response document is broken - missing header.");
1054 }
1055 
1064 string cid_to_header_value(const string &cid)
1065 {
1066  string::size_type offset = cid.find("cid:");
1067  if (offset != 0)
1068  throw Error("expected CID to start with 'cid:'");
1069 
1070  string value = "<";
1071  value.append(cid.substr(offset + 4));
1072  value.append(">");
1073  downcase(value);
1074 
1075  return value;
1076 }
1077 
1086 void
1087 set_mime_error(FILE *out, int code, const string &reason,
1088  const string &version)
1089 {
1090  ostringstream oss;
1091  set_mime_error(oss, code, reason, version);
1092  fwrite(oss.str().data(), 1, oss.str().length(), out);
1093 }
1094 
1103 void
1104 set_mime_error(ostream &strm, int code, const string &reason,
1105  const string &version)
1106 {
1107  strm << "HTTP/1.0 " << code << " " << reason.c_str() << CRLF ;
1108  if (version == "") {
1109  strm << "XDODS-Server: " << DVR << CRLF ;
1110  strm << "XOPeNDAP-Server: " << DVR << CRLF ;
1111  }
1112  else {
1113  strm << "XDODS-Server: " << version.c_str() << CRLF ;
1114  strm << "XOPeNDAP-Server: " << version.c_str() << CRLF ;
1115  }
1116  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
1117 
1118  const time_t t = time(0);
1119  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
1120  strm << "Cache-Control: no-cache" << CRLF ;
1121  strm << CRLF ;
1122 }
1123 
1131 void
1133 {
1134  ostringstream oss;
1135  set_mime_not_modified(oss);
1136  fwrite(oss.str().data(), 1, oss.str().length(), out);
1137 }
1138 
1146 void
1148 {
1149  strm << "HTTP/1.0 304 NOT MODIFIED" << CRLF ;
1150  const time_t t = time(0);
1151  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
1152  strm << CRLF ;
1153 }
1154 
1155 #if 0
1156 
1157 // This was removed because it's not being used by our server.
1158 
1168 bool
1169 found_override(string name, string &doc)
1170 {
1171  ifstream ifs((name + ".ovr").c_str());
1172  if (!ifs)
1173  return false;
1174 
1175  char tmp[256];
1176  doc = "";
1177  while (!ifs.eof()) {
1178  ifs.getline(tmp, 255);
1179  tmp[255] = '\0';
1180  strncat(tmp, "\n", sizeof(tmp) - strlen(tmp) - 1);
1181  doc += tmp;
1182  }
1183 
1184  ifs.close();
1185  return true;
1186 }
1187 #endif
1188 
1198 bool
1200 {
1201  char tmp[256];
1202  while (!feof(in)) {
1203  char *s = fgets(tmp, 255, in);
1204  if (s && strncmp(s, CRLF, 2) == 0)
1205  return true;
1206  }
1207 
1208  return false;
1209 }
1210 
1211 } // namespace libdap
1212 
const size_t line_length
Definition: mime_util.cc:821
#define CRLF
Definition: mime_util.cc:81
void ErrMsgT(const string &Msgt)
Logs an error message.
Definition: mime_util.cc:220
void set_mime_data_boundary(ostream &strm, const string &boundary, const string &cid, ObjectType type, EncodingType enc)
Definition: mime_util.cc:808
time_t last_modified_time(const string &name)
Definition: mime_util.cc:93
void downcase(string &s)
Definition: util.cc:428
void read_multipart_headers(FILE *in, const string &content_type, const ObjectType object_type, const string &cid)
Definition: mime_util.cc:992
#define FILE_DELIMITER
Definition: mime_util.cc:77
ObjectType
The type of object in the stream coming from the data server.
Definition: ObjectType.h:57
string cid_to_header_value(const string &cid)
Definition: mime_util.cc:1064
void set_mime_text(FILE *out, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified)
Definition: mime_util.cc:382
void parse_mime_header(const string &header, string &name, string &value)
Definition: mime_util.cc:899
const char * version
Definition: getdap.cc:60
ObjectType get_description_type(const string &value)
Definition: mime_util.cc:340
bool remove_mime_header(FILE *in)
Read and discard the MIME header of the stream in.
Definition: mime_util.cc:1199
void set_mime_ddx_boundary(ostream &strm, const string &boundary, const string &cid, ObjectType type, EncodingType enc)
Definition: mime_util.cc:795
ObjectType get_type(const string &value)
Definition: mime_util.cc:308
string get_next_mime_header(FILE *in)
Definition: mime_util.cc:837
void set_mime_binary(FILE *out, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified)
Definition: mime_util.cc:624
string read_multipart_boundary(FILE *in, const string &boundary)
Definition: mime_util.cc:944
#define DVR
Definition: config.h:85
void set_mime_error(FILE *out, int code, const string &reason, const string &version)
Definition: mime_util.cc:1087
string rfc822_date(const time_t t)
Definition: mime_util.cc:154
void set_mime_not_modified(FILE *out)
Send a `Not Modified&#39; response.
Definition: mime_util.cc:1132
string name_path(const string &path)
Returns the filename portion of a pathname.
Definition: mime_util.cc:266
bool is_boundary(const char *line, const string &boundary)
Definition: mime_util.cc:926
void set_mime_html(FILE *out, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified)
Definition: mime_util.cc:510
void set_mime_multipart(ostream &strm, const string &boundary, const string &start, ObjectType type, const string &version, EncodingType enc, const time_t last_modified)
Definition: mime_util.cc:721
EncodingType
The type of encoding used on the current stream.
Definition: EncodingType.h:48
A class for error processing.
Definition: Error.h:90
#define DAP_PROTOCOL_VERSION
Definition: config.h:46
bool do_version(const string &script_ver, const string &dataset_ver)
Send a version number.
Definition: mime_util.cc:187