Logo Search packages:      
Sourcecode: kazehakase version File versions

kz-http.c

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2003 Hiroyuki Ikezoe
 *  Copyright (C) 2003 Takuro Ashie
 *  Copyright (C) 2004 Hidetaka Iwai
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdlib.h>
#include <string.h>
#define __USE_XOPEN
#include <time.h>
#include <glib/gi18n.h>
#include <unistd.h>

#include "gobject-utils.h"
#include "kz-http.h"
#include "gnet.h"
#include "kazehakase.h"
#include "kz-profile.h"
#include "kz-proxy-item.h"

#define BUFFER_SIZE 256

enum {
      PROP_0,
      PROP_METHOD,
      PROP_HOSTNAME,
      PROP_PORT,
      PROP_PATH
};

const gchar *methods[] = {
      "GET",
      "HEAD",
      "POST"
};
static guint n_methods = G_N_ELEMENTS(methods);

struct _KzHTTPPrivate 
{
      GIOChannel *iochannel;

      GTcpSocket *socket;

      KzHTTPMethodType method; /* Access Method */
      
      gchar *hostname;
      guint port;
      gchar *path;

      gboolean header;
      gboolean use_proxy;

      gboolean chunked; /* Transfer-Encoding is chunked or not */
      gsize chunk_size;

      gboolean redirection; /* for Redirection 3xx */
      gchar    *location;   /* Redirect-URI */
      gchar    *content_type;
      gchar    *content_encoding;

      gchar *post_data;
};

static void kz_http_class_init      (KzHTTPClass *klass);
static void kz_http_init            (KzHTTP *http);
static void kz_http_set_property    (GObject *object,
                             guint prop_id,
                             const GValue *value,
                             GParamSpec *pspec);
static void kz_http_get_property    (GObject *object,
                             guint prop_id,
                             GValue *value,
                             GParamSpec *pspec);
static void kz_http_dispose         (GObject *object);
static void kz_http_finalize        (GObject *object);

static GIOStatus kz_http_in_header       (KzHTTP *http,
                                GIOChannel *iochannel);
static GIOStatus kz_http_in_body         (KzHTTP *http,
                                GIOChannel *iochannel);
static GIOStatus kz_http_in_chunked_body (KzHTTP *http,
                                GIOChannel *iochannel);
static GIOStatus kz_http_read_from_io    (KzIO *io,
                                GIOChannel *iochannel);

static void     cb_http_connect  (GTcpSocket *socket, 
                          GTcpSocketConnectAsyncStatus status,
                          gpointer data);

static void kz_http_error (KzHTTP *http);
static void kz_http_start (KzIO *io);

static void     kz_http_set_chunked_mode (KzHTTP *http);
static void     kz_http_set_redirection  (KzHTTP *http);
static gboolean kz_http_use_proxy        (KzHTTP *http);
static gboolean kz_http_is_in_header     (KzHTTP *http);
static gboolean kz_http_is_chunked_mode  (KzHTTP *http);
static gboolean kz_http_is_redirection   (KzHTTP *http);

GType
kz_http_method_type_get_type (void)
{
      static GType etype = 0;
      if (etype == 0) {
            static const GEnumValue values[] = {
                  { KZ_HTTP_METHOD_GET,  "KZ_HTTP_METHOD_GET",  "GET" },
                  { KZ_HTTP_METHOD_HEAD, "KZ_HTTP_METHOD_HEAD", "HEAD" },
                  { KZ_HTTP_METHOD_POST, "KZ_HTTP_METHOD_POST", "POST" },
                  { 0, NULL, NULL }
            };
            etype = g_enum_register_static ("KzHTTPMethodType", values);
      }
      return etype;
}


static KzIOClass *parent_class = NULL;

KZ_OBJECT_GET_TYPE(kz_http, "KzHTTP", KzHTTP,
               kz_http_class_init, kz_http_init,
               KZ_TYPE_IO)

