#include #include #include #include #include #include #include #include #include struct PHB { GdkInputFunction func; gpointer data; char *host; int port; gint inpa; GSList *hosts; }; typedef struct { void (*callback)(void *, const char *, size_t); void *user_data; char *url; int inpa; gboolean sentreq; gboolean newline; gboolean startsaving; gboolean has_explicit_data_len; char *webdata; unsigned long len; unsigned long data_len; } GaimFetchUrlData; typedef void (*dns_callback_t)(GSList *hosts, gpointer data, const char *error_message); typedef struct { gpointer data; size_t addrlen; struct sockaddr *addr; dns_callback_t callback; } pending_dns_request_t; static int proxy_connect_none(struct PHB *phb, struct sockaddr *addr, socklen_t addrlen); static int try_connect(struct PHB *phb) { size_t addrlen; struct sockaddr *addr; int ret = -1; while (phb->hosts) { addrlen = GPOINTER_TO_INT(phb->hosts->data); phb->hosts = g_slist_remove(phb->hosts, phb->hosts->data); addr = phb->hosts->data; phb->hosts = g_slist_remove(phb->hosts, phb->hosts->data); ret = proxy_connect_none(phb, addr, addrlen); g_free(addr); if (ret > 0) break; } if (ret < 0) { phb->func(phb->data, -1, GDK_INPUT_READ); g_free(phb->host); g_free(phb); } return ret; } static void no_one_calls(gpointer data, gint source, GdkInputCondition cond) { struct PHB *phb = data; unsigned int len; int error=0; printf("Connected.\n"); len = sizeof(error); /* * getsockopt after a non-blocking connect returns -1 if something is * really messed up (bad descriptor, usually). Otherwise, it returns 0 and * error holds what connect would have returned if it blocked until now. * Thus, error == 0 is success, error == EINPROGRESS means "try again", * and anything else is a real error. * * (error == EINPROGRESS can happen after a select because the kernel can * be overly optimistic sometimes. select is just a hint that you might be * able to do something.) */ if (getsockopt(source, SOL_SOCKET, SO_ERROR, (void *)&error, &len) == SOCKET_ERROR) { error == WSAGetLastError(); if (error == WSAEINPROGRESS) return; /* we'll be called again later */ closesocket(source); g_source_remove(phb->inpa); printf("getsockopt SO_ERROR check: %s\n", strerror(error)); try_connect(phb); return; } u_long imode = 0; ioctlsocket(source, FIONBIO, &imode); g_source_remove(phb->inpa); phb->func(phb->data, source, GDK_INPUT_READ); g_free(phb->host); g_free(phb); } static gboolean clean_connect(gpointer data) { struct PHB *phb = data; phb->func(phb->data, phb->port, GDK_INPUT_READ); g_free(phb->host); g_free(phb); return FALSE; } static int proxy_connect_none(struct PHB *phb, struct sockaddr *addr, socklen_t addrlen) { int fd = -1; printf("Connecting to %s:%d with no proxy\n", phb->host, phb->port); if ((fd = socket(addr->sa_family, SOCK_STREAM, 0)) == INVALID_SOCKET) { printf("Unable to create socket: %s\n", strerror(WSAGetLastError())); return -1; } u_long imode = 1; if (ioctlsocket(fd, FIONBIO, &imode) == SOCKET_ERROR) printf("Unable to set non-blocking mode: %d\n", WSAGetLastError()); if (connect(fd, (struct sockaddr *)addr, addrlen) == SOCKET_ERROR) { errno = WSAGetLastError(); if (WSAGetLastError() == WSAEWOULDBLOCK) { printf("Connect would have blocked.\n"); phb->inpa = gdk_input_add(fd, GDK_INPUT_WRITE, no_one_calls, phb); } else { printf( "Connect failed: %s\n", strerror(errno)); closesocket(fd); return -1; } } else { unsigned int len; int error = WSAETIMEDOUT; printf("Connect didn't block.\n"); len = sizeof(error); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&error, &len) == SOCKET_ERROR) { printf("getsockopt failed.\n"); closesocket(fd); return -1; } u_long imode = 0; ioctlsocket(fd, FIONBIO, &imode); phb->port = fd; /* bleh */ g_timeout_add(50, clean_connect, phb); /* we do this because we never want to call our callback before we return. */ } return fd; } static void destroy_fetch_url_data(GaimFetchUrlData *gfud) { g_free(gfud->webdata); g_free(gfud->url); g_free(gfud); } int gaim_proxy_connect(const char *host, int port, GdkInputFunction func, gpointer data) { struct PHB *phb; struct sockaddr_in sin; struct sockaddr *addr; int addrlen; g_return_val_if_fail(host != NULL, -1); g_return_val_if_fail(port != 0 && port != -1, -1); g_return_val_if_fail(func != NULL, -1); if ((sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE) return -1; sin.sin_family = AF_INET; sin.sin_port = htons(port); addr = (struct sockaddr*) g_memdup(&sin, sizeof(sin)); addrlen = sizeof(sin); phb = g_new0(struct PHB, 1); phb->func = func; phb->data = data; phb->host = g_strdup(host); phb->port = port; phb->hosts = g_slist_append(phb->hosts, GINT_TO_POINTER(addrlen)); phb->hosts = g_slist_append(phb->hosts, addr); return try_connect(phb); } static size_t parse_content_len(const char *data, size_t data_len) { size_t content_len = 0; const char *p = NULL; /* This is still technically wrong, since headers are case-insensitive * [RFC 2616, section 4.2], though this ought to catch the normal case. * Note: data is _not_ nul-terminated. */ if (data_len > 16) { p = strncmp(data, "Content-Length: ", 16) == 0? data: NULL; if (!p) { p = g_strstr_len(data, data_len, "\nContent-Length: "); if (p) p += 1; } } /* If we can find a Content-Length header at all, try to sscanf it. * Response headers should end with at least \r\n, so sscanf is safe, * if we make sure that there is indeed a \n in our header. */ if (p && g_strstr_len(p, data_len - (p - data), "\n")) { sscanf(p, "Content-Length: %" G_GSIZE_FORMAT, &content_len); printf("parsed %u\n", content_len); } return content_len; } static void url_fetched_cb(gpointer url_data, gint sock, GdkInputCondition cond) { GaimFetchUrlData *gfud = url_data; char data; gboolean got_eof = FALSE; if (sock == -1) { gfud->callback(gfud->user_data, NULL, 0); destroy_fetch_url_data(gfud); return; } if (!gfud->sentreq) { char buf[1024]; const char *host = gfud->url + 7; //"http://" g_snprintf(buf, sizeof(buf), "GET %s HTTP/1.1\r\n" "Host: %s\r\n\r\n", gfud->url, host); printf("Request: %s\n", buf); send(sock, buf, strlen(buf), 0); u_long imode = 1; ioctlsocket(sock, FIONBIO, &imode); gfud->sentreq = TRUE; gfud->inpa = gdk_input_add(sock, GDK_INPUT_READ, url_fetched_cb, url_data); gfud->data_len = 4096; gfud->webdata = g_malloc(gfud->data_len); return; } /* Read in data, one byte at a time */ int recv_ret = recv(sock, &data, 1, 0); if ((recv_ret != SOCKET_ERROR && recv_ret > 0) || (recv_ret == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)) { if (WSAGetLastError() == WSAEWOULDBLOCK) { errno = 0; return; } gfud->len++; /* If we've filled up our buffer then make it bigger */ if (gfud->len == gfud->data_len) { gfud->data_len += (gfud->data_len) / 2; gfud->webdata = g_realloc(gfud->webdata, gfud->data_len); } gfud->webdata[gfud->len - 1] = data; gfud->webdata[gfud->len] = '\0'; if (!gfud->startsaving) { if (data == '\r') return; if (data == '\n') { if (gfud->newline) { size_t content_len; gfud->startsaving = TRUE; /* No redirect. See if we can find a content length. */ content_len = parse_content_len(gfud->webdata, gfud->len); if (content_len == 0) { /* We'll stick with an initial 8192 */ content_len = 8192; } else { gfud->has_explicit_data_len = TRUE; } /* Out with the old... */ gfud->len = 0; g_free(gfud->webdata); gfud->webdata = NULL; /* In with the new. */ gfud->data_len = content_len; gfud->webdata = g_try_malloc(gfud->data_len); if (gfud->webdata == NULL) { printf("Failed to allocate %lu bytes: %s\n", gfud->data_len, strerror(errno)); g_source_remove(gfud->inpa); closesocket(sock); gfud->callback(gfud->user_data, NULL, 0); destroy_fetch_url_data(gfud); } } else gfud->newline = TRUE; return; } gfud->newline = FALSE; } else if (gfud->has_explicit_data_len && gfud->len == gfud->data_len) { got_eof = TRUE; } } else if (errno != WSAETIMEDOUT) { got_eof = TRUE; } else { g_source_remove(gfud->inpa); closesocket(sock); gfud->callback(gfud->user_data, NULL, 0); destroy_fetch_url_data(gfud); } if (got_eof) { gfud->webdata = g_realloc(gfud->webdata, gfud->len + 1); gfud->webdata[gfud->len] = 0; g_source_remove(gfud->inpa); closesocket(sock); gfud->callback(gfud->user_data, gfud->webdata, gfud->len); destroy_fetch_url_data(gfud); } } void gaim_url_fetch(const char *host, void (*cb)(gpointer, const char *, size_t), void *user_data) { int sock; GaimFetchUrlData *gfud; g_return_if_fail(host != NULL); g_return_if_fail(cb != NULL); printf("requested to fetch (%s)\n", host); gfud = g_new0(GaimFetchUrlData, 1); gfud->callback = cb; gfud->user_data = user_data; gfud->url = g_strdup_printf("http://%s", host); if ((sock = gaim_proxy_connect(host, 80, url_fetched_cb, gfud)) < 0) { destroy_fetch_url_data(gfud); cb(user_data, g_strdup("g003: Error opening connection.\n"), 0); } } static void done_stuff(void *data, const char *page_data, size_t len) { printf("***************************** DATA DONE *******************************\n%s\n", page_data ? page_data : ""); } void do_stuff(GtkWidget *window, GtkEntry *entry) { const gchar *text; text = gtk_entry_get_text (entry); gaim_url_fetch(text, done_stuff, NULL); } int main (int argc, char *argv[]) { GtkWidget *entry, *window, *vbox, *hbox, *button; DWORD wVersionRequested = MAKEWORD(2, 2); WSADATA wsaData; WSAStartup (wVersionRequested, &wsaData); gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL); gtk_window_set_title (GTK_WINDOW (window), "IO Channel Test"); gtk_container_set_border_width (GTK_CONTAINER (window), 0); vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (window), vbox); hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0); entry = gtk_entry_new (); gtk_box_pack_start (GTK_BOX(hbox), entry, FALSE, FALSE, 0); button = gtk_button_new_with_label ("Do Stuff"); gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0); g_signal_connect (button, "clicked", G_CALLBACK (do_stuff), entry); gtk_widget_show_all(window); gtk_main(); WSACleanup(); return 0; }