Repository: Unidata/netcdf-c
Pull request #2408 (2022-06-17 20:36:36)

Make public a limited API for programmatic access to internal .rc tables
re: https://github.com/Unidata/netcdf-c/issues/2337
re: https://github.com/Unidata/netcdf-c/issues/2407

Add two functions to netcdf.h to allow programs to get/set
selected entries into the internal .rc tables. This should fix
the above issues by allowing HTTP.CAINFO to be set to the
certificates directory.  Note that the changes should be
performed as early as possible in the program because some of
the .rc table entries may get cached internally and changing the
entry after that caching occurs may have no effect.

The new signatures are as follows:

1. Get the value of a simple .rc entry of the form "key=value".
Note that caller must free the returned value, which might be NULL.
````
char* nc_rc_get(char* const * key);

@param key table entry key
@return value if .rc table has entry of the form key=value
@return NULL if no such entry is found.
````

2. Insert/Overwrite the specified key=value pair in the .rc table.
````
int nc_rc_set(const char* key, const char* value);

@param key table entry key -- may not be NULL
@param value table entry value -- may not be NULL
@return NC_NOERR if no error
@return NC_EINVAL if error
````

Addendum:

re: https://github.com/Unidata/netcdf-c/issues/2407

Modify dhttp.c to use the .rc entry HTTP.CAINFO if defined.


Patch:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7bf8641be..78c17e323 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -69,7 +69,7 @@ endif()
 if(MSVC)
 SET(ISMSVC yes)
 endif()
-if(osname MATCHES "MINGW.*")
+if(osname MATCHES "MINGW.*" OR osname MATCHES "MSYS.*")
 SET(ISMINGW yes)
 SET(MINGW yes)
 endif()
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 7693bc9be..3de6ad7ec 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -7,7 +7,7 @@ This file contains a high-level description of this package's evolution. Release
 
 ## 4.9.1 - T.B.D.
 
-
+* [Enhancement] Provide a simple API to allow user access to the internal .rc file table: supports get/set/overwrite of entries of the form "key=value". See [Github #2408](https://github.com/Unidata/netcdf-c/pull/2408).
 * [Bug Fix] Use env variable USERPROFILE instead of HOME for windows and mingw. See [Github #2405](https://github.com/Unidata/netcdf-c/pull/2405).
 * [Bug Fix] Fix the nc_def_var_fletcher32 code in hdf5 to properly test value of the fletcher32 argument. See [Github #2403](https://github.com/Unidata/netcdf-c/pull/2403).
 
diff --git a/configure.ac b/configure.ac
index 1ed132545..b8c28ff6b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -107,6 +107,7 @@ case "`uname`" in
   Darwin*) ISOSX=yes;;
   WIN*) ISMSVC=yes;;
   MINGW*) ISMINGW=yes;;
+  MSYS*) ISMINGW=yes;;
 esac
 
 if test "x$MSYSTEM" != x ; then
diff --git a/include/ncrc.h b/include/ncrc.h
index c2ed2b041..81d62fdff 100644
--- a/include/ncrc.h
+++ b/include/ncrc.h
@@ -28,7 +28,7 @@ and accessing rc files (e.g. .daprc).
 
 typedef struct NCRCentry {
 	char* host; /* combined host:port */
-	char* path; /* prefix to match or NULL */
+	char* urlpath; /* prefix to match or NULL */
         char* key;
         char* value;
 } NCRCentry;
@@ -67,7 +67,7 @@ extern "C" {
 
 /* From drc.c */
 EXTERNL void ncrc_initialize(void);
-EXTERNL int NC_rcfile_insert(const char* key, const char* value, const char* hostport, const char* path);
+EXTERNL int NC_rcfile_insert(const char* key, const char* hostport, const char* path, const char* value);
 EXTERNL char* NC_rclookup(const char* key, const char* hostport, const char* path);
 EXTERNL char* NC_rclookupx(NCURI* uri, const char* key);
 
diff --git a/include/netcdf.h b/include/netcdf.h
index e55f77456..a4b69071d 100644
--- a/include/netcdf.h
+++ b/include/netcdf.h
@@ -2095,6 +2095,14 @@ EXTERNL int nc_initialize(void);
 */
 EXTERNL int nc_finalize(void);
 
+/* Programmatic access to the internal .rc table */
+
+/* Get the value corresponding to key | return NULL; caller frees  result */
+EXTERNL char* nc_rc_get(const char* key);
+
+/* Set/overwrite the value corresponding to key */
+EXTERNL int nc_rc_set(const char* key, const char* value);
+
 #if defined(__cplusplus)
 }
 #endif
diff --git a/libdispatch/ddispatch.c b/libdispatch/ddispatch.c
index 156208a99..c4cc56d5b 100644
--- a/libdispatch/ddispatch.c
+++ b/libdispatch/ddispatch.c
@@ -95,7 +95,6 @@ NCDISPATCH_initialize(void)
         NCpathcanonical(home,&globalstate->home);
 	nullfree(home);
     }