static void
kz_http_class_init (KzHTTPClass *klass)
{
      GObjectClass *object_class;
      KzIOClass *io_class;

      parent_class = g_type_class_peek_parent (klass);
      object_class = (GObjectClass *) klass;
      io_class     = (KzIOClass *) klass;

      object_class->dispose      = kz_http_dispose;
      object_class->finalize     = kz_http_finalize;
      object_class->set_property = kz_http_set_property;
      object_class->get_property = kz_http_get_property;
      
      io_class->read_from_io  = kz_http_read_from_io;
      io_class->io_start      = kz_http_start;

      g_object_class_install_property(
            object_class,
            PROP_METHOD,
            g_param_spec_enum(
                  "method",
                  _("Method"),
                  _("Request Method"),
                  KZ_TYPE_HTTP_METHOD_TYPE,
                  KZ_HTTP_METHOD_GET,
                  G_PARAM_READWRITE));
      g_object_class_install_property(
            object_class,
            PROP_HOSTNAME,
            g_param_spec_string(
                  "hostname",
                  _("Hostname"),
                  _("The Hostname of the URI"),
                  NULL,
                  G_PARAM_READWRITE));
      g_object_class_install_property(
            object_class,
            PROP_PORT,
            g_param_spec_uint(
                  "port",
                  _("Port"),
                  _("The Port number of the URI"),
                  0,
                  65535,
                  80,
                  G_PARAM_READWRITE));
      g_object_class_install_property(
            object_class,
            PROP_PATH,
            g_param_spec_string(
                  "path",
                  _("Path"),
                  _("The Path of the URI"),
                  NULL,
                  G_PARAM_READWRITE));
}


static void
kz_http_init (KzHTTP *http)
{
      http->priv = g_new0(KzHTTPPrivate, 1);

      http->priv->socket          = NULL;

      http->priv->hostname        = NULL;
      http->priv->port            = 80;
      http->priv->path            = NULL;
      
      http->priv->header          = TRUE;
      http->priv->chunked         = FALSE;
      http->priv->redirection     = FALSE;
      http->priv->location        = NULL;
      http->priv->content_type    = NULL;
      http->priv->content_encoding = NULL;

      http->priv->chunk_size      = 0;

      http->priv->post_data       = NULL;
}


void
kz_http_dispose (GObject *object)
{
      KzHTTP *http;

      http = KZ_HTTP(object);

      if (http->priv->socket)
            gnet_tcp_socket_unref(http->priv->socket);
      if (http->priv->hostname)
            g_free(http->priv->hostname);
      if (http->priv->path)
            g_free(http->priv->path);
      if (http->priv->location)
            g_free(http->priv->location);
      if (http->priv->content_type)
            g_free(http->priv->content_type);
      if (http->priv->content_encoding)
            g_free(http->priv->content_encoding);
      if (http->priv->post_data)
            g_free(http->priv->post_data);

      http->priv->socket    = NULL;
      http->priv->hostname  = NULL;
      http->priv->path      = NULL;
      http->priv->location  = NULL;
      http->priv->content_type = NULL;
      http->priv->content_encoding = NULL;
      http->priv->post_data = NULL;

      if (G_OBJECT_CLASS (parent_class)->dispose)
            G_OBJECT_CLASS (parent_class)->dispose(object);
}


KZ_OBJECT_FINALIZE(kz_http, KzHTTP)


static void
kz_http_set_property (GObject *object,
                  guint prop_id,
                  const GValue *value,
                  GParamSpec *pspec)
{
      KzHTTP *http = KZ_HTTP(object);

      switch (prop_id)
      {
      case PROP_METHOD:
            http->priv->method = g_value_get_enum(value);
            break;
      case PROP_HOSTNAME:
            g_free(http->priv->hostname);
            http->priv->hostname = g_value_dup_string(value);
            break;
      case PROP_PORT:
            http->priv->port = g_value_get_uint(value);
            break;
      case PROP_PATH:
            g_free(http->priv->path);
            http->priv->path = g_value_dup_string(value);
            break;
      default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
            break;
      }
}


static void
kz_http_get_property (GObject *object,
                  guint prop_id,
                  GValue *value,
                  GParamSpec *pspec)
{
      KzHTTP *http = KZ_HTTP(object);

      switch (prop_id)
      {
      case PROP_METHOD:
            g_value_get_enum(value);
            break;
      case PROP_HOSTNAME:
            g_value_set_string(value, http->priv->hostname);
            break;
      case PROP_PORT:
            g_value_set_uint(value, http->priv->port);
            break;
      case PROP_PATH:
            g_value_set_string(value, http->priv->path);
            break;
      default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
            break;
      }
}


KzHTTP *
kz_http_new (const gchar *uri)
{
      KzHTTP *http;
      GURI *guri;
      gchar *path = NULL, *hostname = NULL;
      guint port = 80;

      guri = gnet_uri_new(uri);

      if (guri)
      {
            hostname = guri->hostname;
            if (guri->port)
                  port = guri->port;
            else if (strncmp(guri->scheme, "https", 5) == 0)
                  port = 443;
            else
                  port = 80;
            if (guri->query)
                  path = g_strdup_printf("%s?%s",
                                     guri->path, guri->query);
            else
                  path = g_strdup(guri->path);
      }

      
      http = g_object_new(KZ_TYPE_HTTP,
                     "uri",      uri,
                     "hostname", hostname,
                     "port",     port,
                     "path",     path,
                     NULL);
      
      if (guri)
            gnet_uri_delete(guri);
      g_free(path);

      return http;
}


