libdap Updated for version 3.21.1
libdap4 is an implementation of OPeNDAP's DAP protocol.
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 <cstdio>
41#include <cstring>
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/stat.h>
51#include <sys/types.h>
52
53#ifndef WIN32
54#include <sys/wait.h>
55#include <unistd.h> // for access
56#else
57#include <fcntl.h>
58#include <io.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 <fstream>
65#include <iostream>
66#include <sstream>
67#include <string>
68
69#include "media_types.h"
70#include "mime_util.h"
71
72#include "Ancillary.h"
73#include "debug.h"
74#include "util.h" // This supplies flush_stream for WIN32.
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
85using namespace std;
86
87namespace libdap {
88
94time_t last_modified_time(const string &name) {
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
134static const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
135static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
136
137#ifdef _MSC_VER
138#define snprintf sprintf_s
139#endif
148string rfc822_date(const time_t t) {
149 struct tm stm {};
150 const struct tm *ret = gmtime_r(&t, &stm);
151 if (!ret)
152 return "";
153
154 char d[256];
155
156 snprintf(d, 255, "%s, %02d %s %4d %02d:%02d:%02d GMT", days[stm.tm_wday], stm.tm_mday, months[stm.tm_mon],
157 1900 + stm.tm_year, stm.tm_hour, stm.tm_min, stm.tm_sec);
158 d[255] = '\0';
159 return {d};
160}
161
162static const int TimLen = 26; // length of string from asctime()
163// static const int CLUMP_SIZE = 1024; // size of clumps to new in fmakeword()
164
178bool do_version(const string &script_ver, const string &dataset_ver) {
179 fprintf(stdout, "HTTP/1.0 200 OK%s", CRLF);
180 fprintf(stdout, "XDODS-Server: %s%s", DVR, CRLF);
181 fprintf(stdout, "XOPeNDAP-Server: %s%s", DVR, CRLF);
182 fprintf(stdout, "XDAP: %s%s", DAP_PROTOCOL_VERSION, CRLF);
183 fprintf(stdout, "Content-Type: text/plain%s", CRLF);
184 fprintf(stdout, CRLF);
185
186 fprintf(stdout, "Core software version: %s%s", DVR, CRLF);
187
188 if (script_ver != "")
189 fprintf(stdout, "Server Script Revision: %s%s", script_ver.c_str(), CRLF);
190
191 if (dataset_ver != "")
192 fprintf(stdout, "Dataset version: %s%s", dataset_ver.c_str(), CRLF);
193
194 fflush(stdout); // Not sure this is needed. jhrg 12/23/05
195
196 return true;
197}
198
209void ErrMsgT(const string &Msgt) {
210 time_t TimBin;
211 char TimStr[TimLen];
212
213 if (time(&TimBin) == (time_t)-1)
214 strncpy(TimStr, "time() error ", TimLen - 1);
215 else {
216 char ctime_value[TimLen];
217 const char *ret = ctime_r(&TimBin, ctime_value);
218 if (!ret)
219 strncpy(TimStr, "Unknown", TimLen - 1);
220 else {
221 strncpy(TimStr, ctime_value, TimLen - 1);
222 TimStr[TimLen - 2] = '\0'; // overwrite the \n
223 }
224 }
225
226 cerr << "[" << TimStr << "] DAP server error: " << Msgt << endl;
227}
228
229// Given a pathname, return just the filename component with any extension
230// removed. The new string resides in newly allocated memory; the caller must
231// delete it when done using the filename.
232// Originally from the netcdf distribution (ver 2.3.2).
233//
234// *** Change to string class argument and return type. jhrg
235// *** Changed so it also removes the#path#of#the#file# from decompressed
236// files. rph.
237// Returns: A filename, with path and extension information removed. If
238// memory for the new name cannot be allocated, does not return!
239
250string name_path(const string &path) {
251 if (path == "")
252 return string("");
253
254 string::size_type delim = path.find_last_of(FILE_DELIMITER);
255 string::size_type pound = path.find_last_of("#");
256 string new_path;
257
258 if (pound != string::npos)
259 new_path = path.substr(pound + 1);
260 else
261 new_path = path.substr(delim + 1);
262
263 return new_path;
264}
265
266// Send string to set the transfer (mime) type and server version
267// Note that the content description filed is used to indicate whether valid
268// information of an error message is contained in the document and the
269// content-encoding field is used to indicate whether the data is compressed.
270// If the data stream is to be compressed, arrange for a compression output
271// filter so that all information sent after the header will be compressed.
272//
273// Returns: false if the compression output filter was to be used but could
274// not be started, true otherwise.
275#if 0
276static const char *descrip[] =
277 {"unknown", "dods_das", "dods_dds", "dods_data", "dods_ddx",
278 "dods_error", "web_error", "dap4-dmr", "dap4-data", "dap4-error"
279 };
280#endif
281
282static const char *descrip[] = {
283 "unknown_type", "dods_das", "dods_dds", "dods_data",
284 "dods_ddx", // This is the old XML DDS/DAS used prior to dap4
285 "dods_data_ddx", // This is used for caching data responses
286 "dods_error", "web_error",
287
288 "dap4_dmr", // DAP4 metadata
289 "dap4_data", // The DMR with a data blob
290 "dap4_error" // The error response for DAP4
291};
292
293static const char *encoding[] = {"unknown", "deflate", "x-plain", "gzip", "binary"};
294
300ObjectType get_type(const string &value) { return get_description_type(value); }
301
302// TODO Recode to use the constants in media_types.h. jhrg 11/12/13
303
309ObjectType get_description_type(const string &value) {
310 if ((value == DAS1) || (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_ddx") || (value == "dods-ddx"))
317 return dods_ddx;
318 else if ((value == "dods_data_ddx" || (value == "dods-data-ddx")))
319 return dods_data_ddx;
320 else if ((value == "dods_error") || (value == "dods-error"))
321 return dods_error;
322 else if ((value == "web_error") || (value == "web-error"))
323 return web_error;
324
325 else if ((value == "dap4_dmr") || (value == "dap4-dmr") || (value == DMR_Content_Type))
326 return dap4_dmr;
327 else if ((value == "dap4_data") || (value == "dap4-data") || (value == DAP4_DATA_Content_Type))
328 return dap4_data;
329 else if ((value == "dap4_error") || (value == "dap4-error"))
330 return dap4_error;
331
332 else
333 return unknown_type;
334}
335
349void set_mime_text(FILE *out, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified) {
350 ostringstream oss;
351 set_mime_text(oss, type, ver, enc, last_modified);
352 fwrite(oss.str().data(), 1, oss.str().length(), out);
353}
354
368void set_mime_text(ostream &strm, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified) {
369 strm << "HTTP/1.0 200 OK" << CRLF;
370 if (ver == "") {
371 strm << "XDODS-Server: " << DVR << CRLF;
372 strm << "XOPeNDAP-Server: " << DVR << CRLF;
373 } else {
374 strm << "XDODS-Server: " << ver.c_str() << CRLF;
375 strm << "XOPeNDAP-Server: " << ver.c_str() << CRLF;
376 }
377 strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
378
379 const time_t t = time(0);
380 strm << "Date: " << rfc822_date(t).c_str() << CRLF;
381
382 strm << "Last-Modified: ";
383 if (last_modified > 0)
384 strm << rfc822_date(last_modified).c_str() << CRLF;
385 else
386 strm << rfc822_date(t).c_str() << CRLF;
387
388 if (type == dap4_dmr)
389 strm << "Content-Type: application/vnd.org.opendap.dap4.dataset-metadata+xml" << CRLF;
390 else
391 strm << "Content-Type: text/plain" << CRLF;
392
393 // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
394 // jhrg 12/23/05
395 strm << "Content-Description: " << descrip[type] << CRLF;
396 if (type == dods_error) // don't cache our error responses.
397 strm << "Cache-Control: no-cache" << CRLF;
398 // Don't write a Content-Encoding header for x-plain since that breaks
399 // Netscape on NT. jhrg 3/23/97
400 if (enc != x_plain)
401 strm << "Content-Encoding: " << encoding[enc] << CRLF;
402 strm << CRLF;
403}
404
420void set_mime_text(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
421 const string &protocol) {
422 strm << "HTTP/1.0 200 OK" << CRLF;
423
424 strm << "XDODS-Server: " << DVR << CRLF;
425 strm << "XOPeNDAP-Server: " << DVR << CRLF;
426
427 if (protocol == "")
428 strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
429 else
430 strm << "XDAP: " << protocol << CRLF;
431
432 const time_t t = time(0);
433 strm << "Date: " << rfc822_date(t).c_str() << CRLF;
434
435 strm << "Last-Modified: ";
436 if (last_modified > 0)
437 strm << rfc822_date(last_modified).c_str() << CRLF;
438 else
439 strm << rfc822_date(t).c_str() << CRLF;
440
441 if (type == dap4_dmr)
442 strm << "Content-Type: application/vnd.org.opendap.dap4.dataset-metadata+xml" << CRLF;
443 else
444 strm << "Content-Type: text/plain" << CRLF;
445
446 // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
447 // jhrg 12/23/05
448 strm << "Content-Description: " << descrip[type] << CRLF;
449 if (type == dods_error) // don't cache our error responses.
450 strm << "Cache-Control: no-cache" << CRLF;
451 // Don't write a Content-Encoding header for x-plain since that breaks
452 // Netscape on NT. jhrg 3/23/97
453 if (enc != x_plain)
454 strm << "Content-Encoding: " << encoding[enc] << CRLF;
455 strm << CRLF;
456}
457
469void set_mime_html(FILE *out, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified) {
470 ostringstream oss;
471 set_mime_html(oss, type, ver, enc, last_modified);
472 fwrite(oss.str().data(), 1, oss.str().length(), out);
473}
474
486void set_mime_html(ostream &strm, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified) {
487 strm << "HTTP/1.0 200 OK" << CRLF;
488 if (ver == "") {
489 strm << "XDODS-Server: " << DVR << CRLF;
490 strm << "XOPeNDAP-Server: " << DVR << CRLF;
491 } else {
492 strm << "XDODS-Server: " << ver.c_str() << CRLF;
493 strm << "XOPeNDAP-Server: " << ver.c_str() << CRLF;
494 }
495 strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
496
497 const time_t t = time(0);
498 strm << "Date: " << rfc822_date(t).c_str() << CRLF;
499
500 strm << "Last-Modified: ";
501 if (last_modified > 0)
502 strm << rfc822_date(last_modified).c_str() << CRLF;
503 else
504 strm << rfc822_date(t).c_str() << CRLF;
505
506 strm << "Content-type: text/html" << CRLF;
507 // See note above about Content-Description header. jhrg 12/23/05
508 strm << "Content-Description: " << descrip[type] << CRLF;
509 if (type == dods_error) // don't cache our error responses.
510 strm << "Cache-Control: no-cache" << CRLF;
511 // Don't write a Content-Encoding header for x-plain since that breaks
512 // Netscape on NT. jhrg 3/23/97
513 if (enc != x_plain)
514 strm << "Content-Encoding: " << encoding[enc] << CRLF;
515 strm << CRLF;
516}
517
528void set_mime_html(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
529 const string &protocol) {
530 strm << "HTTP/1.0 200 OK" << CRLF;
531
532 strm << "XDODS-Server: " << DVR << CRLF;
533 strm << "XOPeNDAP-Server: " << DVR << CRLF;
534
535 if (protocol == "")
536 strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
537 else
538 strm << "XDAP: " << protocol << CRLF;
539
540 const time_t t = time(0);
541 strm << "Date: " << rfc822_date(t).c_str() << CRLF;
542
543 strm << "Last-Modified: ";
544 if (last_modified > 0)
545 strm << rfc822_date(last_modified).c_str() << CRLF;
546 else
547 strm << rfc822_date(t).c_str() << CRLF;
548
549 strm << "Content-type: text/html" << CRLF;
550 // See note above about Content-Description header. jhrg 12/23/05
551 strm << "Content-Description: " << descrip[type] << CRLF;
552 if (type == dods_error) // don't cache our error responses.
553 strm << "Cache-Control: no-cache" << CRLF;
554 // Don't write a Content-Encoding header for x-plain since that breaks
555 // Netscape on NT. jhrg 3/23/97
556 if (enc != x_plain)
557 strm << "Content-Encoding: " << encoding[enc] << CRLF;
558 strm << CRLF;
559}
560
575void set_mime_binary(FILE *out, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified) {
576 ostringstream oss;
577 set_mime_binary(oss, type, ver, enc, last_modified);
578 fwrite(oss.str().data(), 1, oss.str().length(), out);
579}
580
595void set_mime_binary(ostream &strm, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified) {
596 strm << "HTTP/1.0 200 OK" << CRLF;
597 if (ver == "") {
598 strm << "XDODS-Server: " << DVR << CRLF;
599 strm << "XOPeNDAP-Server: " << DVR << CRLF;
600 } else {
601 strm << "XDODS-Server: " << ver.c_str() << CRLF;
602 strm << "XOPeNDAP-Server: " << ver.c_str() << CRLF;
603 }
604 strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
605
606 const time_t t = time(0);
607 strm << "Date: " << rfc822_date(t).c_str() << CRLF;
608
609 strm << "Last-Modified: ";
610 if (last_modified > 0)
611 strm << rfc822_date(last_modified).c_str() << CRLF;
612 else
613 strm << rfc822_date(t).c_str() << CRLF;
614
615 strm << "Content-Type: application/octet-stream" << CRLF;
616 strm << "Content-Description: " << descrip[type] << CRLF;
617 if (enc != x_plain)
618 strm << "Content-Encoding: " << encoding[enc] << CRLF;
619
620 strm << CRLF;
621}
622
636void set_mime_binary(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
637 const string &protocol) {
638 strm << "HTTP/1.0 200 OK" << CRLF;
639
640 strm << "XDODS-Server: " << DVR << CRLF;
641 strm << "XOPeNDAP-Server: " << DVR << CRLF;
642
643 if (protocol.empty())
644 strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
645 else
646 strm << "XDAP: " << protocol << CRLF;
647
648 const time_t t = time(0);
649 strm << "Date: " << rfc822_date(t).c_str() << CRLF;
650
651 strm << "Last-Modified: ";
652 if (last_modified > 0)
653 strm << rfc822_date(last_modified).c_str() << CRLF;
654 else
655 strm << rfc822_date(t).c_str() << CRLF;
656
657 strm << "Content-Type: application/octet-stream" << CRLF;
658 strm << "Content-Description: " << descrip[type] << CRLF;
659 if (enc != x_plain)
660 strm << "Content-Encoding: " << encoding[enc] << CRLF;
661
662 strm << CRLF;
663}
664
665void set_mime_multipart(ostream &strm, const string &boundary, const string &start, ObjectType type,
666 const string &version, EncodingType enc, const time_t last_modified) {
667 strm << "HTTP/1.0 200 OK" << CRLF;
668 if (version == "") {
669 strm << "XDODS-Server: " << DVR << CRLF;
670 strm << "XOPeNDAP-Server: " << DVR << CRLF;
671 } else {
672 strm << "XDODS-Server: " << version.c_str() << CRLF;
673 strm << "XOPeNDAP-Server: " << version.c_str() << CRLF;
674 }
675 strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
676
677 const time_t t = time(0);
678 strm << "Date: " << rfc822_date(t).c_str() << CRLF;
679
680 strm << "Last-Modified: ";
681 if (last_modified > 0)
682 strm << rfc822_date(last_modified).c_str() << CRLF;
683 else
684 strm << rfc822_date(t).c_str() << CRLF;
685
686 strm << "Content-Type: Multipart/Related; boundary=" << boundary << "; start=\"<" << start
687 << ">\"; type=\"Text/xml\"" << CRLF;
688 strm << "Content-Description: " << descrip[type] << CRLF;
689 if (enc != x_plain)
690 strm << "Content-Encoding: " << encoding[enc] << CRLF;
691
692 strm << CRLF;
693}
694
696
697void set_mime_multipart(ostream &strm, const string &boundary, const string &start, ObjectType type, EncodingType enc,
698 const time_t last_modified, const string &protocol, const string &url) {
699 strm << "HTTP/1.1 200 OK" << 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: multipart/related; boundary=" << boundary << "; start=\"<" << start
711 << ">\"; type=\"text/xml\"" << CRLF;
712
713 // data-ddx;"; removed as a result of the merge of the hyrax 1.8 release
714 // branch.
715 strm << "Content-Description: " << descrip[type] << ";";
716 if (!url.empty())
717 strm << " url=\"" << url << "\"" << CRLF;
718 else
719 strm << CRLF;
720
721 if (enc != x_plain)
722 strm << "Content-Encoding: " << encoding[enc] << CRLF;
723
724 if (protocol == "")
725 strm << "X-DAP: " << DAP_PROTOCOL_VERSION << CRLF;
726 else
727 strm << "X-DAP: " << protocol << CRLF;
728
729 strm << "X-OPeNDAP-Server: " << DVR << CRLF;
730
731 strm << CRLF;
732}
733
734void set_mime_ddx_boundary(ostream &strm, const string &boundary, const string &cid, ObjectType type,
735 EncodingType enc) {
736 strm << "--" << boundary << CRLF;
737 // TODO - Bite the bullet and make the encoding UTF-8 as required by dap4. This will break a lot of tests but the
738 // baselines could be amended using a bash script and sed.
739 strm << "Content-Type: Text/xml; charset=iso-8859-1" << CRLF;
740 strm << "Content-Id: <" << cid << ">" << CRLF;
741 strm << "Content-Description: " << descrip[type] << CRLF;
742 if (enc != x_plain)
743 strm << "Content-Encoding: " << encoding[enc] << CRLF;
744
745 strm << CRLF;
746}
747
748void set_mime_data_boundary(ostream &strm, const string &boundary, const string &cid, ObjectType type,
749 EncodingType enc) {
750 strm << "--" << boundary << CRLF;
751 strm << "Content-Type: application/octet-stream" << CRLF;
752 strm << "Content-Id: <" << cid << ">" << CRLF;
753 strm << "Content-Description: " << descrip[type] << CRLF;
754 if (enc != x_plain)
755 strm << "Content-Encoding: " << encoding[enc] << CRLF;
756
757 strm << CRLF;
758}
759
760const size_t line_length = 1024;
761
777string get_next_mime_header(FILE *in) {
778 // Get the header line and strip \r\n. Some headers end with just \n.
779 // If a blank line is found, return an empty string.
780 char line[line_length];
781 while (!feof(in)) {
782 if (fgets(line, line_length, in) && (strncmp(line, CRLF, 2) == 0 || line[0] == '\n'))
783 return "";
784 else {
785 size_t slen = min(strlen(line), line_length); // Never > line_length
786 line[slen - 1] = '\0'; // remove the newline
787 if (line[slen - 2] == '\r') // ...and the preceding carriage return
788 line[slen - 2] = '\0';
789 return string(line);
790 }
791 }
792
793 throw Error("I expected to find a MIME header, but got EOF instead.");
794}
795
808string get_next_mime_header(istream &in) {
809#if 0
810 // Get the header line and strip \r\n. Some headers end with just \n.
811 // If a blank line is found, return an empty string.
812 char line[line_length];
813 while (!in.eof()) {
814 in.getline(line, line_length);
815 if (strncmp(line, CRLF, 2) == 0 || line[0] == '\n') {
816 return "";
817 }
818 else {
819 size_t slen = min(strlen(line), line_length); // Never > line_length
820 line[slen - 1] = '\0'; // remove the newline
821 if (line[slen - 2] == '\r') // ...and the preceding carriage return
822 line[slen - 2] = '\0';
823 return string(line);
824 }
825 }
826#endif
827 // Get the header line and strip \r\n. Some headers end with just \n.
828 // If a blank line is found, return an empty string.
829 char raw_line[line_length];
830 while (!in.eof()) {
831 in.getline(raw_line, line_length); // strips the trailing newline; terminates with null
832 string line = raw_line;
833 if (line.find('\r') != string::npos)
834 line = line.substr(0, line.size() - 1);
835 return line;
836 }
837
838 throw Error("I expected to find a MIME header, but got EOF instead.");
839}
840
848void parse_mime_header(const string &header, string &name, string &value) {
849 istringstream iss(header);
850
851 size_t length = header.length() + 1;
852 vector<char> s(length);
853 // char s[line_length];
854 iss.getline(s.data(), length, ':');
855 name = s.data();
856
857 iss.ignore(length, ' ');
858 iss.getline(s.data(), length);
859 value = s.data();
860
861 downcase(name);
862 downcase(value);
863}
864
875
876bool is_boundary(const char *line, const string &boundary) {
877 if (strlen(line) < 2 || !(line[0] == '-' && line[1] == '-'))
878 return false;
879 else
880 return strncmp(line, boundary.c_str(), boundary.length()) == 0;
881}
882
893string read_multipart_boundary(FILE *in, const string &boundary) {
894 string boundary_line = get_next_mime_header(in);
895 // If the caller passed in a value for the boundary, test for that value,
896 // else just see that this line starts with '--'.
897 // The value of 'boundary_line' is returned by this function.
898 if ((!boundary.empty() && is_boundary(boundary_line.c_str(), boundary)) || boundary_line.find("--") != 0)
899 throw Error(internal_error, "The DAP4 data response document is broken - missing or malformed boundary.");
900
901 return boundary_line;
902}
903
904string read_multipart_boundary(istream &in, const string &boundary) {
905 string boundary_line = get_next_mime_header(in);
906 // If the caller passed in a value for the boundary, test for that value,
907 // else just see that this line starts with '--'.
908 // The value of 'boundary_line' is returned by this function.
909 if ((!boundary.empty() && is_boundary(boundary_line.c_str(), boundary)) || boundary_line.find("--") != 0)
910 throw Error(internal_error, "The DAP4 data response document is broken - missing or malformed boundary.");
911
912 return boundary_line;
913}
914
935void read_multipart_headers(FILE *in, const string &content_type, const ObjectType object_type, const string &cid) {
936 bool ct = false, cd = false, ci = false;
937
938 // The function get_next_mime_header() returns tainted data. Fix this
939 // if we are going to use this code. jhrg 4/18/17
940 string header = get_next_mime_header(in);
941 while (!header.empty()) {
942 string name, value;
943 parse_mime_header(header, name, value);
944
945 if (name == "content-type") {
946 ct = true;
947 if (value.find(content_type) == string::npos)
948 throw Error(internal_error,
949 "Content-Type for this part of a DAP2 data ddx response must be " + content_type + ".");
950 } else if (name == "content-description") {
951 cd = true;
952 if (get_description_type(value) != object_type)
953 throw Error(
955 "Content-Description for this part of a DAP2 data ddx response must be dods-ddx or dods-data-ddx");
956 } else if (name == "content-id") {
957 ci = true;
958 if (!cid.empty() && value != cid)
959 throw Error("Content-Id mismatch. Expected: " + cid + ", but got: " + value);
960 }
961
962 header = get_next_mime_header(in);
963 }
964
965 if (!(ct && cd && ci))
966 throw Error(internal_error, "The DAP4 data response document is broken - missing header.");
967}
968
969void read_multipart_headers(istream &in, const string &content_type, const ObjectType object_type, const string &cid) {
970 bool ct = false, cd = false, ci = false;
971
972 string header = get_next_mime_header(in);
973 while (!header.empty()) {
974 string name, value;
975 parse_mime_header(header, name, value);
976
977 if (name == "content-type") {
978 ct = true;
979 if (value.find(content_type) == string::npos)
980 throw Error(internal_error,
981 "Content-Type for this part of a DAP4 data response must be " + content_type + ".");
982 } else if (name == "content-description") {
983 cd = true;
984 if (get_description_type(value) != object_type)
985 throw Error("Content-Description '" + value +
986 "' not the expected value (expected: " + descrip[object_type] + ").");
987 } else if (name == "content-id") {
988 ci = true;
989 if (!cid.empty() && value != cid)
990 throw Error("Content-Id mismatch. Expected: " + cid + ", but got: " + value);
991 }
992
993 header = get_next_mime_header(in);
994 }
995
996 if (!(ct && cd && ci))
997 throw Error(internal_error, "The DAP4 data response document is broken - missing header.");
998}
999
1008string cid_to_header_value(const string &cid) {
1009 string::size_type offset = cid.find("cid:");
1010 if (offset != 0)
1011 throw Error(internal_error, "expected CID to start with 'cid:'");
1012
1013 string value = "<";
1014 value.append(cid.substr(offset + 4));
1015 value.append(">");
1016 downcase(value);
1017
1018 return value;
1019}
1020
1029void set_mime_error(FILE *out, int code, const string &reason, const string &version) {
1030 ostringstream oss;
1031 set_mime_error(oss, code, reason, version);
1032 fwrite(oss.str().data(), 1, oss.str().length(), out);
1033}
1034
1043void set_mime_error(ostream &strm, int code, const string &reason, const string &version) {
1044 strm << "HTTP/1.0 " << code << " " << reason.c_str() << CRLF;
1045 if (version == "") {
1046 strm << "XDODS-Server: " << DVR << CRLF;
1047 strm << "XOPeNDAP-Server: " << DVR << CRLF;
1048 } else {
1049 strm << "XDODS-Server: " << version.c_str() << CRLF;
1050 strm << "XOPeNDAP-Server: " << version.c_str() << CRLF;
1051 }
1052 strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
1053
1054 const time_t t = time(0);
1055 strm << "Date: " << rfc822_date(t).c_str() << CRLF;
1056 strm << "Cache-Control: no-cache" << CRLF;
1057 strm << CRLF;
1058}
1059
1067void set_mime_not_modified(FILE *out) {
1068 ostringstream oss;
1070 fwrite(oss.str().data(), 1, oss.str().length(), out);
1071}
1072
1080void set_mime_not_modified(ostream &strm) {
1081 strm << "HTTP/1.0 304 NOT MODIFIED" << CRLF;
1082 const time_t t = time(0);
1083 strm << "Date: " << rfc822_date(t).c_str() << CRLF;
1084 strm << CRLF;
1085}
1086
1087#if 0
1088
1089// This was removed because it's not being used by our server.
1090
1100bool
1101found_override(string name, string &doc)
1102{
1103 ifstream ifs((name + ".ovr").c_str());
1104 if (!ifs)
1105 return false;
1106
1107 char tmp[256];
1108 doc = "";
1109 while (!ifs.eof()) {
1110 ifs.getline(tmp, 255);
1111 tmp[255] = '\0';
1112 strncat(tmp, "\n", sizeof(tmp) - strlen(tmp) - 1);
1113 doc += tmp;
1114 }
1115
1116 ifs.close();
1117 return true;
1118}
1119#endif
1120
1130bool remove_mime_header(FILE *in) {
1131 char tmp[256];
1132 while (!feof(in)) {
1133 char *s = fgets(tmp, 255, in);
1134 if (s && strncmp(s, CRLF, 2) == 0)
1135 return true;
1136 }
1137
1138 return false;
1139}
1140
1145void remove_mime_header(istream &in) {
1146 while (!get_next_mime_header(in).empty())
1147 ;
1148#if 0
1149 string header;
1150 do {
1151 header = get_next_mime_header(in);
1152 } while (!header.empty());
1153#endif
1154}
1155
1156} // namespace libdap
#define CRLF
#define internal_error
Internal server error (500)
Definition Error.h:63
A class for error processing.
Definition Error.h:92
#define DAP_PROTOCOL_VERSION
Definition config.h:41
#define DVR
Definition config.h:80
const char * version
Definition getdap.cc:65
#define FILE_DELIMITER
Definition mime_util.cc:79
top level DAP object to house generic methods
Definition AISConnect.cc:30
void set_mime_data_boundary(ostream &strm, const string &boundary, const string &cid, ObjectType type, EncodingType enc)
Definition mime_util.cc:748
string read_multipart_boundary(FILE *in, const string &boundary)
Definition mime_util.cc:893
void set_mime_error(FILE *out, int code, const string &reason, const string &version)
void set_mime_html(FILE *out, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified)
Definition mime_util.cc:469
ObjectType get_description_type(const string &value)
Definition mime_util.cc:309
string cid_to_header_value(const string &cid)
void parse_mime_header(const string &header, string &name, string &value)
Definition mime_util.cc:848
void set_mime_ddx_boundary(ostream &strm, const string &boundary, const string &cid, ObjectType type, EncodingType enc)
Definition mime_util.cc:734
time_t last_modified_time(const string &name)
Definition mime_util.cc:94
string name_path(const string &path)
Returns the filename portion of a pathname.
Definition mime_util.cc:250
void set_mime_not_modified(FILE *out)
Send a ‘Not Modified’ response.
void set_mime_binary(FILE *out, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified)
Definition mime_util.cc:575
bool do_version(const string &script_ver, const string &dataset_ver)
Send a version number.
Definition mime_util.cc:178
void downcase(string &s)
Definition util.cc:544
bool remove_mime_header(FILE *in)
Read and discard the MIME header of the stream in.
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:665
void read_multipart_headers(FILE *in, const string &content_type, const ObjectType object_type, const string &cid)
Definition mime_util.cc:935
EncodingType
The type of encoding used on the current stream.
void ErrMsgT(const string &Msgt)
Logs an error message.
Definition mime_util.cc:209
ObjectType get_type(const string &value)
Definition mime_util.cc:300
string rfc822_date(const time_t t)
Definition mime_util.cc:148
bool is_boundary(const char *line, const string &boundary)
Definition mime_util.cc:876
ObjectType
The type of object in the stream coming from the data server.
Definition ObjectType.h:57
@ dods_das
Definition ObjectType.h:59
@ dods_data_ddx
Definition ObjectType.h:63
@ dap4_data
Definition ObjectType.h:68
@ dap4_error
Definition ObjectType.h:69
@ unknown_type
Definition ObjectType.h:58
@ dods_error
Definition ObjectType.h:64
@ dods_data
Definition ObjectType.h:61
@ web_error
Definition ObjectType.h:65
@ dap4_dmr
Definition ObjectType.h:67
@ dods_dds
Definition ObjectType.h:60
@ dods_ddx
Definition ObjectType.h:62
void set_mime_text(FILE *out, ObjectType type, const string &ver, EncodingType enc, const time_t last_modified)
Definition mime_util.cc:349
string get_next_mime_header(FILE *in)
Definition mime_util.cc:777
const size_t line_length
Definition mime_util.cc:760