-fprintf(stderr,">>> HOME=|%s|\n",globalstate->home); fflush(stderr);
  
     /* Capture $CWD */
     {
diff --git a/libdispatch/dhttp.c b/libdispatch/dhttp.c
index 250ddab17..729885163 100644
--- a/libdispatch/dhttp.c
+++ b/libdispatch/dhttp.c
@@ -22,7 +22,9 @@
 #include "nclog.h"
 #include "ncbytes.h"
 #include "nclist.h"
+#include "ncuri.h"
 #include "nchttp.h"
+#include "ncauth.h"
 
 #undef TRACE
 
@@ -482,6 +484,23 @@ setupconn(NC_HTTP_STATE* state, const char* objecturl)
     cstat = curl_easy_setopt(state->curl, CURLOPT_FOLLOWLOCATION, 1); 
     if (cstat != CURLE_OK) goto fail;
 
+    /* Pull some values from .rc tables */
+    {
+	NCURI* uri = NULL;
+	char* hostport = NULL;
+	char* value = NULL;
+	ncuriparse(objecturl,&uri);
+	if(uri == NULL) goto fail;
+	hostport = NC_combinehostport(uri);
+	value = NC_rclookup("HTTP.SSL.CAINFO",hostport,NULL);
+	if(value == NULL)
+	    value = NC_rclookup("HTTP.SSL.CAINFO",NULL,NULL);
+	if(value != NULL) {
+	    cstat = CURLERR(curl_easy_setopt(state->curl, CURLOPT_CAINFO, value));
+	    if (cstat != CURLE_OK) goto fail;
+	}
+    }
+
     /* Set the method */
     if((stat = nc_http_set_method(state,state->request.method))) goto done;
 
diff --git a/libdispatch/drc.c b/libdispatch/drc.c
index e427eb8a7..7798052ef 100644
--- a/libdispatch/drc.c
+++ b/libdispatch/drc.c
@@ -23,6 +23,7 @@ See COPYRIGHT for license information.
 #include "ncauth.h"
 #include "ncpathmgr.h"
 #include "nc4internal.h"
+#include "ncdispatch.h"
 
 #ifndef nulldup
  #define nulldup(x) ((x)?strdup(x):(x))
@@ -38,8 +39,6 @@ See COPYRIGHT for license information.
 #define RTAG ']'
 #define LTAG '['
 
-#define TRIMCHARS " \t\r\n"
-
 #undef MEMCHECK
 #define MEMCHECK(x) if((x)==NULL) {goto nomem;} else {}
 
@@ -52,9 +51,12 @@ static char* rcreadline(char** nextlinep);
 static void rctrim(char* text);
 static void rcorder(NClist* rc);
 static int rccompile(const char* path);
-static struct NCRCentry* rclocate(const char* key, const char* hostport, const char* path);
+static int rcequal(NCRCentry* e1, NCRCentry* e2);
+static int rclocatepos(const char* key, const char* hostport, const char* urlpath);
+static struct NCRCentry* rclocate(const char* key, const char* hostport, const char* urlpath);
 static int rcsearch(const char* prefix, const char* rcname, char** pathp);
 static void rcfreeentries(NClist* rc);
+static void rcfreeentry(NCRCentry* t);
 #ifdef DRCDEBUG
 static void storedump(char* msg, NClist* entrys);
 #endif
@@ -68,10 +70,63 @@ static const char* rcfilenames[] = {".ncrc", ".daprc", ".dodsrc",NULL};
 /* Read these files */
 static const char* awsconfigfiles[] = {".aws/credentials",".aws/config",NULL};
 
+static int NCRCinitialized = 0;
+
 /**************************************************/
-/* External Entry Points */
+/* User API */
 
-static int NCRCinitialized = 0;
+/**
+The most common case is to get the most general value for a key,
+where most general means that the urlpath and hostport are null
+So this function returns the value associated with the key
+where the .rc entry has the simple form "key=value".
+If that entry is not found, then return NULL.
+
+@param key table entry key field
+@return value matching the key -- caller frees
+@return NULL if no entry of the form key=value exists
+*/
+char*
+nc_rc_get(const char* key)
+{
+    NCglobalstate* ncg = NULL;
+    char* value = NULL;
+
+    if(!NC_initialized) nc_initialize();
+
+    ncg = NC_getglobalstate();
+    assert(ncg != NULL && ncg->rcinfo != NULL && ncg->rcinfo->entries != NULL);
+    if(ncg->rcinfo->ignore) return NC_NOERR;
+    value = NC_rclookup(key,NULL,NULL);
+    return nulldup(value);    
+}
+
+/**
+Set simple key=value in .rc table.
+Will overwrite any existing value.
+
+@param key
+@param value 
+@return NC_NOERR if success
+@return NC_EINVAL if fail
+*/
+int
+nc_rc_set(const char* key, const char* value)
+{
+    int stat = NC_NOERR;
+    NCglobalstate* ncg = NULL;
+
+    if(!NC_initialized) nc_initialize();
+
+    ncg = NC_getglobalstate();
+    assert(ncg != NULL && ncg->rcinfo != NULL && ncg->rcinfo->entries != NULL);
+    if(ncg->rcinfo->ignore) return NC_NOERR;
+    stat = NC_rcfile_insert(key,NULL,NULL,value);
+    return stat;
+}
+
+/**************************************************/
+/* External Entry Points */
 
 /*
 Initialize defaults and load:
@@ -96,13 +151,14 @@ ncrc_initialize(void)
     if(NCRCinitialized) return;
     NCRCinitialized = 1; /* prevent recursion */
 
+    ncg = NC_getglobalstate();
+
 #ifndef NOREAD
     /* Load entrys */
     if((stat = NC_rcload())) {
         nclog(NCLOGWARN,".rc loading failed");
     }
     /* Load .aws/config */
-    ncg = NC_getglobalstate();
     if((stat = aws_load_credentials(ncg))) {
         nclog(NCLOGWARN,"AWS config file not loaded");
     }
@@ -120,6 +176,9 @@ ncrc_setrchome(void)
     if(tmp == NULL || strlen(tmp) == 0)
 	tmp = ncg->home;
     ncg->rcinfo->rchome = strdup(tmp);
+#ifdef DRCDEBUG
+    fprintf(stderr,"ncrc_setrchome: %s\n",ncg->rcinfo->rchome);
+#endif
 }
 
 void
