mirror of
https://github.com/Unidata/netcdf-c.git
synced 2024-12-21 08:39:46 +08:00
591e6b2f6d
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.
457 lines
10 KiB
C
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 = |'| */
|
|
if(escaped == NULL) return NULL;
|
|
for(p=s,q=escaped;*p;p++) {
|
|
char c = *p;
|
|
switch (c) {
|
|
case '&': entity = "&"; break;
|
|
case '<': entity = "<"; break;
|
|
case '>': entity = ">"; break;
|
|
case '"': entity = """; break;
|
|
case '\'': entity = "'"; 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);
|
|
}
|