Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

665 lines (523 sloc) 15.687 kb
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* Cherokee
*
* Authors:
* Alvaro Lopez Ortega <alvaro@alobbs.com>
*
* Copyright (C) 2001-2011 Alvaro Lopez Ortega
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include "common-internal.h"
#include "downloader.h"
#include "downloader-protected.h"
#include "request.h"
#include "url.h"
#include "header-protected.h"
#include "util.h"
#include <errno.h>
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_FILIO_H
# include <sys/filio.h> /* defines FIONBIO and FIONREAD */
#endif
#ifdef HAVE_SYS_SOCKIO_H
# include <sys/sockio.h> /* defines SIOCATMARK */
#endif
#define ENTRIES "downloader"
ret_t
cherokee_downloader_init (cherokee_downloader_t *n)
{
ret_t ret;
/* Build
*/
ret = cherokee_request_header_init (&n->request);
if (unlikely(ret != ret_ok)) return ret;
ret = cherokee_buffer_init (&n->request_header);
if (unlikely(ret != ret_ok)) return ret;
ret = cherokee_buffer_init (&n->reply_header);
if (unlikely(ret != ret_ok)) return ret;
ret = cherokee_buffer_init (&n->body);
if (unlikely(ret != ret_ok)) return ret;
ret = cherokee_socket_init (&n->socket);
if (unlikely(ret != ret_ok)) return ret;
ret = cherokee_header_new (&n->header, header_type_response);
if (unlikely(ret != ret_ok)) return ret;
cherokee_buffer_init (&n->proxy);
n->proxy_port = 0;
cherokee_buffer_init (&n->tmp1);
cherokee_buffer_init (&n->tmp2);
cherokee_buffer_init (&n->post);
/* Init the properties
*/
n->phase = downloader_phase_init;
/* Lengths
*/
n->content_length = 0;
/* Info
*/
n->info.request_sent = 0;
n->info.headers_recv = 0;
n->info.post_sent = 0;
n->info.body_recv = 0;
n->status = downloader_status_none;
return ret_ok;
}
ret_t
cherokee_downloader_mrproper (cherokee_downloader_t *downloader)
{
/* Free the memory
*/
cherokee_request_header_mrproper (&downloader->request);
cherokee_buffer_mrproper (&downloader->request_header);
cherokee_buffer_mrproper (&downloader->reply_header);
cherokee_buffer_mrproper (&downloader->proxy);
cherokee_buffer_mrproper (&downloader->body);
cherokee_buffer_mrproper (&downloader->post);
cherokee_buffer_mrproper (&downloader->tmp1);
cherokee_buffer_mrproper (&downloader->tmp2);
cherokee_socket_close (&downloader->socket);
cherokee_socket_mrproper (&downloader->socket);
cherokee_header_free (downloader->header);
return ret_ok;
}
CHEROKEE_ADD_FUNC_NEW (downloader);
CHEROKEE_ADD_FUNC_FREE (downloader);
ret_t
cherokee_downloader_set_url (cherokee_downloader_t *downloader, cherokee_buffer_t *url_string)
{
return cherokee_request_header_parse_string (&downloader->request, url_string);
}
ret_t
cherokee_downloader_set_keepalive (cherokee_downloader_t *downloader, cherokee_boolean_t active)
{
downloader->request.keepalive = active;
return ret_ok;
}
ret_t
cherokee_downloader_set_proxy (cherokee_downloader_t *downloader,
cherokee_buffer_t *proxy,
cuint_t port)
{
char *tmp;
/* Skip 'http(s)://'
*/
tmp = strchr (proxy->buf, '/');
if (tmp == NULL)
tmp = proxy->buf;
else
tmp += 2;
/* Copy the values
*/
cherokee_buffer_clean (&downloader->proxy);
cherokee_buffer_add (&downloader->proxy, tmp, strlen(tmp));
downloader->proxy_port = port;
return ret_ok;
}
ret_t
cherokee_downloader_set_auth (cherokee_downloader_t *downloader,
cherokee_buffer_t *user,
cherokee_buffer_t *password)
{
return cherokee_request_header_set_auth (&downloader->request, http_auth_basic, user, password);
}
ret_t
cherokee_downloader_set_cryptor (cherokee_downloader_t *downloader,
cherokee_cryptor_t *cryptor)
{
ret_t ret;
cherokee_socket_t *sock = &downloader->socket;
if (cryptor == NULL)
return ret_ok;
ret = cherokee_cryptor_client_new (cryptor, (cherokee_cryptor_client_t **)&sock->cryptor);
if (ret != ret_ok)
return ret;
return ret_ok;
}
ret_t
cherokee_downloader_get_reply_code (cherokee_downloader_t *downloader, cherokee_http_t *code)
{
*code = downloader->header->response;
return ret_ok;
}
static ret_t
connect_to (cherokee_downloader_t *downloader,
cherokee_buffer_t *host,
cuint_t port,
int protocol)
{
ret_t ret;
cherokee_socket_t *sock = &downloader->socket;
TRACE(ENTRIES, "host=%s port=%d proto=%d\n", host->buf, port, protocol);
/* Create the socket
*/
ret = cherokee_socket_create_fd (sock, AF_INET);
if (unlikely(ret != ret_ok)) return ret_error;
/* Set the port
*/
SOCKET_SIN_PORT(sock) = htons(port);
/* Supposing it's an IP: convert it.
*/
ret = cherokee_socket_pton (sock, host);
if (ret != ret_ok) {
/* Oops! It might be a hostname. Try to resolve it.
*/
ret = cherokee_socket_gethostbyname (sock, host);
if (unlikely(ret != ret_ok)) return ret_error;
}
/* Connect to server
*/
ret = cherokee_socket_connect (sock);
TRACE(ENTRIES, "socket=%p ret=%d\n", sock, ret);
if (unlikely(ret != ret_ok))
return ret;
/* Is this connection TLS?
*/
if ((protocol == https) &&
(sock->cryptor != NULL))
{
ret = cherokee_socket_init_client_tls (sock, host);
if (ret != ret_ok)
return ret;
}
TRACE(ENTRIES, "Exits ok; socket=%p\n", sock);
return ret_ok;
}
ret_t
cherokee_downloader_connect (cherokee_downloader_t *downloader)
{
ret_t ret;
cherokee_boolean_t uses_proxy;
cherokee_url_t *url = &downloader->request.url;
/* Does it use a proxy?
*/
uses_proxy = ! cherokee_buffer_is_empty (&downloader->proxy);
ret = cherokee_request_header_uses_proxy (&downloader->request, uses_proxy);
if (ret != ret_ok) return ret;
/* Connect
*/
if (uses_proxy) {
ret = connect_to (downloader, &downloader->proxy, downloader->proxy_port, http);
if (ret != ret_ok) return ret;
} else {
ret = connect_to (downloader, &url->host, url->port, url->protocol);
if (ret != ret_ok) return ret;
}
return ret_ok;
}
static int
is_connected (cherokee_downloader_t *downloader)
{
return (downloader->socket.socket != -1);
}
static ret_t
downloader_send_buffer (cherokee_downloader_t *downloader, cherokee_buffer_t *buf)
{
ret_t ret;
size_t written = 0;
cherokee_socket_t *sock = &downloader->socket;;
ret = cherokee_socket_bufwrite (sock, buf, &written);
switch (ret) {
case ret_ok:
/* Drop the header that has been sent
*/
cherokee_buffer_drop_ending (buf, written);
if (cherokee_buffer_is_empty (buf)) {
return ret_ok;
}
return ret_eagain;
case ret_eagain:
return ret;
default:
return ret_error;
}
}
static ret_t
downloader_header_read (cherokee_downloader_t *downloader,
cherokee_buffer_t *tmp1,
cherokee_buffer_t *tmp2)
{
ret_t ret;
cuint_t len;
size_t read_ = 0;
cherokee_socket_t *sock = &downloader->socket;
cherokee_http_t error_code = http_bad_request;
UNUSED(tmp2);
ret = cherokee_socket_bufread (sock, &downloader->reply_header, DEFAULT_RECV_SIZE, &read_);
switch (ret) {
case ret_eof:
return ret_eof;
case ret_eagain:
return ret_eagain;
case ret_ok:
/* Count
*/
downloader->info.headers_recv += read_;
/* Check the header. Is it complete?
*/
ret = cherokee_header_has_header (downloader->header, &downloader->reply_header, 0);
switch (ret) {
case ret_ok:
break;
case ret_not_found:
/* It needs to read more headers ...
*/
return ret_eagain;
default:
/* Too many initial CRLF
*/
return ret_error;
}
/* Parse the header
*/
ret = cherokee_header_parse (downloader->header,
&downloader->reply_header,
&error_code);
if (unlikely (ret != ret_ok)) return ret_error;
/* Look for the length, it will need to drop out the header from the buffer
*/
cherokee_header_get_length (downloader->header, &len);
/* Maybe it has some body
*/
if (downloader->reply_header.len > len) {
uint32_t body_chunk;
/* Skip the CRLF separator and copy the body
*/
len += 2;
body_chunk = downloader->reply_header.len - len;
downloader->info.body_recv += body_chunk;
cherokee_buffer_add (&downloader->body, downloader->reply_header.buf + len, body_chunk);
cherokee_buffer_drop_ending (&downloader->reply_header, body_chunk);
}
/* Try to read the "Content-Length" response header
*/
ret = cherokee_header_has_known (downloader->header, header_content_length);
if (ret == ret_ok) {
cherokee_buffer_clean (tmp1);
ret = cherokee_header_copy_known (downloader->header, header_content_length, tmp1);
cherokee_atoi (tmp1->buf, &downloader->content_length);
TRACE (ENTRIES, "Known lenght: %d bytes\n", downloader->content_length);
}
return ret_ok;
case ret_error:
/* Opsss.. something has failed
*/
return ret_error;
default:
RET_UNKNOWN (ret);
return ret;
}
return ret_error;
}
static ret_t
downloader_step (cherokee_downloader_t *downloader)
{
ret_t ret;
size_t read_ = 0;
cherokee_socket_t *sock = &downloader->socket;
ret = cherokee_socket_bufread (sock, &downloader->body, DEFAULT_RECV_SIZE, &read_);
switch (ret) {
case ret_eagain:
return ret_eagain;
case ret_ok:
/* Count
*/
downloader->info.body_recv += read_;
/* Has it finished?
*/
if (downloader->info.body_recv >= downloader->content_length) {
return ret_eof_have_data;
}
return ret_ok;
default:
return ret;
}
return ret_ok;
}
ret_t
cherokee_downloader_step (cherokee_downloader_t *downloader,
cherokee_buffer_t *ext_tmp1,
cherokee_buffer_t *ext_tmp2)
{
ret_t ret;
cherokee_buffer_t *tmp1;
cherokee_buffer_t *tmp2;
/* Set the temporary buffers
*/
tmp1 = (ext_tmp1) ? ext_tmp1 : &downloader->tmp1;
tmp2 = (ext_tmp2) ? ext_tmp2 : &downloader->tmp2;
TRACE (ENTRIES, "phase=%d\n", downloader->phase);
/* Process it
*/
switch (downloader->phase) {
case downloader_phase_init: {
cherokee_request_header_t *req = &downloader->request;
TRACE(ENTRIES, "Phase %s\n", "init");
/* Maybe add the post info
*/
if (! cherokee_buffer_is_empty (&downloader->post)) {
req->method = http_post;
req->post_len = downloader->post.len;
}
/* Build the request header
*/
ret = cherokee_request_header_build_string (req, &downloader->request_header, tmp1, tmp2);
if (unlikely(ret < ret_ok))
return ret;
/* Deal with the connection
*/
if (! is_connected (downloader)) {
ret = cherokee_downloader_connect (downloader);
if (ret < ret_ok) return ret;
}
/* Everything is ok, go ahead!
*/
downloader->phase = downloader_phase_send_headers;
}
case downloader_phase_send_headers:
TRACE(ENTRIES, "Phase %s\n", "send_headers");
ret = downloader_send_buffer (downloader, &downloader->request_header);
if (unlikely(ret != ret_ok))
return ret;
BIT_SET (downloader->status, downloader_status_headers_sent);
downloader->phase = downloader_phase_send_post;
case downloader_phase_send_post:
TRACE(ENTRIES, "Phase %s\n", "send_post");
if (! cherokee_buffer_is_empty (&downloader->post)) {
size_t written = 0;
ret = cherokee_socket_bufwrite (&downloader->socket, &downloader->post, &written);
if (ret != ret_ok) {
return ret;
}
cherokee_buffer_move_to_begin (&downloader->post, written);
if (! cherokee_buffer_is_empty (&downloader->post)) {
return ret_eagain;
}
}
BIT_SET (downloader->status, downloader_status_post_sent);
downloader->phase = downloader_phase_read_headers;
break;
case downloader_phase_read_headers:
TRACE(ENTRIES, "Phase %s\n", "read_headers");
ret = downloader_header_read (downloader, tmp1, tmp2);
if (unlikely(ret != ret_ok))
return ret;
/* We have the header parsed, continue..
*/
BIT_SET (downloader->status, downloader_status_headers_received);
downloader->phase = downloader_phase_step;
/* Does it read the full reply in the first received chunk?
*/
if (downloader->info.body_recv >= downloader->content_length) {
BIT_SET (downloader->status, downloader_status_data_available);
BIT_SET (downloader->status, downloader_status_finished);
return ret_eof_have_data;
}
case downloader_phase_step:
TRACE(ENTRIES, "Phase %s\n", "step");
ret = downloader_step (downloader);
switch (ret) {
case ret_error:
break;
case ret_ok:
BIT_SET (downloader->status, downloader_status_data_available);
break;
case ret_eof_have_data:
BIT_SET (downloader->status, downloader_status_data_available);
BIT_SET (downloader->status, downloader_status_finished);
break;
case ret_eof:
BIT_UNSET (downloader->status, downloader_status_data_available);
BIT_SET (downloader->status, downloader_status_finished);
break;
case ret_eagain:
BIT_UNSET (downloader->status, downloader_status_data_available);
break;
default:
RET_UNKNOWN(ret);
}
return ret;
case downloader_phase_finished:
TRACE(ENTRIES, "Phase %s\n", "finished");
BIT_SET (downloader->status, downloader_status_finished);
BIT_UNSET (downloader->status, downloader_status_data_available);
return ret_ok;
default:
SHOULDNT_HAPPEN;
break;
}
return ret_ok;
}
ret_t
cherokee_downloader_reuse (cherokee_downloader_t *downloader)
{
TRACE(ENTRIES, "%p\n", downloader);
downloader->phase = downloader_phase_init;
downloader->info.request_sent = 0;
downloader->info.headers_recv = 0;
downloader->info.post_sent = 0;
downloader->info.body_recv = 0;
cherokee_buffer_clean (&downloader->request_header);
cherokee_buffer_clean (&downloader->reply_header);
cherokee_buffer_clean (&downloader->body);
cherokee_buffer_clean (&downloader->post);
cherokee_request_header_clean (&downloader->request);
return ret_ok;
}
ret_t
cherokee_downloader_is_request_sent (cherokee_downloader_t *downloader)
{
if (downloader->phase > downloader_phase_send_headers)
return ret_ok;
return ret_deny;
}
ret_t
cherokee_downloader_headers_available(cherokee_downloader_t *downloader)
{
return (downloader->phase == downloader_phase_step) ? ret_ok: ret_deny;
}
ret_t
cherokee_downloader_data_available(cherokee_downloader_t *downloader)
{
return (downloader->phase == downloader_phase_step) ? ret_ok: ret_deny;
}
ret_t
cherokee_downloader_finished(cherokee_downloader_t *downloader)
{
ret_t ret = ret_deny;
if (downloader->info.body_recv >= downloader->content_length) {
ret = ret_ok;
} else if (downloader->phase == downloader_phase_finished) {
ret = ret_ok;
}
return ret;
}
ret_t
cherokee_downloader_get_status(cherokee_downloader_t *downloader, cherokee_downloader_status_t *status)
{
if (status != NULL) {
*status = downloader->status;
}
return ret_ok;
}
ret_t
cherokee_downloader_get_reply_hdr (cherokee_downloader_t *downloader, cherokee_buffer_t **header)
{
if (header != NULL) {
*header = &downloader->reply_header;
}
return ret_ok;
}
Jump to Line
Something went wrong with that request. Please try again.