Blame mime_util.cc

Packit a4aae4
Packit a4aae4
// -*- mode: c++; c-basic-offset:4 -*-
Packit a4aae4
Packit a4aae4
// This file is part of libdap, A C++ implementation of the OPeNDAP Data
Packit a4aae4
// Access Protocol.
Packit a4aae4
Packit a4aae4
// Copyright (c) 2002,2003 OPeNDAP, Inc.
Packit a4aae4
// Author: James Gallagher <jgallagher@opendap.org>
Packit a4aae4
//         Reza Nekovei <rnekovei@intcomm.net>
Packit a4aae4
//
Packit a4aae4
// This library is free software; you can redistribute it and/or
Packit a4aae4
// modify it under the terms of the GNU Lesser General Public
Packit a4aae4
// License as published by the Free Software Foundation; either
Packit a4aae4
// version 2.1 of the License, or (at your option) any later version.
Packit a4aae4
//
Packit a4aae4
// This library is distributed in the hope that it will be useful,
Packit a4aae4
// but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit a4aae4
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit a4aae4
// Lesser General Public License for more details.
Packit a4aae4
//
Packit a4aae4
// You should have received a copy of the GNU Lesser General Public
Packit a4aae4
// License along with this library; if not, write to the Free Software
Packit a4aae4
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
Packit a4aae4
//
Packit a4aae4
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
Packit a4aae4
Packit a4aae4
// (c) COPYRIGHT URI/MIT 1994-2001
Packit a4aae4
// Please read the full copyright statement in the file COPYRIGHT_URI.
Packit a4aae4
//
Packit a4aae4
// Authors:
Packit a4aae4
//      jhrg,jimg       James Gallagher <jgallagher@gso.uri.edu>
Packit a4aae4
//      reza            Reza Nekovei <rnekovei@intcomm.net>
Packit a4aae4
Packit a4aae4
// A few useful routines which are used in CGI programs.
Packit a4aae4
//
Packit a4aae4
// ReZa 9/30/94
Packit a4aae4
Packit a4aae4
#include "config.h"
Packit a4aae4
Packit a4aae4
#include <cstring>
Packit a4aae4
#include <cstdio>
Packit a4aae4
#include <ctype.h>
Packit a4aae4
Packit a4aae4
#ifndef TM_IN_SYS_TIME
Packit a4aae4
#include <time.h>
Packit a4aae4
#else
Packit a4aae4
#include <sys/time.h>
Packit a4aae4
#endif
Packit a4aae4
Packit a4aae4
#include <sys/types.h>
Packit a4aae4
#include <sys/stat.h>
Packit a4aae4
Packit a4aae4
#ifndef WIN32
Packit a4aae4
#include <unistd.h>    // for access
Packit a4aae4
#include <sys/wait.h>
Packit a4aae4
#else
Packit a4aae4
#include <io.h>
Packit a4aae4
#include <fcntl.h>
Packit a4aae4
#include <process.h>
Packit a4aae4
// Win32 does not define this. 08/21/02 jhrg
Packit a4aae4
#define F_OK 0
Packit a4aae4
#endif
Packit a4aae4
Packit a4aae4
#include <iostream>
Packit a4aae4
#include <sstream>
Packit a4aae4
#include <fstream>
Packit a4aae4
#include <string>
Packit a4aae4
Packit a4aae4
#include "mime_util.h"
Packit a4aae4
#include "media_types.h"
Packit a4aae4
Packit a4aae4
#include "Ancillary.h"
Packit a4aae4
#include "util.h"  // This supplies flush_stream for WIN32.
Packit a4aae4
#include "debug.h"
Packit a4aae4
Packit a4aae4
#ifdef WIN32
Packit a4aae4
#define FILE_DELIMITER '\\'
Packit a4aae4
#else  //  default to unix
Packit a4aae4
#define FILE_DELIMITER '/'
Packit a4aae4
#endif
Packit a4aae4
Packit a4aae4
// ...not using a const string here to avoid global objects. jhrg 12/23/05
Packit a4aae4
#define CRLF "\r\n"             // Change here, expr-test.cc, in DODSFilter and ResponseBuilder
Packit a4aae4
Packit a4aae4
using namespace std;
Packit a4aae4
Packit a4aae4
namespace libdap {
Packit a4aae4
Packit a4aae4
/** Get the last modified time. Assume <tt>name</tt> is a file and
Packit a4aae4
    find its last modified time. If <tt>name</tt> is not a file, then
Packit a4aae4
    return now as the last modified time.
Packit a4aae4
    @param name The name of a file.
Packit a4aae4
    @return The last modified time or the current time. */
Packit a4aae4
time_t
Packit a4aae4
last_modified_time(const string &name)
Packit a4aae4
{
Packit a4aae4
    struct stat m;
Packit a4aae4
Packit a4aae4
    if (stat(name.c_str(), &m) == 0 && (S_IFREG & m.st_mode))
Packit a4aae4
        return m.st_mtime;
Packit a4aae4
    else
Packit a4aae4
        return time(0);
Packit a4aae4
}
Packit a4aae4
// Return a MIME rfc-822 date. The grammar for this is:
Packit a4aae4
//       date-time   =  [ day "," ] date time        ; dd mm yy
Packit a4aae4
//                                                   ;  hh:mm:ss zzz
Packit a4aae4
//
Packit a4aae4
//       day         =  "Mon"  / "Tue" /  "Wed"  / "Thu"
Packit a4aae4
//                   /  "Fri"  / "Sat" /  "Sun"
Packit a4aae4
//
Packit a4aae4
//       date        =  1*2DIGIT month 2DIGIT        ; day month year
Packit a4aae4
//                                                   ;  e.g. 20 Jun 82
Packit a4aae4
//                   NB: year is 4 digit; see RFC 1123. 11/30/99 jhrg
Packit a4aae4
//
Packit a4aae4
//       month       =  "Jan"  /  "Feb" /  "Mar"  /  "Apr"
Packit a4aae4
//                   /  "May"  /  "Jun" /  "Jul"  /  "Aug"
Packit a4aae4
//                   /  "Sep"  /  "Oct" /  "Nov"  /  "Dec"
Packit a4aae4
//
Packit a4aae4
//       time        =  hour zone                    ; ANSI and Military
Packit a4aae4
//
Packit a4aae4
//       hour        =  2DIGIT ":" 2DIGIT [":" 2DIGIT]
Packit a4aae4
//                                                   ; 00:00:00 - 23:59:59
Packit a4aae4
//
Packit a4aae4
//       zone        =  "UT"  / "GMT"                ; Universal Time
Packit a4aae4
//                                                   ; North American : UT
Packit a4aae4
//                   /  "EST" / "EDT"                ;  Eastern:  - 5/ - 4
Packit a4aae4
//                   /  "CST" / "CDT"                ;  Central:  - 6/ - 5
Packit a4aae4
//                   /  "MST" / "MDT"                ;  Mountain: - 7/ - 6
Packit a4aae4
//                   /  "PST" / "PDT"                ;  Pacific:  - 8/ - 7
Packit a4aae4
//                   /  1ALPHA                       ; Military: Z = UT;
Packit a4aae4
//                                                   ;  A:-1; (J not used)
Packit a4aae4
//                                                   ;  M:-12; N:+1; Y:+12
Packit a4aae4
//                   / ( ("+" / "-") 4DIGIT )        ; Local differential
Packit a4aae4
//                                                   ;  hours+min. (HHMM)
Packit a4aae4
Packit a4aae4
static const char *days[] =
Packit a4aae4
    {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
Packit a4aae4
    };
Packit a4aae4
static const char *months[] =
Packit a4aae4
    {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
Packit a4aae4
     "Aug", "Sep", "Oct", "Nov", "Dec"
Packit a4aae4
    };
Packit a4aae4
Packit a4aae4
#ifdef _MSC_VER
Packit a4aae4
#define snprintf sprintf_s
Packit a4aae4
#endif
Packit a4aae4
/** Given a constant pointer to a <tt>time_t</tt>, return a RFC
Packit a4aae4
    822/1123 style date.
Packit a4aae4
Packit a4aae4
    This function returns the RFC 822 date with the exception that the RFC
Packit a4aae4
    1123 modification for four-digit years is implemented.
Packit a4aae4
Packit a4aae4
    @return The RFC 822/1123 style date in a C++ string.
Packit a4aae4
    @param t A const <tt>time_t</tt> pointer. */
Packit a4aae4
string
Packit a4aae4
rfc822_date(const time_t t)
Packit a4aae4
{
Packit a4aae4
    struct tm *stm = gmtime(&t);
Packit a4aae4
    if (!stm)
Packit a4aae4
    	return "";
Packit a4aae4
Packit a4aae4
    char d[256];
Packit a4aae4
Packit a4aae4
    snprintf(d, 255, "%s, %02d %s %4d %02d:%02d:%02d GMT", days[stm->tm_wday],
Packit a4aae4
            stm->tm_mday, months[stm->tm_mon],
Packit a4aae4
            1900 + stm->tm_year,
Packit a4aae4
            stm->tm_hour, stm->tm_min, stm->tm_sec);
Packit a4aae4
    d[255] = '\0';
Packit a4aae4
    return string(d);
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
static const int TimLen = 26; // length of string from asctime()
Packit a4aae4
//static const int CLUMP_SIZE = 1024; // size of clumps to new in fmakeword()
Packit a4aae4
Packit a4aae4
/** This sends a formatted block of text to the client, containing
Packit a4aae4
    version information about various aspects of the server.  The
Packit a4aae4
    arguments allow you to enclose version information about the
Packit a4aae4
    filter program and the dataset in the message.  Either argument
Packit a4aae4
    (or both) may be omitted, in which case no script or dataset
Packit a4aae4
    version information will be printed.
Packit a4aae4
Packit a4aae4
    @brief Send a version number.
Packit a4aae4
    @param script_ver The version of the filter script executing this
Packit a4aae4
    function.
Packit a4aae4
    @param dataset_ver The version of the dataset.
Packit a4aae4
    @return TRUE for success. Always returns true.
Packit a4aae4
*/
Packit a4aae4
bool
Packit a4aae4
do_version(const string &script_ver, const string &dataset_ver)
Packit a4aae4
{
Packit a4aae4
    fprintf(stdout, "HTTP/1.0 200 OK%s", CRLF) ;
Packit a4aae4
    fprintf(stdout, "XDODS-Server: %s%s", DVR, CRLF) ;
Packit a4aae4
    fprintf(stdout, "XOPeNDAP-Server: %s%s", DVR, CRLF) ;
Packit a4aae4
    fprintf(stdout, "XDAP: %s%s", DAP_PROTOCOL_VERSION, CRLF) ;
Packit a4aae4
    fprintf(stdout, "Content-Type: text/plain%s", CRLF) ;
Packit a4aae4
    fprintf(stdout, CRLF) ;
Packit a4aae4
Packit a4aae4
    fprintf(stdout, "Core software version: %s%s", DVR, CRLF) ;
Packit a4aae4
Packit a4aae4
    if (script_ver != "")
Packit a4aae4
        fprintf(stdout, "Server Script Revision: %s%s", script_ver.c_str(), CRLF) ;
Packit a4aae4
Packit a4aae4
    if (dataset_ver != "")
Packit a4aae4
        fprintf(stdout,  "Dataset version: %s%s", dataset_ver.c_str(), CRLF) ;
Packit a4aae4
Packit a4aae4
    fflush(stdout) ;            // Not sure this is needed. jhrg 12/23/05
Packit a4aae4
Packit a4aae4
    return true;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Prints an error message in the <tt>httpd</tt> system log file, along with
Packit a4aae4
    a time stamp and the client host name (or address).
Packit a4aae4
Packit a4aae4
    Use this instead of the functions in liberrmsg.a in the programs run by
Packit a4aae4
    the CGIs to report errors so those errors show up in HTTPD's log files.
Packit a4aae4
Packit a4aae4
    @deprecated
Packit a4aae4
    @brief Logs an error message.
Packit a4aae4
    @return void
Packit a4aae4
*/
Packit a4aae4
void
Packit a4aae4
ErrMsgT(const string &Msgt)
Packit a4aae4
{
Packit a4aae4
    time_t TimBin;
Packit a4aae4
    char TimStr[TimLen];
Packit a4aae4
Packit a4aae4
    if (time(&TimBin) == (time_t) - 1)
Packit a4aae4
        strncpy(TimStr, "time() error           ", TimLen-1);
Packit a4aae4
    else {
Packit a4aae4
    	char *ctime_value = ctime(&TimBin);
Packit a4aae4
    	if (!ctime_value)
Packit a4aae4
    		strncpy(TimStr, "Unknown", TimLen-1);
Packit a4aae4
    	else {
Packit a4aae4
    		strncpy(TimStr, ctime_value, TimLen-1);
Packit a4aae4
    		TimStr[TimLen - 2] = '\0'; // overwrite the \n
Packit a4aae4
    	}
Packit a4aae4
#if 0
Packit a4aae4
    	strncpy(TimStr, ctime(&TimBin), TimLen-1);
Packit a4aae4
    	TimStr[TimLen - 2] = '\0'; // overwrite the \n
Packit a4aae4
#endif
Packit a4aae4
    }
Packit a4aae4
Packit a4aae4
    cerr << "[" << TimStr << "] DAP server error: " << Msgt << endl;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
// Given a pathname, return just the filename component with any extension
Packit a4aae4
// removed. The new string resides in newly allocated memory; the caller must
Packit a4aae4
// delete it when done using the filename.
Packit a4aae4
// Originally from the netcdf distribution (ver 2.3.2).
Packit a4aae4
//
Packit a4aae4
// *** Change to string class argument and return type. jhrg
Packit a4aae4
// *** Changed so it also removes the#path#of#the#file# from decompressed
Packit a4aae4
//     files.  rph.
Packit a4aae4
// Returns: A filename, with path and extension information removed. If
Packit a4aae4
// memory for the new name cannot be allocated, does not return!
Packit a4aae4
Packit a4aae4
/** Given a pathname, this function returns just the file name
Packit a4aae4
    component of the path.  That is, given <tt>/a/b/c/ralph.nc.das</tt>, it
Packit a4aae4
    returns <tt>ralph.nc</tt>.
Packit a4aae4
Packit a4aae4
    @brief Returns the filename portion of a pathname.
Packit a4aae4
    @param path A C-style simple string containing a pathname to be
Packit a4aae4
    parsed.
Packit a4aae4
    @return A C-style simple string containing the filename component
Packit a4aae4
    of the given pathname.
Packit a4aae4
*/
Packit a4aae4
string
Packit a4aae4
name_path(const string &path)
Packit a4aae4
{
Packit a4aae4
    if (path == "")
Packit a4aae4
        return string("");
Packit a4aae4
Packit a4aae4
    string::size_type delim = path.find_last_of(FILE_DELIMITER);
Packit a4aae4
    string::size_type pound = path.find_last_of("#");
Packit a4aae4
    string new_path;
Packit a4aae4
Packit a4aae4
    if (pound != string::npos)
Packit a4aae4
        new_path = path.substr(pound + 1);
Packit a4aae4
    else
Packit a4aae4
        new_path = path.substr(delim + 1);
Packit a4aae4
Packit a4aae4
    return new_path;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
// Send string to set the transfer (mime) type and server version
Packit a4aae4
// Note that the content description filed is used to indicate whether valid
Packit a4aae4
// information of an error message is contained in the document and the
Packit a4aae4
// content-encoding field is used to indicate whether the data is compressed.
Packit a4aae4
// If the data stream is to be compressed, arrange for a compression output
Packit a4aae4
// filter so that all information sent after the header will be compressed.
Packit a4aae4
//
Packit a4aae4
// Returns: false if the compression output filter was to be used but could
Packit a4aae4
// not be started, true otherwise.
Packit a4aae4
#if 0
Packit a4aae4
static const char *descrip[] =
Packit a4aae4
    {"unknown", "dods_das", "dods_dds", "dods_data", "dods_ddx",
Packit a4aae4
     "dods_error", "web_error", "dap4-dmr", "dap4-data", "dap4-error"
Packit a4aae4
    };
Packit a4aae4
#endif
Packit a4aae4
Packit a4aae4
static const char *descrip[] = {
Packit a4aae4
"unknown_type",
Packit a4aae4
"dods_das",
Packit a4aae4
"dods_dds",
Packit a4aae4
"dods_data",
Packit a4aae4
"dods_ddx",       // This is the old XML DDS/DAS used prior to dap4
Packit a4aae4
"dods_data_ddx",  // This is used for caching data responses
Packit a4aae4
"dods_error",
Packit a4aae4
"web_error",
Packit a4aae4
Packit a4aae4
"dap4_dmr",       // DAP4 metadata
Packit a4aae4
"dap4_data",      // The DMR with a data blob
Packit a4aae4
"dap4_error"      // The error response for DAP4
Packit a4aae4
};
Packit a4aae4
Packit a4aae4
static const char *encoding[] =
Packit a4aae4
    {"unknown", "deflate", "x-plain", "gzip", "binary"
Packit a4aae4
    };
Packit a4aae4
Packit a4aae4
/** This function returns the ObjectType value that matches the given string.
Packit a4aae4
    Modified to include tests for the descriptions that use hyphens in addition
Packit a4aae4
    to underscores. 8/1/08 jhrg
Packit a4aae4
Packit a4aae4
    @deprecated  */
Packit a4aae4
ObjectType
Packit a4aae4
get_type(const string &value)
Packit a4aae4
{
Packit a4aae4
	return get_description_type(value);
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
// TODO Recode to use the constants in media_types.h. jhrg 11/12/13
Packit a4aae4
Packit a4aae4
/** This function returns the ObjectType value that matches the given string.
Packit a4aae4
    Modified to include tests for the descriptions that use hyphens in addition
Packit a4aae4
    to underscores. 8/1/08 jhrg
Packit a4aae4
Packit a4aae4
    @param value Value from the HTTP response header */
Packit a4aae4
ObjectType
Packit a4aae4
get_description_type(const string &value)
Packit a4aae4
{
Packit a4aae4
    if ((value == DAS1) || (value == "dods-das"))
Packit a4aae4
        return dods_das;
Packit a4aae4
    else if ((value == "dods_dds") || (value == "dods-dds"))
Packit a4aae4
        return dods_dds;
Packit a4aae4
    else if ((value == "dods_data") || (value == "dods-data"))
Packit a4aae4
        return dods_data;
Packit a4aae4
    else if ((value == "dods_ddx") || (value == "dods-ddx"))
Packit a4aae4
        return dods_ddx;
Packit a4aae4
    else if ((value == "dods_data_ddx" || (value == "dods-data-ddx")))
Packit a4aae4
        return dods_data_ddx;
Packit a4aae4
    else if ((value == "dods_error") || (value == "dods-error"))
Packit a4aae4
        return dods_error;
Packit a4aae4
    else if ((value == "web_error") || (value == "web-error"))
Packit a4aae4
        return web_error;
Packit a4aae4
Packit a4aae4
    else if ((value == "dap4_dmr") || (value == "dap4-dmr") || (value == DMR_Content_Type))
Packit a4aae4
        return dap4_dmr;
Packit a4aae4
    else if ((value == "dap4_data") || (value == "dap4-data") || (value == DAP4_DATA_Content_Type))
Packit a4aae4
        return dap4_data;
Packit a4aae4
    else if ((value == "dap4_error") || (value == "dap4-error"))
Packit a4aae4
        return dap4_error;
Packit a4aae4
Packit a4aae4
    else
Packit a4aae4
        return unknown_type;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Generate an HTTP 1.0 response header for a text document. This is used
Packit a4aae4
    when returning a serialized DAS or DDS object.
Packit a4aae4
Packit a4aae4
    @deprecated
Packit a4aae4
    @param out Write the MIME header to this FILE pointer.
Packit a4aae4
    @param type The type of this this response. Defaults to
Packit a4aae4
    application/octet-stream.
Packit a4aae4
    @param ver The version string; denotes the libdap implementation
Packit a4aae4
    version.
Packit a4aae4
    @param enc How is this response encoded? Can be plain or deflate or the
Packit a4aae4
    x_... versions of those. Default is x_plain.
Packit a4aae4
    @param last_modified The time to use for the Last-Modified header value.
Packit a4aae4
    Default is zero which means use the current time. */
Packit a4aae4
void
Packit a4aae4
set_mime_text(FILE *out, ObjectType type, const string &ver,
Packit a4aae4
              EncodingType enc, const time_t last_modified)
Packit a4aae4
{
Packit a4aae4
    ostringstream oss;
Packit a4aae4
    set_mime_text(oss, type, ver, enc, last_modified);
Packit a4aae4
    fwrite(oss.str().data(), 1, oss.str().length(), out);
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Generate an HTTP 1.0 response header for a text document. This is used
Packit a4aae4
    when returning a serialized DAS or DDS object.
Packit a4aae4
Packit a4aae4
    @deprecated
Packit a4aae4
    @param strm Write the MIME header to this stream.
Packit a4aae4
    @param type The type of this this response. Defaults to
Packit a4aae4
    application/octet-stream.
Packit a4aae4
    @param ver The version string; denotes the libdap implementation
Packit a4aae4
    version.
Packit a4aae4
    @param enc How is this response encoded? Can be plain or deflate or the
Packit a4aae4
    x_... versions of those. Default is x_plain.
Packit a4aae4
    @param last_modified The time to use for the Last-Modified header value.
Packit a4aae4
    Default is zero which means use the current time. */
Packit a4aae4
void
Packit a4aae4
set_mime_text(ostream &strm, ObjectType type, const string &ver,
Packit a4aae4
              EncodingType enc, const time_t last_modified)
Packit a4aae4
{
Packit a4aae4
    strm << "HTTP/1.0 200 OK" << CRLF ;
Packit a4aae4
    if (ver == "") {
Packit a4aae4
        strm << "XDODS-Server: " << DVR << CRLF ;
Packit a4aae4
        strm << "XOPeNDAP-Server: " << DVR << CRLF ;
Packit a4aae4
    }
Packit a4aae4
    else {
Packit a4aae4
        strm << "XDODS-Server: " << ver.c_str() << CRLF ;
Packit a4aae4
        strm << "XOPeNDAP-Server: " << ver.c_str() << CRLF ;
Packit a4aae4
    }
Packit a4aae4
    strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
Packit a4aae4
Packit a4aae4
    const time_t t = time(0);
Packit a4aae4
    strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
Packit a4aae4
Packit a4aae4
    strm << "Last-Modified: " ;
Packit a4aae4
    if (last_modified > 0)
Packit a4aae4
        strm << rfc822_date(last_modified).c_str() << CRLF ;
Packit a4aae4
    else
Packit a4aae4
        strm << rfc822_date(t).c_str() << CRLF ;
Packit a4aae4
Packit a4aae4
    if (type == dap4_dmr)
Packit a4aae4
        strm << "Content-Type: application/vnd.org.opendap.dap4.dataset-metadata+xml" << CRLF ;
Packit a4aae4
    else
Packit a4aae4
        strm << "Content-Type: text/plain" << CRLF ;
Packit a4aae4
Packit a4aae4
    // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
Packit a4aae4
    // jhrg 12/23/05
Packit a4aae4
    strm << "Content-Description: " << descrip[type] << CRLF ;
Packit a4aae4
    if (type == dods_error) // don't cache our error responses.
Packit a4aae4
        strm << "Cache-Control: no-cache" << CRLF ;
Packit a4aae4
    // Don't write a Content-Encoding header for x-plain since that breaks
Packit a4aae4
    // Netscape on NT. jhrg 3/23/97
Packit a4aae4
    if (enc != x_plain)
Packit a4aae4
        strm << "Content-Encoding: " << encoding[enc] << CRLF ;
Packit a4aae4
    strm << CRLF ;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Generate an HTTP 1.0 response header for a text document. This is used
Packit a4aae4
 when returning a serialized DAS or DDS object.
Packit a4aae4
Packit a4aae4
 @note In Hyrax these headers are not used. Instead the front end of the
Packit a4aae4
 server will build the response headers
Packit a4aae4
Packit a4aae4
 @param strm Write the MIME header to this stream.
Packit a4aae4
 @param type The type of this this response. Defaults to
Packit a4aae4
 application/octet-stream.
Packit a4aae4
 @param ver The version string; denotes the libdap implementation
Packit a4aae4
 version.
Packit a4aae4
 @param enc How is this response encoded? Can be plain or deflate or the
Packit a4aae4
 x_... versions of those. Default is x_plain.
Packit a4aae4
 @param last_modified The time to use for the Last-Modified header value.
Packit a4aae4
 Default is zero which means use the current time. */
Packit a4aae4
void set_mime_text(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
Packit a4aae4
        const string &protocol)
Packit a4aae4
{
Packit a4aae4
    strm << "HTTP/1.0 200 OK" << CRLF;
Packit a4aae4
Packit a4aae4
    strm << "XDODS-Server: " << DVR << CRLF;
Packit a4aae4
    strm << "XOPeNDAP-Server: " << DVR << CRLF;
Packit a4aae4
Packit a4aae4
    if (protocol == "")
Packit a4aae4
        strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
Packit a4aae4
    else
Packit a4aae4
        strm << "XDAP: " << protocol << CRLF;
Packit a4aae4
Packit a4aae4
    const time_t t = time(0);
Packit a4aae4
    strm << "Date: " << rfc822_date(t).c_str() << CRLF;
Packit a4aae4
Packit a4aae4
    strm << "Last-Modified: ";
Packit a4aae4
    if (last_modified > 0)
Packit a4aae4
        strm << rfc822_date(last_modified).c_str() << CRLF;
Packit a4aae4
    else
Packit a4aae4
        strm << rfc822_date(t).c_str() << CRLF;
Packit a4aae4
Packit a4aae4
    if (type == dap4_dmr)
Packit a4aae4
        strm << "Content-Type: application/vnd.org.opendap.dap4.dataset-metadata+xml" << CRLF;
Packit a4aae4
    else
Packit a4aae4
        strm << "Content-Type: text/plain" << CRLF;
Packit a4aae4
Packit a4aae4
    // Note that Content-Description is from RFC 2045 (MIME, pt 1), not 2616.
Packit a4aae4
    // jhrg 12/23/05
Packit a4aae4
    strm << "Content-Description: " << descrip[type] << CRLF;
Packit a4aae4
    if (type == dods_error) // don't cache our error responses.
Packit a4aae4
        strm << "Cache-Control: no-cache" << CRLF;
Packit a4aae4
    // Don't write a Content-Encoding header for x-plain since that breaks
Packit a4aae4
    // Netscape on NT. jhrg 3/23/97
Packit a4aae4
    if (enc != x_plain)
Packit a4aae4
        strm << "Content-Encoding: " << encoding[enc] << CRLF;
Packit a4aae4
    strm << CRLF;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Generate an HTTP 1.0 response header for a html document.
Packit a4aae4
Packit a4aae4
    @deprecated
Packit a4aae4
    @param out Write the MIME header to this FILE pointer.
Packit a4aae4
    @param type The type of this this response.
Packit a4aae4
    @param ver The version string; denotes the libdap implementation
Packit a4aae4
    version.
Packit a4aae4
    @param enc How is this response encoded? Can be plain or deflate or the
Packit a4aae4
    x_... versions of those. Default is x_plain.
Packit a4aae4
    @param last_modified The time to use for the Last-Modified header value.
Packit a4aae4
    Default is zero which means use the current time. */
Packit a4aae4
void
Packit a4aae4
set_mime_html(FILE *out, ObjectType type, const string &ver,
Packit a4aae4
              EncodingType enc, const time_t last_modified)
Packit a4aae4
{
Packit a4aae4
    ostringstream oss;
Packit a4aae4
    set_mime_html(oss, type, ver, enc, last_modified);
Packit a4aae4
    fwrite(oss.str().data(), 1, oss.str().length(), out);
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Generate an HTTP 1.0 response header for a html document.
Packit a4aae4
Packit a4aae4
    @deprecated
Packit a4aae4
    @param strm Write the MIME header to this stream.
Packit a4aae4
    @param type The type of this this response.
Packit a4aae4
    @param ver The version string; denotes the libdap implementation
Packit a4aae4
    version.
Packit a4aae4
    @param enc How is this response encoded? Can be plain or deflate or the
Packit a4aae4
    x_... versions of those. Default is x_plain.
Packit a4aae4
    @param last_modified The time to use for the Last-Modified header value.
Packit a4aae4
    Default is zero which means use the current time. */
Packit a4aae4
void
Packit a4aae4
set_mime_html(ostream &strm, ObjectType type, const string &ver,
Packit a4aae4
              EncodingType enc, const time_t last_modified)
Packit a4aae4
{
Packit a4aae4
    strm << "HTTP/1.0 200 OK" << CRLF ;
Packit a4aae4
    if (ver == "") {
Packit a4aae4
        strm << "XDODS-Server: " << DVR << CRLF ;
Packit a4aae4
        strm << "XOPeNDAP-Server: " << DVR << CRLF ;
Packit a4aae4
    }
Packit a4aae4
    else {
Packit a4aae4
        strm << "XDODS-Server: " << ver.c_str() << CRLF ;
Packit a4aae4
        strm << "XOPeNDAP-Server: " << ver.c_str() << CRLF ;
Packit a4aae4
    }
Packit a4aae4
    strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
Packit a4aae4
Packit a4aae4
    const time_t t = time(0);
Packit a4aae4
    strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
Packit a4aae4
Packit a4aae4
    strm << "Last-Modified: " ;
Packit a4aae4
    if (last_modified > 0)
Packit a4aae4
        strm << rfc822_date(last_modified).c_str() << CRLF ;
Packit a4aae4
    else
Packit a4aae4
        strm << rfc822_date(t).c_str() << CRLF ;
Packit a4aae4
Packit a4aae4
    strm << "Content-type: text/html" << CRLF ;
Packit a4aae4
    // See note above about Content-Description header. jhrg 12/23/05
Packit a4aae4
    strm << "Content-Description: " << descrip[type] << CRLF ;
Packit a4aae4
    if (type == dods_error) // don't cache our error responses.
Packit a4aae4
        strm << "Cache-Control: no-cache" << CRLF ;
Packit a4aae4
    // Don't write a Content-Encoding header for x-plain since that breaks
Packit a4aae4
    // Netscape on NT. jhrg 3/23/97
Packit a4aae4
    if (enc != x_plain)
Packit a4aae4
        strm << "Content-Encoding: " << encoding[enc] << CRLF ;
Packit a4aae4
    strm << CRLF ;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Generate an HTTP 1.0 response header for a html document.
Packit a4aae4
Packit a4aae4
 @param strm Write the MIME header to this stream.
Packit a4aae4
 @param type The type of this this response.
Packit a4aae4
 @param ver The version string; denotes the libdap implementation
Packit a4aae4
 version.
Packit a4aae4
 @param enc How is this response encoded? Can be plain or deflate or the
Packit a4aae4
 x_... versions of those. Default is x_plain.
Packit a4aae4
 @param last_modified The time to use for the Last-Modified header value.
Packit a4aae4
 Default is zero which means use the current time. */
Packit a4aae4
void set_mime_html(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
Packit a4aae4
        const string &protocol)
Packit a4aae4
{
Packit a4aae4
    strm << "HTTP/1.0 200 OK" << CRLF;
Packit a4aae4
Packit a4aae4
    strm << "XDODS-Server: " << DVR<< CRLF;
Packit a4aae4
    strm << "XOPeNDAP-Server: " << DVR<< CRLF;
Packit a4aae4
Packit a4aae4
    if (protocol == "")
Packit a4aae4
        strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
Packit a4aae4
    else
Packit a4aae4
        strm << "XDAP: " << protocol << CRLF;
Packit a4aae4
Packit a4aae4
    const time_t t = time(0);
Packit a4aae4
    strm << "Date: " << rfc822_date(t).c_str() << CRLF;
Packit a4aae4
Packit a4aae4
    strm << "Last-Modified: ";
Packit a4aae4
    if (last_modified > 0)
Packit a4aae4
        strm << rfc822_date(last_modified).c_str() << CRLF;
Packit a4aae4
    else
Packit a4aae4
        strm << rfc822_date(t).c_str() << CRLF;
Packit a4aae4
Packit a4aae4
    strm << "Content-type: text/html" << CRLF;
Packit a4aae4
    // See note above about Content-Description header. jhrg 12/23/05
Packit a4aae4
    strm << "Content-Description: " << descrip[type] << CRLF;
Packit a4aae4
    if (type == dods_error) // don't cache our error responses.
Packit a4aae4
        strm << "Cache-Control: no-cache" << CRLF;
Packit a4aae4
    // Don't write a Content-Encoding header for x-plain since that breaks
Packit a4aae4
    // Netscape on NT. jhrg 3/23/97
Packit a4aae4
    if (enc != x_plain)
Packit a4aae4
        strm << "Content-Encoding: " << encoding[enc] << CRLF;
Packit a4aae4
    strm << CRLF;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Write an HTTP 1.0 response header for our binary response document (i.e.,
Packit a4aae4
    the DataDDS object).
Packit a4aae4
Packit a4aae4
    @deprecated
Packit a4aae4
    @param out Write the MIME header to this FILE pointer.
Packit a4aae4
    @param type The type of this this response. Defaults to
Packit a4aae4
    application/octet-stream.
Packit a4aae4
    @param ver The version string; denotes the libdap implementation
Packit a4aae4
    version.
Packit a4aae4
    @param enc How is this response encoded? Can be plain or deflate or the
Packit a4aae4
    x_... versions of those. Default is x_plain.
Packit a4aae4
    @param last_modified The time to use for the Last-Modified header value.
Packit a4aae4
    Default is zero which means use the current time.
Packit a4aae4
 */
Packit a4aae4
void
Packit a4aae4
set_mime_binary(FILE *out, ObjectType type, const string &ver,
Packit a4aae4
                EncodingType enc, const time_t last_modified)
Packit a4aae4
{
Packit a4aae4
    ostringstream oss;
Packit a4aae4
    set_mime_binary(oss, type, ver, enc, last_modified);
Packit a4aae4
    fwrite(oss.str().data(), 1, oss.str().length(), out);
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Write an HTTP 1.0 response header for our binary response document (i.e.,
Packit a4aae4
    the DataDDS object).
Packit a4aae4
Packit a4aae4
    @deprecated
Packit a4aae4
    @param strm Write the MIME header to this stream.
Packit a4aae4
    @param type The type of this this response. Defaults to
Packit a4aae4
    application/octet-stream.
Packit a4aae4
    @param ver The version string; denotes the libdap implementation
Packit a4aae4
    version.
Packit a4aae4
    @param enc How is this response encoded? Can be plain or deflate or the
Packit a4aae4
    x_... versions of those. Default is x_plain.
Packit a4aae4
    @param last_modified The time to use for the Last-Modified header value.
Packit a4aae4
    Default is zero which means use the current time.
Packit a4aae4
 */
Packit a4aae4
void
Packit a4aae4
set_mime_binary(ostream &strm, ObjectType type, const string &ver,
Packit a4aae4
                EncodingType enc, const time_t last_modified)
Packit a4aae4
{
Packit a4aae4
    strm << "HTTP/1.0 200 OK" << CRLF ;
Packit a4aae4
    if (ver == "") {
Packit a4aae4
        strm << "XDODS-Server: " << DVR << CRLF ;
Packit a4aae4
        strm << "XOPeNDAP-Server: " << DVR << CRLF ;
Packit a4aae4
    }
Packit a4aae4
    else {
Packit a4aae4
        strm << "XDODS-Server: " << ver.c_str() << CRLF ;
Packit a4aae4
        strm << "XOPeNDAP-Server: " << ver.c_str() << CRLF ;
Packit a4aae4
    }
Packit a4aae4
    strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
Packit a4aae4
Packit a4aae4
    const time_t t = time(0);
Packit a4aae4
    strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
Packit a4aae4
Packit a4aae4
    strm << "Last-Modified: " ;
Packit a4aae4
    if (last_modified > 0)
Packit a4aae4
        strm << rfc822_date(last_modified).c_str() << CRLF ;
Packit a4aae4
    else
Packit a4aae4
        strm << rfc822_date(t).c_str() << CRLF ;
Packit a4aae4
Packit a4aae4
    strm << "Content-Type: application/octet-stream" << CRLF ;
Packit a4aae4
    strm << "Content-Description: " << descrip[type] << CRLF ;
Packit a4aae4
    if (enc != x_plain)
Packit a4aae4
        strm << "Content-Encoding: " << encoding[enc] << CRLF ;
Packit a4aae4
Packit a4aae4
    strm << CRLF ;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Write an HTTP 1.0 response header for our binary response document (i.e.,
Packit a4aae4
 the DataDDS object).
Packit a4aae4
Packit a4aae4
 @param strm Write the MIME header to this stream.
Packit a4aae4
 @param type The type of this this response. Defaults to
Packit a4aae4
 application/octet-stream.
Packit a4aae4
 @param ver The version string; denotes the libdap implementation
Packit a4aae4
 version.
Packit a4aae4
 @param enc How is this response encoded? Can be plain or deflate or the
Packit a4aae4
 x_... versions of those. Default is x_plain.
Packit a4aae4
 @param last_modified The time to use for the Last-Modified header value.
Packit a4aae4
 Default is zero which means use the current time.
Packit a4aae4
 */
Packit a4aae4
void set_mime_binary(ostream &strm, ObjectType type, EncodingType enc, const time_t last_modified,
Packit a4aae4
        const string &protocol)
Packit a4aae4
{
Packit a4aae4
    strm << "HTTP/1.0 200 OK" << CRLF;
Packit a4aae4
Packit a4aae4
    strm << "XDODS-Server: " << DVR << CRLF;
Packit a4aae4
    strm << "XOPeNDAP-Server: " << DVR << CRLF;
Packit a4aae4
Packit a4aae4
    if (protocol.empty())
Packit a4aae4
        strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF;
Packit a4aae4
    else
Packit a4aae4
        strm << "XDAP: " << protocol << CRLF;
Packit a4aae4
Packit a4aae4
    const time_t t = time(0);
Packit a4aae4
    strm << "Date: " << rfc822_date(t).c_str() << CRLF;
Packit a4aae4
Packit a4aae4
    strm << "Last-Modified: ";
Packit a4aae4
    if (last_modified > 0)
Packit a4aae4
        strm << rfc822_date(last_modified).c_str() << CRLF;
Packit a4aae4
    else
Packit a4aae4
        strm << rfc822_date(t).c_str() << CRLF;
Packit a4aae4
Packit a4aae4
    strm << "Content-Type: application/octet-stream" << CRLF;
Packit a4aae4
    strm << "Content-Description: " << descrip[type] << CRLF;
Packit a4aae4
    if (enc != x_plain)
Packit a4aae4
        strm << "Content-Encoding: " << encoding[enc] << CRLF;
Packit a4aae4
Packit a4aae4
    strm << CRLF;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
void set_mime_multipart(ostream &strm, const string &boundary,
Packit a4aae4
	const string &start, ObjectType type,
Packit a4aae4
        const string &version, EncodingType enc,
Packit a4aae4
        const time_t last_modified)
Packit a4aae4
{
Packit a4aae4
    strm << "HTTP/1.0 200 OK" << CRLF ;
Packit a4aae4
    if (version == "") {
Packit a4aae4
        strm << "XDODS-Server: " << DVR << CRLF ;
Packit a4aae4
        strm << "XOPeNDAP-Server: " << DVR << CRLF ;
Packit a4aae4
    }
Packit a4aae4
    else {
Packit a4aae4
        strm << "XDODS-Server: " << version.c_str() << CRLF ;
Packit a4aae4
        strm << "XOPeNDAP-Server: " << version.c_str() << CRLF ;
Packit a4aae4
    }
Packit a4aae4
    strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
Packit a4aae4
Packit a4aae4
    const time_t t = time(0);
Packit a4aae4
    strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
Packit a4aae4
Packit a4aae4
    strm << "Last-Modified: " ;
Packit a4aae4
    if (last_modified > 0)
Packit a4aae4
        strm << rfc822_date(last_modified).c_str() << CRLF ;
Packit a4aae4
    else
Packit a4aae4
        strm << rfc822_date(t).c_str() << CRLF ;
Packit a4aae4
Packit a4aae4
    strm << "Content-Type: Multipart/Related; boundary=" << boundary
Packit a4aae4
	<< "; start=\"<" << start << ">\"; type=\"Text/xml\"" << CRLF ;
Packit a4aae4
    strm << "Content-Description: " << descrip[type] << CRLF ;
Packit a4aae4
    if (enc != x_plain)
Packit a4aae4
        strm << "Content-Encoding: " << encoding[enc] << CRLF ;
Packit a4aae4
Packit a4aae4
    strm << CRLF ;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Build the initial headers for the DAP4 data response */
Packit a4aae4
Packit a4aae4
void set_mime_multipart(ostream &strm, const string &boundary, const string &start, ObjectType type, EncodingType enc,
Packit a4aae4
        const time_t last_modified, const string &protocol, const string &url)
Packit a4aae4
{
Packit a4aae4
    strm << "HTTP/1.1 200 OK" << CRLF;
Packit a4aae4
Packit a4aae4
    const time_t t = time(0);
Packit a4aae4
    strm << "Date: " << rfc822_date(t).c_str() << CRLF;
Packit a4aae4
Packit a4aae4
    strm << "Last-Modified: ";
Packit a4aae4
    if (last_modified > 0)
Packit a4aae4
        strm << rfc822_date(last_modified).c_str() << CRLF;
Packit a4aae4
    else
Packit a4aae4
        strm << rfc822_date(t).c_str() << CRLF;
Packit a4aae4
Packit a4aae4
    strm << "Content-Type: multipart/related; boundary=" << boundary << "; start=\"<" << start
Packit a4aae4
            << ">\"; type=\"text/xml\"" << CRLF;
Packit a4aae4
Packit a4aae4
    // data-ddx;"; removed as a result of the merge of the hyrax 1.8 release
Packit a4aae4
    // branch.
Packit a4aae4
    strm << "Content-Description: " << descrip[type] << ";";
Packit a4aae4
    if (!url.empty())
Packit a4aae4
        strm << " url=\"" << url << "\"" << CRLF;
Packit a4aae4
    else
Packit a4aae4
        strm << CRLF;
Packit a4aae4
Packit a4aae4
    if (enc != x_plain)
Packit a4aae4
        strm << "Content-Encoding: " << encoding[enc] << CRLF;
Packit a4aae4
Packit a4aae4
    if (protocol == "")
Packit a4aae4
        strm << "X-DAP: " << DAP_PROTOCOL_VERSION << CRLF;
Packit a4aae4
    else
Packit a4aae4
        strm << "X-DAP: " << protocol << CRLF;
Packit a4aae4
Packit a4aae4
    strm << "X-OPeNDAP-Server: " << DVR<< CRLF;
Packit a4aae4
Packit a4aae4
    strm << CRLF;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
void set_mime_ddx_boundary(ostream &strm, const string &boundary,
Packit a4aae4
	const string &cid, ObjectType type, EncodingType enc)
Packit a4aae4
{
Packit a4aae4
    strm << "--" << boundary << CRLF;
Packit a4aae4
    // 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.
Packit a4aae4
    strm << "Content-Type: Text/xml; charset=iso-8859-1" << CRLF;
Packit a4aae4
    strm << "Content-Id: <" << cid << ">" << CRLF;
Packit a4aae4
    strm << "Content-Description: " << descrip[type] << CRLF ;
Packit a4aae4
    if (enc != x_plain)
Packit a4aae4
         strm << "Content-Encoding: " << encoding[enc] << CRLF ;
Packit a4aae4
Packit a4aae4
    strm << CRLF;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
void set_mime_data_boundary(ostream &strm, const string &boundary,
Packit a4aae4
	const string &cid, ObjectType type, EncodingType enc)
Packit a4aae4
{
Packit a4aae4
    strm << "--" << boundary << CRLF;
Packit a4aae4
    strm << "Content-Type: application/octet-stream" << CRLF;
Packit a4aae4
    strm << "Content-Id: <" << cid << ">" << CRLF;
Packit a4aae4
    strm << "Content-Description: " << descrip[type] << CRLF ;
Packit a4aae4
    if (enc != x_plain)
Packit a4aae4
         strm << "Content-Encoding: " << encoding[enc] << CRLF ;
Packit a4aae4
Packit a4aae4
    strm << CRLF;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
const size_t line_length = 1024;
Packit a4aae4
Packit a4aae4
/** Read the next MIME header from the input stream and return it in a string
Packit a4aae4
    object. This function consumes any leading whitespace before the next
Packit a4aae4
    header. It returns an empty string when the blank line that separates
Packit a4aae4
    the headers from the body is found. This function works for header and
Packit a4aae4
    separator lines that use either a CRLF pair (the correct line ending) or
Packit a4aae4
    just a newline (a common error).
Packit a4aae4
Packit a4aae4
    @deprecated
Packit a4aae4
    @note FIXME: This function returns tainted data.
Packit a4aae4
    @param in Read from this stream (FILE *)
Packit a4aae4
    @return A string that contains the next header line or is empty indicating
Packit a4aae4
    the separator has been read.
Packit a4aae4
    @exception Error is thrown if no header or separator is found.
Packit a4aae4
    @see parse_mime_header()
Packit a4aae4
 */
Packit a4aae4
string get_next_mime_header(FILE *in)
Packit a4aae4
{
Packit a4aae4
    // Get the header line and strip \r\n. Some headers end with just \n.
Packit a4aae4
    // If a blank line is found, return an empty string.
Packit a4aae4
    char line[line_length];
Packit a4aae4
    while (!feof(in)) {
Packit a4aae4
        if (fgets(line, line_length, in)
Packit a4aae4
        	&& (strncmp(line, CRLF, 2) == 0 || line[0] == '\n'))
Packit a4aae4
            return "";
Packit a4aae4
        else {
Packit a4aae4
            size_t slen = min(strlen(line), line_length); // Never > line_length
Packit a4aae4
            line[slen - 1] = '\0'; // remove the newline
Packit a4aae4
            if (line[slen - 2] == '\r') // ...and the preceding carriage return
Packit a4aae4
                line[slen - 2] = '\0';
Packit a4aae4
            return string(line);
Packit a4aae4
        }
Packit a4aae4
    }
Packit a4aae4
Packit a4aae4
    throw Error("I expected to find a MIME header, but got EOF instead.");
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/**
Packit a4aae4
 * This function and its companion read the next MIME header from an
Packit a4aae4
 * input stream (or FILE*) and return it as a string. The stream
Packit a4aae4
 * pointer is updated to the next line of input. It returns an empty
Packit a4aae4
 * string when the blank line ending the MIME headers is found.
Packit a4aae4
 *
Packit a4aae4
 * @note FIXME: This function returns tainted data.
Packit a4aae4
 *
Packit a4aae4
 * @param in Read the next header from the input stream
Packit a4aae4
 * @return Return the next header in a string; an empty string indicates
Packit a4aae4
 * all headers have been returned.
Packit a4aae4
 */
Packit a4aae4
string get_next_mime_header(istream &in)
Packit a4aae4
{
Packit a4aae4
#if 0
Packit a4aae4
    // Get the header line and strip \r\n. Some headers end with just \n.
Packit a4aae4
    // If a blank line is found, return an empty string.
Packit a4aae4
	char line[line_length];
Packit a4aae4
	while (!in.eof()) {
Packit a4aae4
		in.getline(line, line_length);
Packit a4aae4
		if (strncmp(line, CRLF, 2) == 0 || line[0] == '\n') {
Packit a4aae4
			return "";
Packit a4aae4
		}
Packit a4aae4
		else {
Packit a4aae4
			size_t slen = min(strlen(line), line_length); // Never > line_length
Packit a4aae4
			line[slen - 1] = '\0'; // remove the newline
Packit a4aae4
			if (line[slen - 2] == '\r') // ...and the preceding carriage return
Packit a4aae4
				line[slen - 2] = '\0';
Packit a4aae4
			return string(line);
Packit a4aae4
		}
Packit a4aae4
	}
Packit a4aae4
#endif
Packit a4aae4
    // Get the header line and strip \r\n. Some headers end with just \n.
Packit a4aae4
    // If a blank line is found, return an empty string.
Packit a4aae4
	char raw_line[line_length];
Packit a4aae4
	while (!in.eof()) {
Packit a4aae4
		in.getline(raw_line, line_length); // strips the trailing newline; terminates with null
Packit a4aae4
		string line = raw_line;
Packit a4aae4
		if (line.find('\r') != string::npos)
Packit a4aae4
			line = line.substr(0, line.size()-1);
Packit a4aae4
		return line;
Packit a4aae4
	}
Packit a4aae4
Packit a4aae4
	throw Error("I expected to find a MIME header, but got EOF instead.");
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Given a string that contains a MIME header line, parse it into the
Packit a4aae4
    the header (name) and its value. Both are downcased.
Packit a4aae4
Packit a4aae4
    @param header The input line, striped of the ending crlf pair
Packit a4aae4
    @param name A value-result parameter that holds the header name
Packit a4aae4
    @param value A value-result parameter that holds the header's value.
Packit a4aae4
 */
Packit a4aae4
void parse_mime_header(const string &header, string &name, string &value)
Packit a4aae4
{
Packit a4aae4
    istringstream iss(header);
Packit a4aae4
Packit a4aae4
    size_t length = header.length() + 1;
Packit a4aae4
    vector<char> s(length);
Packit a4aae4
    //char s[line_length];
Packit a4aae4
    iss.getline(&s[0], length, ':');
Packit a4aae4
    name = &s[0];
Packit a4aae4
Packit a4aae4
    iss.ignore(length, ' ');
Packit a4aae4
    iss.getline(&s[0], length);
Packit a4aae4
    value = &s[0];
Packit a4aae4
Packit a4aae4
    downcase(name);
Packit a4aae4
    downcase(value);
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Is this string the same as the MPM boundary value?
Packit a4aae4
Packit a4aae4
    @note Since fgets() is used to read into line, it is guaranteed to be
Packit a4aae4
    null terminated.
Packit a4aae4
Packit a4aae4
    @param line The input to test
Packit a4aae4
    @param boundary The complete boundary line to test for, excluding
Packit a4aae4
    terminating characters.
Packit a4aae4
    @return true is line is a MPM boundary
Packit a4aae4
 */
Packit a4aae4
Packit a4aae4
bool is_boundary(const char *line, const string &boundary)
Packit a4aae4
{
Packit a4aae4
    if (strlen(line) < 2 || !(line[0] == '-' && line[1] == '-'))
Packit a4aae4
		return false;
Packit a4aae4
    else
Packit a4aae4
		return strncmp(line, boundary.c_str(), boundary.length()) == 0;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Read the next line of input and test to see if it is a multipart MIME
Packit a4aae4
    boundary line. If the value of boundary is the default (an empty string)
Packit a4aae4
    then just test that the line starts with "--". In either case, return the
Packit a4aae4
    value of boundary just read.
Packit a4aae4
Packit a4aae4
    @param boundary Value of the boundary to look for - optional
Packit a4aae4
    @param in Read from this FILE*
Packit a4aae4
    @return The value of teh boundary header read
Packit a4aae4
    @exception Error if no boundary was found.
Packit a4aae4
 */
Packit a4aae4
string read_multipart_boundary(FILE *in, const string &boundary)
Packit a4aae4
{
Packit a4aae4
    string boundary_line = get_next_mime_header(in);
Packit a4aae4
    // If the caller passed in a value for the boundary, test for that value,
Packit a4aae4
    // else just see that this line starts with '--'.
Packit a4aae4
    // The value of 'boundary_line' is returned by this function.
Packit a4aae4
    if ((!boundary.empty() && is_boundary(boundary_line.c_str(), boundary))
Packit a4aae4
	    || boundary_line.find("--") != 0)
Packit a4aae4
	throw Error(internal_error, "The DAP4 data response document is broken - missing or malformed boundary.");
Packit a4aae4
Packit a4aae4
    return boundary_line;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
string read_multipart_boundary(istream &in, const string &boundary)
Packit a4aae4
{
Packit a4aae4
    string boundary_line = get_next_mime_header(in);
Packit a4aae4
    // If the caller passed in a value for the boundary, test for that value,
Packit a4aae4
    // else just see that this line starts with '--'.
Packit a4aae4
    // The value of 'boundary_line' is returned by this function.
Packit a4aae4
    if ((!boundary.empty() && is_boundary(boundary_line.c_str(), boundary))
Packit a4aae4
	    || boundary_line.find("--") != 0)
Packit a4aae4
	throw Error(internal_error, "The DAP4 data response document is broken - missing or malformed boundary.");
Packit a4aae4
Packit a4aae4
    return boundary_line;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Consume the Multipart MIME headers that prefix the DDX in a DataDDX
Packit a4aae4
    response. The stream pointer is advanced to the start of the DDX. It might
Packit a4aae4
    seem odd that this function both takes the value of the MPM boundary as
Packit a4aae4
    a parameter _and_ returns that value as a result, but this code can be
Packit a4aae4
    used in two different situations. In one case, it is called on a partial
Packit a4aae4
    document read from stdin and needs to return the value of boundary to the
Packit a4aae4
    downstream DDX parser so that code can sense the end of the DDX. In the
Packit a4aae4
    other case, this function is told the value of boundary and tests for it
Packit a4aae4
    to ensure document correctness.
Packit a4aae4
Packit a4aae4
    @param in Read from this stream
Packit a4aae4
    @param content_type The expected value of the Content-Type header
Packit a4aae4
    @param object_type The expected value of the Content-Description header
Packit a4aae4
    @param cid The expected value of the Content-Id header - optional.
Packit a4aae4
Packit a4aae4
    @return The value of the MIME boundary
Packit a4aae4
    @exception Error if the boundary is not found or if any of the expected
Packit a4aae4
    header values don't match. The optional values are tested only if they
Packit a4aae4
    are given (the default values are not tested).
Packit a4aae4
 */
Packit a4aae4
void read_multipart_headers(FILE *in, const string &content_type, const ObjectType object_type, const string &cid)
Packit a4aae4
{
Packit a4aae4
	bool ct = false, cd = false, ci = false;
Packit a4aae4
Packit a4aae4
	// The function get_next_mime_header() returns tainted data. Fix this
Packit a4aae4
	// if we are going to use this code. jhrg 4/18/17
Packit a4aae4
	string header = get_next_mime_header(in);
Packit a4aae4
	while (!header.empty()) {
Packit a4aae4
		string name, value;
Packit a4aae4
		parse_mime_header(header, name, value);
Packit a4aae4
Packit a4aae4
		if (name == "content-type") {
Packit a4aae4
			ct = true;
Packit a4aae4
			if (value.find(content_type) == string::npos)
Packit a4aae4
				throw Error(internal_error, "Content-Type for this part of a DAP2 data ddx response must be " + content_type + ".");
Packit a4aae4
		}
Packit a4aae4
		else if (name == "content-description") {
Packit a4aae4
			cd = true;
Packit a4aae4
			if (get_description_type(value) != object_type)
Packit a4aae4
				throw Error(internal_error, "Content-Description for this part of a DAP2 data ddx response must be dods-ddx or dods-data-ddx");
Packit a4aae4
		}
Packit a4aae4
		else if (name == "content-id") {
Packit a4aae4
			ci = true;
Packit a4aae4
			if (!cid.empty() && value != cid)
Packit a4aae4
				throw Error("Content-Id mismatch. Expected: " + cid + ", but got: " + value);
Packit a4aae4
		}
Packit a4aae4
Packit a4aae4
		header = get_next_mime_header(in);
Packit a4aae4
	}
Packit a4aae4
Packit a4aae4
	if (!(ct && cd && ci)) throw Error(internal_error, "The DAP4 data response document is broken - missing header.");
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
void read_multipart_headers(istream &in, const string &content_type, const ObjectType object_type, const string &cid)
Packit a4aae4
{
Packit a4aae4
	bool ct = false, cd = false, ci = false;
Packit a4aae4
Packit a4aae4
	string header = get_next_mime_header(in);
Packit a4aae4
	while (!header.empty()) {
Packit a4aae4
		string name, value;
Packit a4aae4
		parse_mime_header(header, name, value);
Packit a4aae4
Packit a4aae4
		if (name == "content-type") {
Packit a4aae4
			ct = true;
Packit a4aae4
			if (value.find(content_type) == string::npos)
Packit a4aae4
				throw Error(internal_error, "Content-Type for this part of a DAP4 data response must be " + content_type + ".");
Packit a4aae4
		}
Packit a4aae4
		else if (name == "content-description") {
Packit a4aae4
			cd = true;
Packit a4aae4
			if (get_description_type(value) != object_type)
Packit a4aae4
				throw Error("Content-Description '" + value + "' not the expected value (expected: " + descrip[object_type] + ").");
Packit a4aae4
		}
Packit a4aae4
		else if (name == "content-id") {
Packit a4aae4
			ci = true;
Packit a4aae4
			if (!cid.empty() && value != cid)
Packit a4aae4
				throw Error("Content-Id mismatch. Expected: " + cid + ", but got: " + value);
Packit a4aae4
		}
Packit a4aae4
Packit a4aae4
		header = get_next_mime_header(in);
Packit a4aae4
	}
Packit a4aae4
Packit a4aae4
	if (!(ct && cd && ci)) throw Error(internal_error, "The DAP4 data response document is broken - missing header.");
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Given a Content-Id read from the DDX, return the value to look for in a
Packit a4aae4
    MPM Content-Id header. This function downcases the CID to match the value
Packit a4aae4
    returned by parse_mime_header.
Packit a4aae4
Packit a4aae4
    @param cid The Content-Id read from the DDX
Packit a4aae4
    @return The header value to look for.
Packit a4aae4
    @exception Error if the CID does not start with the string "cid:"
Packit a4aae4
 */
Packit a4aae4
string cid_to_header_value(const string &cid)
Packit a4aae4
{
Packit a4aae4
    string::size_type offset = cid.find("cid:");
Packit a4aae4
    if (offset != 0)
Packit a4aae4
        throw Error(internal_error, "expected CID to start with 'cid:'");
Packit a4aae4
Packit a4aae4
    string value = "<";
Packit a4aae4
    value.append(cid.substr(offset + 4));
Packit a4aae4
    value.append(">");
Packit a4aae4
    downcase(value);
Packit a4aae4
Packit a4aae4
    return value;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Generate an HTTP 1.0 response header for an Error object.
Packit a4aae4
Packit a4aae4
    @deprecated
Packit a4aae4
    @param out Write the MIME header to this FILE pointer.
Packit a4aae4
    @param code HTTP 1.0 response code. Should be 400, ... 500, ...
Packit a4aae4
    @param reason Reason string of the HTTP 1.0 response header.
Packit a4aae4
    @param version The version string; denotes the DAP spec and implementation
Packit a4aae4
    version. */
Packit a4aae4
void
Packit a4aae4
set_mime_error(FILE *out, int code, const string &reason,
Packit a4aae4
               const string &version)
Packit a4aae4
{
Packit a4aae4
    ostringstream oss;
Packit a4aae4
    set_mime_error(oss, code, reason, version);
Packit a4aae4
    fwrite(oss.str().data(), 1, oss.str().length(), out);
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Generate an HTTP 1.0 response header for an Error object.
Packit a4aae4
Packit a4aae4
    @deprecated
Packit a4aae4
    @param strm Write the MIME header to this stream.
Packit a4aae4
    @param code HTTP 1.0 response code. Should be 400, ... 500, ...
Packit a4aae4
    @param reason Reason string of the HTTP 1.0 response header.
Packit a4aae4
    @param version The version string; denotes the DAP spec and implementation
Packit a4aae4
    version. */
Packit a4aae4
void
Packit a4aae4
set_mime_error(ostream &strm, int code, const string &reason,
Packit a4aae4
               const string &version)
Packit a4aae4
{
Packit a4aae4
    strm << "HTTP/1.0 " << code << " " << reason.c_str() << CRLF ;
Packit a4aae4
    if (version == "") {
Packit a4aae4
        strm << "XDODS-Server: " << DVR << CRLF ;
Packit a4aae4
        strm << "XOPeNDAP-Server: " << DVR << CRLF ;
Packit a4aae4
    }
Packit a4aae4
    else {
Packit a4aae4
        strm << "XDODS-Server: " << version.c_str() << CRLF ;
Packit a4aae4
        strm << "XOPeNDAP-Server: " << version.c_str() << CRLF ;
Packit a4aae4
    }
Packit a4aae4
    strm << "XDAP: " << DAP_PROTOCOL_VERSION << CRLF ;
Packit a4aae4
Packit a4aae4
    const time_t t = time(0);
Packit a4aae4
    strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
Packit a4aae4
    strm << "Cache-Control: no-cache" << CRLF ;
Packit a4aae4
    strm << CRLF ;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Use this function to create a response signaling that the target of a
Packit a4aae4
    conditional get has not been modified relative to the condition given in
Packit a4aae4
    the request. This will have to be a date until the servers support ETags.
Packit a4aae4
Packit a4aae4
    @deprecated
Packit a4aae4
    @brief Send a `Not Modified' response.
Packit a4aae4
    @param out Write the response to this FILE pointer. */
Packit a4aae4
void
Packit a4aae4
set_mime_not_modified(FILE *out)
Packit a4aae4
{
Packit a4aae4
    ostringstream oss;
Packit a4aae4
    set_mime_not_modified(oss);
Packit a4aae4
    fwrite(oss.str().data(), 1, oss.str().length(), out);
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/** Use this function to create a response signaling that the target of a
Packit a4aae4
    conditional get has not been modified relative to the condition given in
Packit a4aae4
    the request. This will have to be a date until the servers support ETags.
Packit a4aae4
Packit a4aae4
    @deprecated
Packit a4aae4
    @brief Send a `Not Modified' response.
Packit a4aae4
    @param strm Write the response to this stream. */
Packit a4aae4
void
Packit a4aae4
set_mime_not_modified(ostream &strm)
Packit a4aae4
{
Packit a4aae4
    strm << "HTTP/1.0 304 NOT MODIFIED" << CRLF ;
Packit a4aae4
    const time_t t = time(0);
Packit a4aae4
    strm << "Date: " << rfc822_date(t).c_str() << CRLF ;
Packit a4aae4
    strm << CRLF ;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
#if 0
Packit a4aae4
Packit a4aae4
// This was removed because it's not being used by our server.
Packit a4aae4
Packit a4aae4
/** Look for the override file by taking the dataset name and
Packit a4aae4
    appending `.ovr' to it. If such a file exists, then read it in and
Packit a4aae4
    store the contents in <tt>doc</tt>. Note that the file contents
Packit a4aae4
    are not checked to see if they are valid HTML (which they must
Packit a4aae4
    be).
Packit a4aae4
Packit a4aae4
    @deprecated
Packit a4aae4
    @return True if the `override file' is present, false otherwise. in the
Packit a4aae4
    later case <tt>doc</tt>'s contents are undefined.  */
Packit a4aae4
bool
Packit a4aae4
found_override(string name, string &doc)
Packit a4aae4
{
Packit a4aae4
    ifstream ifs((name + ".ovr").c_str());
Packit a4aae4
    if (!ifs)
Packit a4aae4
        return false;
Packit a4aae4
Packit a4aae4
    char tmp[256];
Packit a4aae4
    doc = "";
Packit a4aae4
    while (!ifs.eof()) {
Packit a4aae4
        ifs.getline(tmp, 255);
Packit a4aae4
        tmp[255] = '\0';
Packit a4aae4
        strncat(tmp, "\n", sizeof(tmp) - strlen(tmp) - 1);
Packit a4aae4
        doc += tmp;
Packit a4aae4
    }
Packit a4aae4
Packit a4aae4
	ifs.close();
Packit a4aae4
    return true;
Packit a4aae4
}
Packit a4aae4
#endif
Packit a4aae4
Packit a4aae4
/** Read the input stream <tt>in</tt> and discard the MIME header. The MIME
Packit a4aae4
    header is separated from the body of the document by a single blank line.
Packit a4aae4
    If no MIME header is found, then the input stream is `emptied' and will
Packit a4aae4
    contain nothing.
Packit a4aae4
Packit a4aae4
    @deprecated
Packit a4aae4
    @brief Read and discard the MIME header of the stream <tt>in</tt>.
Packit a4aae4
    @return True if a MIME header is found, false otherwise.
Packit a4aae4
*/
Packit a4aae4
bool
Packit a4aae4
remove_mime_header(FILE *in)
Packit a4aae4
{
Packit a4aae4
    char tmp[256];
Packit a4aae4
    while (!feof(in)) {
Packit a4aae4
        char *s = fgets(tmp, 255, in);
Packit a4aae4
        if (s && strncmp(s, CRLF, 2) == 0)
Packit a4aae4
            return true;
Packit a4aae4
    }
Packit a4aae4
Packit a4aae4
    return false;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/**
Packit a4aae4
 * Used for test code; strip the leading MIME headers from a response.
Packit a4aae4
 * @param in
Packit a4aae4
 */
Packit a4aae4
void
Packit a4aae4
remove_mime_header(istream &in)
Packit a4aae4
{
Packit a4aae4
	while(!get_next_mime_header(in).empty()) ;
Packit a4aae4
#if 0
Packit a4aae4
	string header;
Packit a4aae4
	do {
Packit a4aae4
		header = get_next_mime_header(in);
Packit a4aae4
	} while (!header.empty());
Packit a4aae4
#endif
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
} // namespace libdap
Packit a4aae4