comparison libpurple/util.c @ 24592:104f6f755c7e

Make our purple_util_fetch_url_request() function able to handle fetching stuff from https urls. This is needed by yahoo's webmessenger style login, if we want to try enable it again. And it will be needed by the new oscar authentication. I wrote this maybe a year ago and we've been using it at Meebo with no problems, but it would be great if one person could look through these changes.
author Mark Doliner <mark@kingant.net>
date Sat, 06 Dec 2008 01:08:20 +0000
parents f9503aa61fb4
children e7b27ee5e7b6 65cfc59858cf
comparison
equal deleted inserted replaced
24591:a05e9cf91efb 24592:104f6f755c7e
54 gboolean http11; 54 gboolean http11;
55 char *request; 55 char *request;
56 gsize request_written; 56 gsize request_written;
57 gboolean include_headers; 57 gboolean include_headers;
58 58
59 gboolean is_ssl;
60 PurpleSslConnection *ssl_connection;
59 PurpleProxyConnectData *connect_data; 61 PurpleProxyConnectData *connect_data;
60 int fd; 62 int fd;
61 guint inpa; 63 guint inpa;
62 64
63 gboolean got_headers; 65 gboolean got_headers;
3441 char proto[11]; 3443 char proto[11];
3442 const char *tmp, *param_string; 3444 const char *tmp, *param_string;
3443 char *cmd; 3445 char *cmd;
3444 GHashTable *params = NULL; 3446 GHashTable *params = NULL;
3445 int len; 3447 int len;
3446
3447 g_return_if_fail(uri != NULL);
3448
3449 if (!(tmp = strchr(uri, ':')) || tmp == uri) { 3448 if (!(tmp = strchr(uri, ':')) || tmp == uri) {
3450 purple_debug_error("util", "Malformed protocol handler message - missing protocol.\n"); 3449 purple_debug_error("util", "Malformed protocol handler message - missing protocol.\n");
3451 return; 3450 return;
3452 } 3451 }
3453 3452
3516 */ 3515 */
3517 gboolean 3516 gboolean
3518 purple_url_parse(const char *url, char **ret_host, int *ret_port, 3517 purple_url_parse(const char *url, char **ret_host, int *ret_port,
3519 char **ret_path, char **ret_user, char **ret_passwd) 3518 char **ret_path, char **ret_user, char **ret_passwd)
3520 { 3519 {
3520 gboolean is_https = FALSE;
3521 char scan_info[255]; 3521 char scan_info[255];
3522 char port_str[6]; 3522 char port_str[6];
3523 int f; 3523 int f;
3524 const char *at, *slash; 3524 const char *at, *slash;
3525 const char *turl; 3525 const char *turl;
3539 turl += 7; 3539 turl += 7;
3540 url = turl; 3540 url = turl;
3541 } 3541 }
3542 else if ((turl = purple_strcasestr(url, "https://")) != NULL) 3542 else if ((turl = purple_strcasestr(url, "https://")) != NULL)
3543 { 3543 {
3544 is_https = TRUE;
3544 turl += 8; 3545 turl += 8;
3545 url = turl; 3546 url = turl;
3546 } 3547 }
3547 3548
3548 /* parse out authentication information if supplied */ 3549 /* parse out authentication information if supplied */
3579 { 3580 {
3580 g_snprintf(scan_info, sizeof(scan_info), 3581 g_snprintf(scan_info, sizeof(scan_info),
3581 "%%255[%s]/%%255[%s]", 3582 "%%255[%s]/%%255[%s]",
3582 addr_ctrl, page_ctrl); 3583 addr_ctrl, page_ctrl);
3583 f = sscanf(url, scan_info, host, path); 3584 f = sscanf(url, scan_info, host, path);
3584 g_snprintf(port_str, sizeof(port_str), "80"); 3585 /* Use the default port */
3586 if (is_https)
3587 g_snprintf(port_str, sizeof(port_str), "443");
3588 else
3589 g_snprintf(port_str, sizeof(port_str), "80");
3585 } 3590 }
3586 3591
3587 if (f == 0) 3592 if (f == 0)
3588 *host = '\0'; 3593 *host = '\0';
3589 3594
3618 g_free(error_message); 3623 g_free(error_message);
3619 purple_util_fetch_url_cancel(gfud); 3624 purple_util_fetch_url_cancel(gfud);
3620 } 3625 }
3621 3626
3622 static void url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message); 3627 static void url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message);
3628 static void ssl_url_fetch_connect_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond);
3629 static void ssl_url_fetch_error_cb(PurpleSslConnection *ssl_connection, PurpleSslErrorType error, gpointer data);
3623 3630
3624 static gboolean 3631 static gboolean
3625 parse_redirect(const char *data, size_t data_len, 3632 parse_redirect(const char *data, size_t data_len,
3626 PurpleUtilFetchUrlData *gfud) 3633 PurpleUtilFetchUrlData *gfud)
3627 { 3634 {
3684 gfud->url = new_url; 3691 gfud->url = new_url;
3685 gfud->full = full; 3692 gfud->full = full;
3686 g_free(gfud->request); 3693 g_free(gfud->request);
3687 gfud->request = NULL; 3694 gfud->request = NULL;
3688 3695
3689 purple_input_remove(gfud->inpa); 3696 if (gfud->is_ssl) {
3690 gfud->inpa = 0; 3697 gfud->is_ssl = FALSE;
3691 close(gfud->fd); 3698 purple_ssl_close(gfud->ssl_connection);
3692 gfud->fd = -1; 3699 gfud->ssl_connection = NULL;
3700 } else {
3701 purple_input_remove(gfud->inpa);
3702 gfud->inpa = 0;
3703 close(gfud->fd);
3704 gfud->fd = -1;
3705 }
3693 gfud->request_written = 0; 3706 gfud->request_written = 0;
3694 gfud->len = 0; 3707 gfud->len = 0;
3695 gfud->data_len = 0; 3708 gfud->data_len = 0;
3696 3709
3697 g_free(gfud->website.user); 3710 g_free(gfud->website.user);
3699 g_free(gfud->website.address); 3712 g_free(gfud->website.address);
3700 g_free(gfud->website.page); 3713 g_free(gfud->website.page);
3701 purple_url_parse(new_url, &gfud->website.address, &gfud->website.port, 3714 purple_url_parse(new_url, &gfud->website.address, &gfud->website.port,
3702 &gfud->website.page, &gfud->website.user, &gfud->website.passwd); 3715 &gfud->website.page, &gfud->website.user, &gfud->website.passwd);
3703 3716
3704 gfud->connect_data = purple_proxy_connect(NULL, NULL, 3717 if (purple_strcasestr(new_url, "https://") != NULL) {
3705 gfud->website.address, gfud->website.port, 3718 gfud->is_ssl = TRUE;
3706 url_fetch_connect_cb, gfud); 3719 gfud->ssl_connection = purple_ssl_connect(NULL,
3707 3720 gfud->website.address, gfud->website.port,
3708 if (gfud->connect_data == NULL) 3721 ssl_url_fetch_connect_cb, ssl_url_fetch_error_cb, gfud);
3722 } else {
3723 gfud->connect_data = purple_proxy_connect(NULL, NULL,
3724 gfud->website.address, gfud->website.port,
3725 url_fetch_connect_cb, gfud);
3726 }
3727
3728 if (gfud->ssl_connection == NULL && gfud->connect_data == NULL)
3709 { 3729 {
3710 purple_util_fetch_url_error(gfud, _("Unable to connect to %s"), 3730 purple_util_fetch_url_error(gfud, _("Unable to connect to %s"),
3711 gfud->website.address); 3731 gfud->website.address);
3712 } 3732 }
3713 3733
3764 int len; 3784 int len;
3765 char buf[4096]; 3785 char buf[4096];
3766 char *data_cursor; 3786 char *data_cursor;
3767 gboolean got_eof = FALSE; 3787 gboolean got_eof = FALSE;
3768 3788
3769 while((len = read(source, buf, sizeof(buf))) > 0) { 3789 /*
3770 3790 * Read data in a loop until we can't read any more! This is a
3791 * little confusing because we read using a different function
3792 * depending on whether the socket is ssl or cleartext.
3793 */
3794 while ((gfud->is_ssl && ((len = purple_ssl_read(gfud->ssl_connection, buf, sizeof(buf))) > 0)) ||
3795 (!gfud->is_ssl && (len = read(source, buf, sizeof(buf))) > 0))
3796 {
3771 if(gfud->max_len != -1 && (gfud->len + len) > gfud->max_len) { 3797 if(gfud->max_len != -1 && (gfud->len + len) > gfud->max_len) {
3772 purple_util_fetch_url_error(gfud, _("Error reading from %s: response too long (%d bytes limit)"), 3798 purple_util_fetch_url_error(gfud, _("Error reading from %s: response too long (%d bytes limit)"),
3773 gfud->website.address, gfud->max_len); 3799 gfud->website.address, gfud->max_len);
3774 return; 3800 return;
3775 } 3801 }
3885 gfud->callback(gfud, gfud->user_data, gfud->webdata, gfud->len, NULL); 3911 gfud->callback(gfud, gfud->user_data, gfud->webdata, gfud->len, NULL);
3886 purple_util_fetch_url_cancel(gfud); 3912 purple_util_fetch_url_cancel(gfud);
3887 } 3913 }
3888 } 3914 }
3889 3915
3916 static void ssl_url_fetch_recv_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond)
3917 {
3918 url_fetch_recv_cb(data, -1, cond);
3919 }
3920
3921 /*
3922 * This function is called when the socket is available to be written
3923 * to.
3924 *
3925 * @param source The file descriptor that can be written to. This can
3926 * be an http connection or it can be the SSL connection of an
3927 * https request. So be careful what you use it for! If it's
3928 * an https request then use purple_ssl_write() instead of
3929 * writing to it directly.
3930 */
3890 static void 3931 static void
3891 url_fetch_send_cb(gpointer data, gint source, PurpleInputCondition cond) 3932 url_fetch_send_cb(gpointer data, gint source, PurpleInputCondition cond)
3892 { 3933 {
3893 PurpleUtilFetchUrlData *gfud; 3934 PurpleUtilFetchUrlData *gfud;
3894 int len, total_len; 3935 int len, total_len;
3895 3936
3896 gfud = data; 3937 gfud = data;
3897 3938
3898 total_len = strlen(gfud->request); 3939 if (gfud->request == NULL)
3899 3940 {
3900 len = write(gfud->fd, gfud->request + gfud->request_written, 3941 /* Host header is not forbidden in HTTP/1.0 requests, and HTTP/1.1
3901 total_len - gfud->request_written); 3942 * clients must know how to handle the "chunked" transfer encoding.
3902 3943 * Purple doesn't know how to handle "chunked", so should always send
3903 if (len < 0 && errno == EAGAIN) 3944 * the Host header regardless, to get around some observed problems
3904 return; 3945 */
3905 else if (len < 0) {
3906 purple_util_fetch_url_error(gfud, _("Error writing to %s: %s"),
3907 gfud->website.address, g_strerror(errno));
3908 return;
3909 }
3910 gfud->request_written += len;
3911
3912 if (gfud->request_written < total_len)
3913 return;
3914
3915 /* We're done writing our request, now start reading the response */
3916 purple_input_remove(gfud->inpa);
3917 gfud->inpa = purple_input_add(gfud->fd, PURPLE_INPUT_READ, url_fetch_recv_cb,
3918 gfud);
3919 }
3920
3921 static void
3922 url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message)
3923 {
3924 PurpleUtilFetchUrlData *gfud;
3925
3926 gfud = url_data;
3927 gfud->connect_data = NULL;
3928
3929 if (source == -1)
3930 {
3931 purple_util_fetch_url_error(gfud, _("Unable to connect to %s: %s"),
3932 (gfud->website.address ? gfud->website.address : ""), error_message);
3933 return;
3934 }
3935
3936 gfud->fd = source;
3937
3938 if (!gfud->request) {
3939 if (gfud->user_agent) { 3946 if (gfud->user_agent) {
3940 /* Host header is not forbidden in HTTP/1.0 requests, and HTTP/1.1
3941 * clients must know how to handle the "chunked" transfer encoding.
3942 * Purple doesn't know how to handle "chunked", so should always send
3943 * the Host header regardless, to get around some observed problems
3944 */
3945 gfud->request = g_strdup_printf( 3947 gfud->request = g_strdup_printf(
3946 "GET %s%s HTTP/%s\r\n" 3948 "GET %s%s HTTP/%s\r\n"
3947 "Connection: close\r\n" 3949 "Connection: close\r\n"
3948 "User-Agent: %s\r\n" 3950 "User-Agent: %s\r\n"
3949 "Accept: */*\r\n" 3951 "Accept: */*\r\n"
3966 } 3968 }
3967 } 3969 }
3968 3970
3969 purple_debug_misc("util", "Request: '%s'\n", gfud->request); 3971 purple_debug_misc("util", "Request: '%s'\n", gfud->request);
3970 3972
3973 total_len = strlen(gfud->request);
3974
3975 if (gfud->is_ssl)
3976 len = purple_ssl_write(gfud->ssl_connection, gfud->request + gfud->request_written,
3977 total_len - gfud->request_written);
3978 else
3979 len = write(gfud->fd, gfud->request + gfud->request_written,
3980 total_len - gfud->request_written);
3981
3982 if (len < 0 && errno == EAGAIN)
3983 return;
3984 else if (len < 0) {
3985 purple_util_fetch_url_error(gfud, _("Error writing to %s: %s"),
3986 gfud->website.address, g_strerror(errno));
3987 return;
3988 }
3989 gfud->request_written += len;
3990
3991 if (gfud->request_written < total_len)
3992 return;
3993
3994 /* We're done writing our request, now start reading the response */
3995 if (gfud->is_ssl) {
3996 purple_input_remove(gfud->inpa);
3997 gfud->inpa = 0;
3998 purple_ssl_input_add(gfud->ssl_connection, ssl_url_fetch_recv_cb, gfud);
3999 } else {
4000 purple_input_remove(gfud->inpa);
4001 gfud->inpa = purple_input_add(gfud->fd, PURPLE_INPUT_READ, url_fetch_recv_cb,
4002 gfud);
4003 }
4004 }
4005
4006 static void
4007 url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message)
4008 {
4009 PurpleUtilFetchUrlData *gfud;
4010
4011 gfud = url_data;
4012 gfud->connect_data = NULL;
4013
4014 if (source == -1)
4015 {
4016 purple_util_fetch_url_error(gfud, _("Unable to connect to %s: %s"),
4017 (gfud->website.address ? gfud->website.address : ""), error_message);
4018 return;
4019 }
4020
4021 gfud->fd = source;
4022
3971 gfud->inpa = purple_input_add(source, PURPLE_INPUT_WRITE, 4023 gfud->inpa = purple_input_add(source, PURPLE_INPUT_WRITE,
3972 url_fetch_send_cb, gfud); 4024 url_fetch_send_cb, gfud);
3973 url_fetch_send_cb(gfud, source, PURPLE_INPUT_WRITE); 4025 url_fetch_send_cb(gfud, source, PURPLE_INPUT_WRITE);
4026 }
4027
4028 static void ssl_url_fetch_connect_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond)
4029 {
4030 PurpleUtilFetchUrlData *gfud;
4031
4032 gfud = data;
4033
4034 gfud->inpa = purple_input_add(ssl_connection->fd, PURPLE_INPUT_WRITE,
4035 url_fetch_send_cb, gfud);
4036 url_fetch_send_cb(gfud, ssl_connection->fd, PURPLE_INPUT_WRITE);
4037 }
4038
4039 static void ssl_url_fetch_error_cb(PurpleSslConnection *ssl_connection, PurpleSslErrorType error, gpointer data)
4040 {
4041 PurpleUtilFetchUrlData *gfud;
4042
4043 gfud = data;
4044 gfud->ssl_connection = NULL;
4045
4046 purple_util_fetch_url_error(gfud, _("Unable to connect to %s: %s"),
4047 (gfud->website.address ? gfud->website.address : ""),
4048 purple_ssl_strerror(error));
3974 } 4049 }
3975 4050
3976 PurpleUtilFetchUrlData * 4051 PurpleUtilFetchUrlData *
3977 purple_util_fetch_url_request(const char *url, gboolean full, 4052 purple_util_fetch_url_request(const char *url, gboolean full,
3978 const char *user_agent, gboolean http11, 4053 const char *user_agent, gboolean http11,
3981 { 4056 {
3982 return purple_util_fetch_url_request_len(url, full, 4057 return purple_util_fetch_url_request_len(url, full,
3983 user_agent, http11, 4058 user_agent, http11,
3984 request, include_headers, -1, 4059 request, include_headers, -1,
3985 callback, user_data); 4060 callback, user_data);
3986 }
3987
3988 static gboolean
3989 url_fetch_connect_failed(gpointer data)
3990 {
3991 url_fetch_connect_cb(data, -1, "");
3992 return FALSE;
3993 } 4061 }
3994 4062
3995 PurpleUtilFetchUrlData * 4063 PurpleUtilFetchUrlData *
3996 purple_util_fetch_url_request_len(const char *url, gboolean full, 4064 purple_util_fetch_url_request_len(const char *url, gboolean full,
3997 const char *user_agent, gboolean http11, 4065 const char *user_agent, gboolean http11,
4021 gfud->max_len = max_len; 4089 gfud->max_len = max_len;
4022 4090
4023 purple_url_parse(url, &gfud->website.address, &gfud->website.port, 4091 purple_url_parse(url, &gfud->website.address, &gfud->website.port,
4024 &gfud->website.page, &gfud->website.user, &gfud->website.passwd); 4092 &gfud->website.page, &gfud->website.user, &gfud->website.passwd);
4025 4093
4026 gfud->connect_data = purple_proxy_connect(NULL, NULL, 4094 if (purple_strcasestr(url, "https://") != NULL) {
4027 gfud->website.address, gfud->website.port, 4095 gfud->is_ssl = TRUE;
4028 url_fetch_connect_cb, gfud); 4096 gfud->ssl_connection = purple_ssl_connect(NULL,
4029 4097 gfud->website.address, gfud->website.port,
4030 if (gfud->connect_data == NULL) 4098 ssl_url_fetch_connect_cb, ssl_url_fetch_error_cb, gfud);
4031 { 4099 } else {
4032 /* Trigger the connect_cb asynchronously. */ 4100 gfud->connect_data = purple_proxy_connect(NULL, NULL,
4033 purple_timeout_add(10, url_fetch_connect_failed, gfud); 4101 gfud->website.address, gfud->website.port,
4102 url_fetch_connect_cb, gfud);
4103 }
4104
4105 if (gfud->ssl_connection == NULL && gfud->connect_data == NULL)
4106 {
4107 purple_util_fetch_url_error(gfud, _("Unable to connect to %s"),
4108 gfud->website.address);
4109 return NULL;
4034 } 4110 }
4035 4111
4036 return gfud; 4112 return gfud;
4037 } 4113 }
4038 4114
4039 void 4115 void
4040 purple_util_fetch_url_cancel(PurpleUtilFetchUrlData *gfud) 4116 purple_util_fetch_url_cancel(PurpleUtilFetchUrlData *gfud)
4041 { 4117 {
4118 if (gfud->ssl_connection != NULL)
4119 purple_ssl_close(gfud->ssl_connection);
4120
4042 if (gfud->connect_data != NULL) 4121 if (gfud->connect_data != NULL)
4043 purple_proxy_connect_cancel(gfud->connect_data); 4122 purple_proxy_connect_cancel(gfud->connect_data);
4044 4123
4045 if (gfud->inpa > 0) 4124 if (gfud->inpa > 0)
4046 purple_input_remove(gfud->inpa); 4125 purple_input_remove(gfud->inpa);