@@ -133,15 +192,22 @@ NC_rcclear(NCRCinfo* info)
 }
 
 static void
-rcfreeentries(NClist* rc)
+rcfreeentry(NCRCentry* t)
 {
-    int i;
-    for(i=0;i<nclistlength(rc);i++) {
-	NCRCentry* t = (NCRCentry*)nclistget(rc,i);
 	nullfree(t->host);
+	nullfree(t->urlpath);
 	nullfree(t->key);
 	nullfree(t->value);
 	free(t);
+}
+
+static void
+rcfreeentries(NClist* rc)
+{
+    int i;
+    for(i=0;i<nclistlength(rc);i++) {
+	NCRCentry* t = (NCRCentry*)nclistget(rc,i);
+	rcfreeentry(t);
     }
     nclistfree(rc);
 }
@@ -182,8 +248,7 @@ NC_rcload(void)
 	const char* dirnames[3];
 	const char** dir;
 
-
-	/* Make sure rcinfo.rchome is defined */
+        /* Make sure rcinfo.rchome is defined */
 	ncrc_setrchome();
 	dirnames[0] = globalstate->rcinfo->rchome;
 	dirnames[1] = globalstate->cwd;
@@ -214,15 +279,15 @@ done:
 }
 
 /**
- * Locate a entry by property key and host+port (may be null|"")
+ * Locate a entry by property key and host+port (may be null)
  * If duplicate keys, first takes precedence.
  */
 char*
-NC_rclookup(const char* key, const char* hostport, const char* path)
+NC_rclookup(const char* key, const char* hostport, const char* urlpath)
 {
     struct NCRCentry* entry = NULL;
     if(!NCRCinitialized) ncrc_initialize();
-    entry = rclocate(key,hostport,path);
+    entry = rclocate(key,hostport,urlpath);
     return (entry == NULL ? NULL : entry->value);
 }
 
@@ -248,8 +313,7 @@ Set the absolute path to use for the rc file.
 WARNING: this MUST be called before any other
 call in order for this to take effect.
 
-\param[in] rcfile The path to use. If NULL, or "",
-                  then do not use any rcfile.
+\param[in] rcfile The path to use. If NULL then do not use any rcfile.
 
 \retval OC_NOERR if the request succeeded.
 \retval OC_ERCFILE if the file failed to load
@@ -305,25 +369,26 @@ rcreadline(char** nextlinep)
 static void
 rctrim(char* text)
 {
-    char* p = text;
+    char* p;
+    char* q;
     size_t len = 0;
     int i;
 
-    if(text == NULL) return;
+    if(text == NULL || *text == '\0') return;
 
-    /* locate first non-trimchar */
-    for(;*p;p++) {
-       if(strchr(TRIMCHARS,*p) == NULL) break; /* hit non-trim char */
-    }
-    memmove(text,p,strlen(p)+1);
     len = strlen(text);
+
+    /* elide upto first non-trimchar */
+    for(q=text,p=text;*p;p++) {
+	if(*p != ' ' && *p != '\t' && *p != '\r') {*q++ = *p;}
+    }
+    len = strlen(p);
     /* locate last non-trimchar */
     if(len > 0) {
         for(i=(len-1);i>=0;i--) {
-            if(strchr(TRIMCHARS,text[i]) == NULL) {
-                text[i+1] = '\0'; /* elide trailing trimchars */
-                break;
-            }
+	    p = &text[i];
+	    if(*p != ' ' && *p != '\t' && *p != '\r') {break;}
+	    *p = '\0'; /* elide trailing trimchars */
         }
     }
 }
@@ -339,22 +404,22 @@ rcorder(NClist* rc)
     NClist* tmprc = NULL;
     if(rc == NULL || len == 0) return;
     tmprc = nclistnew();
-    /* Copy rc into tmprc and clear rc */
-    for(i=0;i<len;i++) {
-        NCRCentry* ti = nclistget(rc,i);
-        nclistpush(tmprc,ti);
-    }
-    nclistclear(rc);
     /* Two passes: 1) pull entries with host */
     for(i=0;i<len;i++) {
-        NCRCentry* ti = nclistget(tmprc,i);
+        NCRCentry* ti = nclistget(rc,i);
 	if(ti->host == NULL) continue;
-	nclistpush(rc,ti);
+	nclistpush(tmprc,ti);
     }
     /* pass 2 pull entries without host*/
     for(i=0;i<len;i++) {
-        NCRCentry* ti = nclistget(tmprc,i);
+        NCRCentry* ti = nclistget(rc,i);
 	if(ti->host != NULL) continue;
+	nclistpush(tmprc,ti);
+    }
+    /* Move tmp to rc */
+    nclistsetlength(rc,0);
+    for(i=0;i<len;i++) {
+        NCRCentry* ti = nclistget(tmprc,i);
 	nclistpush(rc,ti);
     }
 #ifdef DRCDEBUG
@@ -365,7 +430,7 @@ rcorder(NClist* rc)
 
 /* Merge a entry store from a file*/
 static int
