/** * HTTP async callback, invoked when new HTTP data is read. * The EOF condition is indicated by data being NULL. */ static void soap_data_ind(http_async_t *ha, char *data, int len) { soap_rpc_t *sr = http_async_get_opaque(ha); size_t new_length; soap_rpc_check(sr); /* * When data is NULL, we reached EOF and we're done. Time to process * the data we got back. * * The HTTP asynchronous handle is nullified since it is about to be * closed by the HTTP layer upon return. */ if (NULL == data) { sr->ha = NULL; soap_process_reply(sr); return; } /* * Ensure we don't get too much and resize the memory buffer where * we store the reply if needed. */ new_length = size_saturate_add(sr->reply_len, len); if (new_length > sr->content_len) { http_async_error(ha, HTTP_ASYNC_DATA2BIG); return; } if (new_length > sr->reply_size) { size_t new_size = size_saturate_mult(sr->reply_size, 2); sr->reply_data = hrealloc(sr->reply_data, new_size); sr->reply_size = new_size; } /* * Append new data. */ memcpy(&sr->reply_data[sr->reply_len], data, len); sr->reply_len = new_length; }
/** * Callback invoked when the HTTP data of the request have been sent. */ static void soap_sent_data(const struct http_async *ha, const struct gnutella_socket *s, const char *data, size_t len, gboolean deferred) { soap_rpc_t *sr = http_async_get_opaque(ha); soap_rpc_check(sr); if (GNET_PROPERTY(soap_trace) & SOCK_TRACE_OUT) { g_debug("----Sent SOAP HTTP data%s to %s (%u bytes):", deferred ? " completely" : "", host_addr_port_to_string(s->addr, s->port), (unsigned) len); dump_string(stderr, data, len, "----"); } }
/** * HTTP async callback, invoked on errors. */ static void soap_error_ind(http_async_t *ha, http_errtype_t type, void *val) { soap_rpc_t *sr = http_async_get_opaque(ha); soap_error_t err = SOAP_E_OK; soap_rpc_check(sr); if (GNET_PROPERTY(soap_debug)) { http_async_log_error_dbg(ha, type, val, "SOAP", GNET_PROPERTY(soap_debug) > 1); } if (HTTP_ASYNC_ERROR == type) { switch (GPOINTER_TO_INT(val)) { case HTTP_ASYNC_CANCELLED: /* * Retry with M-POST if cancelled with sr->retry set to TRUE. */ if (sr->retry) { g_assert(NULL == sr->delay_ev); sr->delay_ev = cq_main_insert(1, soap_rpc_launch, sr); if (GNET_PROPERTY(soap_debug) > 1) { g_message("SOAP \"%s\" at \"%s\": retrying with M-POST", sr->action, sr->url); } } break; /* No callback on explicit user cancel */ case HTTP_ASYNC_DATA2BIG: err = SOAP_E_DATA2BIG; break; case HTTP_ASYNC_CONN_TIMEOUT: case HTTP_ASYNC_TIMEOUT: err = SOAP_E_TIMEOUT; break; default: err = SOAP_E_TRANSPORT; break; } } else { err = SOAP_E_TRANSPORT; } if (err != SOAP_E_OK) soap_error(sr, err); }
/** * Redefine callback invoked when we got the whole HTTP reply. */ static void soap_got_reply(const http_async_t *ha, const struct gnutella_socket *s, const char *status, const header_t *header) { soap_rpc_t *sr = http_async_get_opaque(ha); soap_rpc_check(sr); if (GNET_PROPERTY(soap_trace) & SOCK_TRACE_IN) { g_debug("----Got SOAP HTTP reply from %s:", host_addr_to_string(s->addr)); if (log_printable(LOG_STDERR)) { fprintf(stderr, "%s\n", status); header_dump(stderr, header, "----"); } } }
/** * Analyze the data we have received, and give each line to the supplied * dispatcher callback `cb', after having chomped it. On EOF, call `eof' * to finalize parsing. */ static void parse_dispatch_lines(void *handle, const char *buf, size_t len, parse_dispatch_t cb, parse_eof_t eofile) { struct parse_context *ctx; const char *p = buf; size_t remain = len; /* * Retrieve parsing context, stored as an opaque attribute in the * asynchronous HTTP request handle. */ ctx = http_async_get_opaque(handle); g_assert(ctx->handle == handle); /* Make sure it's the right context */ if (len == 0) { /* Nothing to parse, got EOF */ if (eofile != NULL) (*eofile)(ctx); return; } /* * Read a line at a time. */ for (;;) { char *line; bool error; size_t line_len; size_t parsed; switch (getline_read(ctx->getline, p, remain, &parsed)) { case READ_OVERFLOW: http_async_cancel(handle); ghc_connecting = FALSE; return; case READ_DONE: p += parsed; remain -= parsed; break; case READ_MORE: /* ok, but needs more data */ g_assert(parsed == remain); return; } /* * We come here everytime we get a full line. */ line = h_strdup(getline_str(ctx->getline)); line_len = getline_length(ctx->getline); line_len = strchomp(line, line_len); error = !(*cb)(ctx, line, line_len); /* An ERROR was reported */ HFREE_NULL(line); if (error) { ghc_ctx.ha = NULL; ghc_connecting = FALSE; return; } /* * Make sure we don't process lines ad infinitum. */ ctx->lines++; if (ctx->lines >= ctx->maxlines) { const char *req; const char *url = http_async_info(handle, &req, NULL, NULL, NULL); if (GNET_PROPERTY(bootstrap_debug)) g_warning("BOOT GHC got %u+ lines from \"%s %s\", stopping", ctx->lines, req, url); http_async_close(handle); ghc_connecting = FALSE; return; } getline_reset(ctx->getline); } }
/** * Build our own HTTP request. * * See http_async_build_post_request() for the model and details about * the various parameters. * * @return length of generated request. */ static size_t soap_build_request(const http_async_t *ha, char *buf, size_t len, const char *verb, const char *path, const char *content_type, size_t content_len) { soap_rpc_t *sr = http_async_get_opaque(ha); size_t rw; const char *fixed_header; soap_rpc_check(sr); g_assert(len <= INT_MAX); if (sr->options & SOAP_RPC_O_MAN_FORCE) { sr->mandatory = TRUE; } else if ((sr->options & SOAP_RPC_O_MAN_RETRY) && sr->regular) { sr->mandatory = TRUE; } else { sr->mandatory = FALSE; sr->regular = TRUE; } if (sr->options & SOAP_RPC_O_ALL_CAPS) { fixed_header = "ACCEPT-ENCODING: deflate\r\n" "CONNECTION: close\r\n" "CACHE-CONTROL: no-cache\r\n" "PRAGMA: no-cache\r\n"; } else { fixed_header = "Accept-Encoding: deflate\r\n" "Connection: close\r\n" "Cache-Control: no-cache\r\n" "Pragma: no-cache\r\n"; } if (sr->mandatory) { if (sr->options & SOAP_RPC_O_ALL_CAPS) { rw = gm_snprintf(buf, len, "M-%s %s HTTP/1.1\r\n" "HOST: %s\r\n" "USER-AGENT: %s\r\n" "CONTENT-TYPE: %s\r\n" "CONTENT-LENGTH: %s\r\n" "%s" /* Fixed header part */ "MAN: \"%s\"; ns=01\r\n" "01-SOAPACTION: \"%s\"\r\n" "\r\n", verb, path, http_async_remote_host_port(ha), version_string, content_type, size_t_to_string(content_len), fixed_header, SOAP_NAMESPACE, sr->action); } else { rw = gm_snprintf(buf, len, "M-%s %s HTTP/1.1\r\n" "Host: %s\r\n" "User-Agent: %s\r\n" "Content-Type: %s\r\n" "Content-Length: %s\r\n" "%s" /* Fixed header part */ "Man: \"%s\"; ns=01\r\n" "01-SOAPAction: \"%s\"\r\n" "\r\n", verb, path, http_async_remote_host_port(ha), version_string, content_type, size_t_to_string(content_len), fixed_header, SOAP_NAMESPACE, sr->action); } } else { if (sr->options & SOAP_RPC_O_ALL_CAPS) { rw = gm_snprintf(buf, len, "%s %s HTTP/1.1\r\n" "HOST: %s\r\n" "USER-AGENT: %s\r\n" "CONTENT-TYPE: %s\r\n" "CONTENT-LENGTH: %s\r\n" "%s" /* Fixed header part */ "SOAPACTION: \"%s\"\r\n" "\r\n", verb, path, http_async_remote_host_port(ha), version_string, content_type, size_t_to_string(content_len), fixed_header, sr->action); } else { rw = gm_snprintf(buf, len, "%s %s HTTP/1.1\r\n" "Host: %s\r\n" "User-Agent: %s\r\n" "Content-Type: %s\r\n" "Content-Length: %s\r\n" "%s" /* Fixed header part */ "SOAPAction: \"%s\"\r\n" "\r\n", verb, path, http_async_remote_host_port(ha), version_string, content_type, size_t_to_string(content_len), fixed_header, sr->action); } } return rw; }
/** * HTTP async callback, invoked when all the headers have been read. * * @return TRUE if we can continue with the request. */ static gboolean soap_header_ind(http_async_t *ha, header_t *header, int code, const char *message) { soap_rpc_t *sr = http_async_get_opaque(ha); const char *buf; soap_rpc_check(sr); g_assert(ha == sr->ha); if (GNET_PROPERTY(soap_debug) > 2) { g_debug("SOAP \"%s\" at \"%s\": got HTTP %d %s", sr->action, sr->url, code, message); } /* * Grab local socket address if they are interested. */ if (sr->options & SOAP_RPC_O_LOCAL_ADDR) sr->got_local_addr = http_async_get_local_addr(ha, &sr->local_addr); /* * If we sent a non-mandatory request and get a 405 "Method not allowed" * error, retry with M-POST. Likewise, a 510 "Not extended" reply is an * invitation to use the HTTP Extension Framework (RFC 2774). */ if ( (405 == code || 510 == code) && !sr->mandatory && !sr->retry && (sr->options & SOAP_RPC_O_MAN_RETRY) ) { if (GNET_PROPERTY(soap_debug) > 1) { g_message("SOAP \"%s\" at \"%s\": will be retrying with M-POST", sr->action, sr->url); } sr->retry = TRUE; /* Signal we should retry */ http_async_cancel(ha); return FALSE; } /* * If we sent a mandatory request, there needs to be an "Ext:" header * in the reply to show that the mandatory request was understood as such. */ if (sr->mandatory && 200 == code) { const char *ext = header_get(header, "Ext"); if (NULL == ext) { if (GNET_PROPERTY(soap_debug)) { g_warning("SOAP \"%s\" at \"%s\": M-POST not understood", sr->action, sr->url); } http_async_error(ha, HTTP_ASYNC_MAN_FAILURE); return FALSE; } } /* * Save the HTTP headers and code to be able to analyze the reply payload. * * Since the option HTTP_O_READ_REPLY is used, we'll get the reply data * from the server even if the status code is not 200 and we need to be * able to differentiate between a success report and an error. */ sr->header = header_refcnt_inc(header); sr->http_code = code; /* * See whether they advertise a Content-Length, which may not be the * case if chunked transfer encoding is used for the reply. In that * case, we shall dynamically adjust the reception buffer size. */ buf = header_get(header, "Content-Length"); if (buf != NULL) { guint32 len; int error; len = parse_uint32(buf, NULL, 10, &error); if (error) { if (GNET_PROPERTY(soap_debug)) { g_warning("SOAP \"%s\" at \"%s\": " "cannot parse Content-Length header: " "value is \"%s\", error is %s", sr->action, sr->url, buf, g_strerror(error)); } http_async_error(ha, HTTP_ASYNC_BAD_HEADER); return FALSE; } if (len > sr->maxlen) { http_async_error(ha, HTTP_ASYNC_DATA2BIG); return FALSE; } sr->content_len = len; } /* * Allocate data buffer: either they advertised content length, or 1/16th * of the maximum data length we accept to grab from the server. */ sr->reply_size = (buf != NULL) ? sr->content_len : (sr->maxlen >> 4); sr->reply_data = halloc(sr->reply_size); return TRUE; /* OK, go on */ }