Mercurial > pidgin
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 ' | |
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, "&"); | |
968 break; | |
969 | |
970 case '<': | |
971 g_string_append (str, "<"); | |
972 break; | |
973 | |
974 case '>': | |
975 g_string_append (str, ">"); | |
976 break; | |
977 | |
978 case '"': | |
979 g_string_append (str, """); | |
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) |