-rccompile(const char* path)
+rccompile(const char* filepath)
 {
     int ret = NC_NOERR;
     NClist* rc = NULL;
@@ -376,8 +441,8 @@ rccompile(const char* path)
     NCglobalstate* globalstate = NC_getglobalstate();
     char* bucket = NULL;
 
-    if((ret=NC_readfile(path,tmp))) {
-        nclog(NCLOGWARN, "Could not open configuration file: %s",path);
+    if((ret=NC_readfile(filepath,tmp))) {
+        nclog(NCLOGWARN, "Could not open configuration file: %s",filepath);
 	goto done;
     }
     contents = ncbytesextract(tmp);
@@ -394,6 +459,7 @@ rccompile(const char* path)
 	char* key = NULL;
         char* value = NULL;
         char* host = NULL;
+        char* urlpath = NULL;
 	size_t llen;
         NCRCentry* entry;
 
@@ -406,7 +472,7 @@ rccompile(const char* path)
 	    char* url = ++line;
             char* rtag = strchr(line,RTAG);
             if(rtag == NULL) {
-                nclog(NCLOGERR, "Malformed [url] in %s entry: %s",path,line);
+                nclog(NCLOGERR, "Malformed [url] in %s entry: %s",filepath,line);
 		continue;
             }
             line = rtag + 1;
@@ -414,17 +480,18 @@ rccompile(const char* path)
             /* compile the url and pull out the host and protocol */
             if(uri) ncurifree(uri);
             if(ncuriparse(url,&uri)) {
-                nclog(NCLOGERR, "Malformed [url] in %s entry: %s",path,line);
+                nclog(NCLOGERR, "Malformed [url] in %s entry: %s",filepath,line);
 		continue;
             }
 	    { NCURI* newuri = NULL;
-	        /* Rebuild the url to path format */
+	        /* Rebuild the url to S3 "path" format */
 	        nullfree(bucket);
 	        if((ret = NC_s3urlrebuild(uri,&newuri,&bucket,NULL))) goto done;
 		ncurifree(uri);
 		uri = newuri;
 		newuri = NULL;
 	    }
+	    /* Get the host+port */
             ncbytesclear(tmp);
             ncbytescat(tmp,uri->host);
             if(uri->port != NULL) {
@@ -433,8 +500,11 @@ rccompile(const char* path)
             }
             ncbytesnull(tmp);
             host = ncbytesextract(tmp);
-	    if(strlen(host)==0)
+	    if(strlen(host)==0) /* nullify host */
 		{free(host); host = NULL;}
+	    /* Get the url path part */
+	    urlpath = uri->path;
+	    if(urlpath && strlen(urlpath)==0) urlpath = NULL; /* nullify */
 	}
         /* split off key and value */
         key=line;
@@ -446,31 +516,33 @@ rccompile(const char* path)
             value++;
         }
 	/* See if key already exists */
-	entry = rclocate(key,host,path);
-	if(entry != NULL) {
-	    nullfree(entry->host);
-	    nullfree(entry->key);
-	    nullfree(entry->value);
-	} else {
+	entry = rclocate(key,host,urlpath);
+	if(entry == NULL) {
 	    entry = (NCRCentry*)calloc(1,sizeof(NCRCentry));
 	    if(entry == NULL) {ret = NC_ENOMEM; goto done;}
 	    nclistpush(rc,entry);
+	    entry->host = host; host = NULL;
+	    entry->urlpath = nulldup(urlpath);
+    	    entry->key = nulldup(key);
+            rctrim(entry->host);
+            rctrim(entry->urlpath);
+            rctrim(entry->key);
 	}
-	entry->host = host; host = NULL;
-	entry->key = nulldup(key);
+	nullfree(entry->value);
         entry->value = nulldup(value);
-        rctrim(entry->host);
-        rctrim(entry->key);
         rctrim(entry->value);
 
 #ifdef DRCDEBUG
-	fprintf(stderr,"rc: host=%s key=%s value=%s\n",
+	fprintf(stderr,"rc: host=%s urlpath=%s key=%s value=%s\n",
 		(entry->host != NULL ? entry->host : "<null>"),
+		(entry->urlpath != NULL ? entry->urlpath : "<null>"),
 		entry->key,entry->value);
 #endif
-
 	entry = NULL;
     }
+#ifdef DRCDEBUG
+    fprintf(stderr,"reorder.path=%s\n",filepath);
+#endif
     rcorder(rc);
 
 done:
@@ -481,46 +553,83 @@ done:
 }
 
 /**
- * (Internal) Locate a entry by property key and host+port (may be null or "").
+Encapsulate equality comparison: return 1|0
+*/
+static int
+rcequal(NCRCentry* e1, NCRCentry* e2)
+{
+    int nulltest;
+    if(e1->key == NULL || e2->key == NULL) return 0;
+    if(strcmp(e1->key,e2->key) != 0) return 0;
+    /* test hostport; take NULL into account*/
+    nulltest = 0;
+    if(e1->host == NULL) nulltest |= 1;
+    if(e2->host == NULL) nulltest |= 2;
+    switch (nulltest) {
+    case 0: if(strcmp(e1->host,e2->host) != 0) {return 0;}  break;
+    case 1: return 0;
+    case 2: return 0;
+    case 3: break;
+    default: return 0;
+    }
+    /* test urlpath take NULL into account*/
+    nulltest = 0;
+    if(e1->urlpath == NULL) nulltest |= 1;
+    if(e2->urlpath == NULL) nulltest |= 2;
+    switch (nulltest) {
+    case 0: if(strcmp(e1->urlpath,e2->urlpath) != 0) {return 0;} break;
+    case 1: return 0;
+    case 2: return 0;
+    case 3: break;
+    default: return 0;
+    }
+    return 1;
+}
+
+/**
+ * (Internal) Locate a entry by property key and host+port (may be null) and urlpath (may be null)
  * If duplicate keys, first takes precedence.
  */
