libpurple/util.c

changeset 33424
9919d10ee405
parent 33380
c600a505402d
child 33428
ca36fb806037
equal deleted inserted replaced
33423:c86dc169ae9e 33424:9919d10ee405
26 #include "cipher.h" 26 #include "cipher.h"
27 #include "conversation.h" 27 #include "conversation.h"
28 #include "core.h" 28 #include "core.h"
29 #include "debug.h" 29 #include "debug.h"
30 #include "notify.h" 30 #include "notify.h"
31 #include "ntlm.h"
32 #include "prpl.h" 31 #include "prpl.h"
33 #include "prefs.h" 32 #include "prefs.h"
34 #include "util.h" 33 #include "util.h"
35
36 struct _PurpleUtilFetchUrlData
37 {
38 PurpleUtilFetchUrlCallback callback;
39 void *user_data;
40
41 struct
42 {
43 char *user;
44 char *passwd;
45 char *address;
46 int port;
47 char *page;
48
49 } website;
50
51 char *url;
52 int num_times_redirected;
53 gboolean full;
54 char *user_agent;
55 gboolean http11;
56 char *request;
57 gsize request_written;
58 gboolean include_headers;
59
60 gboolean is_ssl;
61 PurpleSslConnection *ssl_connection;
62 PurpleProxyConnectData *connect_data;
63 int fd;
64 guint inpa;
65
66 gboolean got_headers;
67 gboolean has_explicit_data_len;
68 char *webdata;
69 gsize len;
70 unsigned long data_len;
71 gssize max_len;
72 gboolean chunked;
73 PurpleAccount *account;
74 };
75 34
76 struct _PurpleMenuAction 35 struct _PurpleMenuAction
77 { 36 {
78 char *label; 37 char *label;
79 PurpleCallback callback; 38 PurpleCallback callback;
4004 #undef PAGE_CTRL 3963 #undef PAGE_CTRL
4005 #undef USER_CTRL 3964 #undef USER_CTRL
4006 #undef PASSWD_CTRL 3965 #undef PASSWD_CTRL
4007 } 3966 }
4008 3967
4009 /**
4010 * The arguments to this function are similar to printf.
4011 */
4012 static void
4013 purple_util_fetch_url_error(PurpleUtilFetchUrlData *gfud, const char *format, ...)
4014 {
4015 gchar *error_message;
4016 va_list args;
4017
4018 va_start(args, format);
4019 error_message = g_strdup_vprintf(format, args);
4020 va_end(args);
4021
4022 gfud->callback(gfud, gfud->user_data, NULL, 0, error_message);
4023 g_free(error_message);
4024 purple_util_fetch_url_cancel(gfud);
4025 }
4026
4027 static void url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message);
4028 static void ssl_url_fetch_connect_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond);
4029 static void ssl_url_fetch_error_cb(PurpleSslConnection *ssl_connection, PurpleSslErrorType error, gpointer data);
4030
4031 static gboolean
4032 parse_redirect(const char *data, size_t data_len,
4033 PurpleUtilFetchUrlData *gfud)
4034 {
4035 gchar *s;
4036 gchar *new_url, *temp_url, *end;
4037 gboolean full;
4038 int len;
4039
4040 if ((s = g_strstr_len(data, data_len, "\nLocation: ")) == NULL)
4041 /* We're not being redirected */
4042 return FALSE;
4043
4044 s += strlen("Location: ");
4045 end = strchr(s, '\r');
4046
4047 /* Just in case :) */
4048 if (end == NULL)
4049 end = strchr(s, '\n');
4050
4051 if (end == NULL)
4052 return FALSE;
4053
4054 len = end - s;
4055
4056 new_url = g_malloc(len + 1);
4057 strncpy(new_url, s, len);
4058 new_url[len] = '\0';
4059
4060 full = gfud->full;
4061
4062 if (*new_url == '/' || g_strstr_len(new_url, len, "://") == NULL)
4063 {
4064 temp_url = new_url;
4065
4066 new_url = g_strdup_printf("%s:%d%s", gfud->website.address,
4067 gfud->website.port, temp_url);
4068
4069 g_free(temp_url);
4070
4071 full = FALSE;
4072 }
4073
4074 purple_debug_info("util", "Redirecting to %s\n", new_url);
4075
4076 gfud->num_times_redirected++;
4077 if (gfud->num_times_redirected >= 5)
4078 {
4079 purple_util_fetch_url_error(gfud,
4080 _("Could not open %s: Redirected too many times"),
4081 gfud->url);
4082 return TRUE;
4083 }
4084
4085 /*
4086 * Try again, with this new location. This code is somewhat
4087 * ugly, but we need to reuse the gfud because whoever called
4088 * us is holding a reference to it.
4089 */
4090 g_free(gfud->url);
4091 gfud->url = new_url;
4092 gfud->full = full;
4093 g_free(gfud->request);
4094 gfud->request = NULL;
4095
4096 if (gfud->is_ssl) {
4097 gfud->is_ssl = FALSE;
4098 purple_ssl_close(gfud->ssl_connection);
4099 gfud->ssl_connection = NULL;
4100 } else {
4101 purple_input_remove(gfud->inpa);
4102 gfud->inpa = 0;
4103 close(gfud->fd);
4104 gfud->fd = -1;
4105 }
4106 gfud->request_written = 0;
4107 gfud->len = 0;
4108 gfud->data_len = 0;
4109
4110 g_free(gfud->website.user);
4111 g_free(gfud->website.passwd);
4112 g_free(gfud->website.address);
4113 g_free(gfud->website.page);
4114 purple_url_parse(new_url, &gfud->website.address, &gfud->website.port,
4115 &gfud->website.page, &gfud->website.user, &gfud->website.passwd);
4116
4117 if (purple_strcasestr(new_url, "https://") != NULL) {
4118 gfud->is_ssl = TRUE;
4119 gfud->ssl_connection = purple_ssl_connect(gfud->account,
4120 gfud->website.address, gfud->website.port,
4121 ssl_url_fetch_connect_cb, ssl_url_fetch_error_cb, gfud);
4122 } else {
4123 gfud->connect_data = purple_proxy_connect(NULL, gfud->account,
4124 gfud->website.address, gfud->website.port,
4125 url_fetch_connect_cb, gfud);
4126 }
4127
4128 if (gfud->ssl_connection == NULL && gfud->connect_data == NULL)
4129 {
4130 purple_util_fetch_url_error(gfud, _("Unable to connect to %s"),
4131 gfud->website.address);
4132 }
4133
4134 return TRUE;
4135 }
4136
4137 static const char *
4138 find_header_content(const char *data, size_t data_len, const char *header, size_t header_len)
4139 {
4140 const char *p = NULL;
4141
4142 if (header_len <= 0)
4143 header_len = strlen(header);
4144
4145 /* Note: data is _not_ nul-terminated. */
4146 if (data_len > header_len) {
4147 if (header[0] == '\n')
4148 p = (g_ascii_strncasecmp(data, header + 1, header_len - 1) == 0) ? data : NULL;
4149 if (!p)
4150 p = purple_strcasestr(data, header);
4151 if (p)
4152 p += header_len;
4153 }
4154
4155 /* If we can find the header at all, try to sscanf it.
4156 * Response headers should end with at least \r\n, so sscanf is safe,
4157 * if we make sure that there is indeed a \n in our header.
4158 */
4159 if (p && g_strstr_len(p, data_len - (p - data), "\n")) {
4160 return p;
4161 }
4162
4163 return NULL;
4164 }
4165
4166 static size_t
4167 parse_content_len(const char *data, size_t data_len)
4168 {
4169 size_t content_len = 0;
4170 const char *p = NULL;
4171
4172 p = find_header_content(data, data_len, "\nContent-Length: ", sizeof("\nContent-Length: ") - 1);
4173 if (p) {
4174 sscanf(p, "%" G_GSIZE_FORMAT, &content_len);
4175 purple_debug_misc("util", "parsed %" G_GSIZE_FORMAT "\n", content_len);
4176 }
4177
4178 return content_len;
4179 }
4180
4181 static gboolean
4182 content_is_chunked(const char *data, size_t data_len)
4183 {
4184 const char *p = find_header_content(data, data_len, "\nTransfer-Encoding: ", sizeof("\nTransfer-Encoding: ") - 1);
4185 if (p && g_ascii_strncasecmp(p, "chunked", 7) == 0)
4186 return TRUE;
4187
4188 return FALSE;
4189 }
4190
4191 /* Process in-place */
4192 static void
4193 process_chunked_data(char *data, gsize *len)
4194 {
4195 gsize sz;
4196 gsize newlen = 0;
4197 char *p = data;
4198 char *s = data;
4199
4200 while (*s) {
4201 /* Read the size of this chunk */
4202 if (sscanf(s, "%" G_GSIZE_MODIFIER "x", &sz) != 1)
4203 {
4204 purple_debug_error("util", "Error processing chunked data: "
4205 "Expected data length, found: %s\n", s);
4206 break;
4207 }
4208 if (sz == 0) {
4209 /* We've reached the last chunk */
4210 /*
4211 * TODO: The spec allows "footers" to follow the last chunk.
4212 * If there is more data after this line then we should
4213 * treat it like a header.
4214 */
4215 break;
4216 }
4217
4218 /* Advance to the start of the data */
4219 s = strstr(s, "\r\n");
4220 if (s == NULL)
4221 break;
4222 s += 2;
4223
4224 if (s + sz > data + *len) {
4225 purple_debug_error("util", "Error processing chunked data: "
4226 "Chunk size %" G_GSIZE_FORMAT " bytes was longer "
4227 "than the data remaining in the buffer (%"
4228 G_GSIZE_FORMAT " bytes)\n", sz, data + *len - s);
4229 }
4230
4231 /* Move all data overtop of the chunk length that we read in earlier */
4232 g_memmove(p, s, sz);
4233 p += sz;
4234 s += sz;
4235 newlen += sz;
4236 if (*s != '\r' && *(s + 1) != '\n') {
4237 purple_debug_error("util", "Error processing chunked data: "
4238 "Expected \\r\\n, found: %s\n", s);
4239 break;
4240 }
4241 s += 2;
4242 }
4243
4244 /* NULL terminate the data */
4245 *p = 0;
4246
4247 *len = newlen;
4248 }
4249
4250 static void
4251 url_fetch_recv_cb(gpointer url_data, gint source, PurpleInputCondition cond)
4252 {
4253 PurpleUtilFetchUrlData *gfud = url_data;
4254 int len;
4255 char buf[4096];
4256 char *data_cursor;
4257 gboolean got_eof = FALSE;
4258
4259 /*
4260 * Read data in a loop until we can't read any more! This is a
4261 * little confusing because we read using a different function
4262 * depending on whether the socket is ssl or cleartext.
4263 */
4264 while ((gfud->is_ssl && ((len = purple_ssl_read(gfud->ssl_connection, buf, sizeof(buf))) > 0)) ||
4265 (!gfud->is_ssl && (len = read(source, buf, sizeof(buf))) > 0))
4266 {
4267 if(gfud->max_len != -1 && (gfud->len + len) > gfud->max_len) {
4268 purple_util_fetch_url_error(gfud, _("Error reading from %s: response too long (%d bytes limit)"),
4269 gfud->website.address, gfud->max_len);
4270 return;
4271 }
4272
4273 /* If we've filled up our buffer, make it bigger */
4274 if((gfud->len + len) >= gfud->data_len) {
4275 while((gfud->len + len) >= gfud->data_len)
4276 gfud->data_len += sizeof(buf);
4277
4278 gfud->webdata = g_realloc(gfud->webdata, gfud->data_len);
4279 }
4280
4281 data_cursor = gfud->webdata + gfud->len;
4282
4283 gfud->len += len;
4284
4285 memcpy(data_cursor, buf, len);
4286
4287 gfud->webdata[gfud->len] = '\0';
4288
4289 if(!gfud->got_headers) {
4290 char *end_of_headers;
4291
4292 /* See if we've reached the end of the headers yet */
4293 end_of_headers = strstr(gfud->webdata, "\r\n\r\n");
4294 if (end_of_headers) {
4295 char *new_data;
4296 guint header_len = (end_of_headers + 4 - gfud->webdata);
4297 size_t content_len;
4298
4299 purple_debug_misc("util", "Response headers: '%.*s'\n",
4300 header_len, gfud->webdata);
4301
4302 /* See if we can find a redirect. */
4303 if(parse_redirect(gfud->webdata, header_len, gfud))
4304 return;
4305
4306 gfud->got_headers = TRUE;
4307
4308 /* No redirect. See if we can find a content length. */
4309 content_len = parse_content_len(gfud->webdata, header_len);
4310 gfud->chunked = content_is_chunked(gfud->webdata, header_len);
4311
4312 if (content_len == 0) {
4313 /* We'll stick with an initial 8192 */
4314 content_len = 8192;
4315 } else {
4316 gfud->has_explicit_data_len = TRUE;
4317 }
4318
4319
4320 /* If we're returning the headers too, we don't need to clean them out */
4321 if (gfud->include_headers) {
4322 gfud->data_len = content_len + header_len;
4323 gfud->webdata = g_realloc(gfud->webdata, gfud->data_len);
4324 } else {
4325 size_t body_len = gfud->len - header_len;
4326
4327 content_len = MAX(content_len, body_len);
4328
4329 new_data = g_try_malloc(content_len);
4330 if (new_data == NULL) {
4331 purple_debug_error("util",
4332 "Failed to allocate %" G_GSIZE_FORMAT " bytes: %s\n",
4333 content_len, g_strerror(errno));
4334 purple_util_fetch_url_error(gfud,
4335 _("Unable to allocate enough memory to hold "
4336 "the contents from %s. The web server may "
4337 "be trying something malicious."),
4338 gfud->website.address);
4339
4340 return;
4341 }
4342
4343 /* We may have read part of the body when reading the headers, don't lose it */
4344 if (body_len > 0) {
4345 memcpy(new_data, end_of_headers + 4, body_len);
4346 }
4347
4348 /* Out with the old... */
4349 g_free(gfud->webdata);
4350
4351 /* In with the new. */
4352 gfud->len = body_len;
4353 gfud->data_len = content_len;
4354 gfud->webdata = new_data;
4355 }
4356 }
4357 }
4358
4359 if(gfud->has_explicit_data_len && gfud->len >= gfud->data_len) {
4360 got_eof = TRUE;
4361 break;
4362 }
4363 }
4364
4365 if(len < 0) {
4366 if(errno == EAGAIN) {
4367 return;
4368 } else {
4369 purple_util_fetch_url_error(gfud, _("Error reading from %s: %s"),
4370 gfud->website.address, g_strerror(errno));
4371 return;
4372 }
4373 }
4374
4375 if((len == 0) || got_eof) {
4376 gfud->webdata = g_realloc(gfud->webdata, gfud->len + 1);
4377 gfud->webdata[gfud->len] = '\0';
4378
4379 if (!gfud->include_headers && gfud->chunked) {
4380 /* Process only if we don't want the headers. */
4381 process_chunked_data(gfud->webdata, &gfud->len);
4382 }
4383
4384 gfud->callback(gfud, gfud->user_data, gfud->webdata, gfud->len, NULL);
4385 purple_util_fetch_url_cancel(gfud);
4386 }
4387 }
4388
4389 static void ssl_url_fetch_recv_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond)
4390 {
4391 url_fetch_recv_cb(data, -1, cond);
4392 }
4393
4394 /**
4395 * This function is called when the socket is available to be written
4396 * to.
4397 *
4398 * @param source The file descriptor that can be written to. This can
4399 * be an http connection or it can be the SSL connection of an
4400 * https request. So be careful what you use it for! If it's
4401 * an https request then use purple_ssl_write() instead of
4402 * writing to it directly.
4403 */
4404 static void
4405 url_fetch_send_cb(gpointer data, gint source, PurpleInputCondition cond)
4406 {
4407 PurpleUtilFetchUrlData *gfud;
4408 int len, total_len;
4409
4410 gfud = data;
4411
4412 if (gfud->request == NULL) {
4413
4414 PurpleProxyInfo *gpi = purple_proxy_get_setup(gfud->account);
4415 GString *request_str = g_string_new(NULL);
4416
4417 g_string_append_printf(request_str, "GET %s%s HTTP/%s\r\n"
4418 "Connection: close\r\n",
4419 (gfud->full ? "" : "/"),
4420 (gfud->full ? (gfud->url ? gfud->url : "") : (gfud->website.page ? gfud->website.page : "")),
4421 (gfud->http11 ? "1.1" : "1.0"));
4422
4423 if (gfud->user_agent)
4424 g_string_append_printf(request_str, "User-Agent: %s\r\n", gfud->user_agent);
4425
4426 /* Host header is not forbidden in HTTP/1.0 requests, and HTTP/1.1
4427 * clients must know how to handle the "chunked" transfer encoding.
4428 * Purple doesn't know how to handle "chunked", so should always send
4429 * the Host header regardless, to get around some observed problems
4430 */
4431 g_string_append_printf(request_str, "Accept: */*\r\n"
4432 "Host: %s\r\n",
4433 (gfud->website.address ? gfud->website.address : ""));
4434
4435 if (purple_proxy_info_get_username(gpi) != NULL
4436 && (purple_proxy_info_get_type(gpi) == PURPLE_PROXY_USE_ENVVAR
4437 || purple_proxy_info_get_type(gpi) == PURPLE_PROXY_HTTP)) {
4438 /* This chunk of code was copied from proxy.c http_start_connect_tunneling()
4439 * This is really a temporary hack - we need a more complete proxy handling solution,
4440 * so I didn't think it was worthwhile to refactor for reuse
4441 */
4442 char *t1, *t2, *ntlm_type1;
4443 char hostname[256];
4444 int ret;
4445
4446 ret = gethostname(hostname, sizeof(hostname));
4447 hostname[sizeof(hostname) - 1] = '\0';
4448 if (ret < 0 || hostname[0] == '\0') {
4449 purple_debug_warning("util", "proxy - gethostname() failed -- is your hostname set?");
4450 strcpy(hostname, "localhost");
4451 }
4452
4453 t1 = g_strdup_printf("%s:%s",
4454 purple_proxy_info_get_username(gpi),
4455 purple_proxy_info_get_password(gpi) ?
4456 purple_proxy_info_get_password(gpi) : "");
4457 t2 = purple_base64_encode((const guchar *)t1, strlen(t1));
4458 g_free(t1);
4459
4460 ntlm_type1 = purple_ntlm_gen_type1(hostname, "");
4461
4462 g_string_append_printf(request_str,
4463 "Proxy-Authorization: Basic %s\r\n"
4464 "Proxy-Authorization: NTLM %s\r\n"
4465 "Proxy-Connection: Keep-Alive\r\n",
4466 t2, ntlm_type1);
4467 g_free(ntlm_type1);
4468 g_free(t2);
4469 }
4470
4471 g_string_append(request_str, "\r\n");
4472
4473 gfud->request = g_string_free(request_str, FALSE);
4474 }
4475
4476 if(purple_debug_is_unsafe())
4477 purple_debug_misc("util", "Request: '%s'\n", gfud->request);
4478 else
4479 purple_debug_misc("util", "request constructed\n");
4480
4481 total_len = strlen(gfud->request);
4482
4483 if (gfud->is_ssl)
4484 len = purple_ssl_write(gfud->ssl_connection, gfud->request + gfud->request_written,
4485 total_len - gfud->request_written);
4486 else
4487 len = write(gfud->fd, gfud->request + gfud->request_written,
4488 total_len - gfud->request_written);
4489
4490 if (len < 0 && errno == EAGAIN)
4491 return;
4492 else if (len < 0) {
4493 purple_util_fetch_url_error(gfud, _("Error writing to %s: %s"),
4494 gfud->website.address, g_strerror(errno));
4495 return;
4496 }
4497 gfud->request_written += len;
4498
4499 if (gfud->request_written < total_len)
4500 return;
4501
4502 /* We're done writing our request, now start reading the response */
4503 if (gfud->is_ssl) {
4504 purple_input_remove(gfud->inpa);
4505 gfud->inpa = 0;
4506 purple_ssl_input_add(gfud->ssl_connection, ssl_url_fetch_recv_cb, gfud);
4507 } else {
4508 purple_input_remove(gfud->inpa);
4509 gfud->inpa = purple_input_add(gfud->fd, PURPLE_INPUT_READ, url_fetch_recv_cb,
4510 gfud);
4511 }
4512 }
4513
4514 static void
4515 url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message)
4516 {
4517 PurpleUtilFetchUrlData *gfud;
4518
4519 gfud = url_data;
4520 gfud->connect_data = NULL;
4521
4522 if (source == -1)
4523 {
4524 purple_util_fetch_url_error(gfud, _("Unable to connect to %s: %s"),
4525 (gfud->website.address ? gfud->website.address : ""), error_message);
4526 return;
4527 }
4528
4529 gfud->fd = source;
4530
4531 gfud->inpa = purple_input_add(source, PURPLE_INPUT_WRITE,
4532 url_fetch_send_cb, gfud);
4533 url_fetch_send_cb(gfud, source, PURPLE_INPUT_WRITE);
4534 }
4535
4536 static void ssl_url_fetch_connect_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond)
4537 {
4538 PurpleUtilFetchUrlData *gfud;
4539
4540 gfud = data;
4541
4542 gfud->inpa = purple_input_add(ssl_connection->fd, PURPLE_INPUT_WRITE,
4543 url_fetch_send_cb, gfud);
4544 url_fetch_send_cb(gfud, ssl_connection->fd, PURPLE_INPUT_WRITE);
4545 }
4546
4547 static void ssl_url_fetch_error_cb(PurpleSslConnection *ssl_connection, PurpleSslErrorType error, gpointer data)
4548 {
4549 PurpleUtilFetchUrlData *gfud;
4550
4551 gfud = data;
4552 gfud->ssl_connection = NULL;
4553
4554 purple_util_fetch_url_error(gfud, _("Unable to connect to %s: %s"),
4555 (gfud->website.address ? gfud->website.address : ""),
4556 purple_ssl_strerror(error));
4557 }
4558
4559 PurpleUtilFetchUrlData *
4560 purple_util_fetch_url_request(PurpleAccount *account,
4561 const char *url, gboolean full, const char *user_agent, gboolean http11,
4562 const char *request, gboolean include_headers, gssize max_len,
4563 PurpleUtilFetchUrlCallback callback, void *user_data)
4564 {
4565 PurpleUtilFetchUrlData *gfud;
4566
4567 g_return_val_if_fail(url != NULL, NULL);
4568 g_return_val_if_fail(callback != NULL, NULL);
4569
4570 if(purple_debug_is_unsafe())
4571 purple_debug_info("util",
4572 "requested to fetch (%s), full=%d, user_agent=(%s), http11=%d\n",
4573 url, full, user_agent?user_agent:"(null)", http11);
4574 else
4575 purple_debug_info("util", "requesting to fetch a URL\n");
4576
4577 gfud = g_new0(PurpleUtilFetchUrlData, 1);
4578
4579 gfud->callback = callback;
4580 gfud->user_data = user_data;
4581 gfud->url = g_strdup(url);
4582 gfud->user_agent = g_strdup(user_agent);
4583 gfud->http11 = http11;
4584 gfud->full = full;
4585 gfud->request = g_strdup(request);
4586 gfud->include_headers = include_headers;
4587 gfud->fd = -1;
4588 gfud->max_len = max_len;
4589 gfud->account = account;
4590
4591 purple_url_parse(url, &gfud->website.address, &gfud->website.port,
4592 &gfud->website.page, &gfud->website.user, &gfud->website.passwd);
4593
4594 if (purple_strcasestr(url, "https://") != NULL) {
4595 if (!purple_ssl_is_supported()) {
4596 purple_util_fetch_url_error(gfud,
4597 _("Unable to connect to %s: %s"),
4598 gfud->website.address,
4599 _("Server requires TLS/SSL, but no TLS/SSL support was found."));
4600 return NULL;
4601 }
4602
4603 gfud->is_ssl = TRUE;
4604 gfud->ssl_connection = purple_ssl_connect(account,
4605 gfud->website.address, gfud->website.port,
4606 ssl_url_fetch_connect_cb, ssl_url_fetch_error_cb, gfud);
4607 } else {
4608 gfud->connect_data = purple_proxy_connect(NULL, account,
4609 gfud->website.address, gfud->website.port,
4610 url_fetch_connect_cb, gfud);
4611 }
4612
4613 if (gfud->ssl_connection == NULL && gfud->connect_data == NULL)
4614 {
4615 purple_util_fetch_url_error(gfud, _("Unable to connect to %s"),
4616 gfud->website.address);
4617 return NULL;
4618 }
4619
4620 return gfud;
4621 }
4622
4623 void
4624 purple_util_fetch_url_cancel(PurpleUtilFetchUrlData *gfud)
4625 {
4626 if (gfud->ssl_connection != NULL)
4627 purple_ssl_close(gfud->ssl_connection);
4628
4629 if (gfud->connect_data != NULL)
4630 purple_proxy_connect_cancel(gfud->connect_data);
4631
4632 if (gfud->inpa > 0)
4633 purple_input_remove(gfud->inpa);
4634
4635 if (gfud->fd >= 0)
4636 close(gfud->fd);
4637
4638 g_free(gfud->website.user);
4639 g_free(gfud->website.passwd);
4640 g_free(gfud->website.address);
4641 g_free(gfud->website.page);
4642 g_free(gfud->url);
4643 g_free(gfud->user_agent);
4644 g_free(gfud->request);
4645 g_free(gfud->webdata);
4646
4647 g_free(gfud);
4648 }
4649 3968
4650 const char * 3969 const char *
4651 purple_url_decode(const char *str) 3970 purple_url_decode(const char *str)
4652 { 3971 {
4653 static char buf[BUF_LEN]; 3972 static char buf[BUF_LEN];

mercurial