KzHTTP *
kz_http_post_new (const gchar *uri, const gchar *post_data)
{
      KzHTTP *http = kz_http_new(uri);

      g_object_set(G_OBJECT(http), "method", KZ_HTTP_METHOD_POST, NULL);
      http->priv->post_data = g_strdup(post_data);

      return http;
}


static GIOStatus
kz_http_in_header(KzHTTP *http, GIOChannel *iochannel)
{
      GIOStatus iostatus;
      GString *buffer = NULL;

      buffer = g_string_sized_new(0);
      /* Read the data into our buffer */
      iostatus = g_io_channel_read_line_string(iochannel,
                                     buffer,
                                     NULL,
                                     NULL);
      
      if (iostatus == G_IO_STATUS_ERROR)
            return iostatus;
      
      if (strncmp(buffer->str, "HTTP/1.1", 8) == 0)
      {
            switch (buffer->str[9])
            {
             case '2': /* 2xx Succeccful */
                  break;
             case '3': /* 3xx Redirection   */
                  kz_http_set_redirection(http);
                  break;
             case '1': /* 1xx Informational */
             case '4': /* 4xx Client Error  */
             case '5': /* 5xx Server Error  */
             default:
                  {
                        g_warning("%s", buffer->str);
                        iostatus = G_IO_STATUS_ERROR;
                        break;
                  }
            }
      }
      else if (g_ascii_strncasecmp(buffer->str, "Content-Length:", 15) == 0)
      {
            guint size = (guint)strtol(buffer->str + 15, NULL, 10);
            g_object_set(G_OBJECT(KZ_IO(http)),
                       "file_size", size, NULL);
      }
      else if (g_ascii_strncasecmp(buffer->str, "Transfer-Encoding:", 18) == 0)
      {
            const gchar *value = buffer->str + 18;
            while (*value && g_ascii_isspace(*value))
                  ++value;
            if (g_str_has_prefix(value, "chunked"))
                  kz_http_set_chunked_mode(http);
      }
      else if (g_ascii_strncasecmp(buffer->str, "Content-Type:", 13) == 0)
      {
            const gchar *value = buffer->str + 13;
            while (*value && g_ascii_isspace(*value))
                  ++value;
            http->priv->content_type = g_strchomp(g_strdup(value));
      }
      else if (g_ascii_strncasecmp(buffer->str, "Content-Encoding:", 17) == 0)
      {
            const gchar *value = buffer->str + 17;
            while (*value && g_ascii_isspace(*value))
                  ++value;
            http->priv->content_encoding = g_strchomp(g_strdup(value));
      }
      else if (g_ascii_strncasecmp(buffer->str, "Location:", 9) == 0)
      {
            const gchar *value = buffer->str + 9;
            while (*value && g_ascii_isspace(*value))
                  ++value;
            http->priv->location = g_strdup(value);
      }
      else if (g_ascii_strncasecmp(buffer->str, "Last-Modified:", 15) == 0)
      {
            struct tm t;
            strptime(buffer->str + 15,
                   " %a, %d %b %Y %H:%M:%S %z", &t);
            g_object_set(G_OBJECT(KZ_IO(http)),
                       "last_modified", (guint)mktime(&t), NULL);
      }
      else if (strncmp(buffer->str, "\r\n", 2) == 0) /* End of Header*/ 
      {
            http->priv->header = FALSE;
      }

      g_string_free(buffer, TRUE);
      
      return iostatus;
}

