1 /*! \file
2 
3 Copyright 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
4 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014,
5 2015, 2016, 2017, 2018
6 University Corporation for Atmospheric Research/Unidata.
7 
8 See \ref copyright file for more info.
9 
10 */
11 
12 #ifndef NCTESTSERVER_H
13 #define NCTESTSERVER_H 1
14 
15 #include "config.h"
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <curl/curl.h>
20 #include "netcdf.h"
21 
22 #undef FINDTESTSERVER_DEBUG
23 
24 enum KIND {NOKIND, DAP2KIND, DAP4KIND, THREDDSKIND};
25 
26 #define MAXSERVERURL 4096
27 #define TIMEOUT 10 /*seconds*/
28 #define BUFSIZE 8192 /*bytes*/
29 #define MAXREMOTETESTSERVERS 4096
30 
31 #ifndef HAVE_CURLINFO_RESPONSE_CODE
32 #define CURLINFO_RESPONSE_CODE CURLINFO_HTTP_CODE
33 #endif
34 
35 static int ping(const char* url);
36 static int timedping(const char* url, long timeout);
37 
38 static char**
parseServers(const char * remotetestservers)39 parseServers(const char* remotetestservers)
40 {
41     char* rts;
42     char** servers = NULL;
43     char** list = NULL;
44     char* p;
45     char* svc;
46     char** l;
47     size_t rtslen = strlen(remotetestservers);
48 
49     /* Keep LGTM quiet */
50     if(rtslen > MAXREMOTETESTSERVERS) goto done;
51     list = (char**)malloc(sizeof(char*) * (int)(rtslen/2));
52     if(list == NULL) return NULL;
53     rts = strdup(remotetestservers);
54     if(rts == NULL) goto done;
55     l = list;
56     p = rts;
57     for(;;) {
58 	svc = p;
59 	p = strchr(svc,',');
60 	if(p != NULL) *p = '\0';
61 	*l++ = strdup(svc);
62 	if(p == NULL) break;
63 	p++;
64     }
65     *l = NULL;
66     servers = list;
67     list = NULL;
68 done:
69     if(rts) free(rts);
70     if(list) free(list);
71     return servers;
72 }
73 
74 /**
75 Given a partial suffix path and a specified
76 protocol, test if a request to any of the test
77 servers + path returns some kind of result.
78 This indicates that the server is up and running.
79 Return the complete url for the server plus the path.
80 */
81 
82 char*
nc_findtestserver(const char * path,const char * serverlist)83 nc_findtestserver(const char* path, const char* serverlist)
84 {
85     char** svclist;
86     char** svc;
87     char url[MAXSERVERURL];
88     char* match = NULL;
89     int reportsearch;
90 
91     if((svclist = parseServers(serverlist))==NULL) {
92 	fprintf(stderr,"cannot parse test server list: %s\n",serverlist);
93 	return NULL;
94     }
95     reportsearch = (getenv("NC_REPORTSEARCH") != NULL);
96     for(svc=svclist;*svc;svc++) {
97 	if(strlen(*svc) == 0)
98 	    goto done;
99         if(path == NULL) path = "";
100         if(strlen(path) > 0 && path[0] == '/')
101 	    path++;
102 	if(reportsearch)
103 	    fprintf(stderr,"nc_findtestserver: candidate=%s/%s: found=",*svc,path);
104 	/* Try https: first */
105         snprintf(url,MAXSERVERURL,"https://%s/%s",*svc,path);
106 	if(ping(url) == NC_NOERR) {
107 	    if(reportsearch) fprintf(stderr,"yes\n");
108 	    match = strdup(url);
109 	    goto done;
110 	}
111 	/* Try http: next */
112         snprintf(url,MAXSERVERURL,"http://%s/%s",*svc,path);
113 	if(ping(url) == NC_NOERR) {
114 	    if(reportsearch) fprintf(stderr,"yes\n");
115 	    match = strdup(url);
116 	    goto done;
117 	}
118 	if(reportsearch) fprintf(stderr,"no\n");
119     }
120 done:
121     if(reportsearch) fflush(stderr);
122     /* Free up the envv list of servers */
123     if(svclist != NULL) {
124         char** p;
125 	for(p=svclist;*p;p++)
126 	    free(*p);
127 	free(svclist);
128     }
129     return match;
130 }
131 
132 #define CERR(expr) if((cstat=(expr)) != CURLE_OK) goto done;
133 
134 struct Buffer {
135     char data[BUFSIZE];
136     size_t offset; /* into buffer */
137 };
138 
139 static size_t
WriteMemoryCallback(void * ptr,size_t size,size_t nmemb,void * data)140 WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
141 {
142     struct Buffer* buffer = (struct Buffer*)data;
143     size_t total = size * nmemb;
144     size_t canwrite = total; /* assume so */
145     if(total == 0) {
146         fprintf(stderr,"WriteMemoryCallback: zero sized chunk\n");
147 	goto done;
148     }
149     if((buffer->offset + total) > sizeof(buffer->data))
150 	canwrite = (sizeof(buffer->data) - buffer->offset); /* partial read */
151     if(canwrite > 0)
152         memcpy(&(buffer->data[buffer->offset]),ptr,canwrite);
153     buffer->offset += canwrite;
154 done:
155     return total; /* pretend we captured everything */
156 }
157 
158 /*
159 See if a server is responding.
160 Return NC_ECURL if the ping fails, NC_NOERR otherwise
161 */
162 
163 static int
ping(const char * url)164 ping(const char* url)
165 {
166     return timedping(url,TIMEOUT);
167 }
168 
169 static int
timedping(const char * url,long timeout)170 timedping(const char* url, long timeout)
171 {
172     int stat = NC_NOERR;
173     CURLcode cstat = CURLE_OK;
174     CURL* curl = NULL;
175     long http_code = 0;
176     struct Buffer data;
177 
178     /* Create a CURL instance */
179     curl = curl_easy_init();
180     if (curl == NULL) {cstat = CURLE_OUT_OF_MEMORY; goto done;}
181     CERR((curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1)));
182 
183     /* Use redirects */
184     CERR((curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 10L)));
185     CERR((curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L)));
186 
187     /* use very short timeouts: 10 seconds */
188     CERR((curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, (long)timeout)));
189     CERR((curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)timeout)));
190 
191     /* fail on HTTP 400 code errors */
192     CERR((curl_easy_setopt(curl, CURLOPT_FAILONERROR, (long)1)));
193 
194     /* Set the URL */
195     CERR((curl_easy_setopt(curl, CURLOPT_URL, (void*)url)));
196 
197     /* send all data to this function  */
198     CERR((curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback)));
199 
200     /* we pass our file to the callback function */
201     CERR((curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&data)));
202 
203     data.offset = 0;
204     memset(data.data,0,sizeof(data.data));
205 
206     CERR((curl_easy_perform(curl)));
207 
208     /* Don't trust curl to return an error when request gets 404 */
209     CERR((curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE, &http_code)));
210     if(http_code >= 400) {
211 	cstat = CURLE_HTTP_RETURNED_ERROR;
212 	goto done;
213     }
214 
215 done:
216     if(cstat != CURLE_OK) {
217 #ifdef FINDTESTSERVER_DEBUG
218         fprintf(stderr, "curl error: %s; url=%s\n",
219 		curl_easy_strerror(cstat),url);
220 #endif
221 	stat = NC_ECURL;
222     }
223     if (curl != NULL)
224         curl_easy_cleanup(curl);
225     return stat;
226 }
227 #endif /*NCTESTSERVER_H*/
228