// -*- mode: c++; c-basic-offset:4 -*-
// This file is part of libdap, A C++ implementation of the OPeNDAP Data
// Access Protocol.
// Copyright (c) 2002,2003,2013 OPeNDAP, Inc.
// Author: James Gallagher <jgallagher@opendap.org>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
#include <cppunit/TextTestRunner.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cstring>
#include <iterator>
#include <string>
#include <algorithm>
#include <functional>
#include "GNURegex.h"
#include "HTTPConnect.h"
#include "RCReader.h"
#include "debug.h"
#include "test_config.h"
using namespace CppUnit;
using namespace std;
#include"GetOpt.h"
static bool debug = false;
#undef DBG
#define DBG(x) do { if (debug) (x); } while(false);
namespace libdap {
class HTTPConnectTest: public TestFixture {
private:
HTTPConnect * http;
string localhost_url, localhost_pw_url, localhost_digest_pw_url;
string etag;
string lm;
string netcdf_das_url;
// char env_data[128];
protected:
bool re_match(Regex & r, const char *s)
{
return r.match(s, strlen(s)) == (int) strlen(s);
}
struct REMatch: public unary_function<const string &, bool> {
Regex &d_re;
REMatch(Regex &re) :
d_re(re)
{
}
~REMatch()
{
}
bool operator()(const string &str)
{
const char *s = str.c_str();
return d_re.match(s, strlen(s)) == (int) strlen(s);
}
};
// This is defined in HTTPConnect.cc but has to be defined here as well.
// Don't know why... jhrg
class HeaderMatch: public unary_function<const string &, bool> {
const string &d_header;
public:
HeaderMatch(const string &header) :
d_header(header)
{
}
bool operator()(const string &arg)
{
return arg.find(d_header) == 0;
}
};
public:
HTTPConnectTest()
{
}
~HTTPConnectTest()
{
}
void setUp()
{
DBG(cerr << "Setting the DODS_CONF env var" << endl);
setenv("DODS_CONF", "cache-testsuite/dodsrc", 1);
http = new HTTPConnect(RCReader::instance());
localhost_url = "http://test.opendap.org/test-304.html";
// Two request header values that will generate a 304 response to the
// above URL. The values below much match the etag and last-modified
// time returned by the server. Run this test with DODS_DEBUG defined
// to see the values it's returning.
// On 10/13/14 we moved to a new httpd and the etag value changed.
// jhrg 10/14/14
etag = "\"181893-157-3fbcd139c2680\""; //"\"2a008e-157-3fbcd139c2680\""; //\"a10df-157-139c2680\""; // a10df-157-139c2680a
lm = "Wed, 13 Jul 2005 19:32:26 GMT";
localhost_pw_url = "http://jimg:dods_test@test.opendap.org/basic/page.txt";
localhost_digest_pw_url = "http://jimg:dods_digest@test.opendap.org/digest/page.txt";
netcdf_das_url = "http://test.opendap.org/dap/data/nc/fnoc1.nc.das";
}
void tearDown()
{
// normal code doesn't do this - it happens at exit() but not doing
// this here make valgrind think there are leaks.
http->d_http_cache->delete_instance();
delete http;
http = 0;
unsetenv("DODS_CONF");
}
CPPUNIT_TEST_SUITE (HTTPConnectTest);
CPPUNIT_TEST (read_url_test);
CPPUNIT_TEST (fetch_url_test_1);
CPPUNIT_TEST (fetch_url_test_2);
CPPUNIT_TEST (fetch_url_test_3);
CPPUNIT_TEST (fetch_url_test_4);
CPPUNIT_TEST (fetch_url_test_1_cpp);
CPPUNIT_TEST (fetch_url_test_2_cpp);
CPPUNIT_TEST (fetch_url_test_3_cpp);
CPPUNIT_TEST (fetch_url_test_4_cpp);
CPPUNIT_TEST (get_response_headers_test);
CPPUNIT_TEST (server_version_test);
CPPUNIT_TEST (type_test);
CPPUNIT_TEST (cache_test);
CPPUNIT_TEST (cache_test_cpp);
CPPUNIT_TEST (set_accept_deflate_test);
CPPUNIT_TEST (set_xdap_protocol_test);
CPPUNIT_TEST (read_url_password_test);
CPPUNIT_TEST (read_url_password_test2);
// CPPUNIT_TEST(read_url_password_proxy_test);
CPPUNIT_TEST_SUITE_END();
void read_url_test()
{
vector<string> *resp_h = new vector<string>;
;
try {
DBG(cerr << "Entering read_url_test... " << endl);
FILE *dump = fopen("/dev/null", "w");
long status = http->read_url(localhost_url, dump, resp_h);
CPPUNIT_ASSERT(status == 200);
vector<string> request_h;
// First test using a time with if-modified-since
request_h.push_back(string("If-Modified-Since: ") + lm);
status = http->read_url(localhost_url, dump, resp_h, &request_h);
DBG(cerr << "If modified since test, status: " << status << endl);
DBG(copy(resp_h->begin(), resp_h->end(), ostream_iterator<string>(cerr, "\n")));
DBG(cerr << endl);
CPPUNIT_ASSERT(status == 304);
// Now test an etag
resp_h->clear();
request_h.clear();
request_h.push_back(string("If-None-Match: ") + etag);
status = http->read_url(localhost_url, dump, resp_h, &request_h);
DBG(cerr << "If none match test, status: " << status << endl);
DBG(copy(resp_h->begin(), resp_h->end(), ostream_iterator<string>(cerr, "\n")));
DBG(cerr << endl);
CPPUNIT_ASSERT(status == 304);
// now test a combined etag and time4
resp_h->clear();
request_h.clear();
request_h.push_back(string("If-None-Match: ") + etag);
request_h.push_back(string("If-Modified-Since: ") + lm);
status = http->read_url(localhost_url, dump, resp_h, &request_h);
DBG(cerr << "Combined test, status: " << status << endl);
DBG(copy(resp_h->begin(), resp_h->end(), ostream_iterator<string>(cerr, "\n")));
CPPUNIT_ASSERT(status == 304);
delete resp_h;
resp_h = 0;
}
catch (Error & e) {
delete resp_h;
resp_h = 0;
CPPUNIT_FAIL("Error: " + e.get_error_message());
}
}
void fetch_url_test_1()
{
DBG(cerr << "Entering fetch_url_test 1" << endl);
HTTPResponse *stuff = 0;
char c;
try {
stuff = http->fetch_url(localhost_url);
CPPUNIT_ASSERT(
fread(&c, 1, 1, stuff->get_stream()) == 1 && !ferror(stuff->get_stream())
&& !feof(stuff->get_stream()));
delete stuff;
stuff = 0;
}
catch (InternalErr & e) {
delete stuff;
stuff = 0;
CPPUNIT_FAIL("Caught an InternalErr from fetch_url: " + e.get_error_message());
}
catch (Error & e) {
delete stuff;
stuff = 0;
CPPUNIT_FAIL("Caught an Error from fetch_url: " + e.get_error_message());
}
// Catch the exception from a failed ASSERT and clean up. Deleting a
// Response object also unlocks the HTTPCache in some cases. If delete
// is not called, then a failed test can leave the cache with locked
// entries
catch (...) {
cerr << "Caught unknown exception" << endl;
delete stuff;
stuff = 0;
throw;
}
}
void fetch_url_test_1_cpp()
{
DBG(cerr << "Entering fetch_url_test 1" << endl);
HTTPResponse *stuff = 0;
http->set_use_cpp_streams(true);
char c;
try {
stuff = http->fetch_url(localhost_url);
stuff->get_cpp_stream()->read(&c, 1);
CPPUNIT_ASSERT(*(stuff->get_cpp_stream()));
CPPUNIT_ASSERT(!stuff->get_cpp_stream()->bad());
CPPUNIT_ASSERT(!stuff->get_cpp_stream()->eof());
delete stuff;
}
catch (InternalErr &e) {
delete stuff;
CPPUNIT_FAIL("Caught an InternalErr from fetch_url: " + e.get_error_message());
}
catch (Error &e) {
delete stuff;
CPPUNIT_FAIL("Caught an Error from fetch_url: " + e.get_error_message());
}
catch (std::exception &e) {
delete stuff;
CPPUNIT_FAIL(string("Caught an std::exception from fetch_url: ") + e.what());
}
// Catch the exception from a failed ASSERT and clean up. Deleting a
// Response object also unlocks the HTTPCache in some cases. If delete
// is not called, then a failed test can leave the cache with locked
// entries
catch (...) {
cerr << "Caught unknown exception" << endl;
delete stuff;
throw;
}
}
void fetch_url_test_2()
{
DBG(cerr << "Entering fetch_url_test 2" << endl);
HTTPResponse *stuff = 0;
char c;
try {
stuff = http->fetch_url(netcdf_das_url);
DBG2(char ln[1024]; while (!feof(stuff->get_stream())) {
fgets(ln, 1024, stuff->get_stream()); cerr << ln;}
rewind(stuff->get_stream()));
CPPUNIT_ASSERT(
fread(&c, 1, 1, stuff->get_stream()) == 1 && !ferror(stuff->get_stream())
&& !feof(stuff->get_stream()));
delete stuff;
stuff = 0;
}
catch (InternalErr & e) {
delete stuff;
stuff = 0;
CPPUNIT_FAIL("Caught an InternalErr from fetch_url: " + e.get_error_message());
}
catch (Error & e) {
delete stuff;
stuff = 0;
CPPUNIT_FAIL("Caught an Error from fetch_url: " + e.get_error_message());
}
// Catch the exception from a failed ASSERT and clean up. Deleting a
// Response object also unlocks the HTTPCache in some cases. If delete
// is not called, then a failed test can leave the cache with locked
// entries
catch (...) {
delete stuff;
stuff = 0;
throw;
}
}
void fetch_url_test_2_cpp()
{
DBG(cerr << "Entering fetch_url_test 2" << endl);
http->set_use_cpp_streams(true);
HTTPResponse *stuff = 0;
char c;
try {
stuff = http->fetch_url(netcdf_das_url);
stuff->get_cpp_stream()->read(&c, 1);
CPPUNIT_ASSERT(
*(stuff->get_cpp_stream()) && !stuff->get_cpp_stream()->bad() && !stuff->get_cpp_stream()->eof());
delete stuff;
stuff = 0;
}
catch (InternalErr & e) {
delete stuff;
stuff = 0;
CPPUNIT_FAIL("Caught an InternalErr from fetch_url: " + e.get_error_message());
}
catch (Error & e) {
delete stuff;
stuff = 0;
CPPUNIT_FAIL("Caught an Error from fetch_url: " + e.get_error_message());
}
// Catch the exception from a failed ASSERT and clean up. Deleting a
// Response object also unlocks the HTTPCache in some cases. If delete
// is not called, then a failed test can leave the cache with locked
// entries
catch (...) {
delete stuff;
stuff = 0;
throw;
}
}
void fetch_url_test_3()
{
DBG(cerr << "Entering fetch_url_test 3" << endl);
HTTPResponse *stuff = 0;
char c;
try {
stuff = http->fetch_url("file:///etc/passwd");
CPPUNIT_ASSERT(
fread(&c, 1, 1, stuff->get_stream()) == 1 && !ferror(stuff->get_stream())
&& !feof(stuff->get_stream()));
delete stuff;
stuff = 0;
}
catch (InternalErr & e) {
delete stuff;
stuff = 0;
CPPUNIT_FAIL("Caught an InternalErr from fetch_url" + e.get_error_message());
}
catch (Error & e) {
delete stuff;
stuff = 0;
CPPUNIT_FAIL("Caught an Error from fetch_url: " + e.get_error_message());
}
// Catch the exception from a failed ASSERT and clean up. Deleting a
// Response object also unlocks the HTTPCache in some cases. If delete
// is not called, then a failed test can leave the cache with locked
// entries
catch (...) {
delete stuff;
stuff = 0;
throw;
}
}
void fetch_url_test_3_cpp()
{
DBG(cerr << "Entering fetch_url_test 3" << endl);
http->set_use_cpp_streams(true);
HTTPResponse *stuff = 0;
char c;
try {
stuff = http->fetch_url("file:///etc/passwd");
stuff->get_cpp_stream()->read(&c, 1);
CPPUNIT_ASSERT(
*(stuff->get_cpp_stream()) && !stuff->get_cpp_stream()->bad() && !stuff->get_cpp_stream()->eof());
delete stuff;
stuff = 0;
}
catch (InternalErr & e) {
delete stuff;
stuff = 0;
CPPUNIT_FAIL("Caught an InternalErr from fetch_url" + e.get_error_message());
}
catch (Error & e) {
delete stuff;
stuff = 0;
CPPUNIT_FAIL("Caught an Error from fetch_url: " + e.get_error_message());
}
// Catch the exception from a failed ASSERT and clean up. Deleting a
// Response object also unlocks the HTTPCache in some cases. If delete
// is not called, then a failed test can leave the cache with locked
// entries
catch (...) {
delete stuff;
stuff = 0;
throw;
}
}
void fetch_url_test_4()
{
DBG(cerr << "Entering fetch_url_test 4" << endl);
HTTPResponse *stuff = 0;
char c;
try {
string url = (string) "file://" + TEST_SRC_DIR + "/test_config.h";
stuff = http->fetch_url(url);
CPPUNIT_ASSERT(
fread(&c, 1, 1, stuff->get_stream()) == 1 && !ferror(stuff->get_stream())
&& !feof(stuff->get_stream()));
delete stuff;
stuff = 0;
}
catch (InternalErr & e) {
delete stuff;
stuff = 0;
CPPUNIT_FAIL("Caught an InternalErr from fetch_url" + e.get_error_message());
}
catch (Error & e) {
delete stuff;
stuff = 0;
CPPUNIT_FAIL("Caught an Error from fetch_url: " + e.get_error_message());
}
// Catch the exception from a failed ASSERT and clean up. Deleting a
// Response object also unlocks the HTTPCache in some cases. If delete
// is not called, then a failed test can leave the cache with locked
// entries
catch (...) {
delete stuff;
stuff = 0;
throw;
}
}
void fetch_url_test_4_cpp()
{
DBG(cerr << "Entering fetch_url_test 4" << endl);
http->set_use_cpp_streams(true);
HTTPResponse *stuff = 0;
char c;
try {
string url = (string) "file://" + TEST_SRC_DIR + "/test_config.h";
stuff = http->fetch_url(url);
stuff->get_cpp_stream()->read(&c, 1);
CPPUNIT_ASSERT(
*(stuff->get_cpp_stream()) && !stuff->get_cpp_stream()->bad() && !stuff->get_cpp_stream()->eof());
delete stuff;
stuff = 0;
}
catch (InternalErr & e) {
delete stuff;
stuff = 0;
CPPUNIT_FAIL("Caught an InternalErr from fetch_url" + e.get_error_message());
}
catch (Error & e) {
delete stuff;
stuff = 0;
CPPUNIT_FAIL("Caught an Error from fetch_url: " + e.get_error_message());
}
// Catch the exception from a failed ASSERT and clean up. Deleting a
// Response object also unlocks the HTTPCache in some cases. If delete
// is not called, then a failed test can leave the cache with locked
// entries
catch (...) {
delete stuff;
stuff = 0;
throw;
}
}
void get_response_headers_test()
{
HTTPResponse *r = 0;
try {
r = http->fetch_url(netcdf_das_url);
vector<string> *h = r->get_headers();
DBG(copy(h->begin(), h->end(), ostream_iterator<string>(cerr, "\n")));
// Should get five or six headers back.
Regex header("X.*-Server: .*/.*");
CPPUNIT_ASSERT(find_if(h->begin(), h->end(), REMatch(header)) != h->end());
Regex protocol_header("X.*DAP: .*"); // Matches both XDAP and X-DAP
CPPUNIT_ASSERT(find_if(h->begin(), h->end(), REMatch(protocol_header)) != h->end());
delete r;
r = 0;
}
catch (InternalErr & e) {
delete r;
r = 0;
CPPUNIT_FAIL("Caught an InternalErr exception from get_response_headers: " + e.get_error_message());
}
catch (...) {
delete r;
r = 0;
throw;
}
}
void server_version_test()
{
Response *r = 0;
Regex protocol("^[0-9]+\\.[0-9]+$");
try {
r = http->fetch_url(netcdf_das_url);
DBG(cerr << "r->get_version().c_str(): " << r->get_protocol().c_str() << endl);
CPPUNIT_ASSERT(re_match(protocol, r->get_protocol().c_str()));
delete r;
r = 0;
}
catch (InternalErr & e) {
delete r;
r = 0;
CPPUNIT_FAIL("Caught an InternalErr exception from server_version: " + e.get_error_message());
}
catch (...) {
delete r;
r = 0;
throw;
}
}
void type_test()
{
Response *r = 0;
try {
r = http->fetch_url(netcdf_das_url);
DBG(cerr << "r->get_type(): " << r->get_type() << endl);
CPPUNIT_ASSERT(r->get_type() == dods_das);
delete r;
r = 0;
}
catch (InternalErr & e) {
delete r;
r = 0;
CPPUNIT_FAIL("Caught an InternalErr exception from type(): " + e.get_error_message());
}
}
void set_credentials_test()
{
http->set_credentials("jimg", "was_quit");
Response *stuff = http->fetch_url("http://localhost/secret");
try {
char c;
CPPUNIT_ASSERT(
fread(&c, 1, 1, stuff->get_stream()) == 1 && !ferror(stuff->get_stream())
&& !feof(stuff->get_stream()));
delete stuff;
stuff = 0;
}
catch (InternalErr & e) {
delete stuff;
stuff = 0;
CPPUNIT_FAIL("Caught an InternalErrexception from output: " + e.get_error_message());
}
}
void cache_test()
{
DBG(cerr << endl << "Entering Caching tests." << endl);
try {
// The cache-testsuite/dodsrc file turns this off; all the other
// params are set up. It used to be that HTTPConnect had an option to
// turn caching on, but that no longer is present. This hack enables
// caching for this test. 06/18/04 jhrg
http->d_http_cache = HTTPCache::instance(http->d_rcr->get_dods_cache_root(), true);
DBG(cerr << "Instantiate the cache" << endl);
CPPUNIT_ASSERT(http->d_http_cache != 0);
http->d_http_cache->set_cache_enabled(true);
DBG(cerr << "Enable the cache" << endl);
fetch_url_test_4();
DBG(cerr << "fetch_url_test" << endl);
get_response_headers_test();
DBG(cerr << "get_response_headers_test" << endl);
server_version_test();
DBG(cerr << "server_version_test" << endl);
type_test();
DBG(cerr << "type_test" << endl);
}
catch (Error &e) {
CPPUNIT_FAIL((string) "Error: " + e.get_error_message());
}
}
void cache_test_cpp()
{
DBG(cerr << endl << "Entering Caching tests." << endl);
try {
// The cache-testsuite/dodsrc file turns this off; all the other
// params are set up. It used to be that HTTPConnect had an option to
// turn caching on, but that no longer is present. This hack enables
// caching for this test. 06/18/04 jhrg
http->d_http_cache = HTTPCache::instance(http->d_rcr->get_dods_cache_root(), true);
DBG(cerr << "Instantiate the cache" << endl);
CPPUNIT_ASSERT(http->d_http_cache != 0);
http->d_http_cache->set_cache_enabled(true);
DBG(cerr << "Enable the cache" << endl);
fetch_url_test_4_cpp();
DBG(cerr << "fetch_url_test_4_cpp" << endl);
get_response_headers_test();
DBG(cerr << "get_response_headers_test" << endl);
server_version_test();
DBG(cerr << "server_version_test" << endl);
type_test();
DBG(cerr << "type_test" << endl);
}
catch (Error &e) {
CPPUNIT_FAIL((string) "Error: " + e.get_error_message());
}
}
void set_accept_deflate_test()
{
http->set_accept_deflate(false);
CPPUNIT_ASSERT(
count(http->d_request_headers.begin(), http->d_request_headers.end(),
"Accept-Encoding: deflate, gzip, compress") == 0);
http->set_accept_deflate(true);
CPPUNIT_ASSERT(
count(http->d_request_headers.begin(), http->d_request_headers.end(),
"Accept-Encoding: deflate, gzip, compress") == 1);
http->set_accept_deflate(true);
CPPUNIT_ASSERT(
count(http->d_request_headers.begin(), http->d_request_headers.end(),
"Accept-Encoding: deflate, gzip, compress") == 1);
http->set_accept_deflate(false);
CPPUNIT_ASSERT(
count(http->d_request_headers.begin(), http->d_request_headers.end(),
"Accept-Encoding: deflate, gzip, compress") == 0);
}
void set_xdap_protocol_test()
{
// Initially there should be no header and the protocol should be 2.0
CPPUNIT_ASSERT(http->d_dap_client_protocol_major == 2 && http->d_dap_client_protocol_minor == 0);
CPPUNIT_ASSERT(
count_if(http->d_request_headers.begin(), http->d_request_headers.end(), HeaderMatch("XDAP-Accept:")) == 0);
http->set_xdap_protocol(8, 9);
CPPUNIT_ASSERT(http->d_dap_client_protocol_major == 8 && http->d_dap_client_protocol_minor == 9);
CPPUNIT_ASSERT(count(http->d_request_headers.begin(), http->d_request_headers.end(), "XDAP-Accept: 8.9") == 1);
http->set_xdap_protocol(3, 2);
CPPUNIT_ASSERT(http->d_dap_client_protocol_major == 3 && http->d_dap_client_protocol_minor == 2);
CPPUNIT_ASSERT(count(http->d_request_headers.begin(), http->d_request_headers.end(), "XDAP-Accept: 3.2") == 1);
}
void read_url_password_test()
{
FILE *dump = fopen("/dev/null", "w");
vector<string> *resp_h = new vector<string>;
long status = http->read_url(localhost_pw_url, dump, resp_h);
DBG(cerr << endl << http->d_upstring << endl);
CPPUNIT_ASSERT(http->d_upstring == "jimg:dods_test");
DBG(cerr << "Status: " << status << endl);
CPPUNIT_ASSERT(status == 200);
delete resp_h;
resp_h = 0;
}
void read_url_password_test2()
{
FILE *dump = fopen("/dev/null", "w");
vector<string> *resp_h = new vector<string>;
long status = http->read_url(localhost_digest_pw_url, dump, resp_h);
DBG(cerr << endl << http->d_upstring << endl);
CPPUNIT_ASSERT(http->d_upstring == "jimg:dods_digest");
DBG(cerr << "Status: " << status << endl);
CPPUNIT_ASSERT(status == 200);
delete resp_h;
resp_h = 0;
}
};
CPPUNIT_TEST_SUITE_REGISTRATION (HTTPConnectTest);
}
int main(int argc, char*argv[])
{
GetOpt getopt(argc, argv, "dh");
int option_char;
while ((option_char = getopt()) != -1)
switch (option_char) {
case 'd':
debug = 1; // debug is a static global
break;
case 'h': { // help - show test names
cerr << "Usage: HTTPConnectTest has the following tests:" << endl;
const std::vector<Test*> &tests = libdap::HTTPConnectTest::suite()->getTests();
unsigned int prefix_len = libdap::HTTPConnectTest::suite()->getName().append("::").length();
for (std::vector<Test*>::const_iterator i = tests.begin(), e = tests.end(); i != e; ++i) {
cerr << (*i)->getName().replace(0, prefix_len, "") << endl;
}
break;
}
default:
break;
}
CppUnit::TextTestRunner runner;
runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());
bool wasSuccessful = true;
string test = "";
int i = getopt.optind;
if (i == argc) {
// run them all
wasSuccessful = runner.run("");
}
else {
for (; i < argc; ++i) {
if (debug) cerr << "Running " << argv[i] << endl;
test = libdap::HTTPConnectTest::suite()->getName().append("::").append(argv[i]);
wasSuccessful = wasSuccessful && runner.run(test);
}
}
return wasSuccessful ? 0 : 1;
}