static GIOStatus
kz_http_in_chunked_body(KzHTTP *http, GIOChannel *iochannel)
{
      GIOStatus iostatus = G_IO_STATUS_NORMAL;
      GError *e = NULL;
      gchar *buffer = NULL;
      gsize bytes_read;

      /* These codes are quite silly! FIX!! */
      /* Get chunk size */
      if (http->priv->chunk_size <= 0)
      {
            iostatus = g_io_channel_read_line(iochannel,
                                      &buffer,
                                      &bytes_read,
                                      NULL,
                                      &e);
            if (iostatus == G_IO_STATUS_NORMAL)
            {
                  http->priv->chunk_size = strtol(buffer, NULL, 16);
                  if (buffer)
                  {
                        g_free(buffer);
                        buffer = NULL;
                  }
                  /* check EOF */
                  if (http->priv->chunk_size == 0)
                        iostatus = G_IO_STATUS_EOF;
            }
      }
      if (iostatus == G_IO_STATUS_NORMAL)
      {
            /* Get a chunked body */
            buffer = g_new0(gchar, http->priv->chunk_size);
            iostatus = g_io_channel_read_chars(iochannel, buffer,
                                       http->priv->chunk_size,
                                       &bytes_read,
                                       &e);
            if (iostatus == G_IO_STATUS_NORMAL)
            {
                  KZ_IO_CLASS (parent_class)->io_to_buffer(KZ_IO(http), bytes_read, buffer);

                  http->priv->chunk_size -= bytes_read;
            }
            if (buffer)
            {
                  g_free(buffer);
                  buffer = NULL;
            }
      }
      if (iostatus == G_IO_STATUS_NORMAL)
      {
            /* Get CRLF */
            if (http->priv->chunk_size <= 0)
            {
                  iostatus = g_io_channel_read_line(iochannel,
                                            &buffer,
                                            &bytes_read,
                                            NULL,
                                            &e);
                  if (buffer)
                        g_free(buffer);
            }
      }

      if (e)
            g_error_free(e);

      return iostatus;
}


static GIOStatus
kz_http_in_body(KzHTTP *http, GIOChannel *iochannel)
{
      GIOStatus iostatus;
      gsize bytes_read;
      gchar buffer[BUFFER_SIZE];

      /* Read the data into our buffer */
      iostatus = g_io_channel_read_chars(iochannel, buffer, 
                                 sizeof(buffer),
                                 &bytes_read,
                                 NULL);

      if (iostatus == G_IO_STATUS_NORMAL)
      {     
            KZ_IO_CLASS (parent_class)->io_to_buffer(KZ_IO(http), bytes_read, buffer);
            if (bytes_read == 0)
                  iostatus = G_IO_STATUS_EOF;
      }

      return iostatus;
}


static GIOStatus
kz_http_read_from_io (KzIO *io, GIOChannel *iochannel)
{
      KzHTTP *http;
      GIOStatus iostatus;

      g_return_val_if_fail(KZ_IS_HTTP(io), G_IO_STATUS_ERROR);

      http = KZ_HTTP(io);

      if (kz_http_is_in_header(http)) /* Header Section */
            iostatus = kz_http_in_header(http, iochannel);
      else if (kz_http_is_chunked_mode(http)) /* Chunked Body */
            iostatus = kz_http_in_chunked_body(http, iochannel);
      else        /* Entity-Body Section */
            iostatus = kz_http_in_body(http, iochannel);
      
      if (iostatus == G_IO_STATUS_EOF)
      {
            if (kz_http_is_redirection(http))
            {
                  g_object_set(G_OBJECT(http),
                             "uri", g_strchomp(http->priv->location),
                             NULL);
                  iostatus = G_IO_STATUS_AGAIN;
            }
            else if (http->priv->content_encoding)
            {
                  return kz_io_decode_buffer(KZ_IO(http), http->priv->content_encoding);
            }
      }

      return iostatus;
}


static void 
cb_http_connect(GTcpSocket *socket,
            GTcpSocketConnectAsyncStatus status, gpointer data)
{
      KzHTTP *http;
      GIOStatus iostatus;
      GIOChannel *iochannel;
      gchar *command;
      const gchar *method = methods[0];
      gsize n;
      gchar *URL;
      gchar *host_header;

      http = KZ_HTTP(data);

      if ( status != GTCP_SOCKET_CONNECT_ASYNC_STATUS_OK )
      {
            kz_http_error(http);
            return;
      }

      /* Get the IOChannel */
      iochannel = gnet_tcp_socket_get_io_channel(socket);
      if (iochannel == NULL)
      {
            kz_http_error(http);
            return;
      }

      http->priv->socket = socket;
      g_io_channel_ref(iochannel);
      KZ_IO(http)->iochannel = iochannel;

      g_io_channel_set_flags(KZ_IO(http)->iochannel,
                         G_IO_FLAG_NONBLOCK, NULL);

      /* proxy */
      if (kz_http_use_proxy(http))
      {
            URL = g_strdup_printf("http://%s:%u%s",
                              http->priv->hostname,
                              http->priv->port,
                              http->priv->path);
      }
      else
      {
            URL = g_strdup(http->priv->path);
      }

      /* Set method */
      if (http->priv->method >= 0 && http->priv->method < n_methods)
            method = methods[http->priv->method];
      else
            g_warning("KzHTTP: Invalid method type was specified!");


      host_header = g_strdup_printf("Host: %s:%u\r\n",
                            http->priv->hostname, http->priv->port);
      /* Send GET command */
      if (http->priv->method == KZ_HTTP_METHOD_POST)
      {
            gchar *c_len;
            if (http->priv->post_data)
                  c_len = g_strdup_printf("%d", strlen(http->priv->post_data));
            else
                  c_len = g_strdup("0");

            command = g_strconcat(method, " ",
                              URL, " HTTP/1.1\r\n",
                              host_header,
                              "User-Agent: Kazehakase/"VERSION "\r\n",
                              "Content-Type: text/xml\r\n",
                              "Content-Length: ", c_len, "\r\n",
                              "Accept-Encoding: gzip,deflate\r\n",
                              "Connection: close\r\n\r\n",
                              http->priv->post_data, "\r\n", NULL);
            g_free(c_len);
      }
      else
      {
            command = g_strconcat(method, " ",
                              URL, " HTTP/1.1\r\n",
                              host_header,
                              "User-Agent: Kazehakase/"VERSION "\r\n",
                              "Accept-Encoding: gzip,deflate\r\n",
                              "Connection: close\r\n\r\n", NULL);
      }

      iostatus = g_io_channel_write_chars(KZ_IO(http)->iochannel,
                                  command,
                                  strlen(command),
                                  &n,
                                  NULL);

      g_free(command);
      g_free(host_header);
      g_free(URL);

      if (iostatus != G_IO_STATUS_NORMAL)
      {
            kz_http_error(http);
            return;
      }

      /* call io_set_iochannel, start to loading */
      KZ_IO_CLASS (parent_class)->io_set_channel(KZ_IO(http));
}

