diff src/protocols/gg/lib/http.c @ 11360:cf15c1cdcfbd

[gaim-migrate @ 13582] New Gadu-Gadu implementation. committer: Tailor Script <tailor@pidgin.im>
author Bartoz Oler <bartosz@pidgin.im>
date Sun, 28 Aug 2005 22:46:01 +0000
parents
children 3c536224f0d0
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/gg/lib/http.c	Sun Aug 28 22:46:01 2005 +0000
@@ -0,0 +1,522 @@
+/* $Id: http.c 13582 2005-08-28 22:46:01Z boler $ */
+
+/*
+ *  (C) Copyright 2001-2002 Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License Version
+ *  2.1 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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser 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 <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "libgadu-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#ifdef __GG_LIBGADU_HAVE_PTHREAD
+#  include <pthread.h>
+#endif
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "compat.h"
+#include "libgadu.h"
+
+/*
+ * gg_http_connect() // funkcja pomocnicza
+ *
+ * rozpoczyna połączenie po http.
+ *
+ *  - hostname - adres serwera
+ *  - port - port serwera
+ *  - async - asynchroniczne połączenie
+ *  - method - metoda http (GET, POST, cokolwiek)
+ *  - path - ścieżka do zasobu (musi być poprzedzona ,,/'')
+ *  - header - nagłówek zapytania plus ewentualne dane dla POST
+ *
+ * zaalokowana struct gg_http, którą poźniej należy
+ * zwolnić funkcją gg_http_free(), albo NULL jeśli wystąpił błąd.
+ */
+struct gg_http *gg_http_connect(const char *hostname, int port, int async, const char *method, const char *path, const char *header)
+{
+	struct gg_http *h;
+
+	if (!hostname || !port || !method || !path || !header) {
+		gg_debug(GG_DEBUG_MISC, "// gg_http_connect() invalid arguments\n");
+		errno = EFAULT;
+		return NULL;
+	}
+	
+	if (!(h = malloc(sizeof(*h))))
+		return NULL;
+	memset(h, 0, sizeof(*h));
+
+	h->async = async;
+	h->port = port;
+	h->fd = -1;
+	h->type = GG_SESSION_HTTP;
+
+	if (gg_proxy_enabled) {
+		char *auth = gg_proxy_auth();
+
+		h->query = gg_saprintf("%s http://%s:%d%s HTTP/1.0\r\n%s%s",
+				method, hostname, port, path, (auth) ? auth :
+				"", header);
+		hostname = gg_proxy_host;
+		h->port = port = gg_proxy_port;
+
+		if (auth)
+			free(auth);
+	} else {
+		h->query = gg_saprintf("%s %s HTTP/1.0\r\n%s",
+				method, path, header);
+	}
+
+	if (!h->query) {
+		gg_debug(GG_DEBUG_MISC, "// gg_http_connect() not enough memory for query\n");
+		free(h);
+		errno = ENOMEM;
+		return NULL;
+	}
+	
+	gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", h->query);
+
+	if (async) {
+#ifndef __GG_LIBGADU_HAVE_PTHREAD
+		if (gg_resolve(&h->fd, &h->pid, hostname)) {
+#else
+		if (gg_resolve_pthread(&h->fd, &h->resolver, hostname)) {
+#endif
+			gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver failed\n");
+			gg_http_free(h);
+			errno = ENOENT;
+			return NULL;
+		}
+
+		gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver = %p\n", h->resolver);
+
+		h->state = GG_STATE_RESOLVING;
+		h->check = GG_CHECK_READ;
+		h->timeout = GG_DEFAULT_TIMEOUT;
+	} else {
+		struct in_addr *hn, a;
+
+		if (!(hn = gg_gethostbyname(hostname))) {
+			gg_debug(GG_DEBUG_MISC, "// gg_http_connect() host not found\n");
+			gg_http_free(h);
+			errno = ENOENT;
+			return NULL;
+		} else {
+			a.s_addr = hn->s_addr;
+			free(hn);
+		}
+
+		if (!(h->fd = gg_connect(&a, port, 0)) == -1) {
+			gg_debug(GG_DEBUG_MISC, "// gg_http_connect() connection failed (errno=%d, %s)\n", errno, strerror(errno));
+			gg_http_free(h);
+			return NULL;
+		}
+
+		h->state = GG_STATE_CONNECTING;
+
+		while (h->state != GG_STATE_ERROR && h->state != GG_STATE_PARSING) {
+			if (gg_http_watch_fd(h) == -1)
+				break;
+		}
+
+		if (h->state != GG_STATE_PARSING) {
+			gg_debug(GG_DEBUG_MISC, "// gg_http_connect() some strange error\n");
+			gg_http_free(h);
+			return NULL;
+		}
+	}
+
+	h->callback = gg_http_watch_fd;
+	h->destroy = gg_http_free;
+	
+	return h;
+}
+
+#define gg_http_error(x) \
+	close(h->fd); \
+	h->fd = -1; \
+	h->state = GG_STATE_ERROR; \
+	h->error = x; \
+	return 0;
+
+/*
+ * gg_http_watch_fd()
+ *
+ * przy asynchronicznej obsłudze HTTP funkcję tą należy wywołać, jeśli
+ * zmieniło się coś na obserwowanym deskryptorze.
+ *
+ *  - h - struktura opisująca połączenie
+ *
+ * jeśli wszystko poszło dobrze to 0, inaczej -1. połączenie będzie
+ * zakończone, jeśli h->state == GG_STATE_PARSING. jeśli wystąpi jakiś
+ * błąd, to będzie tam GG_STATE_ERROR i odpowiedni kod błędu w h->error.
+ */
+int gg_http_watch_fd(struct gg_http *h)
+{
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_http_watch_fd(%p);\n", h);
+
+	if (!h) {
+		gg_debug(GG_DEBUG_MISC, "// gg_http_watch_fd() invalid arguments\n");
+		errno = EFAULT;
+		return -1;
+	}
+
+	if (h->state == GG_STATE_RESOLVING) {
+		struct in_addr a;
+
+		gg_debug(GG_DEBUG_MISC, "=> http, resolving done\n");
+
+		if (read(h->fd, &a, sizeof(a)) < (signed)sizeof(a) || a.s_addr == INADDR_NONE) {
+			gg_debug(GG_DEBUG_MISC, "=> http, resolver thread failed\n");
+			gg_http_error(GG_ERROR_RESOLVING);
+		}
+
+		close(h->fd);
+		h->fd = -1;
+
+#ifndef __GG_LIBGADU_HAVE_PTHREAD
+		waitpid(h->pid, NULL, 0);
+#else
+		if (h->resolver) {
+			pthread_cancel(*((pthread_t *) h->resolver));
+			free(h->resolver);
+			h->resolver = NULL;
+		}
+#endif
+
+		gg_debug(GG_DEBUG_MISC, "=> http, connecting to %s:%d\n", inet_ntoa(a), h->port);
+
+		if ((h->fd = gg_connect(&a, h->port, h->async)) == -1) {
+			gg_debug(GG_DEBUG_MISC, "=> http, connection failed (errno=%d, %s)\n", errno, strerror(errno));
+			gg_http_error(GG_ERROR_CONNECTING);
+		}
+
+		h->state = GG_STATE_CONNECTING;
+		h->check = GG_CHECK_WRITE;
+		h->timeout = GG_DEFAULT_TIMEOUT;
+
+		return 0;
+	}
+
+	if (h->state == GG_STATE_CONNECTING) {
+		int res = 0;
+		unsigned int res_size = sizeof(res);
+
+		if (h->async && (getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) {
+			gg_debug(GG_DEBUG_MISC, "=> http, async connection failed (errno=%d, %s)\n", (res) ? res : errno , strerror((res) ? res : errno));
+			close(h->fd);
+			h->fd = -1;
+			h->state = GG_STATE_ERROR;
+			h->error = GG_ERROR_CONNECTING;
+			if (res)
+				errno = res;
+			return 0;
+		}
+
+		gg_debug(GG_DEBUG_MISC, "=> http, connected, sending request\n");
+
+		h->state = GG_STATE_SENDING_QUERY;
+	}
+
+	if (h->state == GG_STATE_SENDING_QUERY) {
+		int res;
+
+		if ((res = write(h->fd, h->query, strlen(h->query))) < 1) {
+			gg_debug(GG_DEBUG_MISC, "=> http, write() failed (len=%d, res=%d, errno=%d)\n", strlen(h->query), res, errno);
+			gg_http_error(GG_ERROR_WRITING);
+		}
+
+		if (res < strlen(h->query)) {
+			gg_debug(GG_DEBUG_MISC, "=> http, partial header sent (led=%d, sent=%d)\n", strlen(h->query), res);
+
+			memmove(h->query, h->query + res, strlen(h->query) - res + 1);
+			h->state = GG_STATE_SENDING_QUERY;
+			h->check = GG_CHECK_WRITE;
+			h->timeout = GG_DEFAULT_TIMEOUT;
+		} else {
+			gg_debug(GG_DEBUG_MISC, "=> http, request sent (len=%d)\n", strlen(h->query));
+			free(h->query);
+			h->query = NULL;
+
+			h->state = GG_STATE_READING_HEADER;
+			h->check = GG_CHECK_READ;
+			h->timeout = GG_DEFAULT_TIMEOUT;
+		}
+
+		return 0;
+	}
+
+	if (h->state == GG_STATE_READING_HEADER) {
+		char buf[1024], *tmp;
+		int res;
+
+		if ((res = read(h->fd, buf, sizeof(buf))) == -1) {
+			gg_debug(GG_DEBUG_MISC, "=> http, reading header failed (errno=%d)\n", errno);
+			if (h->header) {
+				free(h->header);
+				h->header = NULL;
+			}
+			gg_http_error(GG_ERROR_READING);
+		}
+
+		if (!res) {
+			gg_debug(GG_DEBUG_MISC, "=> http, connection reset by peer\n");
+			if (h->header) {
+				free(h->header);
+				h->header = NULL;
+			}
+			gg_http_error(GG_ERROR_READING);
+		}
+
+		gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of header\n", res);
+
+		if (!(tmp = realloc(h->header, h->header_size + res + 1))) {
+			gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for header\n");
+			free(h->header);
+			h->header = NULL;
+			gg_http_error(GG_ERROR_READING);
+		}
+
+		h->header = tmp;
+
+		memcpy(h->header + h->header_size, buf, res);
+		h->header_size += res;
+
+		gg_debug(GG_DEBUG_MISC, "=> http, header_buf=%p, header_size=%d\n", h->header, h->header_size);
+
+		h->header[h->header_size] = 0;
+
+		if ((tmp = strstr(h->header, "\r\n\r\n")) || (tmp = strstr(h->header, "\n\n"))) {
+			int sep_len = (*tmp == '\r') ? 4 : 2;
+			unsigned int left;
+			char *line;
+
+			left = h->header_size - ((long)(tmp) - (long)(h->header) + sep_len);
+
+			gg_debug(GG_DEBUG_MISC, "=> http, got all header (%d bytes, %d left)\n", h->header_size - left, left);
+
+			/* HTTP/1.1 200 OK */
+			if (strlen(h->header) < 16 || strncmp(h->header + 9, "200", 3)) {
+				gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header);
+
+				gg_debug(GG_DEBUG_MISC, "=> http, didn't get 200 OK -- no results\n");
+				free(h->header);
+				h->header = NULL;
+				gg_http_error(GG_ERROR_CONNECTING);
+			}
+
+			h->body_size = 0;
+			line = h->header;
+			*tmp = 0;
+                        
+			gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header);
+
+			while (line) {
+				if (!strncasecmp(line, "Content-length: ", 16)) {
+					h->body_size = atoi(line + 16);
+				}
+				line = strchr(line, '\n');
+				if (line)
+					line++;
+			}
+
+			if (h->body_size <= 0) {
+				gg_debug(GG_DEBUG_MISC, "=> http, content-length not found\n");
+				h->body_size = left;
+			}
+
+			if (left > h->body_size) {
+				gg_debug(GG_DEBUG_MISC, "=> http, oversized reply (%d bytes needed, %d bytes left)\n", h->body_size, left);
+				h->body_size = left;
+			}
+
+			gg_debug(GG_DEBUG_MISC, "=> http, body_size=%d\n", h->body_size);
+
+			if (!(h->body = malloc(h->body_size + 1))) {
+				gg_debug(GG_DEBUG_MISC, "=> http, not enough memory (%d bytes for body_buf)\n", h->body_size + 1);
+				free(h->header);
+				h->header = NULL;
+				gg_http_error(GG_ERROR_READING);
+			}
+
+			if (left) {
+				memcpy(h->body, tmp + sep_len, left);
+				h->body_done = left;
+			}
+
+			h->body[left] = 0;
+
+			h->state = GG_STATE_READING_DATA;
+			h->check = GG_CHECK_READ;
+			h->timeout = GG_DEFAULT_TIMEOUT;
+		}
+
+		return 0;
+	}
+
+	if (h->state == GG_STATE_READING_DATA) {
+		char buf[1024];
+		int res;
+
+		if ((res = read(h->fd, buf, sizeof(buf))) == -1) {
+			gg_debug(GG_DEBUG_MISC, "=> http, reading body failed (errno=%d)\n", errno);
+			if (h->body) {
+				free(h->body);
+				h->body = NULL;
+			}
+			gg_http_error(GG_ERROR_READING);
+		}
+
+		if (!res) {
+			if (h->body_done >= h->body_size) {
+				gg_debug(GG_DEBUG_MISC, "=> http, we're done, closing socket\n");
+				h->state = GG_STATE_PARSING;
+				close(h->fd);
+				h->fd = -1;
+			} else {
+				gg_debug(GG_DEBUG_MISC, "=> http, connection closed while reading (have %d, need %d)\n", h->body_done, h->body_size);
+				if (h->body) {
+					free(h->body);
+					h->body = NULL;
+				}
+				gg_http_error(GG_ERROR_READING);
+			}
+
+			return 0;
+		}
+
+		gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of body\n", res);
+
+		if (h->body_done + res > h->body_size) {
+			char *tmp;
+
+			gg_debug(GG_DEBUG_MISC, "=> http, too much data (%d bytes, %d needed), enlarging buffer\n", h->body_done + res, h->body_size);
+
+			if (!(tmp = realloc(h->body, h->body_done + res + 1))) {
+				gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for data (%d needed)\n", h->body_done + res + 1);
+				free(h->body);
+				h->body = NULL;
+				gg_http_error(GG_ERROR_READING);
+			}
+
+			h->body = tmp;
+			h->body_size = h->body_done + res;
+		}
+
+		h->body[h->body_done + res] = 0;
+		memcpy(h->body + h->body_done, buf, res);
+		h->body_done += res;
+
+		gg_debug(GG_DEBUG_MISC, "=> body_done=%d, body_size=%d\n", h->body_done, h->body_size);
+
+		return 0;
+	}
+	
+	if (h->fd != -1)
+		close(h->fd);
+
+	h->fd = -1;
+	h->state = GG_STATE_ERROR;
+	h->error = 0;
+
+	return -1;
+}
+
+#undef gg_http_error
+
+/*
+ * gg_http_stop()
+ *
+ * jeśli połączenie jest w trakcie, przerywa je. nie zwalnia h->data.
+ * 
+ *  - h - struktura opisująca połączenie
+ */
+void gg_http_stop(struct gg_http *h)
+{
+	if (!h)
+		return;
+
+	if (h->state == GG_STATE_ERROR || h->state == GG_STATE_DONE)
+		return;
+
+	if (h->fd != -1)
+		close(h->fd);
+	h->fd = -1;
+}
+
+/*
+ * gg_http_free_fields() // funkcja wewnętrzna
+ *
+ * zwalnia pola struct gg_http, ale nie zwalnia samej struktury.
+ */
+void gg_http_free_fields(struct gg_http *h)
+{
+	if (!h)
+		return;
+
+	if (h->body) {
+		free(h->body);
+		h->body = NULL;
+	}
+
+	if (h->query) {
+		free(h->query);
+		h->query = NULL;
+	}
+	
+	if (h->header) {
+		free(h->header);
+		h->header = NULL;
+	}
+}
+
+/*
+ * gg_http_free()
+ *
+ * próbuje zamknąć połączenie i zwalnia pamięć po nim.
+ *
+ *  - h - struktura, którą należy zlikwidować
+ */
+void gg_http_free(struct gg_http *h)
+{
+	if (!h)
+		return;
+
+	gg_http_stop(h);
+	gg_http_free_fields(h);
+	free(h);
+}
+
+/*
+ * Local variables:
+ * c-indentation-style: k&r
+ * c-basic-offset: 8
+ * indent-tabs-mode: notnil
+ * End:
+ *
+ * vim: shiftwidth=8:
+ */