-static struct NCRCentry*
-rclocate(const char* key, const char* hostport, const char* path)
+static int
+rclocatepos(const char* key, const char* hostport, const char* urlpath)
 {
-    int i,found;
+    int i;
     NCglobalstate* globalstate = NC_getglobalstate();
-    NClist* rc = globalstate->rcinfo->entries;
+    struct NCRCinfo* info = globalstate->rcinfo;
     NCRCentry* entry = NULL;
+    NCRCentry candidate;
+    NClist* rc = info->entries;
 
-    if(globalstate->rcinfo->ignore)
-	return NULL;
+    if(info->ignore) return -1;
 
-    if(key == NULL || rc == NULL) return NULL;
-    if(hostport == NULL) hostport = "";
+    candidate.key = (char*)key;
+    candidate.value = (char*)NULL;
+    candidate.host = (char*)hostport;
+    candidate.urlpath = (char*)urlpath;
 
-    for(found=0,i=0;i<nclistlength(rc);i++) {
-      int t;
-      size_t hplen;
+    for(i=0;i<nclistlength(rc);i++) {
       entry = (NCRCentry*)nclistget(rc,i);
-
-      hplen = (entry->host == NULL ? 0 : strlen(entry->host));
-
-      if(strcmp(key,entry->key) != 0) continue; /* keys do not match */
-      /* If the entry entry has no url, then use it
-         (because we have checked all other cases)*/
-      if(hplen == 0) {found=1;break;}
-      /* do hostport match */
-      t = 0;
-      if(entry->host != NULL)
-        t = strcmp(hostport,entry->host);
-      /* do path prefix match */
-      if(entry->path != NULL) {
-	size_t pathlen = strlen(entry->path);
-        t = strncmp(path,entry->path,pathlen);
-      }
-      if(t ==  0) {found=1; break;}
+      if(rcequal(entry,&candidate)) return i;
     }
-    return (found?entry:NULL);
+    return -1;
+}
+
+/**
+ * (Internal) Locate a entry by property key and host+port (may be null or "").
+ * If duplicate keys, first takes precedence.
+ */
+static struct NCRCentry*
+rclocate(const char* key, const char* hostport, const char* urlpath)
+{
+    int pos;
+    NCglobalstate* globalstate = NC_getglobalstate();
+    struct NCRCinfo* info = globalstate->rcinfo;
+
+    if(globalstate->rcinfo->ignore) return NULL;
+    if(key == NULL || info == NULL) return NULL;
+    pos = rclocatepos(key,hostport,urlpath);
+    if(pos < 0) return NULL;
+    return NC_rcfile_ith(info,(size_t)pos);
 }
 
 /**
@@ -562,7 +671,7 @@ done:
 }
 
 int
-NC_rcfile_insert(const char* key, const char* value, const char* hostport, const char* path)
+NC_rcfile_insert(const char* key, const char* hostport, const char* urlpath, const char* value)
 {
     int ret = NC_NOERR;
     /* See if this key already defined */
@@ -571,6 +680,10 @@ NC_rcfile_insert(const char* key, const char* value, const char* hostport, const
     NClist* rc = NULL;
 
     if(!NCRCinitialized) ncrc_initialize();
+
+    if(key == NULL || value == NULL)
+        {ret = NC_EINVAL; goto done;}
+
     globalstate = NC_getglobalstate();
     rc = globalstate->rcinfo->entries;
 
@@ -579,19 +692,25 @@ NC_rcfile_insert(const char* key, const char* value, const char* hostport, const
         globalstate->rcinfo->entries = rc;
 	if(rc == NULL) {ret = NC_ENOMEM; goto done;}
     }
-    entry = rclocate(key,hostport,path);
+    entry = rclocate(key,hostport,urlpath);
     if(entry == NULL) {
 	entry = (NCRCentry*)calloc(1,sizeof(NCRCentry));
 	if(entry == NULL) {ret = NC_ENOMEM; goto done;}
 	entry->key = strdup(key);
 	entry->value = NULL;
         rctrim(entry->key);
-        entry->host = (hostport == NULL ? NULL : strdup(hostport));
+        entry->host = nulldup(hostport);
+        rctrim(entry->host);
+        entry->urlpath = nulldup(urlpath);
+        rctrim(entry->urlpath);
 	nclistpush(rc,entry);
     }
     if(entry->value != NULL) free(entry->value);
     entry->value = strdup(value);
     rctrim(entry->value);
+#ifdef DRCDEBUG
+    storedump("NC_rcfile_insert",rc);
+#endif    
 done:
     return ret;
 }
diff --git a/libsrc4/nc4internal.c b/libsrc4/nc4internal.c
index 878be8c76..b96291dc0 100644
--- a/libsrc4/nc4internal.c
+++ b/libsrc4/nc4internal.c
@@ -2105,8 +2105,12 @@ NC_createglobalstate(void)
         nc_globalstate = calloc(1,sizeof(NCglobalstate));
     }
     /* Initialize struct pointers */
