libdap++  Updated for version 3.8.2
RCReader.cc
Go to the documentation of this file.
00001 
00002 // -*- mode: c++; c-basic-offset:4 -*-
00003 
00004 // This file is part of libdap, A C++ implementation of the OPeNDAP Data
00005 // Access Protocol.
00006 
00007 // Copyright (c) 2002,2003 OPeNDAP, Inc.
00008 // Author: Jose Garcia <jgarcia@ucar.edu>
00009 //
00010 // This library is free software; you can redistribute it and/or
00011 // modify it under the terms of the GNU Lesser General Public
00012 // License as published by the Free Software Foundation; either
00013 // version 2.1 of the License, or (at your option) any later version.
00014 //
00015 // This library is distributed in the hope that it will be useful,
00016 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00017 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00018 // Lesser General Public License for more details.
00019 //
00020 // You should have received a copy of the GNU Lesser General Public
00021 // License along with this library; if not, write to the Free Software
00022 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00023 //
00024 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
00025 
00026 // (c) COPYRIGHT URI/MIT 2001,2002
00027 // Please read the full copyright statement in the file COPYRIGHT_URI.
00028 //
00029 // Authors:
00030 // jose Jose Garcia <jgarcia@ucar.edu>
00031 
00037 // #define DODS_DEBUG
00038 #include "config.h"
00039 
00040 #include <cstring>
00041 #include <cstdlib>
00042 
00043 #include <unistd.h>  // for stat
00044 #include <sys/types.h>
00045 #include <sys/stat.h>
00046 
00047 #ifdef WIN32
00048 #define FALSE 0
00049 // Win32 does not define F_OK. 08/21/02 jhrg
00050 #define F_OK 0
00051 #define DIR_SEP_STRING "\\"
00052 #define DIR_SEP_CHAR   '\\'
00053 #include <direct.h>
00054 #else
00055 #define DIR_SEP_STRING "/"
00056 #define DIR_SEP_CHAR   '/'
00057 #endif
00058 
00059 #include <pthread.h>
00060 
00061 #include <fstream>
00062 
00063 #include "debug.h"
00064 #include "RCReader.h"
00065 #include "Error.h"
00066 
00067 using namespace std;
00068 
00069 namespace libdap {
00070 
00071 RCReader* RCReader::_instance = 0;
00072 
00073 // This variable (instance_control) is used to ensure that in a MT
00074 // environment _instance is correctly initialized. See the get_instance
00075 // method. 08/07/02 jhrg
00076 static pthread_once_t instance_control = PTHREAD_ONCE_INIT;
00077 
00082 bool
00083 RCReader::write_rc_file(const string &pathname)
00084 {
00085     DBG(cerr << "Writing the RC file to " << pathname << endl);
00086     ofstream fpo(pathname.c_str());
00087 
00088     // If the file couldn't be created.  Nothing needs to be done here,
00089     // the program will simply use the defaults.
00090 
00091     if (fpo) {
00092         // This means we just created the file.  We will now save
00093         // the defaults in it for future use.
00094         fpo << "# OPeNDAP client configuration file. See the OPeNDAP" << endl;
00095         fpo << "# users guide for information." << endl;
00096         fpo << "USE_CACHE=" << _dods_use_cache << endl;
00097         fpo << "# Cache and object size are given in megabytes (20 ==> 20Mb)."
00098         << endl;
00099         fpo << "MAX_CACHE_SIZE=" << _dods_cache_max  << endl;
00100         fpo << "MAX_CACHED_OBJ=" << _dods_cached_obj << endl;
00101         fpo << "IGNORE_EXPIRES=" << _dods_ign_expires  << endl;
00102         fpo << "CACHE_ROOT=" << d_cache_root << endl;
00103         fpo << "DEFAULT_EXPIRES=" << _dods_default_expires << endl;
00104         fpo << "ALWAYS_VALIDATE=" << _dods_always_validate << endl;
00105         fpo << "# Request servers compress responses if possible?" << endl;
00106         fpo << "# 1 (yes) or 0 (false)." << endl;
00107         fpo << "DEFLATE=" << _dods_deflate << endl;
00108 
00109         fpo << "# Should SSL certificates and hosts be validated? SSL" << endl;
00110         fpo << "# will only work with signed certificates." << endl;
00111         fpo << "VALIDATE_SSL=" << d_validate_ssl << endl;
00112 
00113         fpo << "# Proxy configuration (optional parts in []s)." << endl;
00114         fpo << "# You may also use the 'http_proxy' environment variable"
00115             << endl;
00116         fpo << "# but a value in this file will override that env variable."
00117             << endl;
00118         fpo << "# PROXY_SERVER=[http://][username:password@]host[:port]"
00119         << endl;
00120         if (!d_dods_proxy_server_host.empty()) {
00121             fpo << "PROXY_SERVER=" <<  d_dods_proxy_server_protocol << "://"
00122             << (d_dods_proxy_server_userpw.empty()
00123                 ? ""
00124                 : d_dods_proxy_server_userpw + "@")
00125             + d_dods_proxy_server_host
00126             + ":" + long_to_string(d_dods_proxy_server_port) << endl;
00127         }
00128 
00129         fpo << "# NO_PROXY_FOR=<host|domain>" << endl;
00130         if (!d_dods_no_proxy_for_host.empty()) {
00131             fpo << "NO_PROXY_FOR=" << d_dods_no_proxy_for_host << endl;
00132         }
00133 
00134         fpo << "# AIS_DATABASE=<file or url>" << endl;
00135 
00136         fpo << "# COOKIE_JAR=.dods_cookies" << endl;
00137         fpo << "# The cookie jar is a file that holds cookies sent from"
00138             << endl;
00139         fpo << "# servers such as single signon systems. Uncomment this"
00140             << endl;
00141         fpo << "# option and provide a file name to activate this feature."
00142             << endl;
00143         fpo << "# If the value is a filename, it will be created in this"
00144             << endl;
00145         fpo << "# directory; a full pathname can be used to force a specific"
00146             << endl;
00147         fpo << "# location." << endl;
00148 
00149         fpo.close();
00150         return true;
00151     }
00152 
00153     return false;
00154 }
00155 
00156 bool
00157 RCReader::read_rc_file(const string &pathname)
00158 {
00159     DBG(cerr << "Reading the RC file from " << pathname << endl);
00160 
00161     ifstream fpi(pathname.c_str());
00162     if (fpi) {
00163         // The file exists and we may now begin to parse it.
00164         // Defaults are already stored in the variables, if the correct
00165         // tokens are found in the file then those defaults will be
00166         // overwritten.
00167         char *value;
00168         // TODO Replace with a vector<char>
00169         //char *tempstr = new char[1024];
00170         vector<char> tempstr(1024);
00171         int tokenlength;
00172         while (true) {
00173             fpi.getline(&tempstr[0], 1023);
00174             if (!fpi.good())
00175                 break;
00176 
00177             value = strchr(&tempstr[0], '=');
00178             if (!value)
00179                 continue;
00180             tokenlength = value - &tempstr[0];
00181             value++;
00182 
00183             if ((strncmp(&tempstr[0], "USE_CACHE", 9) == 0)
00184                 && tokenlength == 9) {
00185                 _dods_use_cache = atoi(value) ? true : false;
00186             }
00187             else if ((strncmp(&tempstr[0], "MAX_CACHE_SIZE", 14) == 0)
00188                      && tokenlength == 14) {
00189                 _dods_cache_max = atoi(value);
00190             }
00191             else if ((strncmp(&tempstr[0], "MAX_CACHED_OBJ", 14) == 0)
00192                      && tokenlength == 14) {
00193                 _dods_cached_obj = atoi(value);
00194             }
00195             else if ((strncmp(&tempstr[0], "IGNORE_EXPIRES", 14) == 0)
00196                      && tokenlength == 14) {
00197                 _dods_ign_expires = atoi(value);
00198             }
00199             else if ((strncmp(&tempstr[0], "DEFLATE", 7) == 0)
00200                      && tokenlength == 7) {
00201                 _dods_deflate = atoi(value) ? true : false;
00202             }
00203             else if ((strncmp(&tempstr[0], "CACHE_ROOT", 10) == 0)
00204                      && tokenlength == 10) {
00205                 d_cache_root = value;
00206                 if (d_cache_root[d_cache_root.length() - 1] != DIR_SEP_CHAR)
00207                     d_cache_root += string(DIR_SEP_STRING);
00208             }
00209             else if ((strncmp(&tempstr[0], "DEFAULT_EXPIRES", 15) == 0)
00210                      && tokenlength == 15) {
00211                 _dods_default_expires = atoi(value);
00212             }
00213             else if ((strncmp(&tempstr[0], "ALWAYS_VALIDATE", 15) == 0)
00214                      && tokenlength == 15) {
00215                 _dods_always_validate = atoi(value);
00216             }
00217             else if ((strncmp(&tempstr[0], "VALIDATE_SSL", 12) == 0)
00218                      && tokenlength == 12) {
00219                 d_validate_ssl = atoi(value);
00220             }
00221             else if (strncmp(&tempstr[0], "AIS_DATABASE", 12) == 0
00222                      && tokenlength == 12) {
00223                 d_ais_database = value;
00224             }
00225             else if (strncmp(&tempstr[0], "COOKIE_JAR", 10) == 0
00226                      && tokenlength == 10) {
00227                 // if the value of COOKIE_JAR starts with a slash, use it as
00228                 // is. However, if it does not start with a slash, prefix it
00229                 // with the directory that contains the .dodsrc file.
00230                 if (value[0] == '/') {
00231                     d_cookie_jar = value;
00232                 }
00233                 else {
00234                     d_cookie_jar = d_rc_file_path.substr(0, d_rc_file_path.find(".dodsrc")) + string(value);
00235                 }
00236                 DBG(cerr << "set cookie jar to: " << d_cookie_jar << endl);
00237             }
00238             else if ((strncmp(&tempstr[0], "PROXY_SERVER", 12) == 0)
00239                      && tokenlength == 12) {
00240                 // Setup a proxy server for all requests.
00241                 // The original syntax was <protocol>,<machine> where the
00242                 // machine could also contain the user/pass and port info.
00243                 // Support that but also support machine prefixed by
00244                 // 'http://' with and without the '<protocol>,' prefix. jhrg
00245                 // 4/21/08 (see bug 1095).
00246                 string proxy = value;
00247                 string::size_type comma = proxy.find(',');
00248 
00249                 // Since the <protocol> is now optional, the comma might be
00250                 // here. If it is, check that the protocol given is http.
00251                 if (comma != string::npos) {
00252                     d_dods_proxy_server_protocol = proxy.substr(0, comma);
00253                     downcase(d_dods_proxy_server_protocol);
00254                     if (d_dods_proxy_server_protocol != "http")
00255                         throw Error("The only supported protocol for a proxy server is \"HTTP\". Correct your \".dodsrc\" file.");
00256                     proxy = proxy.substr(comma + 1);
00257                 }
00258                 else {
00259                     d_dods_proxy_server_protocol = "http";
00260                 }
00261 
00262                 // Look for a 'protocol://' prefix; skip if found
00263                 string::size_type protocol = proxy.find("://");
00264                 if (protocol != string::npos) {
00265                     proxy = proxy.substr(protocol + 3);
00266                 }
00267 
00268                 // Break apart into userpw, host and port.
00269                 string::size_type at_sign = proxy.find('@');
00270                 if (at_sign != string::npos) { // has userpw
00271                     d_dods_proxy_server_userpw = proxy.substr(0, at_sign);
00272                     proxy = proxy.substr(at_sign + 1);
00273                 }
00274                 else
00275                     d_dods_proxy_server_userpw = "";
00276 
00277                 // Get host and look for a port number
00278                 string::size_type colon = proxy.find(':');
00279                 if (colon != string::npos) {
00280                     d_dods_proxy_server_host = proxy.substr(0, colon);
00281                     d_dods_proxy_server_port
00282                         = strtol(proxy.substr(colon + 1).c_str(), 0, 0);
00283                 }
00284                 else {
00285                     d_dods_proxy_server_host = proxy;
00286                     d_dods_proxy_server_port = 80;
00287                 }
00288             }
00289             else if ((strncmp(&tempstr[0], "NO_PROXY_FOR", 12) == 0)
00290                      && tokenlength == 12) {
00291                 // Setup a proxy server for all requests.
00292                 string no_proxy = value;
00293                 string::size_type comma = no_proxy.find(',');
00294 
00295                 // Since the protocol is required, the comma *must* be
00296                 // present. We could throw an Error on the malformed line...
00297                 if (comma == string::npos) {
00298                     d_dods_no_proxy_for_protocol = "http";
00299                     d_dods_no_proxy_for_host = no_proxy;
00300                     d_dods_no_proxy_for = true;
00301                 }
00302                 else {
00303                     d_dods_no_proxy_for_protocol = no_proxy.substr(0, comma);
00304                     d_dods_no_proxy_for_host = no_proxy.substr(comma + 1);
00305                     d_dods_no_proxy_for = true;
00306                 }
00307             }
00308         }
00309 
00310         //delete [] tempstr; tempstr = 0;
00311 
00312         fpi.close(); // Close the .dodsrc file. 12/14/99 jhrg
00313 
00314         return true;
00315     }  // End of cache file parsing.
00316 
00317     return false;
00318 }
00319 
00320 // Helper for check_env_var(). This is its main logic, separated out for the
00321 // cases under WIN32 where we don't use an environment variable.  09/19/03
00322 // jhrg
00323 string
00324 RCReader::check_string(string env_var)
00325 {
00326         DBG(cerr << "Entering check_string... (" << env_var << ")" << endl);
00327     struct stat stat_info;
00328 
00329         if (stat(env_var.c_str(), &stat_info) != 0) {
00330                 DBG(cerr << "stat returned non-zero" << endl);
00331         return "";  // ENV VAR not a file or dir, bail
00332         }
00333 
00334         if (S_ISREG(stat_info.st_mode)) {
00335                 DBG(cerr << "S_ISREG: " << S_ISREG(stat_info.st_mode) << endl);
00336         return env_var;  // ENV VAR is a file, use it
00337         }
00338 
00339     // ENV VAR is a directory, does it contain .dodsrc? Can we create
00340     // .dodsrc if it's not there?
00341     if (S_ISDIR(stat_info.st_mode)) {
00342                 DBG(cerr << "S_ISDIR: " << S_ISDIR(stat_info.st_mode) << endl);
00343         if (*env_var.rbegin() != DIR_SEP_CHAR) // Add trailing / if missing
00344             env_var += DIR_SEP_STRING;
00345         // Trick: set d_cache_root here in case we're going to create the
00346         // .dodsrc later on. If the .dodsrc file exists, its value will
00347         // overwrite this value, if not write_rc_file() will use the correct
00348         // value. 09/19/03 jhrg
00349         d_cache_root = env_var + string(".dods_cache") + DIR_SEP_STRING;
00350         env_var += ".dodsrc";
00351         if (stat(env_var.c_str(), &stat_info) == 0 &&
00352                         S_ISREG(stat_info.st_mode)) {
00353                         DBG(cerr << "Found .dodsrc in \"" << env_var << "\"" << endl);
00354             return env_var; // Found .dodsrc in ENV VAR
00355                 }
00356 
00357         // Didn't find .dodsrc in ENV VAR and ENV VAR is a directory; try to
00358         // create it. Note write_rc_file uses d_cache_root (set above) when
00359         // it creates the RC file's contents.
00360                 if (write_rc_file(env_var)) {
00361                         DBG(cerr << "Wrote .dodsrc in \"" << env_var << "\"" << endl);
00362             return env_var;
00363                 }
00364     }
00365 
00366     // If we're here, then we've neither found nor created the RC file.
00367         DBG(cerr << "could neither find nor create a .dodsrc file" << endl);
00368     return "";
00369 }
00370 
00380 string
00381 RCReader::check_env_var(const string &variable_name)
00382 {
00383     char *ev = getenv(variable_name.c_str());
00384     if (!ev || strlen(ev) == 0)
00385         return "";
00386 
00387     return check_string(ev);
00388 }
00389 
00390 RCReader::RCReader() throw(Error)
00391 {
00392     d_rc_file_path = "";
00393     d_cache_root = "";
00394 
00395     // ** Set default values **
00396     // Users must explicitly turn caching on.
00397     _dods_use_cache = false;
00398     _dods_cache_max = 20;
00399     _dods_cached_obj = 5;
00400     _dods_ign_expires = 0;
00401     _dods_default_expires = 86400;
00402     _dods_always_validate = 0;
00403 
00404     _dods_deflate = 0;
00405     d_validate_ssl = 1;
00406 
00407     //flags for PROXY_SERVER=<protocol>,<host url>
00408     // New syntax PROXY_SERVER=[http://][user:pw@]host[:port]
00409     d_dods_proxy_server_protocol = "";
00410     d_dods_proxy_server_host = "";
00411     d_dods_proxy_server_port = 0;
00412     d_dods_proxy_server_userpw = "";
00413 
00414     _dods_proxy_server_host_url = ""; // deprecated
00415 
00416     // PROXY_FOR is deprecated.
00417     // flags for PROXY_FOR=<regex>,<proxy host url>,<flags>
00418     _dods_proxy_for = false; // true if proxy_for is used.
00419     _dods_proxy_for_regexp = "";
00420     _dods_proxy_for_proxy_host_url = "";
00421     _dods_proxy_for_regexp_flags = 0;
00422 
00423     //flags for NO_PROXY_FOR=<protocol>,<host>,<port>
00424     // New syntax NO_PROXY_FOR=<host|domain>
00425     d_dods_no_proxy_for = false;
00426     d_dods_no_proxy_for_protocol = ""; // deprecated
00427     d_dods_no_proxy_for_host = "";
00428     // default to port 0 if not specified. This means all ports. Using 80
00429     // will fail when the URL does not contain the port number. That's
00430     // probably a bug in libwww. 10/23/2000 jhrg
00431     _dods_no_proxy_for_port = 0; // deprecated
00432 
00433     d_cookie_jar = "";
00434 
00435 #ifdef WIN32
00436     string homedir = string("C:") + string(DIR_SEP_STRING) + string("Dods");
00437     d_rc_file_path = check_string(homedir);
00438     if (d_rc_file_path.empty()) {
00439         homedir = string("C:") + string(DIR_SEP_STRING) + string("opendap");
00440         d_rc_file_path = check_string(homedir);
00441     }
00442     //  Normally, I'd prefer this for WinNT-based systems.
00443     if (d_rc_file_path.empty())
00444         d_rc_file_path = check_env_var("APPDATA");
00445     if (d_rc_file_path.empty())
00446         d_rc_file_path = check_env_var("TEMP");
00447     if (d_rc_file_path.empty())
00448         d_rc_file_path = check_env_var("TMP");
00449 #else
00450     d_rc_file_path = check_env_var("DODS_CONF");
00451     if (d_rc_file_path.empty())
00452         d_rc_file_path = check_env_var("HOME");
00453 #endif
00454     DBG(cerr << "Looking for .dodsrc in: " << d_rc_file_path << endl);
00455 
00456     if (!d_rc_file_path.empty())
00457         read_rc_file(d_rc_file_path);
00458 }
00459 
00460 RCReader::~RCReader()
00461 {}
00462 
00464 void
00465 RCReader::delete_instance()
00466 {
00467     if (RCReader::_instance) {
00468         delete RCReader::_instance;
00469         RCReader::_instance = 0;
00470     }
00471 }
00472 
00474 void
00475 RCReader::initialize_instance()
00476 {
00477     DBGN(cerr << "RCReader::initialize_instance() ... ");
00478 
00479     RCReader::_instance = new RCReader;
00480     atexit(RCReader::delete_instance);
00481 
00482     DBG(cerr << "exiting." << endl);
00483 }
00484 
00485 RCReader*
00486 RCReader::instance()
00487 {
00488         DBG(cerr << "Entring RCReader::instance" << endl);
00489     // The instance_control variable is defined at the top of this file.
00490     // 08/07/02 jhrg
00491     pthread_once(&instance_control, initialize_instance);
00492 
00493     DBG(cerr << "Instance value: " << hex << _instance << dec << endl);
00494 
00495     return _instance;
00496 }
00497 
00498 } // namespace libdap