/src/netcdf-c/libdispatch/dhttp.c
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * |
4 | | * Read a range of data from a remote dataset. |
5 | | * |
6 | | * Copyright 2018 University Corporation for Atmospheric |
7 | | * Research/Unidata. See COPYRIGHT file for more info. |
8 | | */ |
9 | | |
10 | | #include "config.h" |
11 | | #include <stdlib.h> |
12 | | #include <stdio.h> |
13 | | #include <string.h> |
14 | | #ifdef HAVE_UNISTD_H |
15 | | #include <unistd.h> |
16 | | #endif |
17 | | |
18 | | #define CURL_DISABLE_TYPECHECK 1 |
19 | | #include <curl/curl.h> |
20 | | |
21 | | #include "netcdf.h" |
22 | | #include "nclog.h" |
23 | | #include "ncbytes.h" |
24 | | #include "nclist.h" |
25 | | #include "ncuri.h" |
26 | | #include "ncauth.h" |
27 | | |
28 | | #ifdef ENABLE_S3 |
29 | | #include "ncs3sdk.h" |
30 | | #endif |
31 | | #include "nchttp.h" |
32 | | |
33 | | #undef TRACE |
34 | | |
35 | 0 | #define CURLERR(e) reporterror(state,(e)) |
36 | | |
37 | | #if 0 |
38 | | static const char* LENGTH_ACCEPT[] = {"content-length","accept-ranges",NULL}; |
39 | | #endif |
40 | | static const char* CONTENTLENGTH[] = {"content-length",NULL}; |
41 | | |
42 | | /* Forward */ |
43 | | static int nc_http_set_method(NC_HTTP_STATE* state, HTTPMETHOD method); |
44 | | static int nc_http_set_response(NC_HTTP_STATE* state, NCbytes* buf); |
45 | | static int nc_http_set_payload(NC_HTTP_STATE* state, size_t len, void* payload); |
46 | | |
47 | | static int setupconn(NC_HTTP_STATE* state, const char* objecturl); |
48 | | static int execute(NC_HTTP_STATE* state); |
49 | | static int headerson(NC_HTTP_STATE* state, const char** which); |
50 | | static void headersoff(NC_HTTP_STATE* state); |
51 | | static void showerrors(NC_HTTP_STATE* state); |
52 | | static int reporterror(NC_HTTP_STATE* state, CURLcode cstat); |
53 | | static int lookupheader(NC_HTTP_STATE* state, const char* key, const char** valuep); |
54 | | static int my_trace(CURL *handle, curl_infotype type, char *data, size_t size,void *userp); |
55 | | |
56 | | #ifdef TRACE |
57 | | static void |
58 | | dbgflush() { |
59 | | fflush(stderr); |
60 | | fflush(stdout); |
61 | | } |
62 | | |
63 | | static void |
64 | | Trace(const char* fcn) |
65 | | { |
66 | | fprintf(stdout,"xxx: %s\n",fcn); |
67 | | dbgflush(); |
68 | | } |
69 | | #else |
70 | | #define dbgflush() |
71 | | #define Trace(fcn) |
72 | | #endif /*TRACE*/ |
73 | | |
74 | | /**************************************************/ |
75 | | |
76 | | /** |
77 | | @param statep return a pointer to an allocated NC_HTTP_STATE |
78 | | */ |
79 | | |
80 | | int |
81 | | nc_http_open(const char* url, NC_HTTP_STATE** statep) |
82 | 0 | { |
83 | 0 | return nc_http_open_verbose(url,0,statep); |
84 | 0 | } |
85 | | |
86 | | int |
87 | | nc_http_open_verbose(const char* path, int verbose, NC_HTTP_STATE** statep) |
88 | 0 | { |
89 | 0 | int stat = NC_NOERR; |
90 | 0 | NC_HTTP_STATE* state = NULL; |
91 | 0 | NCURI* uri = NULL; |
92 | |
|
93 | 0 | Trace("open"); |
94 | |
|
95 | 0 | ncuriparse(path,&uri); |
96 | 0 | if(uri == NULL) {stat = NCTHROW(NC_EURL); goto done;} |
97 | | |
98 | 0 | if((state = calloc(1,sizeof(NC_HTTP_STATE))) == NULL) |
99 | 0 | {stat = NCTHROW(NC_ENOMEM); goto done;} |
100 | 0 | state->path = strdup(path); |
101 | 0 | state->url = uri; uri = NULL; |
102 | 0 | state->format = (NC_iss3(state->url)?HTTPS3:HTTPCURL); |
103 | |
|
104 | 0 | switch (state->format) { |
105 | 0 | case HTTPCURL: { |
106 | | /* initialize curl*/ |
107 | 0 | state->curl.curl = curl_easy_init(); |
108 | 0 | if (state->curl.curl == NULL) {stat = NCTHROW(NC_ECURL); goto done;} |
109 | 0 | showerrors(state); |
110 | 0 | state->errmsg = state->curl.errbuf; |
111 | 0 | if(verbose) { |
112 | 0 | long onoff = 1; |
113 | 0 | CURLcode cstat = CURLE_OK; |
114 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_VERBOSE, onoff)); |
115 | 0 | if(cstat != CURLE_OK) {stat = NCTHROW(NC_ECURL); goto done;} |
116 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_DEBUGFUNCTION, my_trace)); |
117 | 0 | if(cstat != CURLE_OK) {stat = NCTHROW(NC_ECURL); goto done;} |
118 | 0 | } |
119 | 0 | } break; |
120 | | #ifdef ENABLE_S3 |
121 | | case HTTPS3: { |
122 | | if((state->s3.info = (NCS3INFO*)calloc(1,sizeof(NCS3INFO)))==NULL) |
123 | | {stat = NCTHROW(NC_ENOMEM); goto done;} |
124 | | if((stat = NC_s3urlprocess(state->url,state->s3.info))) goto done; |
125 | | if((state->s3.s3client = NC_s3sdkcreateclient(state->s3.info))==NULL) |
126 | | {stat = NCTHROW(NC_EURL); goto done;} |
127 | | } break; |
128 | | #endif |
129 | 0 | default: return NCTHROW(NC_ENOTBUILT); |
130 | 0 | } |
131 | 0 | stat = nc_http_reset(state); |
132 | 0 | if(statep) {*statep = state; state = NULL;} |
133 | 0 | done: |
134 | 0 | if(state) nc_http_close(state); |
135 | 0 | dbgflush(); |
136 | 0 | return NCTHROW(stat); |
137 | 0 | } |
138 | | |
139 | | int |
140 | | nc_http_close(NC_HTTP_STATE* state) |
141 | 0 | { |
142 | 0 | int stat = NC_NOERR; |
143 | |
|
144 | 0 | Trace("close"); |
145 | |
|
146 | 0 | if(state == NULL) return NCTHROW(stat); |
147 | 0 | switch (state->format) { |
148 | 0 | case HTTPCURL: |
149 | 0 | if(state->curl.curl != NULL) |
150 | 0 | (void)curl_easy_cleanup(state->curl.curl); |
151 | 0 | nclistfreeall(state->curl.response.headset); state->curl.response.headset = NULL; |
152 | 0 | nclistfreeall(state->curl.response.headers); state->curl.response.headers = NULL; |
153 | 0 | ncbytesfree(state->curl.response.buf); |
154 | 0 | nclistfreeall(state->curl.request.headers); state->curl.request.headers = NULL; |
155 | 0 | break; |
156 | | #ifdef ENABLE_S3 |
157 | | case HTTPS3: { |
158 | | if(state->s3.s3client) |
159 | | NC_s3sdkclose(state->s3.s3client, state->s3.info, 0, NULL); |
160 | | NC_s3clear(state->s3.info); |
161 | | nullfree(state->s3.info); |
162 | | state->s3.s3client = NULL; |
163 | | } break; |
164 | | #endif |
165 | 0 | default: stat = NCTHROW(NC_ENOTBUILT); goto done; |
166 | 0 | } |
167 | 0 | nullfree(state->path); |
168 | 0 | ncurifree(state->url); |
169 | 0 | nullfree(state); |
170 | 0 | done: |
171 | 0 | dbgflush(); |
172 | 0 | return NCTHROW(stat); |
173 | 0 | } |
174 | | |
175 | | /* Reset after a request */ |
176 | | int |
177 | | nc_http_reset(NC_HTTP_STATE* state) |
178 | 0 | { |
179 | 0 | int stat = NC_NOERR; |
180 | 0 | CURLcode cstat = CURLE_OK; |
181 | |
|
182 | 0 | switch (state->format) { |
183 | 0 | case HTTPCURL: |
184 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_HTTPGET, 1L)); |
185 | 0 | if(cstat != CURLE_OK) {stat = NCTHROW(NC_ECURL); goto done;} |
186 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_NOBODY, 0L)); |
187 | 0 | if(cstat != CURLE_OK) {stat = NCTHROW(NC_ECURL); goto done;} |
188 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_UPLOAD, 0L)); |
189 | 0 | if(cstat != CURLE_OK) {stat = NCTHROW(NC_ECURL); goto done;} |
190 | 0 | cstat = curl_easy_setopt(state->curl.curl, CURLOPT_CUSTOMREQUEST, NULL); |
191 | 0 | if(cstat != CURLE_OK) {stat = NCTHROW(NC_ECURL); goto done;} |
192 | 0 | cstat = curl_easy_setopt(state->curl.curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)-1); |
193 | 0 | if(cstat != CURLE_OK) {stat = NCTHROW(NC_ECURL); goto done;} |
194 | 0 | state->curl.request.method = HTTPGET; |
195 | 0 | (void)CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_WRITEFUNCTION, NULL)); |
196 | 0 | (void)CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_WRITEDATA, NULL)); |
197 | 0 | (void)CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_READFUNCTION, NULL)); |
198 | 0 | (void)CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_READDATA, NULL)); |
199 | 0 | headersoff(state); |
200 | 0 | break; |
201 | | #ifdef ENABLE_S3 |
202 | | case HTTPS3: |
203 | | break; /* Done automatically */ |
204 | | #endif |
205 | 0 | default: stat = NCTHROW(NC_ENOTBUILT); goto done; |
206 | 0 | } |
207 | 0 | done: |
208 | 0 | return NCTHROW(stat); |
209 | 0 | } |
210 | | |
211 | | /**************************************************/ |
212 | | /**************************************************/ |
213 | | /** |
214 | | @param state state handle |
215 | | @param objecturl to read |
216 | | @param start starting offset |
217 | | @param count number of bytes to read |
218 | | @param buf store read data here -- caller must allocate and free |
219 | | */ |
220 | | |
221 | | int |
222 | | nc_http_read(NC_HTTP_STATE* state, size64_t start, size64_t count, NCbytes* buf) |
223 | 0 | { |
224 | 0 | int stat = NC_NOERR; |
225 | 0 | char range[64]; |
226 | 0 | CURLcode cstat = CURLE_OK; |
227 | |
|
228 | 0 | Trace("read"); |
229 | |
|
230 | 0 | if(count == 0) |
231 | 0 | goto done; /* do not attempt to read */ |
232 | | |
233 | 0 | switch (state->format) { |
234 | 0 | case HTTPCURL: |
235 | 0 | if((stat = nc_http_set_response(state,buf))) goto fail; |
236 | 0 | if((stat = setupconn(state,state->path))) |
237 | 0 | goto fail; |
238 | | |
239 | | /* Set to read byte range */ |
240 | 0 | snprintf(range,sizeof(range),"%ld-%ld",(long)start,(long)((start+count)-1)); |
241 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_RANGE, range)); |
242 | 0 | if(cstat != CURLE_OK) |
243 | 0 | {stat = NCTHROW(NC_ECURL); goto done;} |
244 | | |
245 | 0 | if((stat = execute(state))) |
246 | 0 | goto done; |
247 | 0 | break; |
248 | | #ifdef ENABLE_S3 |
249 | | case HTTPS3: { |
250 | | /* Make sure buf has enough space allocated */ |
251 | | ncbytessetalloc(buf,count); |
252 | | ncbytessetlength(buf,count); |
253 | | if((stat = NC_s3sdkread(state->s3.s3client, |
254 | | state->s3.info->bucket, |
255 | | state->s3.info->rootkey, |
256 | | start, |
257 | | count, |
258 | | ncbytescontents(buf), |
259 | | &state->errmsg))) goto done; |
260 | | } break; |
261 | | #endif |
262 | 0 | default: stat = NCTHROW(NC_ENOTBUILT); goto done; |
263 | 0 | } |
264 | 0 | done: |
265 | 0 | nc_http_reset(state); |
266 | 0 | if(state->format == HTTPCURL) |
267 | 0 | state->curl.response.buf = NULL; |
268 | 0 | dbgflush(); |
269 | 0 | return NCTHROW(stat); |
270 | | |
271 | 0 | fail: |
272 | 0 | stat = NCTHROW(NC_ECURL); |
273 | 0 | goto done; |
274 | 0 | } |
275 | | |
276 | | /** |
277 | | @param state state handle |
278 | | @param objectpath to write |
279 | | @param payload send as body of a PUT |
280 | | */ |
281 | | |
282 | | int |
283 | | nc_http_write(NC_HTTP_STATE* state, NCbytes* payload) |
284 | 0 | { |
285 | 0 | int stat = NC_NOERR; |
286 | |
|
287 | 0 | Trace("write"); |
288 | |
|
289 | 0 | if(payload == NULL || ncbyteslength(payload) == 0) goto done; |
290 | | |
291 | 0 | switch (state->format) { |
292 | 0 | case HTTPCURL: |
293 | 0 | if((stat = nc_http_set_payload(state,ncbyteslength(payload),ncbytescontents(payload)))) goto fail; |
294 | 0 | if((stat = nc_http_set_method(state,HTTPPUT))) goto fail; |
295 | 0 | if((stat = setupconn(state,state->path))) goto fail; |
296 | 0 | if((stat = execute(state))) |
297 | 0 | goto done; |
298 | 0 | break; |
299 | | #ifdef ENABLE_S3 |
300 | | case HTTPS3: |
301 | | if((stat = NC_s3sdkwriteobject(state->s3.s3client, |
302 | | state->s3.info->bucket, |
303 | | state->s3.info->rootkey, |
304 | | ncbyteslength(payload), |
305 | | ncbytescontents(payload), |
306 | | &state->errmsg))) goto done; |
307 | | break; |
308 | | #endif |
309 | 0 | default: stat = NCTHROW(NC_ENOTBUILT); goto done; |
310 | 0 | } |
311 | 0 | done: |
312 | 0 | nc_http_reset(state); |
313 | 0 | return NCTHROW(stat); |
314 | | |
315 | 0 | fail: |
316 | 0 | stat = NCTHROW(NC_ECURL); |
317 | 0 | goto done; |
318 | 0 | } |
319 | | |
320 | | /** |
321 | | Return length of an object. |
322 | | Assume URL etc has already been set. |
323 | | @param curl curl handle |
324 | | */ |
325 | | |
326 | | int |
327 | | nc_http_size(NC_HTTP_STATE* state, long long* sizep) |
328 | 0 | { |
329 | 0 | int stat = NC_NOERR; |
330 | 0 | const char* hdr = NULL; |
331 | |
|
332 | 0 | Trace("size"); |
333 | 0 | if(sizep == NULL) |
334 | 0 | goto done; /* do not attempt to read */ |
335 | | |
336 | 0 | switch (state->format) { |
337 | 0 | case HTTPCURL: |
338 | 0 | if((stat = nc_http_set_method(state,HTTPHEAD))) goto done; |
339 | 0 | if((stat = setupconn(state,state->path))) |
340 | 0 | goto done; |
341 | | /* Make sure we get headers */ |
342 | 0 | if((stat = headerson(state,CONTENTLENGTH))) goto done; |
343 | | |
344 | 0 | state->httpcode = 200; |
345 | 0 | if((stat = execute(state))) |
346 | 0 | goto done; |
347 | | |
348 | 0 | if(nclistlength(state->curl.response.headers) == 0) |
349 | 0 | {stat = NCTHROW(NC_EURL); goto done;} |
350 | | |
351 | | /* Get the content length header */ |
352 | 0 | if((stat = lookupheader(state,"content-length",&hdr))==NC_NOERR) |
353 | 0 | sscanf(hdr,"%llu",sizep); |
354 | 0 | break; |
355 | | #ifdef ENABLE_S3 |
356 | | case HTTPS3: { |
357 | | size64_t len = 0; |
358 | | if((stat = NC_s3sdkinfo(state->s3.s3client,state->s3.info->bucket,state->s3.info->rootkey,&len,&state->errmsg))) goto done; |
359 | | if(sizep) *sizep = len; |
360 | | } break; |
361 | | #endif |
362 | 0 | default: stat = NCTHROW(NC_ENOTBUILT); goto done; |
363 | 0 | } |
364 | 0 | done: |
365 | 0 | nc_http_reset(state); |
366 | 0 | if(state->format == HTTPCURL) |
367 | 0 | headersoff(state); |
368 | 0 | dbgflush(); |
369 | 0 | return NCTHROW(stat); |
370 | 0 | } |
371 | | |
372 | | /**************************************************/ |
373 | | /* Set misc parameters */ |
374 | | |
375 | | static int |
376 | | nc_http_set_method(NC_HTTP_STATE* state, HTTPMETHOD method) |
377 | 0 | { |
378 | 0 | int stat = NC_NOERR; |
379 | 0 | CURLcode cstat = CURLE_OK; |
380 | 0 | switch (method) { |
381 | 0 | case HTTPGET: |
382 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_HTTPGET, 1L)); |
383 | 0 | break; |
384 | 0 | case HTTPHEAD: |
385 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_HTTPGET, 1L)); |
386 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_NOBODY, 1L)); |
387 | 0 | break; |
388 | 0 | case HTTPPUT: |
389 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_UPLOAD, 1L)); |
390 | 0 | break; |
391 | 0 | case HTTPDELETE: |
392 | 0 | cstat = curl_easy_setopt(state->curl.curl, CURLOPT_CUSTOMREQUEST, "DELETE"); |
393 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_NOBODY, 1L)); |
394 | 0 | break; |
395 | 0 | default: stat = NCTHROW(NC_EINVAL); break; |
396 | 0 | } |
397 | 0 | if(cstat != CURLE_OK) {stat = NCTHROW(NC_ECURL); goto done;} |
398 | 0 | state->curl.request.method = method; |
399 | 0 | done: |
400 | 0 | return NCTHROW(stat); |
401 | 0 | } |
402 | | |
403 | | static int |
404 | | nc_http_set_payload(NC_HTTP_STATE* state, size_t size, void* payload) |
405 | 0 | { |
406 | 0 | int stat = NC_NOERR; |
407 | 0 | state->curl.request.payloadsize = size; |
408 | 0 | state->curl.request.payload = payload; |
409 | 0 | state->curl.request.payloadpos = 0; |
410 | 0 | return NCTHROW(stat); |
411 | 0 | } |
412 | | |
413 | | static int |
414 | | nc_http_set_response(NC_HTTP_STATE* state, NCbytes* buf) |
415 | 0 | { |
416 | 0 | int stat = NC_NOERR; |
417 | 0 | state->curl.response.buf = buf; |
418 | 0 | return NCTHROW(stat); |
419 | 0 | } |
420 | | |
421 | | #if 0 |
422 | | static int |
423 | | nc_http_response_headset(NC_HTTP_STATE* state, const NClist* keys) |
424 | | { |
425 | | int i; |
426 | | if(keys == NULL) return NC_NOERR; |
427 | | if(state->curl.response.headset == NULL) |
428 | | state->curl.response.headset = nclistnew(); |
429 | | for(i=0;i<nclistlength(keys);i++) { |
430 | | const char* key = (const char*)nclistget(keys,i); |
431 | | if(!nclistmatch(state->curl.response.headset,key,0)) /* remove duplicates */ |
432 | | nclistpush(state->curl.response.headset,strdup(key)); |
433 | | } |
434 | | return NC_NOERR; |
435 | | } |
436 | | |
437 | | static int |
438 | | nc_http_response_headers(NC_HTTP_STATE* state, NClist** headersp) |
439 | | { |
440 | | NClist* headers = NULL; |
441 | | if(headersp != NULL) { |
442 | | headers = nclistclone(state->curl.response.headers,1); |
443 | | *headersp = headers; headers = NULL; |
444 | | } |
445 | | return NC_NOERR; |
446 | | } |
447 | | |
448 | | static int |
449 | | nc_http_request_setheaders(NC_HTTP_STATE* state, const NClist* headers) |
450 | | { |
451 | | nclistfreeall(state->curl.request.headers); |
452 | | state->curl.request.headers = nclistclone(headers,1); |
453 | | return NC_NOERR; |
454 | | } |
455 | | #endif |
456 | | |
457 | | /**************************************************/ |
458 | | |
459 | | static size_t |
460 | | ReadMemoryCallback(char* buffer, size_t size, size_t nmemb, void *data) |
461 | 0 | { |
462 | 0 | NC_HTTP_STATE* state = data; |
463 | 0 | size_t transfersize = size * nmemb; |
464 | 0 | size_t avail = (state->curl.request.payloadsize - state->curl.request.payloadpos); |
465 | |
|
466 | 0 | Trace("ReadMemoryCallback"); |
467 | 0 | if(transfersize == 0) |
468 | 0 | nclog(NCLOGWARN,"ReadMemoryCallback: zero sized buffer"); |
469 | 0 | if(transfersize > avail) transfersize = avail; |
470 | 0 | memcpy(buffer,((char*)state->curl.request.payload)+state->curl.request.payloadpos,transfersize); |
471 | 0 | state->curl.request.payloadpos += transfersize; |
472 | 0 | return transfersize; |
473 | 0 | } |
474 | | |
475 | | static size_t |
476 | | WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data) |
477 | 0 | { |
478 | 0 | NC_HTTP_STATE* state = data; |
479 | 0 | size_t realsize = size * nmemb; |
480 | |
|
481 | 0 | Trace("WriteMemoryCallback"); |
482 | 0 | if(realsize == 0) |
483 | 0 | nclog(NCLOGWARN,"WriteMemoryCallback: zero sized chunk"); |
484 | 0 | ncbytesappendn(state->curl.response.buf, ptr, realsize); |
485 | 0 | return realsize; |
486 | 0 | } |
487 | | |
488 | | static void |
489 | | trim(char* s) |
490 | 0 | { |
491 | 0 | size_t l = strlen(s); |
492 | 0 | char* p = s; |
493 | 0 | char* q = s + l; |
494 | 0 | if(l == 0) return; |
495 | 0 | q--; /* point to last char of string */ |
496 | | /* Walk backward to first non-whitespace */ |
497 | 0 | for(;q > p;q--) { |
498 | 0 | if(*q > ' ') break; /* found last non-whitespace */ |
499 | 0 | } |
500 | | /* invariant: p == q || *q > ' ' */ |
501 | 0 | if(p == q) /* string is all whitespace */ |
502 | 0 | {*p = '\0';} |
503 | 0 | else {/* *q is last non-whitespace */ |
504 | 0 | q++; /* point to actual whitespace */ |
505 | 0 | *q = '\0'; |
506 | 0 | } |
507 | | /* Ok, skip past leading whitespace */ |
508 | 0 | for(p=s;*p;p++) {if(*p > ' ') break;} |
509 | | /* invariant: *p == '\0' || *p > ' ' */ |
510 | 0 | if(*p == 0) return; /* no leading whitespace */ |
511 | | /* Ok, overwrite any leading whitespace */ |
512 | 0 | for(q=s;*p;) {*q++ = *p++;} |
513 | 0 | *q = '\0'; |
514 | 0 | return; |
515 | 0 | } |
516 | | |
517 | | static size_t |
518 | | HeaderCallback(char *buffer, size_t size, size_t nitems, void *data) |
519 | 0 | { |
520 | 0 | size_t realsize = size * nitems; |
521 | 0 | char* name = NULL; |
522 | 0 | char* value = NULL; |
523 | 0 | char* p = NULL; |
524 | 0 | size_t i; |
525 | 0 | int havecolon; |
526 | 0 | NC_HTTP_STATE* state = data; |
527 | 0 | int match; |
528 | 0 | const char* hdr; |
529 | |
|
530 | 0 | Trace("HeaderCallback"); |
531 | 0 | if(realsize == 0) |
532 | 0 | nclog(NCLOGWARN,"HeaderCallback: zero sized chunk"); |
533 | 0 | i = 0; |
534 | | /* Look for colon separator */ |
535 | 0 | for(p=buffer;(i < realsize) && (*p != ':');p++,i++); |
536 | 0 | havecolon = (i < realsize); |
537 | 0 | if(i == 0) |
538 | 0 | nclog(NCLOGWARN,"HeaderCallback: malformed header: %s",buffer); |
539 | 0 | name = malloc(i+1); |
540 | 0 | memcpy(name,buffer,i); |
541 | 0 | name[i] = '\0'; |
542 | 0 | if(state->curl.response.headset != NULL) { |
543 | 0 | for(match=0,i=0;i<nclistlength(state->curl.response.headset);i++) { |
544 | 0 | hdr = (const char*)nclistget(state->curl.response.headset,i); |
545 | 0 | if(strcasecmp(hdr,name)==0) {match = 1; break;} |
546 | 0 | } |
547 | 0 | if(!match) goto done; |
548 | 0 | } |
549 | | /* Capture this header */ |
550 | 0 | value = NULL; |
551 | 0 | if(havecolon) { |
552 | 0 | size_t vlen = (realsize - i); |
553 | 0 | value = malloc(vlen+1); |
554 | 0 | p++; /* skip colon */ |
555 | 0 | memcpy(value,p,vlen); |
556 | 0 | value[vlen] = '\0'; |
557 | 0 | trim(value); |
558 | 0 | } |
559 | 0 | if(state->curl.response.headers == NULL) |
560 | 0 | state->curl.response.headers = nclistnew(); |
561 | 0 | nclistpush(state->curl.response.headers,name); |
562 | 0 | name = NULL; |
563 | 0 | if(value == NULL) value = strdup(""); |
564 | 0 | nclistpush(state->curl.response.headers,value); |
565 | 0 | value = NULL; |
566 | 0 | done: |
567 | 0 | nullfree(name); |
568 | 0 | return realsize; |
569 | 0 | } |
570 | | |
571 | | static int |
572 | | setupconn(NC_HTTP_STATE* state, const char* objecturl) |
573 | 0 | { |
574 | 0 | int stat = NC_NOERR; |
575 | 0 | CURLcode cstat = CURLE_OK; |
576 | |
|
577 | 0 | if(objecturl != NULL) { |
578 | | /* Set the URL */ |
579 | | #ifdef TRACE |
580 | | fprintf(stderr,"curl.setup: url |%s|\n",objecturl); |
581 | | #endif |
582 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_URL, (void*)objecturl)); |
583 | 0 | if (cstat != CURLE_OK) goto fail; |
584 | 0 | } |
585 | | /* Set options */ |
586 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_TIMEOUT, 100)); /* 30sec timeout*/ |
587 | 0 | if (cstat != CURLE_OK) goto fail; |
588 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_CONNECTTIMEOUT, 100)); |
589 | 0 | if (cstat != CURLE_OK) goto fail; |
590 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_NOPROGRESS, 1)); |
591 | 0 | if (cstat != CURLE_OK) goto fail; |
592 | 0 | cstat = curl_easy_setopt(state->curl.curl, CURLOPT_FOLLOWLOCATION, 1); |
593 | 0 | if (cstat != CURLE_OK) goto fail; |
594 | | |
595 | | /* Pull some values from .rc tables */ |
596 | 0 | { |
597 | 0 | NCURI* uri = NULL; |
598 | 0 | char* hostport = NULL; |
599 | 0 | char* value = NULL; |
600 | 0 | ncuriparse(objecturl,&uri); |
601 | 0 | if(uri == NULL) goto fail; |
602 | 0 | hostport = NC_combinehostport(uri); |
603 | 0 | ncurifree(uri); uri = NULL; |
604 | 0 | value = NC_rclookup("HTTP.SSL.CAINFO",hostport,NULL); |
605 | 0 | nullfree(hostport); hostport = NULL; |
606 | 0 | if(value == NULL) |
607 | 0 | value = NC_rclookup("HTTP.SSL.CAINFO",NULL,NULL); |
608 | 0 | if(value != NULL) { |
609 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_CAINFO, value)); |
610 | 0 | if (cstat != CURLE_OK) goto fail; |
611 | 0 | } |
612 | 0 | } |
613 | | |
614 | | /* Set the method */ |
615 | 0 | if((stat = nc_http_set_method(state,state->curl.request.method))) goto done; |
616 | | |
617 | 0 | if(state->curl.response.buf) { |
618 | | /* send all data to this function */ |
619 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback)); |
620 | 0 | if (cstat != CURLE_OK) goto fail; |
621 | | /* Set argument for WriteMemoryCallback */ |
622 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_WRITEDATA, (void*)state)); |
623 | 0 | if (cstat != CURLE_OK) goto fail; |
624 | 0 | } else {/* turn off data capture */ |
625 | 0 | (void)CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_WRITEFUNCTION, NULL)); |
626 | 0 | (void)CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_WRITEDATA, NULL)); |
627 | 0 | } |
628 | 0 | if(state->curl.request.payloadsize > 0) { |
629 | 0 | state->curl.request.payloadpos = 0; /* track reading */ |
630 | | /* send all data to this function */ |
631 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_READFUNCTION, ReadMemoryCallback)); |
632 | 0 | if (cstat != CURLE_OK) goto fail; |
633 | | /* Set argument for ReadMemoryCallback */ |
634 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_READDATA, (void*)state)); |
635 | 0 | if (cstat != CURLE_OK) goto fail; |
636 | 0 | } else {/* turn off data capture */ |
637 | 0 | (void)CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_READFUNCTION, NULL)); |
638 | 0 | (void)CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_READDATA, NULL)); |
639 | 0 | } |
640 | | |
641 | | /* Do method specific actions */ |
642 | 0 | switch(state->curl.request.method) { |
643 | 0 | case HTTPPUT: |
644 | 0 | if(state->curl.request.payloadsize > 0) |
645 | 0 | cstat = curl_easy_setopt(state->curl.curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)state->curl.request.payloadsize); |
646 | 0 | break; |
647 | 0 | default: break; |
648 | 0 | } |
649 | | |
650 | 0 | done: |
651 | 0 | return NCTHROW(stat); |
652 | 0 | fail: |
653 | | /* Turn off header capture */ |
654 | 0 | headersoff(state); |
655 | 0 | stat = NCTHROW(NC_ECURL); |
656 | 0 | goto done; |
657 | 0 | } |
658 | | |
659 | | static int |
660 | | execute(NC_HTTP_STATE* state) |
661 | 0 | { |
662 | 0 | int stat = NC_NOERR; |
663 | 0 | CURLcode cstat = CURLE_OK; |
664 | |
|
665 | 0 | cstat = CURLERR(curl_easy_perform(state->curl.curl)); |
666 | 0 | if(cstat != CURLE_OK) goto fail; |
667 | | |
668 | 0 | cstat = CURLERR(curl_easy_getinfo(state->curl.curl,CURLINFO_RESPONSE_CODE,&state->httpcode)); |
669 | 0 | if(cstat != CURLE_OK) state->httpcode = 0; |
670 | |
|
671 | 0 | done: |
672 | 0 | return NCTHROW(stat); |
673 | 0 | fail: |
674 | 0 | stat = NCTHROW(NC_ECURL); |
675 | 0 | goto done; |
676 | 0 | } |
677 | | |
678 | | static int |
679 | | headerson(NC_HTTP_STATE* state, const char** headset) |
680 | 0 | { |
681 | 0 | int stat = NC_NOERR; |
682 | 0 | CURLcode cstat = CURLE_OK; |
683 | 0 | const char** p; |
684 | |
|
685 | 0 | if(state->curl.response.headers != NULL) |
686 | 0 | nclistfreeall(state->curl.response.headers); |
687 | 0 | state->curl.response.headers = nclistnew(); |
688 | 0 | if(state->curl.response.headset != NULL) |
689 | 0 | nclistfreeall(state->curl.response.headset); |
690 | 0 | state->curl.response.headset = nclistnew(); |
691 | 0 | for(p=headset;*p;p++) |
692 | 0 | nclistpush(state->curl.response.headset,strdup(*p)); |
693 | |
|
694 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_HEADERFUNCTION, HeaderCallback)); |
695 | 0 | if(cstat != CURLE_OK) goto fail; |
696 | 0 | cstat = CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_HEADERDATA, (void*)state)); |
697 | 0 | if (cstat != CURLE_OK) goto fail; |
698 | | |
699 | 0 | done: |
700 | 0 | return NCTHROW(stat); |
701 | 0 | fail: |
702 | 0 | stat = NCTHROW(NC_ECURL); |
703 | 0 | goto done; |
704 | 0 | } |
705 | | |
706 | | static void |
707 | | headersoff(NC_HTTP_STATE* state) |
708 | 0 | { |
709 | 0 | nclistfreeall(state->curl.response.headers); |
710 | 0 | state->curl.response.headers = NULL; |
711 | 0 | (void)CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_HEADERFUNCTION, NULL)); |
712 | 0 | (void)CURLERR(curl_easy_setopt(state->curl.curl, CURLOPT_HEADERDATA, NULL)); |
713 | 0 | } |
714 | | |
715 | | static int |
716 | | lookupheader(NC_HTTP_STATE* state, const char* key, const char** valuep) |
717 | 0 | { |
718 | 0 | int i; |
719 | 0 | const char* value = NULL; |
720 | | /* Get the content length header */ |
721 | 0 | for(i=0;i<nclistlength(state->curl.response.headers);i+=2) { |
722 | 0 | char* s = nclistget(state->curl.response.headers,i); |
723 | 0 | if(strcasecmp(s,key)==0) { |
724 | 0 | value = nclistget(state->curl.response.headers,i+1); |
725 | 0 | break; |
726 | 0 | } |
727 | 0 | } |
728 | 0 | if(value == NULL) return NCTHROW(NC_ENOOBJECT); |
729 | 0 | if(valuep) |
730 | 0 | *valuep = value; |
731 | 0 | return NC_NOERR; |
732 | 0 | } |
733 | | |
734 | | static void |
735 | | showerrors(NC_HTTP_STATE* state) |
736 | 0 | { |
737 | 0 | (void)curl_easy_setopt(state->curl.curl, CURLOPT_ERRORBUFFER, state->curl.errbuf); |
738 | 0 | } |
739 | | |
740 | | static int |
741 | | reporterror(NC_HTTP_STATE* state, CURLcode cstat) |
742 | 0 | { |
743 | 0 | if(cstat != CURLE_OK) |
744 | 0 | fprintf(stderr,"curlcode: (%d)%s : %s\n", |
745 | 0 | cstat,curl_easy_strerror(cstat), |
746 | 0 | state->errmsg?state->errmsg:"?"); |
747 | 0 | return cstat; |
748 | 0 | } |
749 | | |
750 | | static |
751 | | void dump(const char *text, FILE *stream, unsigned char *ptr, size_t size) |
752 | 0 | { |
753 | 0 | size_t i; |
754 | 0 | size_t c; |
755 | 0 | unsigned int width=0x10; |
756 | | |
757 | 0 | fprintf(stream, "%s, %10.10ld bytes (0x%8.8lx)\n", |
758 | 0 | text, (long)size, (long)size); |
759 | | |
760 | 0 | for(i=0; i<size; i+= width) { |
761 | 0 | fprintf(stream, "%4.4lx: ", (long)i); |
762 | | |
763 | | /* show hex to the left */ |
764 | 0 | for(c = 0; c < width; c++) { |
765 | 0 | if(i+c < size) |
766 | 0 | fprintf(stream, "%02x ", ptr[i+c]); |
767 | 0 | else |
768 | 0 | fputs(" ", stream); |
769 | 0 | } |
770 | | |
771 | | /* show data on the right */ |
772 | 0 | for(c = 0; (c < width) && (i+c < size); c++) { |
773 | 0 | char x = (ptr[i+c] >= 0x20 && ptr[i+c] < 0x80) ? ptr[i+c] : '.'; |
774 | 0 | fputc(x, stream); |
775 | 0 | } |
776 | | |
777 | 0 | fputc('\n', stream); /* newline */ |
778 | 0 | } |
779 | 0 | } |
780 | | |
781 | | static int |
782 | | my_trace(CURL *handle, curl_infotype type, char *data, size_t size,void *userp) |
783 | 0 | { |
784 | 0 | const char *text; |
785 | 0 | (void)handle; /* prevent compiler warning */ |
786 | 0 | (void)userp; |
787 | | |
788 | 0 | switch (type) { |
789 | 0 | case CURLINFO_TEXT: |
790 | 0 | fprintf(stderr, "== Info: %s", data); |
791 | 0 | default: /* in case a new one is introduced to shock us */ |
792 | 0 | return 0; |
793 | | |
794 | 0 | case CURLINFO_HEADER_OUT: |
795 | 0 | text = "=> Send header"; |
796 | 0 | break; |
797 | 0 | case CURLINFO_DATA_OUT: |
798 | 0 | text = "=> Send data"; |
799 | 0 | break; |
800 | 0 | case CURLINFO_SSL_DATA_OUT: |
801 | 0 | text = "=> Send SSL data"; |
802 | 0 | break; |
803 | 0 | case CURLINFO_HEADER_IN: |
804 | 0 | text = "<= Recv header"; |
805 | 0 | break; |
806 | 0 | case CURLINFO_DATA_IN: |
807 | 0 | text = "<= Recv data"; |
808 | 0 | break; |
809 | 0 | case CURLINFO_SSL_DATA_IN: |
810 | 0 | text = "<= Recv SSL data"; |
811 | 0 | break; |
812 | 0 | } |
813 | | |
814 | 0 | dump(text, stderr, (unsigned char *)data, size); |
815 | 0 | return 0; |
816 | 0 | } |
817 | | |
818 | | #if 0 |
819 | | static char* |
820 | | urlify(NC_HTTP_STATE* state, const char* path) |
821 | | { |
822 | | NCbytes* buf = ncbytesnew(); |
823 | | char* tmp = NULL; |
824 | | |
825 | | tmp = ncuribuild(state->url,NULL,NULL,NCURIPWD); |
826 | | ncbytescat(buf,tmp); |
827 | | nullfree(tmp); tmp = NULL; |
828 | | ncbytescat(buf,"/"); |
829 | | if(state->url->path != NULL) { |
830 | | if(state->url->path[0] == '/') |
831 | | ncbytescat(buf,state->url->path+1); |
832 | | else |
833 | | ncbytescat(buf,state->url->path); |
834 | | if(ncbytesget(buf,ncbyteslength(buf)-1) == '/') |
835 | | ncbytessetlength(buf,ncbyteslength(buf)-1); |
836 | | } |
837 | | if(path != NULL) { |
838 | | if(path[0] != '/') |
839 | | ncbytescat(buf,"/"); |
840 | | ncbytescat(buf,path); |
841 | | } |
842 | | tmp = ncbytesextract(buf); |
843 | | ncbytesfree(buf); |
844 | | return tmp; |
845 | | } |
846 | | |
847 | | int |
848 | | nc_http_urisplit(const char* url, char** rootp, char** pathp) |
849 | | { |
850 | | int stat = NC_NOERR; |
851 | | NCURI* uri = NULL; |
852 | | |
853 | | ncuriparse(url,&uri); |
854 | | if(uri == NULL) {stat = NCTHROW(NC_EURL); goto done;} |
855 | | if(rootp) { |
856 | | char* tmp = ncuribuild(uri,NULL,NULL,NCURIPWD); |
857 | | *rootp = tmp; |
858 | | nullfree(tmp); |
859 | | tmp = NULL; |
860 | | } |
861 | | if(pathp) {*pathp = strdup(uri->path);} |
862 | | done: |
863 | | return NCTHROW(stat); |
864 | | } |
865 | | #endif |