-    nc_globalstate->rcinfo = (struct NCRCinfo*)calloc(1,sizeof(struct NCRCinfo));
-    if(nc_globalstate == NULL) return NC_ENOMEM;
+    if((nc_globalstate->rcinfo = calloc(1,sizeof(struct NCRCinfo)))==NULL)
+        {stat = NC_ENOMEM; goto done;}
+    if((nc_globalstate->rcinfo->entries = nclistnew())==NULL)
+        {stat = NC_ENOMEM; goto done;}
+    if((nc_globalstate->rcinfo->s3profiles = nclistnew())==NULL)
+        {stat = NC_ENOMEM; goto done;}
 
     /* Get environment variables */
     if(getenv(NCRCENVIGNORE) != NULL)
@@ -2119,6 +2123,7 @@ NC_createglobalstate(void)
     nc_globalstate->chunkcache.nelems = CHUNK_CACHE_NELEMS;        /**< Default chunk cache number of elements. */
     nc_globalstate->chunkcache.preemption = CHUNK_CACHE_PREEMPTION; /**< Default chunk cache preemption. */
     
+done:
     return stat;
 }
 
diff --git a/ncdump/CMakeLists.txt b/ncdump/CMakeLists.txt
index 056810f3c..7e53c13a2 100644
--- a/ncdump/CMakeLists.txt
+++ b/ncdump/CMakeLists.txt
@@ -105,6 +105,7 @@ IF(ENABLE_TESTS)
   ADD_EXECUTABLE(nctrunc nctrunc.c)
 if(RCMERGE)
   ADD_EXECUTABLE(tst_rcmerge tst_rcmerge.c)
+  ADD_EXECUTABLE(tst_rcmerge tst_rcapi.c)
 endif()
   TARGET_LINK_LIBRARIES(rewrite-scalar netcdf)
   TARGET_LINK_LIBRARIES(bom netcdf)
@@ -112,6 +113,7 @@ endif()
   TARGET_LINK_LIBRARIES(nctrunc netcdf)
 if(RCMERGE)
   TARGET_LINK_LIBRARIES(tst_rcmerge netcdf)
+  TARGET_LINK_LIBRARIES(tst_rcapi netcdf)
 endif()
 
   IF(USE_HDF5)
@@ -154,6 +156,11 @@ endif()
         SET_TARGET_PROPERTIES(tst_rcmerge PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG
           ${CMAKE_CURRENT_BINARY_DIR})
         SET_TARGET_PROPERTIES(tst_rcmerge PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_BINARY_DIR})
+        SET_TARGET_PROPERTIES(tst_rcapi PROPERTIES RUNTIME_OUTPUT_DIRECTORY
+          ${CMAKE_CURRENT_BINARY_DIR})
+        SET_TARGET_PROPERTIES(tst_rcapi PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG
+          ${CMAKE_CURRENT_BINARY_DIR})
+        SET_TARGET_PROPERTIES(tst_rcapi PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_BINARY_DIR})
     endif()
 
     IF(USE_HDF5)
diff --git a/ncdump/Makefile.am b/ncdump/Makefile.am
index bcce244b5..3f958b159 100644
--- a/ncdump/Makefile.am
+++ b/ncdump/Makefile.am
@@ -79,7 +79,7 @@ man_MANS = ncdump.1 nccopy.1
 if BUILD_TESTSETS
 # C programs needed by shell scripts for classic tests.
 check_PROGRAMS = rewrite-scalar ref_ctest ref_ctest64 ncdump tst_utf8   \
-bom tst_dimsizes nctrunc tst_rcmerge
+bom tst_dimsizes nctrunc tst_rcmerge tst_rcapi
 
 # Tests for classic and 64-bit offset files.
 TESTS = tst_inttags.sh run_tests.sh tst_64bit.sh ref_ctest	\
@@ -203,7 +203,8 @@ ref_roman_szip_simple.cdl ref_roman_szip_unlim.cdl ref_tst_perdimspecs.cdl \
 test_keywords.sh ref_keyword1.cdl ref_keyword2.cdl ref_keyword3.cdl ref_keyword4.cdl \
 ref_tst_nofilters.cdl test_scope.sh  \
 test_rcmerge.sh ref_rcmerge1.txt ref_rcmerge2.txt ref_rcmerge3.txt \
-scope_ancestor_only.cdl scope_ancestor_subgroup.cdl scope_group_only.cdl scope_preorder.cdl
+scope_ancestor_only.cdl scope_ancestor_subgroup.cdl scope_group_only.cdl scope_preorder.cdl \
+ref_rcapi.txt
 
 # The L512.bin file is file containing exactly 512 bytes each of value 0.
 # It is used for creating hdf5 files with varying offsets for testing.
