libdap  Updated for version 3.20.6
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
147 
155 string
156 rfc822_date(const time_t t)
157 {
158  struct tm *stm = gmtime(&t);
159  if (!stm)
160  return "";
161 
162  char d[256];
163 
164  snprintf(d, 255, "%s, %02d %s %4d %02d:%02d:%02d GMT", days[stm->tm_wday],
165  stm->tm_mday, months[stm->tm_mon],
166  1900 + stm->tm_year,
167  stm->tm_hour, stm->tm_min, stm->tm_sec);
168  d[255] = '\0';
169  return string(d);
170 }
171 
172 static const int TimLen = 26; // length of string from asctime()
173 //static const int CLUMP_SIZE = 1024; // size of clumps to new in fmakeword()
174 
188 bool
189 do_version(const string &script_ver, const string &dataset_ver)
190 {
191  fprintf(stdout, "HTTP/1.0 200 OK%s", CRLF) ;
192  fprintf(stdout, "XDODS-Server: %s%s", DVR, CRLF) ;
193  fprintf(stdout, "XOPeNDAP-Server: %s%s", DVR, CRLF) ;
194  fprintf(stdout, "XDAP: %s%s", DAP_PROTOCOL_VERSION, CRLF) ;
195  fprintf(stdout, "Content-Type: text/plain%s", CRLF) ;
196  fprintf(stdout, CRLF) ;
197 
198  fprintf(stdout, "Core software version: %s%s", DVR, CRLF) ;
199 
200  if (script_ver != "")
201  fprintf(stdout, "Server Script Revision: %s%s", script_ver.c_str(), CRLF) ;
202 
203  if (dataset_ver != "")
204  fprintf(stdout, "Dataset version: %s%s", dataset_ver.c_str(), CRLF) ;
205 
206  fflush(stdout) ; // Not sure this is needed. jhrg 12/23/05
207 
208  return true;
209 }
210 
221 void
222 ErrMsgT(const string &Msgt)
223 {
224  time_t TimBin;
225  char TimStr[TimLen];
226 
227  if (time(&TimBin) == (time_t) - 1)
228  strncpy(TimStr, "time() error ", TimLen-1);
229  else {
230  char *ctime_value = ctime(&TimBin);
231  if (!ctime_value)
232  strncpy(TimStr, "Unknown", TimLen-1);
233  else {
234  strncpy(TimStr, ctime_value, TimLen-1);
235  TimStr[TimLen - 2] = '\0'; // overwrite the \n
236  }
237 #if 0
238  strncpy(TimStr, ctime(&TimBin), TimLen-1);
239  TimStr[TimLen - 2] = '\0'; // overwrite the \n
240 #endif
241  }
242 
243  cerr << "[" << TimStr << "] DAP server error: " << Msgt << endl;
244 }
245 
246 // Given a pathname, return just the filename component with any extension
247 // removed. The new string resides in newly allocated memory; the caller must
248 // delete it when done using the filename.
249 // Originally from the netcdf distribution (ver 2.3.2).
250 //
251 // *** Change to string class argument and return type. jhrg
252 // *** Changed so it also removes the#path#of#the#file# from decompressed
253 // files. rph.
254 // Returns: A filename, with path and extension information removed. If
255 // memory for the new name cannot be allocated, does not return!
256 
267 string
268 name_path(const string &path)
269 {
270  if (path == "")
271  return string("");
272 
273  string::size_type delim = path.find_last_of(FILE_DELIMITER);
274  string::size_type pound = path.find_last_of("#");
275  string new_path;
276 
277  if (pound != string::npos)
278  new_path = path.substr(pound + 1);
279  else
280  new_path = path.substr(delim + 1);
281 
282  return new_path;
283 }
284 
285 // Send string to set the transfer (mime) type and server version
286 // Note that the content description filed is used to indicate whether valid
287 // information of an error message is contained in the document and the
288 // content-encoding field is used to indicate whether the data is compressed.
289 // If the data stream is to be compressed, arrange for a compression output
290 // filter so that all information sent after the header will be compressed.
291 //
292 // Returns: false if the compression output filter was to be used but could
293 // not be started, true otherwise.
294 #if 0
295 static const char *descrip[] =
296  {"unknown", "dods_das", "dods_dds", "dods_data", "dods_ddx",
297  "dods_error", "web_error", "dap4-dmr", "dap4-data", "dap4-error"
298  };
299 #endif
300 
301 static const char *descrip[] = {
302 "unknown_type",
303 "dods_das",
304 "dods_dds",
305 "dods_data",
306 "dods_ddx", // This is the old XML DDS/DAS used prior to dap4
307 "dods_data_ddx", // This is used for caching data responses
308 "dods_error",
309 "web_error",
310 
311 "dap4_dmr", // DAP4 metadata
312 "dap4_data", // The DMR with a data blob
313 "dap4_error" // The error response for DAP4
314 };
315 
316 static const char *encoding[] =
317  {"unknown", "deflate", "x-plain", "gzip", "binary"
318  };
319 
326 get_type(const string &value)
327 {
328  return get_description_type(value);
329 }
330 
331 // TODO Recode to use the constants in media_types.h. jhrg 11/12/13
332 
339 get_description_type(const string &value)
340 {
341  if ((value == DAS1) || (value == "dods-das"))
342  return dods_das;
343  else if ((value == "dods_dds") || (value == "dods-dds"))
344  return dods_dds;
345  else if ((value == "dods_data") || (value == "dods-data"))
346  return dods_data;
347  else if ((value == "dods_ddx") || (value == "dods-ddx"))
348  return dods_ddx;
349  else if ((value == "dods_data_ddx" || (value == "dods-data-ddx")))
350  return dods_data_ddx;
351  else if ((value == "dods_error") || (value == "dods-error"))
352  return dods_error;
353  else if ((value == "web_error") || (value == "web-error"))
354  return web_error;
355 
356  else if ((value == "dap4_dmr") || (value == "dap4-dmr") || (value == DMR_Content_Type))
357  return dap4_dmr;
358  else if ((value == "dap4_data") || (value == "dap4-data") || (value == DAP4_DATA_Content_Type))
359  return dap4_data;
360  else if ((value == "dap4_error") || (value == "dap4-error"))
361  return dap4_error;
362 
363  else
364  return unknown_type;
365 }
366 
380 void
381 set_mime_text(FILE *out, ObjectType type, const string &ver,
382  EncodingType enc, const time_t last_modified)
383 {
384  ostringstream oss;
385  set_mime_text(oss, type, ver, enc, last_modified);
386  fwrite(oss.str().data(), 1, oss.str().length(), out);
387 }
388 
402 void
403 set_mime_text(ostream &strm, ObjectType type, const string &ver,
404  EncodingType enc, const time_t last_modified)
405 {
406  strm << "HTTP/1.0 200 OK" << CRLF ;
407  if (ver == "") {
408  strm << "XDODS-Server: " << DVR << CRLF ;
409  strm << "XOPeNDAP-Server: " << DVR << CRLF ;
410  }
411  else {
412  strm << "XDODS-Server: " << ver.c_str() << CRLF ;
413  strm << "XOPeNDAP-Server: " << ver.c_str() << CRLF ;
414  }
415  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
416 
417  const time_t t = time(0);
418  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
419 
420  strm << "Last-Modified: " ;
421  if (last_modified > 0)
422  strm << rfc822_date(last_modified).c_str() << CRLF ;
423  else
424  strm << rfc822_date(t).c_str() << CRLF ;
425 
426  if (type == dap4_dmr)
427  strm << "Content-Type: application/vnd.org.opendap.dap4.dataset-metadata+xml" << CRLF ;
428  else
429  strm << "Content-Type: text/plain" << CRLF ;
430 
431  // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
432  // jhrg 12/23/05
433  strm << "Content-Description: " << descrip[type] << CRLF ;
434  if (type == dods_error) // don't cache our error responses.
435  strm << "Cache-Control: no-cache" << CRLF ;
436  // Don't write a Content-Encoding header for x-plain since that breaks
437  // Netscape on NT. jhrg 3/23/97
438  if (enc != x_plain)
439  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
440  strm << CRLF ;
441 }
442 
458 void set_mime_text(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
459  const string &protocol)
460 {
461  strm << "HTTP/1.0 200 OK" << CRLF;
462 
463  strm << "XDODS-Server: " << DVR << CRLF;
464  strm << "XOPeNDAP-Server: " << DVR << CRLF;
465 
466  if (protocol == "")
467  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
468  else
469  strm << "XDAP: " << protocol << CRLF;
470 
471  const time_t t = time(0);
472  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
473 
474  strm << "Last-Modified: ";
475  if (last_modified > 0)
476  strm << rfc822_date(last_modified).c_str() << CRLF;
477  else
478  strm << rfc822_date(t).c_str() << CRLF;
479 
480  if (type == dap4_dmr)
481  strm << "Content-Type: application/vnd.org.opendap.dap4.dataset-metadata+xml" << CRLF;
482  else
483  strm << "Content-Type: text/plain" << CRLF;
484 
485  // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
486  // jhrg 12/23/05
487  strm << "Content-Description: " << descrip[type] << CRLF;
488  if (type == dods_error) // don't cache our error responses.
489  strm << "Cache-Control: no-cache" << CRLF;
490  // Don't write a Content-Encoding header for x-plain since that breaks
491  // Netscape on NT. jhrg 3/23/97
492  if (enc != x_plain)
493  strm << "Content-Encoding: " << encoding[enc] << CRLF;
494  strm << CRLF;
495 }
496 
508 void
509 set_mime_html(FILE *out, ObjectType type, const string &ver,
510  EncodingType enc, const time_t last_modified)
511 {
512  ostringstream oss;
513  set_mime_html(oss, type, ver, enc, last_modified);
514  fwrite(oss.str().data(), 1, oss.str().length(), out);
515 }
516 
528 void
529 set_mime_html(ostream &strm, ObjectType type, const string &ver,
530  EncodingType enc, const time_t last_modified)
531 {
532  strm << "HTTP/1.0 200 OK" << CRLF ;
533  if (ver == "") {
534  strm << "XDODS-Server: " << DVR << CRLF ;
535  strm << "XOPeNDAP-Server: " << DVR << CRLF ;
536  }
537  else {
538  strm << "XDODS-Server: " << ver.c_str() << CRLF ;
539  strm << "XOPeNDAP-Server: " << ver.c_str() << CRLF ;
540  }
541  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
542 
543  const time_t t = time(0);
544  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
545 
546  strm << "Last-Modified: " ;
547  if (last_modified > 0)
548  strm << rfc822_date(last_modified).c_str() << CRLF ;
549  else
550  strm << rfc822_date(t).c_str() << CRLF ;
551 
552  strm << "Content-type: text/html" << CRLF ;
553  // See note above about Content-Description header. jhrg 12/23/05
554  strm << "Content-Description: " << descrip[type] << CRLF ;
555  if (type == dods_error) // don't cache our error responses.
556  strm << "Cache-Control: no-cache" << CRLF ;
557  // Don't write a Content-Encoding header for x-plain since that breaks
558  // Netscape on NT. jhrg 3/23/97
559  if (enc != x_plain)
560  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
561  strm << CRLF ;
562 }
563 
574 void set_mime_html(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
575  const string &protocol)
576 {
577  strm << "HTTP/1.0 200 OK" << CRLF;
578 
579  strm << "XDODS-Server: " << DVR<< CRLF;
580  strm << "XOPeNDAP-Server: " << DVR<< CRLF;
581 
582  if (protocol == "")
583  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
584  else
585  strm << "XDAP: " << protocol << CRLF;
586 
587  const time_t t = time(0);
588  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
589 
590  strm << "Last-Modified: ";
591  if (last_modified > 0)
592  strm << rfc822_date(last_modified).c_str() << CRLF;
593  else
594  strm << rfc822_date(t).c_str() << CRLF;
595 
596  strm << "Content-type: text/html" << CRLF;
597  // See note above about Content-Description header. jhrg 12/23/05
598  strm << "Content-Description: " << descrip[type] << CRLF;
599  if (type == dods_error) // don't cache our error responses.
600  strm << "Cache-Control: no-cache" << CRLF;
601  // Don't write a Content-Encoding header for x-plain since that breaks
602  // Netscape on NT. jhrg 3/23/97
603  if (enc != x_plain)
604  strm << "Content-Encoding: " << encoding[enc] << CRLF;
605  strm << CRLF;
606 }
607 
622 void
623 set_mime_binary(FILE *out, ObjectType type, const string &ver,
624  EncodingType enc, const time_t last_modified)
625 {
626  ostringstream oss;
627  set_mime_binary(oss, type, ver, enc, last_modified);
628  fwrite(oss.str().data(), 1, oss.str().length(), out);
629 }
630 
645 void
646 set_mime_binary(ostream &strm, ObjectType type, const string &ver,
647  EncodingType enc, const time_t last_modified)
648 {
649  strm << "HTTP/1.0 200 OK" << CRLF ;
650  if (ver == "") {
651  strm << "XDODS-Server: " << DVR << CRLF ;
652  strm << "XOPeNDAP-Server: " << DVR << CRLF ;
653  }
654  else {
655  strm << "XDODS-Server: " << ver.c_str() << CRLF ;
656  strm << "XOPeNDAP-Server: " << ver.c_str() << CRLF ;
657  }
658  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
659 
660  const time_t t = time(0);
661  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
662 
663  strm << "Last-Modified: " ;
664  if (last_modified > 0)
665  strm << rfc822_date(last_modified).c_str() << CRLF ;
666  else
667  strm << rfc822_date(t).c_str() << CRLF ;
668 
669  strm << "Content-Type: application/octet-stream" << CRLF ;
670  strm << "Content-Description: " << descrip[type] << CRLF ;
671  if (enc != x_plain)
672  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
673 
674  strm << CRLF ;
675 }
676 
690 void set_mime_binary(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
691  const string &protocol)
692 {
693  strm << "HTTP/1.0 200 OK" << CRLF;
694 
695  strm << "XDODS-Server: " << DVR << CRLF;
696  strm << "XOPeNDAP-Server: " << DVR << CRLF;
697 
698  if (protocol.empty())
699  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
700  else
701  strm << "XDAP: " << protocol << CRLF;
702 
703  const time_t t = time(0);
704  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
705 
706  strm << "Last-Modified: ";
707  if (last_modified > 0)
708  strm << rfc822_date(last_modified).c_str() << CRLF;
709  else
710  strm << rfc822_date(t).c_str() << CRLF;
711 
712  strm << "Content-Type: application/octet-stream" << CRLF;
713  strm << "Content-Description: " << descrip[type] << CRLF;
714  if (enc != x_plain)
715  strm << "Content-Encoding: " << encoding[enc] << CRLF;
716 
717  strm << CRLF;
718 }
719 
720 void set_mime_multipart(ostream &strm, const string &boundary,
721  const string &start, ObjectType type,
722  const string &version, EncodingType enc,
723  const time_t last_modified)
724 {
725  strm << "HTTP/1.0 200 OK" << CRLF ;
726  if (version == "") {
727  strm << "XDODS-Server: " << DVR << CRLF ;
728  strm << "XOPeNDAP-Server: " << DVR << CRLF ;
729  }
730  else {
731  strm << "XDODS-Server: " << version.c_str() << CRLF ;
732  strm << "XOPeNDAP-Server: " << version.c_str() << CRLF ;
733  }
734  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
735 
736  const time_t t = time(0);
737  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
738 
739  strm << "Last-Modified: " ;
740  if (last_modified > 0)
741  strm << rfc822_date(last_modified).c_str() << CRLF ;
742  else
743  strm << rfc822_date(t).c_str() << CRLF ;
744 
745  strm << "Content-Type: Multipart/Related; boundary=" << boundary
746  << "; start=\"<" << start << ">\"; type=\"Text/xml\"" << CRLF ;
747  strm << "Content-Description: " << descrip[type] << CRLF ;
748  if (enc != x_plain)
749  strm << "Content-Encoding: " << encoding[enc] << CRLF ;
750 
751  strm << CRLF ;
752 }
753 
756 void set_mime_multipart(ostream &strm, const string &boundary, const string &start, ObjectType type, EncodingType enc,
757  const time_t last_modified, const string &protocol, const string &url)
758 {
759  strm << "HTTP/1.1 200 OK" << CRLF;
760 
761  const time_t t = time(0);
762  strm << "Date: " << rfc822_date(t).c_str() << CRLF;
763 
764  strm << "Last-Modified: ";
765  if (last_modified > 0)
766  strm << rfc822_date(last_modified).c_str() << CRLF;
767  else
768  strm << rfc822_date(t).c_str() << CRLF;
769 
770  strm << "Content-Type: multipart/related; boundary=" << boundary << "; start=\"<" << start
771  << ">\"; type=\"text/xml\"" << CRLF;
772 
773  // data-ddx;"; removed as a result of the merge of the hyrax 1.8 release
774  // branch.
775  strm << "Content-Description: " << descrip[type] << ";";
776  if (!url.empty())
777  strm << " url=\"" << url << "\"" << CRLF;
778  else
779  strm << CRLF;
780 
781  if (enc != x_plain)
782  strm << "Content-Encoding: " << encoding[enc] << CRLF;
783 
784  if (protocol == "")
785  strm << "X-DAP: " << DAP_PROTOCOL_VERSION << CRLF;
786  else
787  strm << "X-DAP: " << protocol << CRLF;
788 
789  strm << "X-OPeNDAP-Server: " << DVR<< CRLF;
790 
791  strm << CRLF;
792 }
793 
794 void set_mime_ddx_boundary(ostream &strm, const string &boundary,
795  const string &cid, ObjectType type, EncodingType enc)
796 {
797  strm << "--" << boundary << CRLF;
798  // 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.
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 
838 string get_next_mime_header(FILE *in)
839 {
840  // Get the header line and strip \r\n. Some headers end with just \n.
841  // If a blank line is found, return an empty string.
842  char line[line_length];
843  while (!feof(in)) {
844  if (fgets(line, line_length, in)
845  && (strncmp(line, CRLF, 2) == 0 || line[0] == '\n'))
846  return "";
847  else {
848  size_t slen = min(strlen(line), line_length); // Never > line_length
849  line[slen - 1] = '\0'; // remove the newline
850  if (line[slen - 2] == '\r') // ...and the preceding carriage return
851  line[slen - 2] = '\0';
852  return string(line);
853  }
854  }
855 
856  throw Error("I expected to find a MIME header, but got EOF instead.");
857 }
858 
871 string get_next_mime_header(istream &in)
872 {
873 #if 0
874  // Get the header line and strip \r\n. Some headers end with just \n.
875  // If a blank line is found, return an empty string.
876  char line[line_length];
877  while (!in.eof()) {
878  in.getline(line, line_length);
879  if (strncmp(line, CRLF, 2) == 0 || line[0] == '\n') {
880  return "";
881  }
882  else {
883  size_t slen = min(strlen(line), line_length); // Never > line_length
884  line[slen - 1] = '\0'; // remove the newline
885  if (line[slen - 2] == '\r') // ...and the preceding carriage return
886  line[slen - 2] = '\0';
887  return string(line);
888  }
889  }
890 #endif
891  // Get the header line and strip \r\n. Some headers end with just \n.
892  // If a blank line is found, return an empty string.
893  char raw_line[line_length];
894  while (!in.eof()) {
895  in.getline(raw_line, line_length); // strips the trailing newline; terminates with null
896  string line = raw_line;
897  if (line.find('\r') != string::npos)
898  line = line.substr(0, line.size()-1);
899  return line;
900  }
901 
902  throw Error("I expected to find a MIME header, but got EOF instead.");
903 }
904 
912 void parse_mime_header(const string &header, string &name, string &value)
913 {
914  istringstream iss(header);
915 
916  size_t length = header.length() + 1;
917  vector<char> s(length);
918  //char s[line_length];
919  iss.getline(&s[0], length, ':');
920  name = &s[0];
921 
922  iss.ignore(length, ' ');
923  iss.getline(&s[0], length);
924  value = &s[0];
925 
926  downcase(name);
927  downcase(value);
928 }
929 
941 bool is_boundary(const char *line, const string &boundary)
942 {
943  if (strlen(line) < 2 || !(line[0] == '-' && line[1] == '-'))
944  return false;
945  else
946  return strncmp(line, boundary.c_str(), boundary.length()) == 0;
947 }
948 
959 string read_multipart_boundary(FILE *in, const string &boundary)
960 {
961  string boundary_line = get_next_mime_header(in);
962  // If the caller passed in a value for the boundary, test for that value,
963  // else just see that this line starts with '--'.
964  // The value of 'boundary_line' is returned by this function.
965  if ((!boundary.empty() && is_boundary(boundary_line.c_str(), boundary))
966  || boundary_line.find("--") != 0)
967  throw Error(internal_error, "The DAP4 data response document is broken - missing or malformed boundary.");
968 
969  return boundary_line;
970 }
971 
972 string read_multipart_boundary(istream &in, const string &boundary)
973 {
974  string boundary_line = get_next_mime_header(in);
975  // If the caller passed in a value for the boundary, test for that value,
976  // else just see that this line starts with '--'.
977  // The value of 'boundary_line' is returned by this function.
978  if ((!boundary.empty() && is_boundary(boundary_line.c_str(), boundary))
979  || boundary_line.find("--") != 0)
980  throw Error(internal_error, "The DAP4 data response document is broken - missing or malformed boundary.");
981 
982  return boundary_line;
983 }
984 
1005 void read_multipart_headers(FILE *in, const string &content_type, const ObjectType object_type, const string &cid)
1006 {
1007  bool ct = false, cd = false, ci = false;
1008 
1009  // The function get_next_mime_header() returns tainted data. Fix this
1010  // if we are going to use this code. jhrg 4/18/17
1011  string header = get_next_mime_header(in);
1012  while (!header.empty()) {
1013  string name, value;
1014  parse_mime_header(header, name, value);
1015 
1016  if (name == "content-type") {
1017  ct = true;
1018  if (value.find(content_type) == string::npos)
1019  throw Error(internal_error, "Content-Type for this part of a DAP2 data ddx response must be " + content_type + ".");
1020  }
1021  else if (name == "content-description") {
1022  cd = true;
1023  if (get_description_type(value) != object_type)
1024  throw Error(internal_error, "Content-Description for this part of a DAP2 data ddx response must be dods-ddx or dods-data-ddx");
1025  }
1026  else if (name == "content-id") {
1027  ci = true;
1028  if (!cid.empty() && value != cid)
1029  throw Error("Content-Id mismatch. Expected: " + cid + ", but got: " + value);
1030  }
1031 
1032  header = get_next_mime_header(in);
1033  }
1034 
1035  if (!(ct && cd && ci)) throw Error(internal_error, "The DAP4 data response document is broken - missing header.");
1036 }
1037 
1038 void read_multipart_headers(istream &in, const string &content_type, const ObjectType object_type, const string &cid)
1039 {
1040  bool ct = false, cd = false, ci = false;
1041 
1042  string header = get_next_mime_header(in);
1043  while (!header.empty()) {
1044  string name, value;
1045  parse_mime_header(header, name, value);
1046 
1047  if (name == "content-type") {
1048  ct = true;
1049  if (value.find(content_type) == string::npos)
1050  throw Error(internal_error, "Content-Type for this part of a DAP4 data response must be " + content_type + ".");
1051  }
1052  else if (name == "content-description") {
1053  cd = true;
1054  if (get_description_type(value) != object_type)
1055  throw Error("Content-Description '" + value + "' not the expected value (expected: " + descrip[object_type] + ").");
1056  }
1057  else if (name == "content-id") {
1058  ci = true;
1059  if (!cid.empty() && value != cid)
1060  throw Error("Content-Id mismatch. Expected: " + cid + ", but got: " + value);
1061  }
1062 
1063  header = get_next_mime_header(in);
1064  }
1065 
1066  if (!(ct && cd && ci)) throw Error(internal_error, "The DAP4 data response document is broken - missing header.");
1067 }
1068 
1077 string cid_to_header_value(const string &cid)
1078 {
1079  string::size_type offset = cid.find("cid:");
1080  if (offset != 0)
1081  throw Error(internal_error, "expected CID to start with 'cid:'");
1082 
1083  string value = "<";
1084  value.append(cid.substr(offset + 4));
1085  value.append(">");
1086  downcase(value);
1087 
1088  return value;
1089 }
1090 
1099 void
1100 set_mime_error(FILE *out, int code, const string &reason,
1101  const string &version)
1102 {
1103  ostringstream oss;
1104  set_mime_error(oss, code, reason, version);
1105  fwrite(oss.str().data(), 1, oss.str().length(), out);
1106 }
1107 
1116 void
1117 set_mime_error(ostream &strm, int code, const string &reason,
1118  const string &version)
1119 {
1120  strm << "HTTP/1.0 " << code << " " << reason.c_str() << CRLF ;
1121  if (version == "") {
1122  strm << "XDODS-Server: " << DVR << CRLF ;
1123  strm << "XOPeNDAP-Server: " << DVR << CRLF ;
1124  }
1125  else {
1126  strm << "XDODS-Server: " << version.c_str() << CRLF ;
1127  strm << "XOPeNDAP-Server: " << version.c_str() << CRLF ;
1128  }
1129  strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
1130 
1131  const time_t t = time(0);
1132  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
1133  strm << "Cache-Control: no-cache" << CRLF ;
1134  strm << CRLF ;
1135 }
1136 
1144 void
1146 {
1147  ostringstream oss;
1148  set_mime_not_modified(oss);
1149  fwrite(oss.str().data(), 1, oss.str().length(), out);
1150 }
1151 
1159 void
1161 {
1162  strm << "HTTP/1.0 304 NOT MODIFIED" << CRLF ;
1163  const time_t t = time(0);
1164  strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
1165  strm << CRLF ;
1166 }
1167 
1168 #if 0
1169 
1170 // This was removed because it's not being used by our server.
1171 
1181 bool
1182 found_override(string name, string &doc)
1183 {
1184  ifstream ifs((name + ".ovr").c_str());
1185  if (!ifs)
1186  return false;
1187 
1188  char tmp[256];
1189  doc = "";
1190  while (!ifs.eof()) {
1191  ifs.getline(tmp, 255);
1192  tmp[255] = '\0';
1193  strncat(tmp, "\n", sizeof(tmp) - strlen(tmp) - 1);
1194  doc += tmp;
1195  }
1196 
1197  ifs.close();
1198  return true;
1199 }
1200 #endif
1201 
1211 bool
1213 {
1214  char tmp[256];
1215  while (!feof(in)) {
1216  char *s = fgets(tmp, 255, in);
1217  if (s && strncmp(s, CRLF, 2) == 0)
1218  return true;
1219  }
1220 
1221  return false;
1222 }
1223 
1228 void
1230 {
1231  while(!get_next_mime_header(in).empty()) ;
1232 #if 0
1233  string header;
1234  do {
1235  header = get_next_mime_header(in);
1236  } while (!header.empty());
1237 #endif
1238 }
1239 
1240 } // namespace libdap
1241 
string get_next_mime_header(FILE *in)
Definition: mime_util.cc:838
void ErrMsgT(const string &Msgt)
Logs an error message.
Definition: mime_util.cc:222
time_t last_modified_time(const string &name)
Definition: mime_util.cc:95
void downcase(string &s)
Definition: util.cc:563
string rfc822_date(const time_t t)
Definition: mime_util.cc:156
void read_multipart_headers(FILE *in, const string &content_type, const ObjectType object_type, const string &cid)
Definition: mime_util.cc:1005
string name_path(const string &path)
Returns the filename portion of a pathname.
Definition: mime_util.cc:268
STL namespace.
string read_multipart_boundary(FILE *in, const string &boundary)
Definition: mime_util.cc:959
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:381
top level DAP object to house generic methods
Definition: AISConnect.cc:30
void parse_mime_header(const string &header, string &name, string &value)
Definition: mime_util.cc:912
ObjectType get_description_type(const string &value)
Definition: mime_util.cc:339
bool remove_mime_header(FILE *in)
Read and discard the MIME header of the stream in.
Definition: mime_util.cc:1212
ObjectType get_type(const string &value)
Definition: mime_util.cc:326
void set_mime_binary(FILE *out, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified)
Definition: mime_util.cc:623
void set_mime_error(FILE *out, int code, const string &reason, const string &version)
Definition: mime_util.cc:1100
string cid_to_header_value(const string &cid)
Definition: mime_util.cc:1077
void set_mime_not_modified(FILE *out)
Send a `Not Modified&#39; response.
Definition: mime_util.cc:1145
bool is_boundary(const char *line, const string &boundary)
Definition: mime_util.cc:941
void set_mime_html(FILE *out, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified)
Definition: mime_util.cc:509
EncodingType
The type of encoding used on the current stream.
Definition: EncodingType.h:48
A class for error processing.
Definition: Error.h:92
bool do_version(const string &script_ver, const string &dataset_ver)
Send a version number.
Definition: mime_util.cc:189