static void
kz_http_start (KzIO *io)
{
      gchar proxy_name[1024];
      gboolean exist, use_proxy;
      gchar *http_host = NULL;
      guint http_port;
      KzHTTP *http;
      KzProxyItem *item = NULL;
      
      g_return_if_fail(KZ_IS_HTTP(io));
      http = KZ_HTTP(io);
      
      /* proxy check */
      KZ_CONF_GET("Global", "use_proxy", use_proxy, BOOL);
      if (!use_proxy)
            goto NO_PROXY;
      
      exist = KZ_CONF_GET("Global", "proxy_name", proxy_name, STRING);
      if (!exist)
      {
            /* We can find no proxy setting, so connect without proxy. */
            goto NO_PROXY;
      }

      item = kz_proxy_find(proxy_name);
      if(!item)
      {
            /* There is no proxy object whose name is proxy_name, 
               so connect without proxy */
            goto NO_PROXY;
      }
      
      /* check the need of the poxy */
      if (item->no_proxies_on)
      {
            gchar **no_proxies;
            gint i = 0;
            no_proxies = g_strsplit_set(item->no_proxies_on, ", ", -1);

            if (!no_proxies)  
                  goto NO_PROXY;
            while (no_proxies[i])
            {
                  if (g_str_has_suffix(http->priv->hostname, no_proxies[i]))
                  {
                        g_strfreev(no_proxies);
                        goto NO_PROXY;
                  }
                  i++;
            }
            g_strfreev(no_proxies);
      }

      /* There is a valid proxy setting, so connect with this proxy*/
      http->priv->use_proxy = TRUE;
      gnet_tcp_socket_connect_async(item->http_host, item->http_port,
                              cb_http_connect, http);

      g_object_unref(G_OBJECT(item));
      return;
      
NO_PROXY:
      http_host = http->priv->hostname;
      http_port = http->priv->port;
      gnet_tcp_socket_connect_async(http_host, http_port,
                              cb_http_connect, http);
      return;
}


static void
kz_http_error (KzHTTP *http)
{
      g_return_if_fail(KZ_IS_HTTP(http));

      KZ_IO_CLASS (parent_class)->io_error(KZ_IO(http));
}


static void
kz_http_set_chunked_mode (KzHTTP *http)
{
      g_return_if_fail(KZ_IS_HTTP(http));

      http->priv->chunked = TRUE;
}

static void
kz_http_set_redirection (KzHTTP *http)
{
      g_return_if_fail(KZ_IS_HTTP(http));

      http->priv->redirection = TRUE;
}

static gboolean
kz_http_use_proxy (KzHTTP *http)
{
      return http->priv->use_proxy;
}

static gboolean
kz_http_is_in_header (KzHTTP *http)
{
      return http->priv->header;
}

static gboolean
kz_http_is_chunked_mode (KzHTTP *http)
{
      return http->priv->chunked;
}

static gboolean
kz_http_is_redirection (KzHTTP *http)
{
      return http->priv->redirection;
}

Generated by  Doxygen 1.6.0   Back to index