netcdf-c/oc/ocinternal.c
2012-01-30 02:55:06 +00:00

586 lines
16 KiB
C

/* Copyright 2009, UCAR/Unidata and OPeNDAP, Inc.
See the COPYRIGHT file for more information. */
#include "config.h"
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "ocinternal.h"
#include "ocdebug.h"
#include "ocdata.h"
#include "occontent.h"
#include "occlientparams.h"
#include "rc.h"
#include "curlfunctions.h"
#include "http.h"
#include "read.h"
/* Note: TMPPATH must end in '/' */
#ifdef __CYGWIN__
#define TMPPATH1 "c:/temp/"
#define TMPPATH2 "./"
#else
#define TMPPATH1 "/tmp/"
#define TMPPATH2 "./"
#endif
/* Define default rc files and aliases*/
static char* rcfilenames[3] = {".dodsrc",".ocrc",NULL};
static int ocextractddsinmemory(OCstate*,OCtree*,int);
static int ocextractddsinfile(OCstate*,OCtree*,int);
static char* constraintescape(const char* url);
static OCerror createtempfile(OCstate*,OCtree*);
static int createtempfile1(char*,char**);
static void ocsetcurlproperties(OCstate*);
extern OCnode* makeunlimiteddimension(void);
#ifdef WIN32
#include <fcntl.h>
#define _S_IREAD 256
#define _S_IWRITE 128
#else
#include <sys/stat.h>
#endif
/* Global flags*/
int oc_curl_file_supported;
int oc_curl_https_supported;
int
ocinternalinitialize(void)
{
int stat = OC_NOERR;
/* Compute some xdr related flags */
xxdr_init();
oc_loginit();
/* Determine if this version of curl supports
"file://..." &/or "https://..." urls.
*/
{
const char* const* proto; /*weird*/
curl_version_info_data* curldata;
curldata = curl_version_info(CURLVERSION_NOW);
oc_curl_file_supported = 0;
oc_curl_https_supported = 0;
for(proto=curldata->protocols;*proto;proto++) {
if(strcmp("file",*proto)==0) {oc_curl_file_supported=1;break;}
if(strcmp("https",*proto)==0) {oc_curl_https_supported=1;break;}
}
if(ocdebug > 0) {
oc_log(LOGNOTE,"Curl file:// support = %d",oc_curl_file_supported);
oc_log(LOGNOTE,"Curl https:// support = %d",oc_curl_file_supported);
}
}
/* compile the .dodsrc, if any */
{
char* path = NULL;
char* homepath = NULL;
char** alias;
FILE* f = NULL;
/* locate the configuration files: . first in '.', then $HOME */
for(alias=rcfilenames;*alias;alias++) {
path = (char*)malloc(strlen("./")+strlen(*alias)+1);
if(path == NULL) return OC_ENOMEM;
strcpy(path,"./");
strcat(path,*alias);
/* see if file is readable */
f = fopen(path,"r");
if(f != NULL) break;
if(path != NULL) {free(path); path = NULL;} /* cleanup */
}
if(f == NULL) { /* try $HOME */
OCASSERT(path == NULL);
homepath = getenv("HOME");
if (homepath!= NULL) {
for(alias=rcfilenames;*alias;alias++) {
path = (char*)malloc(strlen(homepath)+1+strlen(*alias)+1);
if(path == NULL) return OC_ENOMEM;
strcpy(path,homepath);
strcat(path,"/");
strcat(path,*alias);
f = fopen(path,"r");
if(f != NULL) break;
if(path != NULL) {free(path); path=NULL;}
}
}
}
if(f == NULL) {
oc_log(LOGDBG,"Cannot find runtime configuration file");
} else {
OCASSERT(path != NULL);
fclose(f);
if(ocdebug > 1)
fprintf(stderr, "DODS RC file: %s\n", path);
if(ocdodsrc_read(*alias,path) == 0)
oc_log(LOGERR, "Error parsing %s\n",path);
}
if(path != NULL) free(path);
}
return OCTHROW(stat);
}
/**************************************************/
OCerror
ocopen(OCstate** statep, const char* url)
{
int stat = OC_NOERR;
OCstate * state = NULL;
OCURI* tmpurl = NULL;
CURL* curl = NULL; /* curl handle*/
if(!ocuriparse(url,&tmpurl)) {OCTHROWCHK(stat=OC_EBADURL); goto fail;}
stat = occurlopen(&curl);
if(stat != OC_NOERR) {OCTHROWCHK(stat); goto fail;}
state = (OCstate*)ocmalloc(sizeof(OCstate)); /* ocmalloc zeros memory*/
if(state == NULL) {OCTHROWCHK(stat=OC_ENOMEM); goto fail;}
/* Setup DAP state*/
state->magic = OCMAGIC;
state->curl = curl;
state->trees = oclistnew();
state->uri = tmpurl;
if(!ocuridecodeparams(state->uri)) {
oc_log(LOGWARN,"Could not parse client parameters");
}
state->packet = ocbytesnew();
ocbytessetalloc(state->packet,DFALTPACKETSIZE); /*initial reasonable size*/
/* set curl properties for this link */
ocsetcurlproperties(state);
/* Set up list to support reuse/reclamation of OCcontent objects. */
state->contentlist = NULL;
if(statep) *statep = state;
return OCTHROW(stat);
fail:
ocurifree(tmpurl);
if(state != NULL) ocfree(state);
if(curl != NULL) occurlclose(curl);
return OCTHROW(stat);
}
OCerror
ocfetchf(OCstate* state, const char* constraint, OCdxd kind, OCflags flags,
OCnode** rootp)
{
OCtree* tree = NULL;
OCnode* root = NULL;
OCerror stat = OC_NOERR;
tree = (OCtree*)ocmalloc(sizeof(OCtree));
MEMCHECK(tree,OC_ENOMEM);
memset((void*)tree,0,sizeof(OCtree));
tree->dxdclass = kind;
tree->state = state;
tree->constraint = constraintescape(constraint);
if(tree->constraint == NULL)
tree->constraint = nulldup(constraint);
/* Set curl properties: pwd, flags, proxies, ssl */
if((stat=ocset_user_password(state))!= OC_NOERR) goto fail;
if((stat=ocset_curl_flags(state)) != OC_NOERR) goto fail;
if((stat=ocset_proxy(state)) != OC_NOERR) goto fail;
if((stat=ocset_ssl(state)) != OC_NOERR) goto fail;
ocbytesclear(state->packet);
switch (kind) {
case OCDAS:
stat = readDAS(state,tree);
if(stat == OC_NOERR) {
tree->text = ocbytesdup(state->packet);
if(tree->text == NULL) stat = OC_EDAS;
}
break;
case OCDDS:
stat = readDDS(state,tree);
if(stat == OC_NOERR) {
tree->text = ocbytesdup(state->packet);
if(tree->text == NULL) stat = OC_EDDS;
}
break;
case OCDATADDS:
if((flags & OCINMEMORY) == 0) {/* store in file */
/* Create the datadds file immediately
so that DRNO can reference it*/
/* Make the tmp file*/
stat = createtempfile(state,tree);
if(stat) {OCTHROWCHK(stat); goto unwind;}
stat = readDATADDS(state,tree,flags);
if(stat == OC_NOERR) {
/* Separate the DDS from data and return the dds;
will modify packet */
stat = ocextractddsinfile(state,tree,flags);
}
} else { /*inmemory*/
stat = readDATADDS(state,tree,flags);
if(stat == OC_NOERR) {
/* Separate the DDS from data and return the dds;
will modify packet */
stat = ocextractddsinmemory(state,tree,flags);
}
}
break;
}/*switch*/
if(stat != OC_NOERR) {
/* Obtain any http code */
state->error.httpcode = ocfetchhttpcode(state->curl);
if(state->error.httpcode >= 400) {
oc_log(LOGWARN,"oc_open: Could not read url; http error = %l",state->error.httpcode);
} else {
oc_log(LOGWARN,"oc_open: Could not read url");
}
return OCTHROW(stat);
}
tree->nodes = NULL;
stat = DAPparse(state,tree,tree->text);
/* Check and report on an error return from the server */
if(stat == OC_EDAPSVC && state->error.code != NULL) {
oc_log(LOGERR,"oc_open: server error retrieving url: code=%s message=\"%s\"",
state->error.code,
(state->error.message?state->error.message:""));
}
if(stat) {OCTHROWCHK(stat); goto unwind;}
root = tree->root;
/* make sure */
tree->root = root;
root->tree = tree;
/* Verify the parse */
switch (kind) {
case OCDAS:
if(root->octype != OC_Attributeset)
{OCTHROWCHK(stat=OC_EDAS); goto unwind;}
break;
case OCDDS:
if(root->octype != OC_Dataset)
{OCTHROWCHK(stat=OC_EDDS); goto unwind;}
break;
case OCDATADDS:
if(root->octype != OC_Dataset)
{OCTHROWCHK(stat=OC_EDATADDS); goto unwind;}
/* Modify the tree kind */
tree->dxdclass = OCDATADDS;
break;
default: return OC_EINVAL;
}
if(kind != OCDAS) {
/* Process ocnodes to assign offsets and sizes where possible */
occomputeskipdata(state,root);
/* Process ocnodes to mark those that are cacheable */
ocmarkcacheable(state,root);
/* Process ocnodes to handle various semantic issues*/
occomputesemantics(tree->nodes);
}
/* Process ocnodes to compute name info*/
occomputefullnames(tree->root);
if(kind == OCDATADDS) {
if((flags & OCINMEMORY) == 0) {
tree->data.xdrs = xxdr_filecreate(tree->data.file,tree->data.bod);
} else {
/* Switch to zero based memory */
tree->data.xdrs
= xxdr_memcreate(tree->data.memory,tree->data.datasize,tree->data.bod);
}
MEMCHECK(tree->data.xdrs,OC_ENOMEM);
}
/* Put root into the state->trees list */
oclistpush(state->trees,(ocelem)root);
if(rootp) *rootp = root;
return stat;
unwind:
ocfreetree(tree);
fail:
return OCTHROW(stat);
}
void
occlose(OCstate* state)
{
unsigned int i;
if(state == NULL) return;
/* Warning: ocfreeroot will attempt to remove the root from state->trees */
/* Ok in this case because we are popping the root out of state->trees */
for(i=0;i<oclistlength(state->trees);i++) {
OCnode* root = (OCnode*)oclistpop(state->trees);
ocfreeroot(root);
}
oclistfree(state->trees);
ocurifree(state->uri);
ocbytesfree(state->packet);
ocfree(state->error.code);
ocfree(state->error.message);
if(state->contentlist != NULL) {
struct OCcontent* next;
struct OCcontent* curr = state->contentlist;
while(curr != NULL) {
next = curr->next;
ocfree(curr);
curr = next;
}
}
ocfree(state->curlflags.useragent);
ocfree(state->curlflags.cookiejar);
ocfree(state->curlflags.cookiefile);
ocfree(state->ssl.certificate);
ocfree(state->ssl.key);
ocfree(state->ssl.keypasswd);
ocfree(state->ssl.cainfo);
ocfree(state->ssl.capath);
ocfree(state->proxy.host);
ocfree(state->creds.username);
ocfree(state->creds.password);
if(state->curl != NULL) occurlclose(state->curl);
ocfree(state);
}
static OCerror
ocextractddsinmemory(OCstate* state, OCtree* tree, OCflags flags)
{
OCerror stat = OC_NOERR;
size_t ddslen, bod, bodfound;
/* Read until we find the separator (or EOF)*/
bodfound = findbod(state->packet,&bod,&ddslen);
if(!bodfound) {/* No BOD; pretend */
bod = tree->data.bod;
ddslen = tree->data.datasize;
}
tree->data.bod = bod;
tree->data.ddslen = ddslen;
/* copy out the dds */
if(ddslen > 0) {
tree->text = (char*)ocmalloc(ddslen+1);
memcpy((void*)tree->text,(void*)ocbytescontents(state->packet),ddslen);
tree->text[ddslen] = '\0';
} else
tree->text = NULL;
/* Extract the inmemory contents */
tree->data.memory = ocbytesextract(state->packet);
#ifdef IGNORE
/* guarantee the data part is on an 8 byte boundary */
if(tree->data.bod % 8 != 0) {
unsigned long count = tree->data.datasize - tree->data.bod;
memcpy(tree->xdrmemory,tree->xdrmemory+tree->data.bod,count);
tree->data.datasize = count;
tree->data.bod = 0;
tree->data.ddslen = 0;
}
#endif
if(tree->text == NULL) stat = OC_EDATADDS;
return OCTHROW(stat);
}
static OCerror
ocextractddsinfile(OCstate* state, OCtree* tree, OCflags flags)
{
OCerror stat = OC_NOERR;
size_t ddslen, bod, bodfound;
/* Read until we find the separator (or EOF)*/
ocbytesclear(state->packet);
rewind(tree->data.file);
do {
char chunk[1024];
size_t count;
/* read chunks of the file until we find the separator*/
count = fread(chunk,1,sizeof(chunk),tree->data.file);
if(count <= 0) break; /* EOF;*/
ocbytesappendn(state->packet,chunk,count);
bodfound = findbod(state->packet,&bod,&ddslen);
} while(!bodfound);
if(!bodfound) {/* No BOD; pretend */
bod = tree->data.bod;
ddslen = tree->data.datasize;
}
tree->data.bod = bod;
tree->data.ddslen = ddslen;
/* copy out the dds */
if(ddslen > 0) {
tree->text = (char*)ocmalloc(ddslen+1);
memcpy((void*)tree->text,(void*)ocbytescontents(state->packet),ddslen);
tree->text[ddslen] = '\0';
} else
tree->text = NULL;
/* reset the position of the tmp file*/
fseek(tree->data.file,tree->data.bod,SEEK_SET);
if(tree->text == NULL) stat = OC_EDATADDS;
return OCTHROW(stat);
}
static OCerror
createtempfile(OCstate* state, OCtree* tree)
{
int fd;
char* name = NULL;
fd = createtempfile1(TMPPATH1,&name);
if(fd < 0)
fd = createtempfile1(TMPPATH2,&name);
if(fd < 0) {
oc_log(LOGERR,"oc_open: attempt to open tmp file failed: %s",name);
return errno;
}
#ifdef OCDEBUG
oc_log(LOGNOTE,"oc_open: using tmp file: %s",name);
#endif
tree->data.filename = name; /* remember our tmp file name */
tree->data.file = fdopen(fd,"w+");
if(tree->data.file == NULL) return OC_EOPEN;
/* unlink the temp file so it will automatically be reclaimed */
if(ocdebug == 0) unlink(tree->data.filename);
return OC_NOERR;
}
int
createtempfile1(char* tmppath, char** tmpnamep)
{
int fd;
char* tmpname = NULL;
{
char* p;
char c;
tmpname = (char*)malloc(strlen(tmppath)+strlen("dataddsXXXXXX")+1);
if(tmpname == NULL) return -1;
strcpy(tmpname,tmppath);
#ifdef HAVE_MKSTEMP
strcat(tmpname,"dataddsXXXXXX");
#else
strcat(tmpname,"datadds");
{
long pid = -1;
char spid[7];
# ifdef HAVE_GETPID
pid = (long)getpid();
# endif
if(pid < 0) return -1;
sprintf(spid,"%06ld",pid);
strcat(tmpname,spid);
}
#endif
p = tmpname + strlen("datadds");
/* \', and '/' to '_' and '.' to '-'*/
for(;(c=*p);p++) {
if(c == '\\' || c == '/') {*p = '_';}
else if(c == '.') {*p = '-';}
}
/* Note Potential problem: old versions of this function
leave the file in mode 0666 instead of 0600 */
#ifdef HAVE_MKSTEMP
fd = mkstemp(tmpname);
#else
# ifdef WIN32
fd=open(tmpname,O_RDWR|O_BINARY|O_CREAT|O_EXCL|_O_SHORT_LIVED, _S_IREAD|_S_IWRITE);
# else
fd=open(tmpname,O_RDWR|O_CREAT|O_EXCL, S_IRWXU);
# endif
#endif
}
if(tmpname == NULL) return -1;
if(tmpnamep) *tmpnamep = tmpname;
return fd;
}
/* Allow these (non-alpha-numerics) to pass thru */
static char okchars[] = "&/:;,.=?@'\"<>{}!|\\^[]`~";
static char hexdigits[] = "0123456789abcdef";
/* Modify constraint to use %XX escapes */
static char*
constraintescape(const char* url)
{
size_t len;
char* p;
int c;
char* eurl;
if(url == NULL) return NULL;
len = strlen(url);
eurl = ocmalloc(1+3*len); /* worst case: c -> %xx */
MEMCHECK(eurl,NULL);
p = eurl;
*p = '\0';
while((c=*url++)) {
if(c >= '0' && c <= '9') {*p++ = c;}
else if(c >= 'a' && c <= 'z') {*p++ = c;}
else if(c >= 'A' && c <= 'Z') {*p++ = c;}
else if(strchr(okchars,c) != NULL) {*p++ = c;}
else {
*p++ = '%';
*p++ = hexdigits[(c & 0xf0)>>4];
*p++ = hexdigits[(c & 0xf)];
}
}
*p = '\0';
return eurl;
}
OCerror
ocupdatelastmodifieddata(OCstate* state)
{
OCerror status = OC_NOERR;
long lastmodified;
char* base = NULL;
base = ocuribuild(state->uri,NULL,NULL,OCURIENCODE);
status = ocfetchlastmodified(state->curl, base, &lastmodified);
free(base);
if(status == OC_NOERR) {
state->datalastmodified = lastmodified;
}
return status;
}
/*
Set curl properties for link based on rc files
*/
static void
ocsetcurlproperties(OCstate* state)
{
CURLcode cstat = CURLE_OK;
/* process the triple store wrt to this state */
if(ocdodsrc_process(state) != OC_NOERR) {
oc_log(LOGERR,"Malformed .opendaprc configuration file");
goto fail;
}
if(state->creds.username == NULL && state->creds.password == NULL) {
if(state->uri->user != NULL && state->uri->password != NULL) {
/* this overrides .dodsrc */
if(state->creds.password) free(state->creds.password);
state->creds.password = nulldup(state->uri->password);
if(state->creds.username) free(state->creds.username);
state->creds.username = nulldup(state->uri->user);
}
}
return;
fail:
if(cstat != CURLE_OK)
oc_log(LOGERR, "curl error: %s", curl_easy_strerror(cstat));
return;
}