-const char jcc_rcs[] = "$Id: jcc.c,v 1.271 2009/07/14 17:50:34 fabiankeil Exp $";
+const char jcc_rcs[] = "$Id: jcc.c,v 1.294 2009/09/18 18:57:20 fabiankeil Exp $";
/*********************************************************************
*
* File : $Source: /cvsroot/ijbswa/current/jcc.c,v $
}
+#ifdef FEATURE_CONNECTION_SHARING
/*********************************************************************
*
* Function : wait_for_alive_connections
log_error(LOG_LEVEL_CONNECT, "No connections to wait for left.");
}
+#endif /* def FEATURE_CONNECTION_SHARING */
/*********************************************************************
}
server_connection->forward_port = fwd->forward_port;
}
+
+
+/*********************************************************************
+ *
+ * Function : verify_request_length
+ *
+ * Description : Checks if we already got the whole client requests
+ * and sets CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ if
+ * we do.
+ *
+ * Data that doesn't belong to the current request is
+ * thrown away to let the client retry on a clean socket.
+ *
+ * XXX: This is a hack until we can deal with multiple
+ * pipelined requests at the same time.
+ *
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns : void
+ *
+ *********************************************************************/
+static void verify_request_length(struct client_state *csp)
+{
+ unsigned long long buffered_request_bytes =
+ (unsigned long long)(csp->iob->eod - csp->iob->cur);
+
+ if ((csp->expected_client_content_length != 0)
+ && (buffered_request_bytes != 0))
+ {
+ if (csp->expected_client_content_length >= buffered_request_bytes)
+ {
+ csp->expected_client_content_length -= buffered_request_bytes;
+ log_error(LOG_LEVEL_CONNECT, "Reduced expected bytes to %llu "
+ "to account for the %llu ones we already got.",
+ csp->expected_client_content_length, buffered_request_bytes);
+ }
+ else
+ {
+ assert(csp->iob->eod > csp->iob->cur + csp->expected_client_content_length);
+ csp->iob->eod = csp->iob->cur + csp->expected_client_content_length;
+ log_error(LOG_LEVEL_CONNECT, "Reducing expected bytes to 0. "
+ "Marking the server socket tainted after throwing %llu bytes away.",
+ buffered_request_bytes - csp->expected_client_content_length);
+ csp->expected_client_content_length = 0;
+ csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED;
+ }
+
+ if (csp->expected_client_content_length == 0)
+ {
+ csp->flags |= CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ;
+ }
+ }
+
+ if (!(csp->flags & CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ)
+ && ((csp->iob->cur[0] != '\0') || (csp->expected_client_content_length != 0)))
+ {
+ csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED;
+ if (strcmpic(csp->http->gpc, "GET")
+ && strcmpic(csp->http->gpc, "HEAD")
+ && strcmpic(csp->http->gpc, "TRACE")
+ && strcmpic(csp->http->gpc, "OPTIONS")
+ && strcmpic(csp->http->gpc, "DELETE"))
+ {
+ /* XXX: this is an incomplete hack */
+ csp->flags &= ~CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ;
+ log_error(LOG_LEVEL_CONNECT,
+ "There might be a request body. The connection will not be kept alive.");
+ }
+ else
+ {
+ /* XXX: and so is this */
+ csp->flags |= CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ;
+ log_error(LOG_LEVEL_CONNECT,
+ "Possible pipeline attempt detected. The connection will not "
+ "be kept alive and we will only serve the first request.");
+ /* Nuke the pipelined requests from orbit, just to be sure. */
+ csp->iob->buf[0] = '\0';
+ csp->iob->eod = csp->iob->cur = csp->iob->buf;
+ }
+ }
+ else
+ {
+ csp->flags |= CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ;
+ log_error(LOG_LEVEL_CONNECT, "Complete client request received.");
+ }
+}
#endif /* FEATURE_CONNECTION_KEEP_ALIVE */
*********************************************************************/
static void mark_server_socket_tainted(struct client_state *csp)
{
- if ((csp->flags & CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE))
+ if ((csp->flags & CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE)
+ && !(csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED))
{
log_error(LOG_LEVEL_CONNECT,
"Marking the server socket %d tainted.", csp->sfd);
req = get_request_line(csp);
if (req == NULL)
{
+ mark_server_socket_tainted(csp);
return JB_ERR_PARSE;
}
assert(*req != '\0');
jb_err err;
#ifdef FEATURE_CONNECTION_KEEP_ALIVE
- if ((!strcmpic(csp->http->ver, "HTTP/1.1"))
+ if ((csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE)
+ && (!strcmpic(csp->http->ver, "HTTP/1.1"))
&& (csp->http->ssl == 0))
{
/* Assume persistence until further notice */
}
#ifdef FEATURE_CONNECTION_KEEP_ALIVE
- if ((csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE))
+ if (csp->http->ssl == 0)
{
- if (csp->iob->cur[0] != '\0')
- {
- csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED;
- if (strcmpic(csp->http->gpc, "GET")
- && strcmpic(csp->http->gpc, "HEAD")
- && strcmpic(csp->http->gpc, "TRACE")
- && strcmpic(csp->http->gpc, "OPTIONS")
- && strcmpic(csp->http->gpc, "DELETE"))
- {
- /* XXX: this is an incomplete hack */
- csp->flags &= ~CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ;
- log_error(LOG_LEVEL_CONNECT,
- "There might be a request body. The connection will not be kept alive.");
- }
- else
- {
- /* XXX: and so is this */
- csp->flags |= CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ;
- log_error(LOG_LEVEL_CONNECT,
- "Possible pipeline attempt detected. The connection will not "
- "be kept alive and we will only serve the first request.");
- /* Nuke the pipelined requests from orbit, just to be sure. */
- csp->iob->buf[0] = '\0';
- csp->iob->eod = csp->iob->cur = csp->iob->buf;
- }
- }
- else
- {
- csp->flags |= CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ;
- log_error(LOG_LEVEL_CONNECT, "Complete client request received.");
- }
+ verify_request_length(csp);
}
#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
/* Skeleton for HTTP response, if we should intercept the request */
struct http_response *rsp;
struct timeval timeout;
+ int watch_client_socket = 1;
memset(buf, 0, sizeof(buf));
FD_ZERO(&rfds);
#endif
#ifdef FEATURE_CONNECTION_KEEP_ALIVE
- if ((csp->flags & CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ))
+ if (!watch_client_socket)
{
maxfd = csp->sfd;
}
}
if (server_body && server_response_is_complete(csp, byte_count))
{
- log_error(LOG_LEVEL_CONNECT,
- "Done reading from server. Expected content length: %llu. "
- "Actual content length: %llu. Most recently received: %d.",
- csp->expected_content_length, byte_count, len);
+ if (csp->expected_content_length == byte_count)
+ {
+ log_error(LOG_LEVEL_CONNECT,
+ "Done reading from server. Content length: %llu as expected. "
+ "Bytes most recently read: %d.",
+ byte_count, len);
+ }
+ else
+ {
+ log_error(LOG_LEVEL_CONNECT,
+ "Done reading from server. Expected content length: %llu. "
+ "Actual content length: %llu. Bytes most recently read: %d.",
+ csp->expected_content_length, byte_count, len);
+ }
len = 0;
/*
* XXX: should not jump around,
*/
if (FD_ISSET(csp->cfd, &rfds))
{
- len = read_socket(csp->cfd, buf, sizeof(buf) - 1);
+ int max_bytes_to_read = sizeof(buf) - 1;
+
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+ if ((csp->flags & CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ))
+ {
+ if (data_is_available(csp->cfd, 0))
+ {
+ /*
+ * If the next request is already waiting, we have
+ * to stop select()ing the client socket. Otherwise
+ * we would always return right away and get nothing
+ * else done.
+ */
+ watch_client_socket = 0;
+ log_error(LOG_LEVEL_CONNECT,
+ "Stopping to watch the client socket. "
+ "There's already another request waiting.");
+ continue;
+ }
+ /*
+ * If the client socket is set, but there's no data
+ * available on the socket, the client went fishing
+ * and continuing talking to the server makes no sense.
+ */
+ log_error(LOG_LEVEL_CONNECT, "The client closed socket %d while "
+ "the server socket %d is still open.", csp->cfd, csp->sfd);
+ mark_server_socket_tainted(csp);
+ break;
+ }
+ if (csp->expected_client_content_length != 0)
+ {
+ if (csp->expected_client_content_length < (sizeof(buf) - 1))
+ {
+ max_bytes_to_read = (int)csp->expected_client_content_length;
+ }
+ log_error(LOG_LEVEL_CONNECT,
+ "Waiting for up to %d bytes from the client.",
+ max_bytes_to_read);
+ }
+ assert(max_bytes_to_read < sizeof(buf));
+#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+
+ len = read_socket(csp->cfd, buf, max_bytes_to_read);
if (len <= 0)
{
break; /* "game over, man" */
}
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+ if (csp->expected_client_content_length != 0)
+ {
+ assert(len <= max_bytes_to_read);
+ csp->expected_client_content_length -= (unsigned)len;
+ log_error(LOG_LEVEL_CONNECT,
+ "Expected client content length set to %llu "
+ "after reading %d bytes.",
+ csp->expected_client_content_length, len);
+ if (csp->expected_client_content_length == 0)
+ {
+ log_error(LOG_LEVEL_CONNECT,
+ "Done reading from the client.");
+ csp->flags |= CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ;
+ }
+ }
+#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+
if (write_socket(csp->sfd, buf, (size_t)len))
{
log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
#ifdef FEATURE_CONNECTION_KEEP_ALIVE
if (!socket_is_still_usable(csp->cfd))
{
+#ifdef _WIN32
+ log_error(LOG_LEVEL_CONNECT,
+ "The server still wants to talk, but the client may already have hung up on us.");
+#else
log_error(LOG_LEVEL_CONNECT,
"The server still wants to talk, but the client hung up on us.");
mark_server_socket_tainted(csp);
return;
+#endif /* def _WIN32 */
}
#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
}
else
{
- const char *header_start;
/*
* We're still looking for the end of the server's header.
* Buffer up the data we just read. If that fails, there's
return;
}
- header_start = csp->iob->cur;
-
/* Convert iob into something sed() can digest */
if (JB_ERR_PARSE == get_server_headers(csp))
{
* Since we have to wait for more from the server before
* we can parse the headers we just continue here.
*/
- long header_offset = csp->iob->cur - header_start;
- assert(csp->iob->cur >= header_start);
- byte_count += (unsigned long long)(len - header_offset);
- log_error(LOG_LEVEL_CONNECT, "Continuing buffering headers. "
- "byte_count: %llu. header_offset: %d. len: %d.",
- byte_count, header_offset, len);
+ log_error(LOG_LEVEL_CONNECT,
+ "Continuing buffering headers. Bytes most recently read: %d.",
+ len);
continue;
}
}
+ else
+ {
+ /*
+ * Account for the content bytes we
+ * might have gotten with the headers.
+ */
+ assert(csp->iob->eod >= csp->iob->cur);
+ byte_count = (unsigned long long)(csp->iob->eod - csp->iob->cur);
+ }
/* Did we actually get anything? */
if (NULL == csp->headers->first)
mark_server_socket_tainted(csp);
return;
}
-
- byte_count += (unsigned long long)len;
- }
- else
- {
- /*
- * XXX: the header lenght should probably
- * be calculated by get_server_headers().
- */
- long header_length = csp->iob->cur - header_start;
- assert(csp->iob->cur > header_start);
- byte_count += (unsigned long long)(len - header_length);
}
/* we're finished with the server's header */
#endif /* def AMIGA */
{
#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+#ifdef FEATURE_CONNECTION_SHARING
static int monitor_thread_running = 0;
+#endif /* def FEATURE_CONNECTION_SHARING */
int continue_chatting = 0;
- unsigned int latency = 0;
do
{
+ unsigned int latency;
+
chat(csp);
- if ((csp->flags & CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE)
- && !(csp->flags & CSP_FLAG_SERVER_KEEP_ALIVE_TIMEOUT_SET))
- {
- log_error(LOG_LEVEL_CONNECT, "The server didn't specify how long "
- "the connection will stay open. Assume it's only a second.");
- csp->server_connection.keep_alive_timeout = 1;
- }
+ latency = (unsigned)(csp->server_connection.response_received -
+ csp->server_connection.request_sent);
continue_chatting = (csp->config->feature_flags
& RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE)
if (continue_chatting)
{
- unsigned int client_timeout = (unsigned)csp->server_connection.keep_alive_timeout - latency;
+ unsigned int client_timeout;
+
+ if (!(csp->flags & CSP_FLAG_SERVER_KEEP_ALIVE_TIMEOUT_SET))
+ {
+ log_error(LOG_LEVEL_CONNECT, "The server didn't specify how long "
+ "the connection will stay open. Assume it's only a second.");
+ csp->server_connection.keep_alive_timeout = 1;
+ }
+
+ client_timeout = (unsigned)csp->server_connection.keep_alive_timeout - latency;
+
log_error(LOG_LEVEL_CONNECT,
"Waiting for the next client request. "
"Keeping the server socket %d to %s open.",
csp->content_type = 0;
csp->content_length = 0;
csp->expected_content_length = 0;
+ csp->expected_client_content_length = 0;
list_remove_all(csp->headers);
freez(csp->iob->buf);
memset(csp->iob, 0, sizeof(csp->iob));
{
log_error(LOG_LEVEL_CONNECT,
"No additional client request received in time.");
+#ifdef FEATURE_CONNECTION_SHARING
if ((csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING)
&& (socket_is_still_usable(csp->sfd)))
{
+ time_t time_open = time(NULL) - csp->server_connection.timestamp;
+
+ if (csp->server_connection.keep_alive_timeout < time_open + latency)
+ {
+ break;
+ }
+
remember_connection(csp, forward_url(csp, csp->http));
csp->sfd = JB_INVALID_SOCKET;
close_socket(csp->cfd);
}
privoxy_mutex_unlock(&connection_reuse_mutex);
}
+#endif /* def FEATURE_CONNECTION_SHARING */
break;
}
}
if (csp->sfd != JB_INVALID_SOCKET)
{
-#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+#ifdef FEATURE_CONNECTION_SHARING
forget_connection(csp->sfd);
-#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+#endif /* def FEATURE_CONNECTION_SHARING */
close_socket(csp->sfd);
}
*
*********************************************************************/
#ifdef __MINGW32__
-int real_main(int argc, const char *argv[])
+int real_main(int argc, char **argv)
#else
-int main(int argc, const char *argv[])
+int main(int argc, char **argv)
#endif
{
int argc_pos = 0;
config = load_config();
-#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+#ifdef FEATURE_CONNECTION_SHARING
/*
* XXX: Should be relocated once it no
* longer needs to emit log messages.
*/
initialize_reusable_connections();
-#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+#endif /* def FEATURE_CONNECTION_SHARING */
bfd = bind_port_helper(config);