libdap  Updated for version 3.20.6
libdap4 is an implementation of OPeNDAP's DAP protocol.
RCReader.cc
1 // -*- mode: c++; c-basic-offset:4 -*-
2 
3 // This file is part of libdap, A C++ implementation of the OPeNDAP Data
4 // Access Protocol.
5 
6 // Copyright (c) 2002,2003 OPeNDAP, Inc.
7 // Author: Jose Garcia <jgarcia@ucar.edu>
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public
11 // License as published by the Free Software Foundation; either
12 // version 2.1 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // Lesser General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 //
23 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
24 
25 // (c) COPYRIGHT URI/MIT 2001,2002
26 // Please read the full copyright statement in the file COPYRIGHT_URI.
27 //
28 // Authors:
29 // jose Jose Garcia <jgarcia@ucar.edu>
30 
36 // #define DODS_DEBUG
37 #include "config.h"
38 
39 #include <cstring>
40 #include <cstdlib>
41 
42 #include <unistd.h> // for stat
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 
46 #ifdef WIN32
47 #define FALSE 0
48 // Win32 does not define F_OK. 08/21/02 jhrg
49 #define F_OK 0
50 #define DIR_SEP_STRING "\\"
51 #define DIR_SEP_CHAR '\\'
52 #include <direct.h>
53 #else
54 #define DIR_SEP_STRING "/"
55 #define DIR_SEP_CHAR '/'
56 #endif
57 
58 #include <pthread.h>
59 
60 #include <fstream>
61 
62 #include "debug.h"
63 #include "RCReader.h"
64 #include "Error.h"
65 
66 using namespace std;
67 
68 namespace libdap {
69 
70 RCReader* RCReader::_instance = 0;
71 
72 // This variable (instance_control) is used to ensure that in a MT
73 // environment _instance is correctly initialized. See the get_instance
74 // method. 08/07/02 jhrg
75 static pthread_once_t instance_control = PTHREAD_ONCE_INIT;
76 
81 bool RCReader::write_rc_file(const string &pathname)
82 {
83  DBG(cerr << "Writing the RC file to " << pathname << endl);
84  ofstream fpo(pathname.c_str());
85 
86  // If the file couldn't be created. Nothing needs to be done here,
87  // the program will simply use the defaults.
88 
89  if (fpo) {
90  // This means we just created the file. We will now save
91  // the defaults in it for future use.
92  fpo << "# OPeNDAP client configuration file. See the OPeNDAP" << endl;
93  fpo << "# users guide for information." << endl;
94  fpo << "USE_CACHE=" << _dods_use_cache << endl;
95  fpo << "# Cache and object size are given in megabytes (20 ==> 20Mb)." << endl;
96  fpo << "MAX_CACHE_SIZE=" << _dods_cache_max << endl;
97  fpo << "MAX_CACHED_OBJ=" << _dods_cached_obj << endl;
98  fpo << "IGNORE_EXPIRES=" << _dods_ign_expires << endl;
99  fpo << "CACHE_ROOT=" << d_cache_root << endl;
100  fpo << "DEFAULT_EXPIRES=" << _dods_default_expires << endl;
101  fpo << "ALWAYS_VALIDATE=" << _dods_always_validate << endl;
102  fpo << "# Request servers compress responses if possible?" << endl;
103  fpo << "# 1 (yes) or 0 (false)." << endl;
104  fpo << "DEFLATE=" << _dods_deflate << endl;
105 
106  fpo << "# Should SSL certificates and hosts be validated? SSL" << endl;
107  fpo << "# will only work with signed certificates." << endl;
108  fpo << "VALIDATE_SSL=" << d_validate_ssl << endl;
109 
110  fpo << "# Proxy configuration (optional parts in []s)." << endl;
111  fpo << "# You may also use the 'http_proxy' environment variable" << endl;
112  fpo << "# but a value in this file will override that env variable." << endl;
113  fpo << "# PROXY_SERVER=[http://][username:password@]host[:port]" << endl;
114  if (!d_dods_proxy_server_host.empty()) {
115  fpo << "PROXY_SERVER=" << d_dods_proxy_server_protocol << "://"
116  << (d_dods_proxy_server_userpw.empty() ? "" : d_dods_proxy_server_userpw + "@")
117  + d_dods_proxy_server_host + ":" + long_to_string(d_dods_proxy_server_port) << endl;
118  }
119 
120  fpo << "# NO_PROXY_FOR=<host|domain>" << endl;
121  if (!d_dods_no_proxy_for_host.empty()) {
122  fpo << "NO_PROXY_FOR=" << d_dods_no_proxy_for_host << endl;
123  }
124 
125  fpo << "# AIS_DATABASE=<file or url>" << endl;
126 
127  fpo << "# COOKIE_JAR=.dods_cookies" << endl;
128  fpo << "# The cookie jar is a file that holds cookies sent from" << endl;
129  fpo << "# servers such as single signon systems. Uncomment this" << endl;
130  fpo << "# option and provide a file name to activate this feature." << endl;
131  fpo << "# If the value is a filename, it will be created in this" << endl;
132  fpo << "# directory; a full pathname can be used to force a specific" << endl;
133  fpo << "# location." << endl;
134 
135  fpo.close();
136  return true;
137  }
138 
139  return false;
140 }
141 
142 bool RCReader::read_rc_file(const string &pathname)
143 {
144  DBG(cerr << "Reading the RC file from " << pathname << endl);
145 
146  ifstream fpi(pathname.c_str());
147  if (fpi) {
148  // The file exists and we may now begin to parse it.
149  // Defaults are already stored in the variables, if the correct
150  // tokens are found in the file then those defaults will be
151  // overwritten.
152  char *value;
153  // TODO Replace with a vector<char>
154  //char *tempstr = new char[1024];
155  vector<char> tempstr(1024);
156  int tokenlength;
157  while (true) {
158  fpi.getline(&tempstr[0], 1023);
159  if (!fpi.good()) break;
160 
161  value = strchr(&tempstr[0], '=');
162  if (!value) continue;
163  tokenlength = value - &tempstr[0];
164  value++;
165 
166  if ((strncmp(&tempstr[0], "USE_CACHE", 9) == 0) && tokenlength == 9) {
167  _dods_use_cache = atoi(value) ? true : false;
168  }
169  else if ((strncmp(&tempstr[0], "MAX_CACHE_SIZE", 14) == 0) && tokenlength == 14) {
170  _dods_cache_max = atoi(value);
171  }
172  else if ((strncmp(&tempstr[0], "MAX_CACHED_OBJ", 14) == 0) && tokenlength == 14) {
173  _dods_cached_obj = atoi(value);
174  }
175  else if ((strncmp(&tempstr[0], "IGNORE_EXPIRES", 14) == 0) && tokenlength == 14) {
176  _dods_ign_expires = atoi(value);
177  }
178  else if ((strncmp(&tempstr[0], "DEFLATE", 7) == 0) && tokenlength == 7) {
179  _dods_deflate = atoi(value) ? true : false;
180  }
181  else if ((strncmp(&tempstr[0], "CACHE_ROOT", 10) == 0) && tokenlength == 10) {
182  d_cache_root = value;
183  if (d_cache_root[d_cache_root.length() - 1] != DIR_SEP_CHAR) d_cache_root += string(DIR_SEP_STRING);
184  }
185  else if ((strncmp(&tempstr[0], "DEFAULT_EXPIRES", 15) == 0) && tokenlength == 15) {
186  _dods_default_expires = atoi(value);
187  }
188  else if ((strncmp(&tempstr[0], "ALWAYS_VALIDATE", 15) == 0) && tokenlength == 15) {
189  _dods_always_validate = atoi(value);
190  }
191  else if ((strncmp(&tempstr[0], "VALIDATE_SSL", 12) == 0) && tokenlength == 12) {
192  d_validate_ssl = atoi(value);
193  }
194  else if (strncmp(&tempstr[0], "AIS_DATABASE", 12) == 0 && tokenlength == 12) {
195  d_ais_database = value;
196  }
197  else if (strncmp(&tempstr[0], "COOKIE_JAR", 10) == 0 && tokenlength == 10) {
198  // if the value of COOKIE_JAR starts with a slash, use it as
199  // is. However, if it does not start with a slash, prefix it
200  // with the directory that contains the .dodsrc file.
201  if (value[0] == '/') {
202  d_cookie_jar = value;
203  }
204  else {
205  d_cookie_jar = d_rc_file_path.substr(0, d_rc_file_path.find(".dodsrc")) + string(value);
206  } DBG(cerr << "set cookie jar to: " << d_cookie_jar << endl);
207  }
208  else if ((strncmp(&tempstr[0], "PROXY_SERVER", 12) == 0) && tokenlength == 12) {
209  // Setup a proxy server for all requests.
210  // The original syntax was <protocol>,<machine> where the
211  // machine could also contain the user/pass and port info.
212  // Support that but also support machine prefixed by
213  // 'http://' with and without the '<protocol>,' prefix. jhrg
214  // 4/21/08 (see bug 1095).
215  string proxy = value;
216  string::size_type comma = proxy.find(',');
217 
218  // Since the <protocol> is now optional, the comma might be
219  // here. If it is, check that the protocol given is http.
220  if (comma != string::npos) {
221  d_dods_proxy_server_protocol = proxy.substr(0, comma);
222  downcase(d_dods_proxy_server_protocol);
223  if (d_dods_proxy_server_protocol != "http")
224  throw Error("The only supported protocol for a proxy server is \"HTTP\". Correct your \".dodsrc\" file.");
225  proxy = proxy.substr(comma + 1);
226  }
227  else {
228  d_dods_proxy_server_protocol = "http";
229  }
230 
231  // Look for a 'protocol://' prefix; skip if found
232  string::size_type protocol = proxy.find("://");
233  if (protocol != string::npos) {
234  proxy = proxy.substr(protocol + 3);
235  }
236 
237  // Break apart into userpw, host and port.
238  string::size_type at_sign = proxy.find('@');
239  if (at_sign != string::npos) { // has userpw
240  d_dods_proxy_server_userpw = proxy.substr(0, at_sign);
241  proxy = proxy.substr(at_sign + 1);
242  }
243  else
244  d_dods_proxy_server_userpw = "";
245 
246  // Get host and look for a port number
247  string::size_type colon = proxy.find(':');
248  if (colon != string::npos) {
249  d_dods_proxy_server_host = proxy.substr(0, colon);
250  d_dods_proxy_server_port = strtol(proxy.substr(colon + 1).c_str(), 0, 0);
251  }
252  else {
253  d_dods_proxy_server_host = proxy;
254  d_dods_proxy_server_port = 80;
255  }
256  }
257  else if ((strncmp(&tempstr[0], "NO_PROXY_FOR", 12) == 0) && tokenlength == 12) {
258  // Setup a proxy server for all requests.
259  string no_proxy = value;
260  string::size_type comma = no_proxy.find(',');
261 
262  // Since the protocol is required, the comma *must* be
263  // present. We could throw an Error on the malformed line...
264  if (comma == string::npos) {
265  d_dods_no_proxy_for_protocol = "http";
266  d_dods_no_proxy_for_host = no_proxy;
267  d_dods_no_proxy_for = true;
268  }
269  else {
270  d_dods_no_proxy_for_protocol = no_proxy.substr(0, comma);
271  d_dods_no_proxy_for_host = no_proxy.substr(comma + 1);
272  d_dods_no_proxy_for = true;
273  }
274  }
275  }
276 
277  //delete [] tempstr; tempstr = 0;
278 
279  fpi.close(); // Close the .dodsrc file. 12/14/99 jhrg
280 
281  return true;
282  } // End of cache file parsing.
283 
284  return false;
285 }
286 
287 // Helper for check_env_var(). This is its main logic, separated out for the
288 // cases under WIN32 where we don't use an environment variable. 09/19/03
289 // jhrg
290 string RCReader::check_string(string env_var)
291 {
292  DBG(cerr << "Entering check_string... (" << env_var << ")" << endl);
293  struct stat stat_info;
294 
295  if (stat(env_var.c_str(), &stat_info) != 0) {
296  DBG(cerr << "stat returned non-zero" << endl);
297  return ""; // ENV VAR not a file or dir, bail
298  }
299 
300  if (S_ISREG(stat_info.st_mode)) {
301  DBG(cerr << "S_ISREG: " << S_ISREG(stat_info.st_mode) << endl);
302  return env_var; // ENV VAR is a file, use it
303  }
304 
305  // ENV VAR is a directory, does it contain .dodsrc? Can we create
306  // .dodsrc if it's not there?
307  if (S_ISDIR(stat_info.st_mode)) {
308  DBG(cerr << "S_ISDIR: " << S_ISDIR(stat_info.st_mode) << endl);
309  if (*env_var.rbegin() != DIR_SEP_CHAR) // Add trailing / if missing
310  env_var += DIR_SEP_STRING;
311  // Trick: set d_cache_root here in case we're going to create the
312  // .dodsrc later on. If the .dodsrc file exists, its value will
313  // overwrite this value, if not write_rc_file() will use the correct
314  // value. 09/19/03 jhrg
315  d_cache_root = env_var + string(".dods_cache") + DIR_SEP_STRING;
316  env_var += ".dodsrc";
317  if (stat(env_var.c_str(), &stat_info) == 0 && S_ISREG(stat_info.st_mode)) {
318  DBG(cerr << "Found .dodsrc in \"" << env_var << "\"" << endl);
319  return env_var; // Found .dodsrc in ENV VAR
320  }
321 
322  // Didn't find .dodsrc in ENV VAR and ENV VAR is a directory; try to
323  // create it. Note write_rc_file uses d_cache_root (set above) when
324  // it creates the RC file's contents.
325  if (write_rc_file(env_var)) {
326  DBG(cerr << "Wrote .dodsrc in \"" << env_var << "\"" << endl);
327  return env_var;
328  }
329  }
330 
331  // If we're here, then we've neither found nor created the RC file.
332  DBG(cerr << "could neither find nor create a .dodsrc file" << endl);
333  return "";
334 }
335 
345 string RCReader::check_env_var(const string &variable_name)
346 {
347  char *ev = getenv(variable_name.c_str());
348  if (!ev || strlen(ev) == 0) return "";
349 
350  return check_string(ev);
351 }
352 
353 RCReader::RCReader() // throw (Error) jhrg 7/2/15
354 {
355  d_rc_file_path = "";
356  d_cache_root = "";
357 
358  // ** Set default values **
359  // Users must explicitly turn caching on.
360  _dods_use_cache = false;
361  _dods_cache_max = 20;
362  _dods_cached_obj = 5;
363  _dods_ign_expires = 0;
364  _dods_default_expires = 86400;
365  _dods_always_validate = 0;
366 
367  _dods_deflate = 0;
368  d_validate_ssl = 1;
369 
370  //flags for PROXY_SERVER=<protocol>,<host url>
371  // New syntax PROXY_SERVER=[http://][user:pw@]host[:port]
372  d_dods_proxy_server_protocol = "";
373  d_dods_proxy_server_host = "";
374  d_dods_proxy_server_port = 0;
375  d_dods_proxy_server_userpw = "";
376 
377  _dods_proxy_server_host_url = ""; // deprecated
378 
379  // PROXY_FOR is deprecated.
380  // flags for PROXY_FOR=<regex>,<proxy host url>,<flags>
381  _dods_proxy_for = false; // true if proxy_for is used.
382  _dods_proxy_for_regexp = "";
383  _dods_proxy_for_proxy_host_url = "";
384  _dods_proxy_for_regexp_flags = 0;
385 
386  //flags for NO_PROXY_FOR=<protocol>,<host>,<port>
387  // New syntax NO_PROXY_FOR=<host|domain>
388  d_dods_no_proxy_for = false;
389  d_dods_no_proxy_for_protocol = ""; // deprecated
390  d_dods_no_proxy_for_host = "";
391  // default to port 0 if not specified. This means all ports. Using 80
392  // will fail when the URL does not contain the port number. That's
393  // probably a bug in libwww. 10/23/2000 jhrg
394  _dods_no_proxy_for_port = 0; // deprecated
395 
396  d_cookie_jar = "";
397 
398 #ifdef WIN32
399  string homedir = string("C:") + string(DIR_SEP_STRING) + string("Dods");
400  d_rc_file_path = check_string(homedir);
401  if (d_rc_file_path.empty()) {
402  homedir = string("C:") + string(DIR_SEP_STRING) + string("opendap");
403  d_rc_file_path = check_string(homedir);
404  }
405  // Normally, I'd prefer this for WinNT-based systems.
406  if (d_rc_file_path.empty())
407  d_rc_file_path = check_env_var("APPDATA");
408  if (d_rc_file_path.empty())
409  d_rc_file_path = check_env_var("TEMP");
410  if (d_rc_file_path.empty())
411  d_rc_file_path = check_env_var("TMP");
412 #else
413  d_rc_file_path = check_env_var("DODS_CONF");
414  if (d_rc_file_path.empty()) d_rc_file_path = check_env_var("HOME");
415 #endif
416  DBG(cerr << "Looking for .dodsrc in: " << d_rc_file_path << endl);
417 
418  if (!d_rc_file_path.empty()) read_rc_file(d_rc_file_path);
419 }
420 
421 RCReader::~RCReader()
422 {
423 }
424 
426 void RCReader::delete_instance()
427 {
428  if (RCReader::_instance) {
429  delete RCReader::_instance;
430  RCReader::_instance = 0;
431  }
432 }
433 
435 void RCReader::initialize_instance()
436 {
437  DBGN(cerr << "RCReader::initialize_instance() ... ");
438 
439  RCReader::_instance = new RCReader;
440  atexit(RCReader::delete_instance);
441 
442  DBG(cerr << "exiting." << endl);
443 }
444 
445 RCReader*
446 RCReader::instance()
447 {
448  DBG(cerr << "Entring RCReader::instance" << endl);
449  // The instance_control variable is defined at the top of this file.
450  // 08/07/02 jhrg
451  pthread_once(&instance_control, initialize_instance);
452 
453  DBG(cerr << "Instance value: " << hex << _instance << dec << endl);
454 
455  return _instance;
456 }
457 
458 #if 0
459 RCReader*
460 RCReader::instance(const string &rc_file_path)
461 {
462  DBG(cerr << "Entring RCReader::instance" << endl);
463 
464  d_rc_file_path = rc_file_path;
465  // The instance_control variable is defined at the top of this file.
466  // 08/07/02 jhrg
467  pthread_once(&instance_control, initialize_instance);
468 
469  DBG(cerr << "Instance value: " << hex << _instance << dec << endl);
470 
471  return _instance;
472 }
473 #endif
474 } // namespace libdap
void downcase(string &s)
Definition: util.cc:563
STL namespace.
top level DAP object to house generic methods
Definition: AISConnect.cc:30