comparison libpurple/util.c @ 27590:a08e84032814

merge of '2348ff22f0ff3453774b8b25b36238465580c609' and 'e76f11543c2a4aa05bdf584f087cbe3439029661'
author Paul Aurich <paul@darkrain42.org>
date Sun, 12 Jul 2009 05:43:38 +0000
parents 1d30e9d6de9b
children b7ce89597a89
comparison
equal deleted inserted replaced
27104:048bcf41deef 27590:a08e84032814
63 guint inpa; 63 guint inpa;
64 64
65 gboolean got_headers; 65 gboolean got_headers;
66 gboolean has_explicit_data_len; 66 gboolean has_explicit_data_len;
67 char *webdata; 67 char *webdata;
68 unsigned long len; 68 gsize len;
69 unsigned long data_len; 69 unsigned long data_len;
70 gssize max_len; 70 gssize max_len;
71 gboolean chunked;
71 }; 72 };
72 73
73 static char *custom_user_dir = NULL; 74 static char *custom_user_dir = NULL;
74 static char *user_dir = NULL; 75 static char *user_dir = NULL;
75 76
217 "0123456789abcdef"; 218 "0123456789abcdef";
218 219
219 gchar * 220 gchar *
220 purple_base64_encode(const guchar *data, gsize len) 221 purple_base64_encode(const guchar *data, gsize len)
221 { 222 {
223 #if GLIB_CHECK_VERSION(2,12,0)
224 return g_base64_encode(data, len);
225 #else
222 char *out, *rv; 226 char *out, *rv;
223 227
224 g_return_val_if_fail(data != NULL, NULL); 228 g_return_val_if_fail(data != NULL, NULL);
225 g_return_val_if_fail(len > 0, NULL); 229 g_return_val_if_fail(len > 0, NULL);
226 230
251 } 255 }
252 256
253 *out = '\0'; 257 *out = '\0';
254 258
255 return rv; 259 return rv;
260 #endif /* GLIB < 2.12.0 */
256 } 261 }
257 262
258 guchar * 263 guchar *
259 purple_base64_decode(const char *str, gsize *ret_len) 264 purple_base64_decode(const char *str, gsize *ret_len)
260 { 265 {
266 #if GLIB_CHECK_VERSION(2,12,0)
267 /*
268 * We want to allow ret_len to be NULL for backward compatibility,
269 * but g_base64_decode() requires a valid length variable. So if
270 * ret_len is NULL then pass in a dummy variable.
271 */
272 gsize unused;
273 return g_base64_decode(str, ret_len != NULL ? ret_len : &unused);
274 #else
261 guchar *out = NULL; 275 guchar *out = NULL;
262 char tmp = 0; 276 char tmp = 0;
263 const char *c; 277 const char *c;
264 gint32 tmp2 = 0; 278 gint32 tmp2 = 0;
265 int len = 0, n = 0; 279 int len = 0, n = 0;
317 331
318 if (ret_len != NULL) 332 if (ret_len != NULL)
319 *ret_len = len; 333 *ret_len = len;
320 334
321 return out; 335 return out;
336 #endif /* GLIB < 2.12.0 */
322 } 337 }
323 338
324 /************************************************************************** 339 /**************************************************************************
325 * Quoted Printable Functions (see RFC 2045). 340 * Quoted Printable Functions (see RFC 2045).
326 **************************************************************************/ 341 **************************************************************************/
924 } 939 }
925 940
926 /************************************************************************** 941 /**************************************************************************
927 * Markup Functions 942 * Markup Functions
928 **************************************************************************/ 943 **************************************************************************/
944
945 /*
946 * This function is stolen from glib's gmarkup.c and modified to not
947 * replace ' with &apos;
948 */
949 static void append_escaped_text(GString *str,
950 const gchar *text, gssize length)
951 {
952 const gchar *p;
953 const gchar *end;
954 gunichar c;
955
956 p = text;
957 end = text + length;
958
959 while (p != end)
960 {
961 const gchar *next;
962 next = g_utf8_next_char (p);
963
964 switch (*p)
965 {
966 case '&':
967 g_string_append (str, "&amp;");
968 break;
969
970 case '<':
971 g_string_append (str, "&lt;");
972 break;
973
974 case '>':
975 g_string_append (str, "&gt;");
976 break;
977
978 case '"':
979 g_string_append (str, "&quot;");
980 break;
981
982 default:
983 c = g_utf8_get_char (p);
984 if ((0x1 <= c && c <= 0x8) ||
985 (0xb <= c && c <= 0xc) ||
986 (0xe <= c && c <= 0x1f) ||
987 (0x7f <= c && c <= 0x84) ||
988 (0x86 <= c && c <= 0x9f))
989 g_string_append_printf (str, "&#x%x;", c);
990 else
991 g_string_append_len (str, p, next - p);
992 break;
993 }
994
995 p = next;
996 }
997 }
998
999 /* This function is stolen from glib's gmarkup.c */
1000 gchar *purple_markup_escape_text(const gchar *text, gssize length)
1001 {
1002 GString *str;
1003
1004 g_return_val_if_fail(text != NULL, NULL);
1005
1006 if (length < 0)
1007 length = strlen(text);
1008
1009 /* prealloc at least as long as original text */
1010 str = g_string_sized_new(length);
1011 append_escaped_text(str, text, length);
1012
1013 return g_string_free(str, FALSE);
1014 }
929 1015
930 const char * 1016 const char *
931 purple_markup_unescape_entity(const char *text, int *length) 1017 purple_markup_unescape_entity(const char *text, int *length)
932 { 1018 {
933 const char *pln; 1019 const char *pln;
962 static char buf[7]; 1048 static char buf[7];
963 int buflen = g_unichar_to_utf8((gunichar)pound, buf); 1049 int buflen = g_unichar_to_utf8((gunichar)pound, buf);
964 buf[buflen] = '\0'; 1050 buf[buflen] = '\0';
965 pln = buf; 1051 pln = buf;
966 1052
967 len = 2; 1053 len = (*(text+2) == 'x' ? 3 : 2);
968 while(isdigit((gint) text[len])) len++; 1054 while(isxdigit((gint) text[len])) len++;
969 if(text[len] == ';') len++; 1055 if(text[len] == ';') len++;
970 } 1056 }
971 else 1057 else
972 return NULL; 1058 return NULL;
973 1059
2382 2468
2383 } 2469 }
2384 return g_string_free(ret, FALSE); 2470 return g_string_free(ret, FALSE);
2385 } 2471 }
2386 2472
2387 char * 2473 char *purple_unescape_html(const char *html)
2388 purple_unescape_html(const char *html) { 2474 {
2389 if (html != NULL) { 2475 GString *ret;
2390 const char *c = html; 2476 const char *c = html;
2391 GString *ret = g_string_new(""); 2477
2392 while (*c) { 2478 if (html == NULL)
2393 int len; 2479 return NULL;
2394 const char *ent; 2480
2395 2481 ret = g_string_new("");
2396 if ((ent = purple_markup_unescape_entity(c, &len)) != NULL) { 2482 while (*c) {
2397 ret = g_string_append(ret, ent); 2483 int len;
2398 c += len; 2484 const char *ent;
2399 } else if (!strncmp(c, "<br>", 4)) { 2485
2400 ret = g_string_append_c(ret, '\n'); 2486 if ((ent = purple_markup_unescape_entity(c, &len)) != NULL) {
2401 c += 4; 2487 g_string_append(ret, ent);
2402 } else { 2488 c += len;
2403 ret = g_string_append_c(ret, *c); 2489 } else if (!strncmp(c, "<br>", 4)) {
2404 c++; 2490 g_string_append_c(ret, '\n');
2405 } 2491 c += 4;
2406 } 2492 } else {
2407 return g_string_free(ret, FALSE); 2493 g_string_append_c(ret, *c);
2408 } 2494 c++;
2409 2495 }
2410 return NULL; 2496 }
2497
2498 return g_string_free(ret, FALSE);
2411 } 2499 }
2412 2500
2413 char * 2501 char *
2414 purple_markup_slice(const char *str, guint x, guint y) 2502 purple_markup_slice(const char *str, guint x, guint y)
2415 { 2503 {
2877 2965
2878 return "icon"; 2966 return "icon";
2879 } 2967 }
2880 2968
2881 /* 2969 /*
2882 * TODO: Consider using something faster than SHA-1, such as MD5, MD4 2970 * We thought about using non-cryptographic hashes like CRC32 here.
2883 * or CRC32. Are there security implications to that? Would 2971 * They would be faster, but we think using something more secure is
2884 * probably be a good idea to benchmark some algorithms with 2972 * important, so that it is more difficult for someone to maliciously
2885 * 3KB-10KB chunks of data (typical buddy icon sizes). 2973 * replace one buddy's icon with something else.
2886 */ 2974 */
2887 char * 2975 char *
2888 purple_util_get_image_checksum(gconstpointer image_data, size_t image_len) 2976 purple_util_get_image_checksum(gconstpointer image_data, size_t image_len)
2889 { 2977 {
2890 PurpleCipherContext *context; 2978 PurpleCipherContext *context;
3009 char * 3097 char *
3010 purple_fd_get_ip(int fd) 3098 purple_fd_get_ip(int fd)
3011 { 3099 {
3012 struct sockaddr addr; 3100 struct sockaddr addr;
3013 socklen_t namelen = sizeof(addr); 3101 socklen_t namelen = sizeof(addr);
3102 struct in_addr in;
3014 3103
3015 g_return_val_if_fail(fd != 0, NULL); 3104 g_return_val_if_fail(fd != 0, NULL);
3016 3105
3017 if (getsockname(fd, &addr, &namelen)) 3106 if (getsockname(fd, &addr, &namelen))
3018 return NULL; 3107 return NULL;
3019 3108
3020 return g_strdup(inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr)); 3109 in = ((struct sockaddr_in *)&addr)->sin_addr;
3110 return g_strdup(inet_ntoa(in));
3021 } 3111 }
3022 3112
3023 3113
3024 /************************************************************************** 3114 /**************************************************************************
3025 * String Functions 3115 * String Functions
3712 } 3802 }
3713 3803
3714 return TRUE; 3804 return TRUE;
3715 } 3805 }
3716 3806
3717 static size_t 3807 static const char *
3718 parse_content_len(const char *data, size_t data_len) 3808 find_header_content(const char *data, size_t data_len, const char *header, size_t header_len)
3719 { 3809 {
3720 size_t content_len = 0;
3721 const char *p = NULL; 3810 const char *p = NULL;
3722 3811
3723 /* This is still technically wrong, since headers are case-insensitive 3812 if (header_len <= 0)
3724 * [RFC 2616, section 4.2], though this ought to catch the normal case. 3813 header_len = strlen(header);
3725 * Note: data is _not_ nul-terminated. 3814
3726 */ 3815 /* Note: data is _not_ nul-terminated. */
3727 if(data_len > 16) { 3816 if (data_len > header_len) {
3728 p = (strncmp(data, "Content-Length: ", 16) == 0) ? data : NULL; 3817 if (header[0] == '\n')
3729 if(!p) 3818 p = (g_strncasecmp(data, header + 1, header_len - 1) == 0) ? data : NULL;
3730 p = (strncmp(data, "CONTENT-LENGTH: ", 16) == 0) 3819 if (!p)
3731 ? data : NULL; 3820 p = purple_strcasestr(data, header);
3732 if(!p) { 3821 if (p)
3733 p = g_strstr_len(data, data_len, "\nContent-Length: "); 3822 p += header_len;
3734 if (p) 3823 }
3735 p++; 3824
3736 } 3825 /* If we can find the header at all, try to sscanf it.
3737 if(!p) {
3738 p = g_strstr_len(data, data_len, "\nCONTENT-LENGTH: ");
3739 if (p)
3740 p++;
3741 }
3742
3743 if(p)
3744 p += 16;
3745 }
3746
3747 /* If we can find a Content-Length header at all, try to sscanf it.
3748 * Response headers should end with at least \r\n, so sscanf is safe, 3826 * Response headers should end with at least \r\n, so sscanf is safe,
3749 * if we make sure that there is indeed a \n in our header. 3827 * if we make sure that there is indeed a \n in our header.
3750 */ 3828 */
3751 if (p && g_strstr_len(p, data_len - (p - data), "\n")) { 3829 if (p && g_strstr_len(p, data_len - (p - data), "\n")) {
3830 return p;
3831 }
3832
3833 return NULL;
3834 }
3835
3836 static size_t
3837 parse_content_len(const char *data, size_t data_len)
3838 {
3839 size_t content_len = 0;
3840 const char *p = NULL;
3841
3842 p = find_header_content(data, data_len, "\nContent-Length: ", sizeof("\nContent-Length: ") - 1);
3843 if (p) {
3752 sscanf(p, "%" G_GSIZE_FORMAT, &content_len); 3844 sscanf(p, "%" G_GSIZE_FORMAT, &content_len);
3753 purple_debug_misc("util", "parsed %" G_GSIZE_FORMAT "\n", content_len); 3845 purple_debug_misc("util", "parsed %" G_GSIZE_FORMAT "\n", content_len);
3754 } 3846 }
3755 3847
3756 return content_len; 3848 return content_len;
3757 } 3849 }
3758 3850
3851 static gboolean
3852 content_is_chunked(const char *data, size_t data_len)
3853 {
3854 const char *p = find_header_content(data, data_len, "\nTransfer-Encoding: ", sizeof("\nTransfer-Encoding: ") - 1);
3855 if (p && g_strncasecmp(p, "chunked", 7) == 0)
3856 return TRUE;
3857
3858 return FALSE;
3859 }
3860
3861 /* Process in-place */
3862 static void
3863 process_chunked_data(char *data, gsize *len)
3864 {
3865 gsize sz;
3866 gsize newlen = 0;
3867 char *p = data;
3868 char *s = data;
3869
3870 while (*s) {
3871 /* Read the size of this chunk */
3872 if (sscanf(s, "%" G_GSIZE_MODIFIER "x", &sz) != 1)
3873 {
3874 purple_debug_error("util", "Error processing chunked data: "
3875 "Expected data length, found: %s\n", s);
3876 break;
3877 }
3878 if (sz == 0) {
3879 /* We've reached the last chunk */
3880 /*
3881 * TODO: The spec allows "footers" to follow the last chunk.
3882 * If there is more data after this line then we should
3883 * treat it like a header.
3884 */
3885 break;
3886 }
3887
3888 /* Advance to the start of the data */
3889 s = strstr(s, "\r\n");
3890 if (s == NULL)
3891 break;
3892 s += 2;
3893
3894 if (s + sz > data + *len) {
3895 purple_debug_error("util", "Error processing chunked data: "
3896 "Chunk size %" G_GSIZE_FORMAT " bytes was longer "
3897 "than the data remaining in the buffer (%"
3898 G_GSIZE_FORMAT " bytes)\n", sz, data + *len - s);
3899 }
3900
3901 /* Move all data overtop of the chunk length that we read in earlier */
3902 g_memmove(p, s, sz);
3903 p += sz;
3904 s += sz;
3905 newlen += sz;
3906 if (*s != '\r' && *(s + 1) != '\n') {
3907 purple_debug_error("util", "Error processing chunked data: "
3908 "Expected \\r\\n, found: %s\n", s);
3909 break;
3910 }
3911 s += 2;
3912 }
3913
3914 /* NULL terminate the data */
3915 *p = 0;
3916
3917 *len = newlen;
3918 }
3759 3919
3760 static void 3920 static void
3761 url_fetch_recv_cb(gpointer url_data, gint source, PurpleInputCondition cond) 3921 url_fetch_recv_cb(gpointer url_data, gint source, PurpleInputCondition cond)
3762 { 3922 {
3763 PurpleUtilFetchUrlData *gfud = url_data; 3923 PurpleUtilFetchUrlData *gfud = url_data;
3814 3974
3815 gfud->got_headers = TRUE; 3975 gfud->got_headers = TRUE;
3816 3976
3817 /* No redirect. See if we can find a content length. */ 3977 /* No redirect. See if we can find a content length. */
3818 content_len = parse_content_len(gfud->webdata, header_len); 3978 content_len = parse_content_len(gfud->webdata, header_len);
3979 gfud->chunked = content_is_chunked(gfud->webdata, header_len);
3819 3980
3820 if(content_len == 0) { 3981 if(content_len == 0) {
3821 /* We'll stick with an initial 8192 */ 3982 /* We'll stick with an initial 8192 */
3822 content_len = 8192; 3983 content_len = 8192;
3823 } else { 3984 } else {
3886 4047
3887 if((len == 0) || got_eof) { 4048 if((len == 0) || got_eof) {
3888 gfud->webdata = g_realloc(gfud->webdata, gfud->len + 1); 4049 gfud->webdata = g_realloc(gfud->webdata, gfud->len + 1);
3889 gfud->webdata[gfud->len] = '\0'; 4050 gfud->webdata[gfud->len] = '\0';
3890 4051
4052 if (!gfud->include_headers && gfud->chunked) {
4053 /* Process only if we don't want the headers. */
4054 process_chunked_data(gfud->webdata, &gfud->len);
4055 }
4056
3891 gfud->callback(gfud, gfud->user_data, gfud->webdata, gfud->len, NULL); 4057 gfud->callback(gfud, gfud->user_data, gfud->webdata, gfud->len, NULL);
3892 purple_util_fetch_url_cancel(gfud); 4058 purple_util_fetch_url_cancel(gfud);
3893 } 4059 }
3894 } 4060 }
3895 4061
3896 static void ssl_url_fetch_recv_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond) 4062 static void ssl_url_fetch_recv_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond)
3897 { 4063 {
3898 url_fetch_recv_cb(data, -1, cond); 4064 url_fetch_recv_cb(data, -1, cond);
3899 } 4065 }
3900 4066
3901 /* 4067 /**
3902 * This function is called when the socket is available to be written 4068 * This function is called when the socket is available to be written
3903 * to. 4069 * to.
3904 * 4070 *
3905 * @param source The file descriptor that can be written to. This can 4071 * @param source The file descriptor that can be written to. This can
3906 * be an http connection or it can be the SSL connection of an 4072 * be an http connection or it can be the SSL connection of an
3946 (gfud->http11 ? "1.1" : "1.0"), 4112 (gfud->http11 ? "1.1" : "1.0"),
3947 (gfud->website.address ? gfud->website.address : "")); 4113 (gfud->website.address ? gfud->website.address : ""));
3948 } 4114 }
3949 } 4115 }
3950 4116
3951 if(g_getenv("PURPLE_UNSAFE_DEBUG")) 4117 if(purple_debug_is_unsafe())
3952 purple_debug_misc("util", "Request: '%s'\n", gfud->request); 4118 purple_debug_misc("util", "Request: '%s'\n", gfud->request);
3953 else 4119 else
3954 purple_debug_misc("util", "request constructed\n"); 4120 purple_debug_misc("util", "request constructed\n");
3955 4121
3956 total_len = strlen(gfud->request); 4122 total_len = strlen(gfud->request);
4063 PurpleUtilFetchUrlData *gfud; 4229 PurpleUtilFetchUrlData *gfud;
4064 4230
4065 g_return_val_if_fail(url != NULL, NULL); 4231 g_return_val_if_fail(url != NULL, NULL);
4066 g_return_val_if_fail(callback != NULL, NULL); 4232 g_return_val_if_fail(callback != NULL, NULL);
4067 4233
4068 if(g_getenv("PURPLE_UNSAFE_DEBUG")) 4234 if(purple_debug_is_unsafe())
4069 purple_debug_info("util", 4235 purple_debug_info("util",
4070 "requested to fetch (%s), full=%d, user_agent=(%s), http11=%d\n", 4236 "requested to fetch (%s), full=%d, user_agent=(%s), http11=%d\n",
4071 url, full, user_agent?user_agent:"(null)", http11); 4237 url, full, user_agent?user_agent:"(null)", http11);
4072 else 4238 else
4073 purple_debug_info("util", "requesting to fetch a URL\n"); 4239 purple_debug_info("util", "requesting to fetch a URL\n");
4089 &gfud->website.page, &gfud->website.user, &gfud->website.passwd); 4255 &gfud->website.page, &gfud->website.user, &gfud->website.passwd);
4090 4256
4091 if (purple_strcasestr(url, "https://") != NULL) { 4257 if (purple_strcasestr(url, "https://") != NULL) {
4092 if (!purple_ssl_is_supported()) { 4258 if (!purple_ssl_is_supported()) {
4093 purple_util_fetch_url_error(gfud, 4259 purple_util_fetch_url_error(gfud,
4094 _("Unable to connect to %s: Server requires TLS/SSL, but no TLS/SSL support was found."), 4260 _("Unable to connect to %s: %s"),
4095 gfud->website.address); 4261 gfud->website.address,
4262 _("Server requires TLS/SSL, but no TLS/SSL support was found."));
4096 return NULL; 4263 return NULL;
4097 } 4264 }
4098 4265
4099 gfud->is_ssl = TRUE; 4266 gfud->is_ssl = TRUE;
4100 gfud->ssl_connection = purple_ssl_connect(account, 4267 gfud->ssl_connection = purple_ssl_connect(account,
4278 4445
4279 return ((c - domain) > 3 ? TRUE : FALSE); 4446 return ((c - domain) > 3 ? TRUE : FALSE);
4280 } 4447 }
4281 4448
4282 gboolean 4449 gboolean
4283 purple_ip_address_is_valid(const char *ip) 4450 purple_ipv4_address_is_valid(const char *ip)
4284 { 4451 {
4285 int c, o1, o2, o3, o4; 4452 int c, o1, o2, o3, o4;
4286 char end; 4453 char end;
4287 4454
4288 g_return_val_if_fail(ip != NULL, FALSE); 4455 g_return_val_if_fail(ip != NULL, FALSE);
4289 4456
4290 c = sscanf(ip, "%d.%d.%d.%d%c", &o1, &o2, &o3, &o4, &end); 4457 c = sscanf(ip, "%d.%d.%d.%d%c", &o1, &o2, &o3, &o4, &end);
4291 if (c != 4 || o1 < 0 || o1 > 255 || o2 < 0 || o2 > 255 || o3 < 0 || o3 > 255 || o4 < 0 || o4 > 255) 4458 if (c != 4 || o1 < 0 || o1 > 255 || o2 < 0 || o2 > 255 || o3 < 0 || o3 > 255 || o4 < 0 || o4 > 255)
4292 return FALSE; 4459 return FALSE;
4293 return TRUE; 4460 return TRUE;
4461 }
4462
4463 gboolean
4464 purple_ipv6_address_is_valid(const gchar *ip)
4465 {
4466 const gchar *c;
4467 gboolean double_colon = FALSE;
4468 gint chunks = 1;
4469 gint in = 0;
4470
4471 g_return_val_if_fail(ip != NULL, FALSE);
4472
4473 if (*ip == '\0')
4474 return FALSE;
4475
4476 for (c = ip; *c; ++c) {
4477 if ((*c >= '0' && *c <= '9') ||
4478 (*c >= 'a' && *c <= 'f') ||
4479 (*c >= 'A' && *c <= 'F')) {
4480 if (++in > 4)
4481 /* Only four hex digits per chunk */
4482 return FALSE;
4483 continue;
4484 } else if (*c == ':') {
4485 /* The start of a new chunk */
4486 ++chunks;
4487 in = 0;
4488 if (*(c + 1) == ':') {
4489 /*
4490 * '::' indicates a consecutive series of chunks full
4491 * of zeroes. There can be only one of these per address.
4492 */
4493 if (double_colon)
4494 return FALSE;
4495 double_colon = TRUE;
4496 }
4497 } else
4498 return FALSE;
4499 }
4500
4501 /*
4502 * Either we saw a '::' and there were fewer than 8 chunks -or-
4503 * we didn't see a '::' and saw exactly 8 chunks.
4504 */
4505 return (double_colon && chunks < 8) || (!double_colon && chunks == 8);
4506 }
4507
4508 /* TODO 3.0.0: Add ipv6 check, too */
4509 gboolean
4510 purple_ip_address_is_valid(const char *ip)
4511 {
4512 return purple_ipv4_address_is_valid(ip);
4294 } 4513 }
4295 4514
4296 /* Stolen from gnome_uri_list_extract_uris */ 4515 /* Stolen from gnome_uri_list_extract_uris */
4297 GList * 4516 GList *
4298 purple_uri_list_extract_uris(const gchar *uri_list) 4517 purple_uri_list_extract_uris(const gchar *uri_list)