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