netcdf-c/libdap4/d4util.c
Dennis Heimbigner 591e6b2f6d Fix DAP4 remotetest server
Warning: This PR is a follow on to PR https://github.com/Unidata/netcdf-c/pull/2555 and should not be merged until that prior PR has been merged. The changeset for this PR is a delta on the PR https://github.com/Unidata/netcdf-c/pull/2555.

This PR re-enables the use of the server *remotetest.unidata.ucar.edu/d4ts*
to test several features:
1. Show that access over the Internet to servers using the DAP4 protocol works.
2. Test that DAP4 support in the [Thredds Data Server](https://github.com/Unidata/tds) is operating correctly.
4. Test that the DAP4 support in the [netcdf-java library](https://github.com/Unidata/netcdf-java) library and the DAP4 support in the netcdf-c library are consistent and are interoperable.

The test inputs (primarily *\*.nc* files) provided in the netcdf-c library
are also used by the DAP4 Test Server (aka d4ts) to present web access to a
collection of data files accessible via the DAP4 protocol and which can be
used for testing Internet access to a working server.

To be precise, this version of d4ts is currently in unmerged branches
of the *netcdf-java* and *tds* Github repositories and so are not actually
in the main repositories *yet*. However, the *d4ts.war* file was created
from that branch and used to populate the *remotetest.unidata.ucar.edu*
server

The two other remote servers that were used in the past are *Hyrax* (OPenDAP.org)
and *thredds-test*. These will continue to remain disabled until
those servers can be fixed.

## Primary Changes

* Rebuild the *baselineremote* directory. This directory contains the validation data needed to test the remote servers.
* Re-enable using remotetest.unidata.ucar.edu as part of the DAP4 testing process.
* Fix the *dap4_test/test_remote.sh* test script to match the current available test data.
* Make some changes to libdap4 to improve the ability to catch malformed data streams [affects a lot of files in libdap4].

## Misc. Unrelated Changes

* Remove a raft of warnings, especially in nc_test4/tst_quantize.c.
* Add some additional explanatory information to the NCZarr documentation.
* Cleanup some Doxygen errors in the docs file and reorder some files.
2022-11-15 20:29:21 -07:00

457 lines
10 KiB
C

/*********************************************************************
* Copyright 2018, UCAR/Unidata
* See netcdf/COPYRIGHT file for copying and redistribution conditions.
*********************************************************************/
#include "d4includes.h"
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef _MSC_VER
#include <io.h>
#endif
extern int mkstemp(char *template);
#define LBRACKET '['
#define RBRACKET ']'
#define VERIFY(off,space) assert((off)->offset+(space) <= (off)->limit)
/**************************************************/
/* Forward */
static char* backslashEscape(const char* s);
/**************************************************/
/**
* Provide a hidden interface to allow utilities
* to check if a given path name is really an ncdap4 url.
* If no, return null, else return basename of the url
* minus any extension.
*/
int
ncd4__testurl(const char* path, char** basenamep)
{
NCURI* uri;
int ok = NC_NOERR;
if(ncuriparse(path,&uri))
ok = NC_EURL;
else {
char* slash = (uri->path == NULL ? NULL : strrchr(uri->path, '/'));
char* dot;
if(slash == NULL) slash = (char*)path; else slash++;
slash = nulldup(slash);
if(slash == NULL)
dot = NULL;
else
dot = strrchr(slash, '.');
if(dot != NULL && dot != slash) *dot = '\0';
if(basenamep)
*basenamep=slash;
else if(slash)
free(slash);
}
ncurifree(uri);
return ok;
}
/* Return 1 if this machine is little endian */
int
NCD4_isLittleEndian(void)
{
union {
unsigned char bytes[SIZEOF_INT];
int i;
} u;
u.i = 1;
return (u.bytes[0] == 1 ? 1 : 0);
}
/* Compute the size of an atomic type, except opaque */
size_t
NCD4_typesize(nc_type tid)
{
switch(tid) {
case NC_BYTE: case NC_UBYTE: case NC_CHAR: return 1;
case NC_SHORT: case NC_USHORT: return sizeof(short);
case NC_INT: case NC_UINT: return sizeof(int);
case NC_FLOAT: return sizeof(float);
case NC_DOUBLE: return sizeof(double);
case NC_INT64: case NC_UINT64: return sizeof(long long);
case NC_STRING: return sizeof(char*);
default: break;
}
return 0;
}
d4size_t
NCD4_dimproduct(NCD4node* node)
{
int i;
d4size_t product = 1;
for(i=0;i<nclistlength(node->dims);i++) {
NCD4node* dim = (NCD4node*)nclistget(node->dims,i);
product *= dim->dim.size;
}
return product;
}
/* Caller must free return value */
char*
NCD4_makeFQN(NCD4node* node)
{
char* fqn = NULL;
char* escaped;
int i;
NCD4node* g = node;
NClist* path = nclistnew();
size_t estimate;
for(estimate=0;g != NULL;g=g->container) {
estimate += strlen(g->name);
nclistinsert(path,0,g);
}
estimate = (estimate*2) + 2*nclistlength(path);
estimate++; /*strlcat nul*/
fqn = (char*)malloc(estimate+1);
if(fqn == NULL) goto done;
fqn[0] = '\0';
/* Create the group-based fqn prefix */
/* start at 1 to avoid dataset */
for(i=1;i<nclistlength(path);i++) {
NCD4node* elem = (NCD4node*)nclistget(path,i);
if(elem->sort != NCD4_GROUP) break;
/* Add in the group name */
escaped = backslashEscape(elem->name);
if(escaped == NULL) {free(fqn); fqn = NULL; goto done;}
strlcat(fqn,"/",estimate);
strlcat(fqn,escaped,estimate);
free(escaped);
}
/* Add in the final name part (if not group) */
if(i < nclistlength(path)) {
int last = nclistlength(path)-1;
NCD4node* n = (NCD4node*)nclistget(path,last);
char* name = NCD4_makeName(n,".");
strlcat(fqn,"/",estimate);
strlcat(fqn,name,estimate);
nullfree(name);
}
done:
nclistfree(path);
return fqn;
}
/*
create the last part of the fqn
(post groups)
*/
char*
NCD4_makeName(NCD4node* elem, const char* sep)
{
int i;
size_t estimate = 0;
NCD4node* n;
NClist* path = nclistnew();
char* fqn = NULL;
/* Collect the path up to, but not including, the first containing group */
for(estimate=0,n=elem;n->sort != NCD4_GROUP;n=n->container) {
nclistinsert(path,0,n);
estimate += (1+(2*strlen(n->name)));
}
estimate++; /*strlcat nul*/
fqn = (char*)malloc(estimate+1);
if(fqn == NULL) goto done;
fqn[0] = '\0';
for(i=0;i<nclistlength(path);i++) {
NCD4node* elem = (NCD4node*)nclistget(path,i);
char* escaped = backslashEscape(elem->name);
if(escaped == NULL) {free(fqn); fqn = NULL; goto done;}
if(i > 0)
strlcat(fqn,sep,estimate);
strlcat(fqn,escaped,estimate);
free(escaped);
}
done:
nclistfree(path);
return fqn;
}
static char*
backslashEscape(const char* s)
{
const char* p;
char* q;
size_t len;
char* escaped = NULL;
len = strlen(s);
escaped = (char*)malloc(1+(2*len)); /* max is everychar is escaped */
if(escaped == NULL) return NULL;
for(p=s,q=escaped;*p;p++) {
char c = *p;
switch (c) {
case '\\':
case '/':
case '.':
case '@':
*q++ = '\\'; *q++ = '\\';
break;
default: *q++ = c; break;
}
}
*q = '\0';
return escaped;
}
/* Parse an fqn into a sequence of names;
using '/', and then (conditionally) '.' */
int
NCD4_parseFQN(const char* fqn0, NClist* pieces)
{
int ret = NC_NOERR;
int count;
char* p;
char* start;
char* fqn = NULL;
if(fqn0 == NULL) fqn0 = "/";
fqn = strdup(fqn0[0] == '/' ? fqn0+1 : fqn0);
start = fqn;
/* Step 0: insert rootname */
nclistpush(pieces,strdup("/"));
/* Step 1: Break fqn into pieces at occurrences of '/' */
count = 0;
for(p=start;*p;) {
switch(*p) {
case '\\': /* leave the escapes in place */
p+=2;
break;
case '/': /*capture the piece name */
*p++ = '\0';
start = p; /* mark start of the next part */
count++;
break;
default: /* ordinary char */
p++;
break;
}
}
#ifdef ALLOWFIELDMAPS
/* Step 2, walk the final piece to break up based on '.' */
for(p=start;*p;) {
switch(*p) {
case '\\': /* leave the escapes in place */
p+=2;
break;
case '.': /*capture the piece name */
*p++ = '\0';
start = p;
count++;
break;
default: /* ordinary char */
p++;
break;
}
}
#endif
count++; /* acct for last piece */
/* Step 3: capture and de-scape the pieces */
for(p=fqn;count > 0;count--) {
char* descaped = NCD4_deescape(p);
nclistpush(pieces,descaped);
p = p + strlen(p) + 1; /* skip past the terminating nul */
}
if(fqn != NULL) free(fqn);
return THROW(ret);
}
char*
NCD4_deescape(const char* esc)
{
size_t len;
char* s;
const char* p;
char* q;
if(esc == NULL) return NULL;
len = strlen(esc);
s = (char*)malloc(len+1);
if(s == NULL) return NULL;
for(p=esc,q=s;*p;) {
switch (*p) {
case '\\':
p++;
/* fall thru */
default: *q++ = *p++; break;
}
}
*q = '\0';
return s;
}
char*
NCD4_entityescape(const char* s)
{
const char* p;
char* q;
size_t len;
char* escaped = NULL;
const char* entity;
len = strlen(s);
escaped = (char*)malloc(1+(6*len)); /* 6 = |&apos;| */
if(escaped == NULL) return NULL;
for(p=s,q=escaped;*p;p++) {
char c = *p;
switch (c) {
case '&': entity = "&amp;"; break;
case '<': entity = "&lt;"; break;
case '>': entity = "&gt;"; break;
case '"': entity = "&quot;"; break;
case '\'': entity = "&apos;"; break;
default : entity = NULL; break;
}
if(entity == NULL)
*q++ = c;
else {
len = strlen(entity);
memcpy(q,entity,len);
q+=len;
}
}
*q = '\0';
return escaped;
}
/* Elide all nul characters from an XML document as a precaution*/
size_t
NCD4_elidenuls(char* s, size_t slen)
{
size_t i,j;
for(j=0,i=0;i<slen;i++) {
int c = s[i];
if(c != 0)
s[j++] = (char)c;
}
/* if we remove any nuls then nul term */
if(j < i)
s[j] = '\0';
return j;
}
void
NCD4_hostport(NCURI* uri, char* space, size_t len)
{
if(space != NULL && len > 0) {
space[0] = '\0'; /* so we can use strlcat */
if(uri->host != NULL) {
strlcat(space,uri->host,len);
if(uri->port != NULL) {
strlcat(space,":",len);
strlcat(space,uri->port,len);
}
}
}
}
void
NCD4_userpwd(NCURI* uri, char* space, size_t len)
{
if(space != NULL && len > 0) {
space[0] = '\0'; /* so we can use strlcat */
if(uri->user != NULL && uri->password != NULL) {
strlcat(space,uri->user,len);
strlcat(space,":",len);
strlcat(space,uri->password,len);
}
}
}
/**************************************************/
/* Error reporting */
int
NCD4_error(int code, const int line, const char* file, const char* fmt, ...)
{
va_list argv;
fprintf(stderr,"(%s:%d) ",file,line);
va_start(argv,fmt);
ncvlog(NCLOGERR,fmt,argv);
return code;
}
int
NCD4_errorNC(int code, const int line, const char* file)
{
return NCD4_error(code,line,file,nc_strerror(code));
}
NCD4offset*
NCD4_buildoffset(void* base, d4size_t limit)
{
NCD4offset* offset = (NCD4offset*)calloc(1,sizeof(NCD4offset));
assert(offset != NULL);
offset->base = base;
offset->limit = ((char*)base)+limit;
offset->offset = base;
return offset;
}
d4size_t
NCD4_getcounter(NCD4offset* p)
{
COUNTERTYPE v;
VERIFY(p,sizeof(v));
memcpy(&v,p->offset,sizeof(v));
return (d4size_t)v;
}
void
NCD4_incr(NCD4offset* p, d4size_t size)
{
VERIFY(p,size);
p->offset += size;
}
void
NCD4_decr(NCD4offset* p, d4size_t size)
{
VERIFY(p,size);
p->offset -= size;
}
void*
NCD4_getheader(void* p, NCD4HDR* hdr, int hostlittleendian)
{
unsigned char bytes[4];
memcpy(bytes,p,sizeof(bytes));
p = ((char*)p) + 4; /* on-the-wire hdr is 4 bytes */
/* assume header is network (big) order */
hdr->flags = bytes[0]; /* big endian => flags are in byte 0 */
hdr->flags &= NCD4_ALL_CHUNK_FLAGS; /* Ignore extraneous flags */
bytes[0] = 0; /* so we can do byte swap to get count */
if(hostlittleendian)
swapinline32(bytes); /* host is little endian */
hdr->count = *(unsigned int*)bytes; /* get count */
return p;
}
void
NCD4_reporterror(NCD4INFO* state)
{
NCD4meta* meta = state->substrate.metadata;
char* u = NULL;
if(meta == NULL) return;
u = ncuribuild(state->uri,NULL,NULL,NCURIALL);
fprintf(stderr,"***FAIL: url=%s httpcode=%d errmsg->\n%s\n",u,meta->error.httpcode,meta->error.message);
}