PageRenderTime 52ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/couchjs/c_src/http.c

http://github.com/cloudant/bigcouch
C | 638 lines | 479 code | 129 blank | 30 comment | 88 complexity | 4808bbc976367de3b731342f38bf86fc MD5 | raw file
Possible License(s): Apache-2.0
  1. // Licensed under the Apache License, Version 2.0 (the "License"); you may not
  2. // use this file except in compliance with the License. You may obtain a copy of
  3. // the License at
  4. //
  5. // http://www.apache.org/licenses/LICENSE-2.0
  6. //
  7. // Unless required by applicable law or agreed to in writing, software
  8. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. // License for the specific language governing permissions and limitations under
  11. // the License.
  12. #include <stdio.h>
  13. #include <stdlib.h>
  14. #include <string.h>
  15. #include "config.h"
  16. #include "sm.h"
  17. #include "utf8.h"
  18. // Soft dependency on cURL bindings because they're
  19. // only used when running the JS tests from the
  20. // command line which is rare.
  21. #ifndef HAVE_LIBCURL
  22. void
  23. http_check_enabled()
  24. {
  25. fprintf(stderr, "HTTP API was disabled at compile time.\n");
  26. exit(3);
  27. }
  28. JSBool
  29. http_ctor(JSContext* cx, JSObject* req)
  30. {
  31. return JS_FALSE;
  32. }
  33. JSBool
  34. http_dtor(JSContext* cx, JSObject* req)
  35. {
  36. return JS_FALSE;
  37. }
  38. JSBool
  39. http_open(JSContext* cx, JSObject* req, jsval mth, jsval url, jsval snc)
  40. {
  41. return JS_FALSE;
  42. }
  43. JSBool
  44. http_set_hdr(JSContext* cx, JSObject* req, jsval name, jsval val)
  45. {
  46. return JS_FALSE;
  47. }
  48. JSBool
  49. http_send(JSContext* cx, JSObject* req, jsval body)
  50. {
  51. return JS_FALSE;
  52. }
  53. int
  54. http_status(JSContext* cx, JSObject* req)
  55. {
  56. return -1;
  57. }
  58. #else
  59. #include <curl/curl.h>
  60. void
  61. http_check_enabled()
  62. {
  63. return;
  64. }
  65. // Map some of the string function names to things which exist on Windows
  66. #ifdef XP_WIN
  67. #define strcasecmp _strcmpi
  68. #define strncasecmp _strnicmp
  69. #define snprintf _snprintf
  70. #endif
  71. typedef struct curl_slist CurlHeaders;
  72. typedef struct {
  73. int method;
  74. char* url;
  75. CurlHeaders* req_headers;
  76. jsint last_status;
  77. } HTTPData;
  78. const char* METHODS[] = {"GET", "HEAD", "POST", "PUT", "DELETE", "COPY", NULL};
  79. #define GET 0
  80. #define HEAD 1
  81. #define POST 2
  82. #define PUT 3
  83. #define DELETE 4
  84. #define COPY 5
  85. static JSBool
  86. go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t blen);
  87. static JSString*
  88. str_from_binary(JSContext* cx, char* data, size_t length);
  89. JSBool
  90. http_ctor(JSContext* cx, JSObject* req)
  91. {
  92. HTTPData* http = NULL;
  93. JSBool ret = JS_FALSE;
  94. http = (HTTPData*) malloc(sizeof(HTTPData));
  95. if(!http)
  96. {
  97. JS_ReportError(cx, "Failed to create CouchHTTP instance.");
  98. goto error;
  99. }
  100. http->method = -1;
  101. http->url = NULL;
  102. http->req_headers = NULL;
  103. http->last_status = -1;
  104. if(!JS_SetPrivate(cx, req, http))
  105. {
  106. JS_ReportError(cx, "Failed to set private CouchHTTP data.");
  107. goto error;
  108. }
  109. ret = JS_TRUE;
  110. goto success;
  111. error:
  112. if(http) free(http);
  113. success:
  114. return ret;
  115. }
  116. void
  117. http_dtor(JSContext* cx, JSObject* obj)
  118. {
  119. HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj);
  120. if(http) {
  121. if(http->url) free(http->url);
  122. if(http->req_headers) curl_slist_free_all(http->req_headers);
  123. free(http);
  124. }
  125. }
  126. JSBool
  127. http_open(JSContext* cx, JSObject* req, jsval mth, jsval url, jsval snc)
  128. {
  129. HTTPData* http = (HTTPData*) JS_GetPrivate(cx, req);
  130. char* method = NULL;
  131. int methid;
  132. JSBool ret = JS_FALSE;
  133. if(!http) {
  134. JS_ReportError(cx, "Invalid CouchHTTP instance.");
  135. goto done;
  136. }
  137. if(JSVAL_IS_VOID(mth)) {
  138. JS_ReportError(cx, "You must specify a method.");
  139. goto done;
  140. }
  141. method = enc_string(cx, mth, NULL);
  142. if(!method) {
  143. JS_ReportError(cx, "Failed to encode method.");
  144. goto done;
  145. }
  146. for(methid = 0; METHODS[methid] != NULL; methid++) {
  147. if(strcasecmp(METHODS[methid], method) == 0) break;
  148. }
  149. if(methid > COPY) {
  150. JS_ReportError(cx, "Invalid method specified.");
  151. goto done;
  152. }
  153. http->method = methid;
  154. if(JSVAL_IS_VOID(url)) {
  155. JS_ReportError(cx, "You must specify a URL.");
  156. goto done;
  157. }
  158. if(http->url != NULL) {
  159. free(http->url);
  160. http->url = NULL;
  161. }
  162. http->url = enc_string(cx, url, NULL);
  163. if(http->url == NULL) {
  164. JS_ReportError(cx, "Failed to encode URL.");
  165. goto done;
  166. }
  167. if(JSVAL_IS_BOOLEAN(snc) && JSVAL_TO_BOOLEAN(snc)) {
  168. JS_ReportError(cx, "Synchronous flag must be false.");
  169. goto done;
  170. }
  171. if(http->req_headers) {
  172. curl_slist_free_all(http->req_headers);
  173. http->req_headers = NULL;
  174. }
  175. // Disable Expect: 100-continue
  176. http->req_headers = curl_slist_append(http->req_headers, "Expect:");
  177. ret = JS_TRUE;
  178. done:
  179. if(method) free(method);
  180. return ret;
  181. }
  182. JSBool
  183. http_set_hdr(JSContext* cx, JSObject* req, jsval name, jsval val)
  184. {
  185. HTTPData* http = (HTTPData*) JS_GetPrivate(cx, req);
  186. char* keystr = NULL;
  187. char* valstr = NULL;
  188. char* hdrbuf = NULL;
  189. size_t hdrlen = -1;
  190. JSBool ret = JS_FALSE;
  191. if(!http) {
  192. JS_ReportError(cx, "Invalid CouchHTTP instance.");
  193. goto done;
  194. }
  195. if(JSVAL_IS_VOID(name))
  196. {
  197. JS_ReportError(cx, "You must speciy a header name.");
  198. goto done;
  199. }
  200. keystr = enc_string(cx, name, NULL);
  201. if(!keystr)
  202. {
  203. JS_ReportError(cx, "Failed to encode header name.");
  204. goto done;
  205. }
  206. if(JSVAL_IS_VOID(val))
  207. {
  208. JS_ReportError(cx, "You must specify a header value.");
  209. goto done;
  210. }
  211. valstr = enc_string(cx, val, NULL);
  212. if(!valstr)
  213. {
  214. JS_ReportError(cx, "Failed to encode header value.");
  215. goto done;
  216. }
  217. hdrlen = strlen(keystr) + strlen(valstr) + 3;
  218. hdrbuf = (char*) malloc(hdrlen * sizeof(char));
  219. if(!hdrbuf) {
  220. JS_ReportError(cx, "Failed to allocate header buffer.");
  221. goto done;
  222. }
  223. snprintf(hdrbuf, hdrlen, "%s: %s", keystr, valstr);
  224. http->req_headers = curl_slist_append(http->req_headers, hdrbuf);
  225. ret = JS_TRUE;
  226. done:
  227. if(keystr) free(keystr);
  228. if(valstr) free(valstr);
  229. if(hdrbuf) free(hdrbuf);
  230. return ret;
  231. }
  232. JSBool
  233. http_send(JSContext* cx, JSObject* req, jsval body)
  234. {
  235. HTTPData* http = (HTTPData*) JS_GetPrivate(cx, req);
  236. char* bodystr = NULL;
  237. size_t bodylen = 0;
  238. JSBool ret = JS_FALSE;
  239. if(!http) {
  240. JS_ReportError(cx, "Invalid CouchHTTP instance.");
  241. goto done;
  242. }
  243. if(!JSVAL_IS_VOID(body)) {
  244. bodystr = enc_string(cx, body, &bodylen);
  245. if(!bodystr) {
  246. JS_ReportError(cx, "Failed to encode body.");
  247. goto done;
  248. }
  249. }
  250. ret = go(cx, req, http, bodystr, bodylen);
  251. done:
  252. if(bodystr) free(bodystr);
  253. return ret;
  254. }
  255. int
  256. http_status(JSContext* cx, JSObject* req)
  257. {
  258. HTTPData* http = (HTTPData*) JS_GetPrivate(cx, req);
  259. if(!http) {
  260. JS_ReportError(cx, "Invalid CouchHTTP instance.");
  261. return JS_FALSE;
  262. }
  263. return http->last_status;
  264. }
  265. // Curl Helpers
  266. typedef struct {
  267. HTTPData* http;
  268. JSContext* cx;
  269. JSObject* resp_headers;
  270. char* sendbuf;
  271. size_t sendlen;
  272. size_t sent;
  273. int sent_once;
  274. char* recvbuf;
  275. size_t recvlen;
  276. size_t read;
  277. } CurlState;
  278. /*
  279. * I really hate doing this but this doesn't have to be
  280. * uber awesome, it just has to work.
  281. */
  282. CURL* HTTP_HANDLE = NULL;
  283. char ERRBUF[CURL_ERROR_SIZE];
  284. static size_t send_body(void *ptr, size_t size, size_t nmem, void *data);
  285. static int seek_body(void *ptr, curl_off_t offset, int origin);
  286. static size_t recv_body(void *ptr, size_t size, size_t nmem, void *data);
  287. static size_t recv_header(void *ptr, size_t size, size_t nmem, void *data);
  288. static JSBool
  289. go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
  290. {
  291. CurlState state;
  292. JSString* jsbody;
  293. JSBool ret = JS_FALSE;
  294. jsval tmp;
  295. state.cx = cx;
  296. state.http = http;
  297. state.sendbuf = body;
  298. state.sendlen = bodylen;
  299. state.sent = 0;
  300. state.sent_once = 0;
  301. state.recvbuf = NULL;
  302. state.recvlen = 0;
  303. state.read = 0;
  304. if(HTTP_HANDLE == NULL) {
  305. HTTP_HANDLE = curl_easy_init();
  306. curl_easy_setopt(HTTP_HANDLE, CURLOPT_READFUNCTION, send_body);
  307. curl_easy_setopt(HTTP_HANDLE, CURLOPT_SEEKFUNCTION,
  308. (curl_seek_callback) seek_body);
  309. curl_easy_setopt(HTTP_HANDLE, CURLOPT_HEADERFUNCTION, recv_header);
  310. curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEFUNCTION, recv_body);
  311. curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOPROGRESS, 1);
  312. curl_easy_setopt(HTTP_HANDLE, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  313. curl_easy_setopt(HTTP_HANDLE, CURLOPT_ERRORBUFFER, ERRBUF);
  314. curl_easy_setopt(HTTP_HANDLE, CURLOPT_COOKIEFILE, "");
  315. curl_easy_setopt(HTTP_HANDLE, CURLOPT_USERAGENT,
  316. "CouchHTTP Client - Relax");
  317. }
  318. if(!HTTP_HANDLE) {
  319. JS_ReportError(cx, "Failed to initialize cURL handle.");
  320. goto done;
  321. }
  322. if(http->method < 0 || http->method > COPY) {
  323. JS_ReportError(cx, "INTERNAL: Unknown method.");
  324. goto done;
  325. }
  326. curl_easy_setopt(HTTP_HANDLE, CURLOPT_CUSTOMREQUEST, METHODS[http->method]);
  327. curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOBODY, 0);
  328. curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 1);
  329. curl_easy_setopt(HTTP_HANDLE, CURLOPT_UPLOAD, 0);
  330. if(http->method == HEAD) {
  331. curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOBODY, 1);
  332. curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 0);
  333. } else if(http->method == POST || http->method == PUT) {
  334. curl_easy_setopt(HTTP_HANDLE, CURLOPT_UPLOAD, 1);
  335. curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 0);
  336. }
  337. if(body && bodylen) {
  338. curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, bodylen);
  339. } else {
  340. curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, 0);
  341. }
  342. // curl_easy_setopt(HTTP_HANDLE, CURLOPT_VERBOSE, 1);
  343. curl_easy_setopt(HTTP_HANDLE, CURLOPT_URL, http->url);
  344. curl_easy_setopt(HTTP_HANDLE, CURLOPT_HTTPHEADER, http->req_headers);
  345. curl_easy_setopt(HTTP_HANDLE, CURLOPT_READDATA, &state);
  346. curl_easy_setopt(HTTP_HANDLE, CURLOPT_SEEKDATA, &state);
  347. curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEHEADER, &state);
  348. curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEDATA, &state);
  349. if(curl_easy_perform(HTTP_HANDLE) != 0) {
  350. JS_ReportError(cx, "Failed to execute HTTP request: %s", ERRBUF);
  351. goto done;
  352. }
  353. if(!state.resp_headers) {
  354. JS_ReportError(cx, "Failed to recieve HTTP headers.");
  355. goto done;
  356. }
  357. tmp = OBJECT_TO_JSVAL(state.resp_headers);
  358. if(!JS_DefineProperty(
  359. cx, obj,
  360. "_headers",
  361. tmp,
  362. NULL, NULL,
  363. JSPROP_READONLY
  364. )) {
  365. JS_ReportError(cx, "INTERNAL: Failed to set response headers.");
  366. goto done;
  367. }
  368. if(state.recvbuf) {
  369. state.recvbuf[state.read] = '\0';
  370. jsbody = dec_string(cx, state.recvbuf, state.read+1);
  371. if(!jsbody) {
  372. // If we can't decode the body as UTF-8 we forcefully
  373. // convert it to a string by just forcing each byte
  374. // to a jschar.
  375. jsbody = str_from_binary(cx, state.recvbuf, state.read);
  376. if(!jsbody) {
  377. if(!JS_IsExceptionPending(cx)) {
  378. JS_ReportError(cx, "INTERNAL: Failed to decode body.");
  379. }
  380. goto done;
  381. }
  382. }
  383. tmp = STRING_TO_JSVAL(jsbody);
  384. } else {
  385. tmp = JS_GetEmptyStringValue(cx);
  386. }
  387. if(!JS_DefineProperty(
  388. cx, obj,
  389. "responseText",
  390. tmp,
  391. NULL, NULL,
  392. JSPROP_READONLY
  393. )) {
  394. JS_ReportError(cx, "INTERNAL: Failed to set responseText.");
  395. goto done;
  396. }
  397. ret = JS_TRUE;
  398. done:
  399. if(state.recvbuf) JS_free(cx, state.recvbuf);
  400. return ret;
  401. }
  402. static size_t
  403. send_body(void *ptr, size_t size, size_t nmem, void *data)
  404. {
  405. CurlState* state = (CurlState*) data;
  406. size_t length = size * nmem;
  407. size_t towrite = state->sendlen - state->sent;
  408. // Assume this is cURL trying to resend a request that
  409. // failed.
  410. if(towrite == 0 && state->sent_once == 0) {
  411. state->sent_once = 1;
  412. return 0;
  413. } else if(towrite == 0) {
  414. state->sent = 0;
  415. state->sent_once = 0;
  416. towrite = state->sendlen;
  417. }
  418. if(length < towrite) towrite = length;
  419. memcpy(ptr, state->sendbuf + state->sent, towrite);
  420. state->sent += towrite;
  421. return towrite;
  422. }
  423. static int
  424. seek_body(void* ptr, curl_off_t offset, int origin)
  425. {
  426. CurlState* state = (CurlState*) ptr;
  427. if(origin != SEEK_SET) return -1;
  428. state->sent = (size_t) offset;
  429. return (int) state->sent;
  430. }
  431. static size_t
  432. recv_header(void *ptr, size_t size, size_t nmem, void *data)
  433. {
  434. CurlState* state = (CurlState*) data;
  435. char code[4];
  436. char* header = (char*) ptr;
  437. size_t length = size * nmem;
  438. JSString* hdr = NULL;
  439. jsuint hdrlen;
  440. jsval hdrval;
  441. if(length > 7 && strncasecmp(header, "HTTP/1.", 7) == 0) {
  442. if(length < 12) {
  443. return CURLE_WRITE_ERROR;
  444. }
  445. memcpy(code, header+9, 3*sizeof(char));
  446. code[3] = '\0';
  447. state->http->last_status = atoi(code);
  448. state->resp_headers = JS_NewArrayObject(state->cx, 0, NULL);
  449. if(!state->resp_headers) {
  450. return CURLE_WRITE_ERROR;
  451. }
  452. return length;
  453. }
  454. // We get a notice at the \r\n\r\n after headers.
  455. if(length <= 2) {
  456. return length;
  457. }
  458. // Append the new header to our array.
  459. hdr = dec_string(state->cx, header, length);
  460. if(!hdr) {
  461. return CURLE_WRITE_ERROR;
  462. }
  463. if(!JS_GetArrayLength(state->cx, state->resp_headers, &hdrlen)) {
  464. return CURLE_WRITE_ERROR;
  465. }
  466. hdrval = STRING_TO_JSVAL(hdr);
  467. if(!JS_SetElement(state->cx, state->resp_headers, hdrlen, &hdrval)) {
  468. return CURLE_WRITE_ERROR;
  469. }
  470. return length;
  471. }
  472. static size_t
  473. recv_body(void *ptr, size_t size, size_t nmem, void *data)
  474. {
  475. CurlState* state = (CurlState*) data;
  476. size_t length = size * nmem;
  477. char* tmp = NULL;
  478. if(!state->recvbuf) {
  479. state->recvlen = 4096;
  480. state->read = 0;
  481. state->recvbuf = (char*) JS_malloc(state->cx, state->recvlen);
  482. }
  483. if(!state->recvbuf) {
  484. return CURLE_WRITE_ERROR;
  485. }
  486. // +1 so we can add '\0' back up in the go function.
  487. while(length+1 > state->recvlen - state->read) state->recvlen *= 2;
  488. tmp = (char*) JS_realloc(state->cx, state->recvbuf, state->recvlen);
  489. if(!tmp) return CURLE_WRITE_ERROR;
  490. state->recvbuf = tmp;
  491. memcpy(state->recvbuf + state->read, ptr, length);
  492. state->read += length;
  493. return length;
  494. }
  495. JSString*
  496. str_from_binary(JSContext* cx, char* data, size_t length)
  497. {
  498. jschar* conv = (jschar*) JS_malloc(cx, length * sizeof(jschar));
  499. JSString* ret = NULL;
  500. size_t i;
  501. if(!conv) return NULL;
  502. for(i = 0; i < length; i++) {
  503. conv[i] = (jschar) data[i];
  504. }
  505. ret = JS_NewUCString(cx, conv, length);
  506. if(!ret) JS_free(cx, conv);
  507. return ret;
  508. }
  509. #endif /* HAVE_CURL */