diff --git a/ncdump/ref_rcapi.txt b/ncdump/ref_rcapi.txt
new file mode 100644
index 000000000..9faf22604
--- /dev/null
+++ b/ncdump/ref_rcapi.txt
@@ -0,0 +1,7 @@
+load:
+insert: key=key1 value=value1
+insert: before: key1 = (null)
+insert: after: key1 = value1
+replace: key=key1 value=value2
+replace: before: key1 = value1
+replace: after: key1 = value2
diff --git a/ncdump/tst_rcapi.c b/ncdump/tst_rcapi.c
new file mode 100755
index 000000000..a7bd0f239
--- /dev/null
+++ b/ncdump/tst_rcapi.c
@@ -0,0 +1,94 @@
+/* This is part of the netCDF package.
+   Copyright 2018 University Corporation for Atmospheric Research/Unidata
+   See COPYRIGHT file for conditions of use.
+
+   Test RC interface
+   Dennis Heimbigner
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "netcdf.h"
+
+#undef DEBUG
+
+#ifndef nullfree
+#define nullfree(x) {if((x)!=NULL) free(x);}
+#endif
+
+#if 0
+static void
+printrc(NCglobalstate* ngs)
+{
+    size_t i,nentries = 0;
+    NCRCinfo* info = NULL;
+    NCRCentry* entry = NULL;
+
+    info = ngs->rcinfo;
+    if(info->ignore) {
+	fprintf(stderr,".rc ignored\n");
+	return;
+    }
+
+    /* Print out the .rc entries */
+    if((nentries = NC_rcfile_length(info))==0) {
+        printf("<empty>\n");
+	exit(0);
+    }
+    for(i=0;i<nentries;i++) {
+	entry = NC_rcfile_ith(info,i);
+	if(entry == NULL) abort();
+        if(entry->host != NULL) {
+	    printf("[%s ",entry->host);
+            if(entry->urlpath != NULL)
+	        printf("/%s] ",entry->urlpath);
+	    printf("]");					
+        }
+	printf("|%s|->|%s|\n",entry->key,entry->value);
+    }
+}
+#endif
+
+int
+main(int argc, char **argv)
+{
+    int stat = NC_NOERR;
+    const char* key;
+    const char* value;
+    char* newvalue;
+
+    printf("load:\n");
+
+    key = "key1";
+    value = "value1";
+    printf("insert: key=%s value=%s\n",key, value);
+    newvalue = nc_rc_get(key);
+    printf("insert: before: %s = %s\n",key,newvalue);
+    nullfree(newvalue);
+    stat = nc_rc_set(key, value);
+    if(stat) {fprintf(stderr,"***Fail: nc_rc_set: %s\n",nc_strerror(stat)); goto done;}
+    newvalue = nc_rc_get(key);
+    printf("insert: after: %s = %s\n",key,newvalue);
+    nullfree(newvalue);
+
+    key = "key1";
+    value = "value2";
+    printf("replace: key=%s value=%s\n",key, value);
+    newvalue = nc_rc_get(key);
+    printf("replace: before: %s = %s\n",key,newvalue);
+    nullfree(newvalue);
+    stat = nc_rc_set(key, value);
+    if(stat) {fprintf(stderr,"***Fail: nc_rc_set: %s\n",nc_strerror(stat)); goto done;}
+    newvalue = nc_rc_get(key);
+    printf("replace: after: %s = %s\n",key,newvalue);
+    nullfree(newvalue);
+
+done:
+    nc_finalize();
+    return 0;
+}
diff --git a/ncdump/tst_rcmerge.c b/ncdump/tst_rcmerge.c
index daa3c7bbb..047b3ebe0 100644
--- a/ncdump/tst_rcmerge.c
+++ b/ncdump/tst_rcmerge.c
@@ -5,23 +5,17 @@
 #include "ncrc.h"
 #include "nc4internal.h"
 
-int
-main(int argc, char** argv)
+static void
+printrc(NCglobalstate* ngs)
 {
     size_t i,nentries = 0;
-    NCglobalstate* ngs = NC_getglobalstate();
     NCRCinfo* info = NULL;
     NCRCentry* entry = NULL;
 
-    /* Cause the .rc files to be read and merged */
-    nc_initialize();
-
-    if((ngs = NC_getglobalstate())==NULL) abort();
     info = ngs->rcinfo;
-
     if(info->ignore) {
 	fprintf(stderr,".rc ignored\n");
-	exit(0);
+	return;
     }
 
     /* Print out the .rc entries */
@@ -34,11 +28,24 @@ main(int argc, char** argv)
 	if(entry == NULL) abort();
         if(entry->host != NULL) {
 	    printf("[%s ",entry->host);
-            if(entry->path != NULL)
-	        printf("/%s] ",entry->path);
+            if(entry->urlpath != NULL)
+	        printf("/%s] ",entry->urlpath);
 	    printf("]");					
         }
 	printf("|%s|->|%s|\n",entry->key,entry->value);
     }
+}
+
+int
+main(int argc, char** argv)
+{
+    NCglobalstate* ngs = NC_getglobalstate();
+
+    /* Cause the .rc files to be read and merged */
+    nc_initialize();
+
+    if((ngs = NC_getglobalstate())==NULL) abort();
+    printrc(ngs);
+
     return 0;
 }


Test patch:
diff --git a/nc_test4/tst_dims3.c b/nc_test4/tst_dims3.c
index 6132328d2..ac12faac1 100644
--- a/nc_test4/tst_dims3.c
+++ b/nc_test4/tst_dims3.c
@@ -397,7 +397,7 @@ main(int argc, char **argv)
        /* This test code based on test code from Jeff Whitaker. See
         * https://github.com/Unidata/netcdf-c/issues/2357. This is a
         * simplified version. */
-       int i, varid, ncid;
+       int varid, ncid;
        int dimids[NDIM2];
        size_t start[NDIM2] = {0, 0}, count[NDIM2] = {DATALEN2, 1}, dimlen;
        int data[DATALEN2] = {42, 42};
@@ -422,7 +422,7 @@ main(int argc, char **argv)
    SUMMARIZE_ERR;
    printf("*** testing dimlen with NC_UNLIMITED reopen and rewrite...");
    {
-       int i, varid, ncid;
+       int varid, ncid;
        int dimid;
        size_t start[NDIM1] = {0}, count[NDIM1] = {DATALEN2}, dimlen;
        int data[DATALEN2] = {42, 42};
diff --git a/nc_test4/tst_vars3.c b/nc_test4/tst_vars3.c
index 1b5a7ce79..175878966 100644
--- a/nc_test4/tst_vars3.c
+++ b/nc_test4/tst_vars3.c
@@ -418,7 +418,7 @@ main(int argc, char **argv)
         if (nc_close(ncid)) ERR;
     }
     SUMMARIZE_ERR;
-#ifdef HAVE_H5Z_SZIP
+#ifdef HAVE_SZ
     printf("**** testing simple szip filter setup...");
     {
         int ncid;
@@ -757,6 +757,6 @@ main(int argc, char **argv)
         if (nc_close(ncid)) ERR;
     }
     SUMMARIZE_ERR;
-#endif /* HAVE_H5Z_SZIP */
+#endif /* HAVE_SZ */
     FINAL_RESULTS;
 }
diff --git a/ncdump/test_rcmerge.sh b/ncdump/test_rcmerge.sh
index d1668a17c..51020d82e 100755
--- a/ncdump/test_rcmerge.sh
+++ b/ncdump/test_rcmerge.sh
@@ -54,7 +54,8 @@ resetrc() {
   rm -fr $HOMERCFILES
   rm -f $LOCALRCFILES
   unset NCRCENV_RC
-  rm -f tmpoutput.txt
+  rm -f tmp_rcmerge.txt
+  rm -f tmp_rcapi.txt
   rm -f allfiles1 allfiles2 allfiles3
 }
 
@@ -70,36 +71,40 @@ fi
 mergecase1() {
     # create everything with different keys to test merge 
     resetrc
-    rm -f tmp_rcmerge.txt tmpoutput.txt
+    rm -f tmp_rcmerge.txt tmp_rcmerge.txt
     echo  "for r=ncrc daprc dodsrc"
     for r in "ncrc" "daprc" "dodsrc"; do
         echo "${r}_home=${r}" >> $RCHOME/".${r}";
         echo "${r}_local=${r}" >> $WD/".${r}"
     done;
     union 1
-    ${abs_execdir}/tst_rcmerge |sort > tmpoutput.txt
+    ${abs_execdir}/tst_rcmerge |sort > tmp_rcmerge.txt
 #    echo ">>merge1"; cat ${abs_srcdir}/ref_rcmerge1.txt;
-#    echo "====="; cat tmpoutput.txt
-    diff -b ${abs_srcdir}/ref_rcmerge1.txt tmpoutput.txt
+#    echo "====="; cat tmp_rcmerge.txt
+    diff -b ${abs_srcdir}/ref_rcmerge1.txt tmp_rcmerge.txt
 }
 
 mergecase2() {
     # create with some same keys to test override
     resetrc
-    rm -f tmp_rcmerge.txt tmpoutput.txt
+    rm -f tmp_rcmerge.txt tmp_rcmerge.txt
+    echo "Create in $RCHOME"
     for r in "ncrc" "daprc" "dodsrc" ; do
         echo "${r}=${r}" >> $RCHOME/".${r}";
+    done;
+    echo "Create in $WD"
+    for r in "ncrc" "daprc" "dodsrc" ; do
         echo "${r}=${r}" >> $WD/".${r}"
     done;
     union 2
-    ${abs_execdir}/tst_rcmerge |sort > tmpoutput.txt
-    diff -b ${abs_srcdir}/ref_rcmerge2.txt tmpoutput.txt
+    ${abs_execdir}/tst_rcmerge |sort > tmp_rcmerge.txt
+    diff -b ${abs_srcdir}/ref_rcmerge2.txt tmp_rcmerge.txt
 }
 
 mergecase3() {
     # Test cross file overrides
     resetrc
-    rm -f tmp_rcmerge.txt tmpoutput.txt
+    rm -f tmp_rcmerge.txt tmp_rcmerge.txt
     echo "ncrc=ncrc1" >> $HOME/.ncrc
     echo "ncrcx=ncrcx" >> $RCHOME/.ncrc
     echo "ncrc=ncrc2" >> $RCHOME/.dodsrc
@@ -111,8 +116,18 @@ mergecase3() {
     echo "daprc=daprc" >> $WD/.dodsrc
     echo "ncrcx=ncrcy" >> $WD/.dodsrc
     union 3
-    ${abs_execdir}/tst_rcmerge |sort -d > tmpoutput.txt
-    diff -b ${abs_srcdir}/ref_rcmerge3.txt tmpoutput.txt
+    ${abs_execdir}/tst_rcmerge |sort -d > tmp_rcmerge.txt
+    diff -b ${abs_srcdir}/ref_rcmerge3.txt tmp_rcmerge.txt
+}
+
+rcapi1() {
+    resetrc
+    echo "[http://github.com/a/b/c]ncrc=ncrc1" >> $WD/.ncrc
+    echo "ncrc=ncrc2" >> $WD/.ncrc
+    echo "[http://github.com:8080/a/b/c]key0=v0" >> $WD/.ncrc
+    echo "[http://github.com]key0=v1" >> $WD/.ncrc
+    ${abs_execdir}/tst_rcapi > tmp_rcapi.txt
+    diff -b ${abs_srcdir}/ref_rcapi.txt ./tmp_rcapi.txt
 }
 
 resetrc
@@ -120,5 +135,10 @@ resetrc
 mergecase1
 mergecase2
 mergecase3
+# Test the .rc api
+rcapi1
+
+# Test the .rc api
+rcapi1
 
 resetrc