Imported Upstream version 2.67.1
[platform/upstream/glib.git] / glib / ghostutils.c
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2
3 /* GLIB - Library of useful routines for C programming
4  * Copyright (C) 2008 Red Hat, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General
17  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include "config.h"
21
22 #include <string.h>
23
24 #ifdef G_OS_UNIX
25 #include <unistd.h>
26 #endif
27
28 #include "ghostutils.h"
29
30 #include "garray.h"
31 #include "gmem.h"
32 #include "gstring.h"
33 #include "gstrfuncs.h"
34 #include "glibintl.h"
35
36 #ifdef G_PLATFORM_WIN32
37 #include <windows.h>
38 #endif
39
40
41 /**
42  * SECTION:ghostutils
43  * @short_description: Internet hostname utilities
44  *
45  * Functions for manipulating internet hostnames; in particular, for
46  * converting between Unicode and ASCII-encoded forms of
47  * Internationalized Domain Names (IDNs).
48  *
49  * The
50  * [Internationalized Domain Names for Applications (IDNA)](http://www.ietf.org/rfc/rfc3490.txt)
51  * standards allow for the use
52  * of Unicode domain names in applications, while providing
53  * backward-compatibility with the old ASCII-only DNS, by defining an
54  * ASCII-Compatible Encoding of any given Unicode name, which can be
55  * used with non-IDN-aware applications and protocols. (For example,
56  * "Παν語.org" maps to "xn--4wa8awb4637h.org".)
57  **/
58
59 #define IDNA_ACE_PREFIX     "xn--"
60 #define IDNA_ACE_PREFIX_LEN 4
61
62 /* Punycode constants, from RFC 3492. */
63
64 #define PUNYCODE_BASE          36
65 #define PUNYCODE_TMIN           1
66 #define PUNYCODE_TMAX          26
67 #define PUNYCODE_SKEW          38
68 #define PUNYCODE_DAMP         700
69 #define PUNYCODE_INITIAL_BIAS  72
70 #define PUNYCODE_INITIAL_N   0x80
71
72 #define PUNYCODE_IS_BASIC(cp) ((guint)(cp) < 0x80)
73
74 /* Encode/decode a single base-36 digit */
75 static inline gchar
76 encode_digit (guint dig)
77 {
78   if (dig < 26)
79     return dig + 'a';
80   else
81     return dig - 26 + '0';
82 }
83
84 static inline guint
85 decode_digit (gchar dig)
86 {
87   if (dig >= 'A' && dig <= 'Z')
88     return dig - 'A';
89   else if (dig >= 'a' && dig <= 'z')
90     return dig - 'a';
91   else if (dig >= '0' && dig <= '9')
92     return dig - '0' + 26;
93   else
94     return G_MAXUINT;
95 }
96
97 /* Punycode bias adaptation algorithm, RFC 3492 section 6.1 */
98 static guint
99 adapt (guint    delta,
100        guint    numpoints,
101        gboolean firsttime)
102 {
103   guint k;
104
105   delta = firsttime ? delta / PUNYCODE_DAMP : delta / 2;
106   delta += delta / numpoints;
107
108   k = 0;
109   while (delta > ((PUNYCODE_BASE - PUNYCODE_TMIN) * PUNYCODE_TMAX) / 2)
110     {
111       delta /= PUNYCODE_BASE - PUNYCODE_TMIN;
112       k += PUNYCODE_BASE;
113     }
114
115   return k + ((PUNYCODE_BASE - PUNYCODE_TMIN + 1) * delta /
116               (delta + PUNYCODE_SKEW));
117 }
118
119 /* Punycode encoder, RFC 3492 section 6.3. The algorithm is
120  * sufficiently bizarre that it's not really worth trying to explain
121  * here.
122  */
123 static gboolean
124 punycode_encode (const gchar *input_utf8,
125                  gsize        input_utf8_length,
126                  GString     *output)
127 {
128   guint delta, handled_chars, num_basic_chars, bias, j, q, k, t, digit;
129   gunichar n, m, *input;
130   glong input_length;
131   gboolean success = FALSE;
132
133   /* Convert from UTF-8 to Unicode code points */
134   input = g_utf8_to_ucs4 (input_utf8, input_utf8_length, NULL,
135                           &input_length, NULL);
136   if (!input)
137     return FALSE;
138
139   /* Copy basic chars */
140   for (j = num_basic_chars = 0; j < input_length; j++)
141     {
142       if (PUNYCODE_IS_BASIC (input[j]))
143         {
144           g_string_append_c (output, g_ascii_tolower (input[j]));
145           num_basic_chars++;
146         }
147     }
148   if (num_basic_chars)
149     g_string_append_c (output, '-');
150
151   handled_chars = num_basic_chars;
152
153   /* Encode non-basic chars */
154   delta = 0;
155   bias = PUNYCODE_INITIAL_BIAS;
156   n = PUNYCODE_INITIAL_N;
157   while (handled_chars < input_length)
158     {
159       /* let m = the minimum {non-basic} code point >= n in the input */
160       for (m = G_MAXUINT, j = 0; j < input_length; j++)
161         {
162           if (input[j] >= n && input[j] < m)
163             m = input[j];
164         }
165
166       if (m - n > (G_MAXUINT - delta) / (handled_chars + 1))
167         goto fail;
168       delta += (m - n) * (handled_chars + 1);
169       n = m;
170
171       for (j = 0; j < input_length; j++)
172         {
173           if (input[j] < n)
174             {
175               if (++delta == 0)
176                 goto fail;
177             }
178           else if (input[j] == n)
179             {
180               q = delta;
181               for (k = PUNYCODE_BASE; ; k += PUNYCODE_BASE)
182                 {
183                   if (k <= bias)
184                     t = PUNYCODE_TMIN;
185                   else if (k >= bias + PUNYCODE_TMAX)
186                     t = PUNYCODE_TMAX;
187                   else
188                     t = k - bias;
189                   if (q < t)
190                     break;
191                   digit = t + (q - t) % (PUNYCODE_BASE - t);
192                   g_string_append_c (output, encode_digit (digit));
193                   q = (q - t) / (PUNYCODE_BASE - t);
194                 }
195
196               g_string_append_c (output, encode_digit (q));
197               bias = adapt (delta, handled_chars + 1, handled_chars == num_basic_chars);
198               delta = 0;
199               handled_chars++;
200             }
201         }
202
203       delta++;
204       n++;
205     }
206
207   success = TRUE;
208
209  fail:
210   g_free (input);
211   return success;
212 }
213
214 /* From RFC 3454, Table B.1 */
215 #define idna_is_junk(ch) ((ch) == 0x00AD || (ch) == 0x1806 || (ch) == 0x200B || (ch) == 0x2060 || (ch) == 0xFEFF || (ch) == 0x034F || (ch) == 0x180B || (ch) == 0x180C || (ch) == 0x180D || (ch) == 0x200C || (ch) == 0x200D || ((ch) >= 0xFE00 && (ch) <= 0xFE0F))
216
217 /* Scan @str for "junk" and return a cleaned-up string if any junk
218  * is found. Else return %NULL.
219  */
220 static gchar *
221 remove_junk (const gchar *str,
222              gint         len)
223 {
224   GString *cleaned = NULL;
225   const gchar *p;
226   gunichar ch;
227
228   for (p = str; len == -1 ? *p : p < str + len; p = g_utf8_next_char (p))
229     {
230       ch = g_utf8_get_char (p);
231       if (idna_is_junk (ch))
232         {
233           if (!cleaned)
234             {
235               cleaned = g_string_new (NULL);
236               g_string_append_len (cleaned, str, p - str);
237             }
238         }
239       else if (cleaned)
240         g_string_append_unichar (cleaned, ch);
241     }
242
243   if (cleaned)
244     return g_string_free (cleaned, FALSE);
245   else
246     return NULL;
247 }
248
249 static inline gboolean
250 contains_uppercase_letters (const gchar *str,
251                             gint         len)
252 {
253   const gchar *p;
254
255   for (p = str; len == -1 ? *p : p < str + len; p = g_utf8_next_char (p))
256     {
257       if (g_unichar_isupper (g_utf8_get_char (p)))
258         return TRUE;
259     }
260   return FALSE;
261 }
262
263 static inline gboolean
264 contains_non_ascii (const gchar *str,
265                     gint         len)
266 {
267   const gchar *p;
268
269   for (p = str; len == -1 ? *p : p < str + len; p++)
270     {
271       if ((guchar)*p > 0x80)
272         return TRUE;
273     }
274   return FALSE;
275 }
276
277 /* RFC 3454, Appendix C. ish. */
278 static inline gboolean
279 idna_is_prohibited (gunichar ch)
280 {
281   switch (g_unichar_type (ch))
282     {
283     case G_UNICODE_CONTROL:
284     case G_UNICODE_FORMAT:
285     case G_UNICODE_UNASSIGNED:
286     case G_UNICODE_PRIVATE_USE:
287     case G_UNICODE_SURROGATE:
288     case G_UNICODE_LINE_SEPARATOR:
289     case G_UNICODE_PARAGRAPH_SEPARATOR:
290     case G_UNICODE_SPACE_SEPARATOR:
291       return TRUE;
292
293     case G_UNICODE_OTHER_SYMBOL:
294       if (ch == 0xFFFC || ch == 0xFFFD ||
295           (ch >= 0x2FF0 && ch <= 0x2FFB))
296         return TRUE;
297       return FALSE;
298
299     case G_UNICODE_NON_SPACING_MARK:
300       if (ch == 0x0340 || ch == 0x0341)
301         return TRUE;
302       return FALSE;
303
304     default:
305       return FALSE;
306     }
307 }
308
309 /* RFC 3491 IDN cleanup algorithm. */
310 static gchar *
311 nameprep (const gchar *hostname,
312           gint         len,
313           gboolean    *is_unicode)
314 {
315   gchar *name, *tmp = NULL, *p;
316
317   /* It would be nice if we could do this without repeatedly
318    * allocating strings and converting back and forth between
319    * gunichars and UTF-8... The code does at least avoid doing most of
320    * the sub-operations when they would just be equivalent to a
321    * g_strdup().
322    */
323
324   /* Remove presentation-only characters */
325   name = remove_junk (hostname, len);
326   if (name)
327     {
328       tmp = name;
329       len = -1;
330     }
331   else
332     name = (gchar *)hostname;
333
334   /* Convert to lowercase */
335   if (contains_uppercase_letters (name, len))
336     {
337       name = g_utf8_strdown (name, len);
338       g_free (tmp);
339       tmp = name;
340       len = -1;
341     }
342
343   /* If there are no UTF8 characters, we're done. */
344   if (!contains_non_ascii (name, len))
345     {
346       *is_unicode = FALSE;
347       if (name == (gchar *)hostname)
348         return len == -1 ? g_strdup (hostname) : g_strndup (hostname, len);
349       else
350         return name;
351     }
352
353   *is_unicode = TRUE;
354
355   /* Normalize */
356   name = g_utf8_normalize (name, len, G_NORMALIZE_NFKC);
357   g_free (tmp);
358   tmp = name;
359
360   if (!name)
361     return NULL;
362
363   /* KC normalization may have created more capital letters (eg,
364    * angstrom -> capital A with ring). So we have to lowercasify a
365    * second time. (This is more-or-less how the nameprep algorithm
366    * does it. If tolower(nfkc(tolower(X))) is guaranteed to be the
367    * same as tolower(nfkc(X)), then we could skip the first tolower,
368    * but I'm not sure it is.)
369    */
370   if (contains_uppercase_letters (name, -1))
371     {
372       name = g_utf8_strdown (name, -1);
373       g_free (tmp);
374       tmp = name;
375     }
376
377   /* Check for prohibited characters */
378   for (p = name; *p; p = g_utf8_next_char (p))
379     {
380       if (idna_is_prohibited (g_utf8_get_char (p)))
381         {
382           name = NULL;
383           g_free (tmp);
384           goto done;
385         }
386     }
387
388   /* FIXME: We're supposed to verify certain constraints on bidi
389    * characters, but glib does not appear to have that information.
390    */
391
392  done:
393   return name;
394 }
395
396 /* RFC 3490, section 3.1 says '.', 0x3002, 0xFF0E, and 0xFF61 count as
397  * label-separating dots. @str must be '\0'-terminated.
398  */
399 #define idna_is_dot(str) ( \
400   ((guchar)(str)[0] == '.') ||                                                 \
401   ((guchar)(str)[0] == 0xE3 && (guchar)(str)[1] == 0x80 && (guchar)(str)[2] == 0x82) || \
402   ((guchar)(str)[0] == 0xEF && (guchar)(str)[1] == 0xBC && (guchar)(str)[2] == 0x8E) || \
403   ((guchar)(str)[0] == 0xEF && (guchar)(str)[1] == 0xBD && (guchar)(str)[2] == 0xA1) )
404
405 static const gchar *
406 idna_end_of_label (const gchar *str)
407 {
408   for (; *str; str = g_utf8_next_char (str))
409     {
410       if (idna_is_dot (str))
411         return str;
412     }
413   return str;
414 }
415
416 static gsize
417 get_hostname_max_length_bytes (void)
418 {
419 #if defined(G_OS_WIN32)
420   wchar_t tmp[MAX_COMPUTERNAME_LENGTH];
421   return sizeof (tmp) / sizeof (tmp[0]);
422 #elif defined(_SC_HOST_NAME_MAX)
423   glong max = sysconf (_SC_HOST_NAME_MAX);
424   if (max > 0)
425     return (gsize) max;
426
427 #ifdef HOST_NAME_MAX
428   return HOST_NAME_MAX;
429 #else
430   return _POSIX_HOST_NAME_MAX;
431 #endif /* HOST_NAME_MAX */
432 #else
433   /* Fallback to some reasonable value
434    * See https://stackoverflow.com/questions/8724954/what-is-the-maximum-number-of-characters-for-a-host-name-in-unix/28918017#28918017 */
435   return 255;
436 #endif
437 }
438
439 /* Returns %TRUE if `strlen (str) > comparison_length`, but without actually
440  * running `strlen(str)`, as that would take a very long time for long
441  * (untrusted) input strings. */
442 static gboolean
443 strlen_greater_than (const gchar *str,
444                      gsize        comparison_length)
445 {
446   gsize i;
447
448   for (i = 0; str[i] != '\0'; i++)
449     if (i > comparison_length)
450       return TRUE;
451
452   return FALSE;
453 }
454
455 /**
456  * g_hostname_to_ascii:
457  * @hostname: a valid UTF-8 or ASCII hostname
458  *
459  * Converts @hostname to its canonical ASCII form; an ASCII-only
460  * string containing no uppercase letters and not ending with a
461  * trailing dot.
462  *
463  * Returns: (nullable) (transfer full): an ASCII hostname, which must be freed,
464  *    or %NULL if @hostname is in some way invalid.
465  *
466  * Since: 2.22
467  **/
468 gchar *
469 g_hostname_to_ascii (const gchar *hostname)
470 {
471   gchar *name, *label, *p;
472   GString *out;
473   gssize llen, oldlen;
474   gboolean unicode;
475   gsize hostname_max_length_bytes = get_hostname_max_length_bytes ();
476
477   /* Do an initial check on the hostname length, as overlong hostnames take a
478    * long time in the IDN cleanup algorithm in nameprep(). The ultimate
479    * restriction is that the IDN-decoded (i.e. pure ASCII) hostname cannot be
480    * longer than 255 bytes. That’s the least restrictive limit on hostname
481    * length of all the ways hostnames can be interpreted. Typically, the
482    * hostname will be an FQDN, which is limited to 253 bytes long. POSIX
483    * hostnames are limited to `get_hostname_max_length_bytes()` (typically 255
484    * bytes).
485    *
486    * See https://stackoverflow.com/a/28918017/2931197
487    *
488    * It’s possible for a hostname to be %-encoded, in which case its decoded
489    * length will be as much as 3× shorter.
490    *
491    * It’s also possible for a hostname to use overlong UTF-8 encodings, in which
492    * case its decoded length will be as much as 4× shorter.
493    *
494    * Note: This check is not intended as an absolute guarantee that a hostname
495    * is the right length and will be accepted by other systems. It’s intended to
496    * stop wildly-invalid hostnames from taking forever in nameprep().
497    */
498   if (hostname_max_length_bytes <= G_MAXSIZE / 4 &&
499       strlen_greater_than (hostname, 4 * MAX (255, hostname_max_length_bytes)))
500     return NULL;
501
502   label = name = nameprep (hostname, -1, &unicode);
503   if (!name || !unicode)
504     return name;
505
506   out = g_string_new (NULL);
507
508   do
509     {
510       unicode = FALSE;
511       for (p = label; *p && !idna_is_dot (p); p++)
512         {
513           if ((guchar)*p > 0x80)
514             unicode = TRUE;
515         }
516
517       oldlen = out->len;
518       llen = p - label;
519       if (unicode)
520         {
521           if (!strncmp (label, IDNA_ACE_PREFIX, IDNA_ACE_PREFIX_LEN))
522             goto fail;
523
524           g_string_append (out, IDNA_ACE_PREFIX);
525           if (!punycode_encode (label, llen, out))
526             goto fail;
527         }
528       else
529         g_string_append_len (out, label, llen);
530
531       if (out->len - oldlen > 63)
532         goto fail;
533
534       label += llen;
535       if (*label)
536         label = g_utf8_next_char (label);
537       if (*label)
538         g_string_append_c (out, '.');
539     }
540   while (*label);
541
542   g_free (name);
543   return g_string_free (out, FALSE);
544
545  fail:
546   g_free (name);
547   g_string_free (out, TRUE);
548   return NULL;
549 }
550
551 /**
552  * g_hostname_is_non_ascii:
553  * @hostname: a hostname
554  *
555  * Tests if @hostname contains Unicode characters. If this returns
556  * %TRUE, you need to encode the hostname with g_hostname_to_ascii()
557  * before using it in non-IDN-aware contexts.
558  *
559  * Note that a hostname might contain a mix of encoded and unencoded
560  * segments, and so it is possible for g_hostname_is_non_ascii() and
561  * g_hostname_is_ascii_encoded() to both return %TRUE for a name.
562  *
563  * Returns: %TRUE if @hostname contains any non-ASCII characters
564  *
565  * Since: 2.22
566  **/
567 gboolean
568 g_hostname_is_non_ascii (const gchar *hostname)
569 {
570   return contains_non_ascii (hostname, -1);
571 }
572
573 /* Punycode decoder, RFC 3492 section 6.2. As with punycode_encode(),
574  * read the RFC if you want to understand what this is actually doing.
575  */
576 static gboolean
577 punycode_decode (const gchar *input,
578                  gsize        input_length,
579                  GString     *output)
580 {
581   GArray *output_chars;
582   gunichar n;
583   guint i, bias;
584   guint oldi, w, k, digit, t;
585   const gchar *split;
586
587   n = PUNYCODE_INITIAL_N;
588   i = 0;
589   bias = PUNYCODE_INITIAL_BIAS;
590
591   split = input + input_length - 1;
592   while (split > input && *split != '-')
593     split--;
594   if (split > input)
595     {
596       output_chars = g_array_sized_new (FALSE, FALSE, sizeof (gunichar),
597                                         split - input);
598       input_length -= (split - input) + 1;
599       while (input < split)
600         {
601           gunichar ch = (gunichar)*input++;
602           if (!PUNYCODE_IS_BASIC (ch))
603             goto fail;
604           g_array_append_val (output_chars, ch);
605         }
606       input++;
607     }
608   else
609     output_chars = g_array_new (FALSE, FALSE, sizeof (gunichar));
610
611   while (input_length)
612     {
613       oldi = i;
614       w = 1;
615       for (k = PUNYCODE_BASE; ; k += PUNYCODE_BASE)
616         {
617           if (!input_length--)
618             goto fail;
619           digit = decode_digit (*input++);
620           if (digit >= PUNYCODE_BASE)
621             goto fail;
622           if (digit > (G_MAXUINT - i) / w)
623             goto fail;
624           i += digit * w;
625           if (k <= bias)
626             t = PUNYCODE_TMIN;
627           else if (k >= bias + PUNYCODE_TMAX)
628             t = PUNYCODE_TMAX;
629           else
630             t = k - bias;
631           if (digit < t)
632             break;
633           if (w > G_MAXUINT / (PUNYCODE_BASE - t))
634             goto fail;
635           w *= (PUNYCODE_BASE - t);
636         }
637
638       bias = adapt (i - oldi, output_chars->len + 1, oldi == 0);
639
640       if (i / (output_chars->len + 1) > G_MAXUINT - n)
641         goto fail;
642       n += i / (output_chars->len + 1);
643       i %= (output_chars->len + 1);
644
645       g_array_insert_val (output_chars, i++, n);
646     }
647
648   for (i = 0; i < output_chars->len; i++)
649     g_string_append_unichar (output, g_array_index (output_chars, gunichar, i));
650   g_array_free (output_chars, TRUE);
651   return TRUE;
652
653  fail:
654   g_array_free (output_chars, TRUE);
655   return FALSE;
656 }
657
658 /**
659  * g_hostname_to_unicode:
660  * @hostname: a valid UTF-8 or ASCII hostname
661  *
662  * Converts @hostname to its canonical presentation form; a UTF-8
663  * string in Unicode normalization form C, containing no uppercase
664  * letters, no forbidden characters, and no ASCII-encoded segments,
665  * and not ending with a trailing dot.
666  *
667  * Of course if @hostname is not an internationalized hostname, then
668  * the canonical presentation form will be entirely ASCII.
669  *
670  * Returns: (nullable) (transfer full): a UTF-8 hostname, which must be freed,
671  *    or %NULL if @hostname is in some way invalid.
672  *
673  * Since: 2.22
674  **/
675 gchar *
676 g_hostname_to_unicode (const gchar *hostname)
677 {
678   GString *out;
679   gssize llen;
680   gsize hostname_max_length_bytes = get_hostname_max_length_bytes ();
681
682   /* See the comment at the top of g_hostname_to_ascii(). */
683   if (hostname_max_length_bytes <= G_MAXSIZE / 4 &&
684       strlen_greater_than (hostname, 4 * MAX (255, hostname_max_length_bytes)))
685     return NULL;
686
687   out = g_string_new (NULL);
688
689   do
690     {
691       llen = idna_end_of_label (hostname) - hostname;
692       if (!g_ascii_strncasecmp (hostname, IDNA_ACE_PREFIX, IDNA_ACE_PREFIX_LEN))
693         {
694           hostname += IDNA_ACE_PREFIX_LEN;
695           llen -= IDNA_ACE_PREFIX_LEN;
696           if (!punycode_decode (hostname, llen, out))
697             {
698               g_string_free (out, TRUE);
699               return NULL;
700             }
701         }
702       else
703         {
704           gboolean unicode;
705           gchar *canonicalized = nameprep (hostname, llen, &unicode);
706
707           if (!canonicalized)
708             {
709               g_string_free (out, TRUE);
710               return NULL;
711             }
712           g_string_append (out, canonicalized);
713           g_free (canonicalized);
714         }
715
716       hostname += llen;
717       if (*hostname)
718         hostname = g_utf8_next_char (hostname);
719       if (*hostname)
720         g_string_append_c (out, '.');
721     }
722   while (*hostname);
723
724   return g_string_free (out, FALSE);
725 }
726
727 /**
728  * g_hostname_is_ascii_encoded:
729  * @hostname: a hostname
730  *
731  * Tests if @hostname contains segments with an ASCII-compatible
732  * encoding of an Internationalized Domain Name. If this returns
733  * %TRUE, you should decode the hostname with g_hostname_to_unicode()
734  * before displaying it to the user.
735  *
736  * Note that a hostname might contain a mix of encoded and unencoded
737  * segments, and so it is possible for g_hostname_is_non_ascii() and
738  * g_hostname_is_ascii_encoded() to both return %TRUE for a name.
739  *
740  * Returns: %TRUE if @hostname contains any ASCII-encoded
741  * segments.
742  *
743  * Since: 2.22
744  **/
745 gboolean
746 g_hostname_is_ascii_encoded (const gchar *hostname)
747 {
748   while (1)
749     {
750       if (!g_ascii_strncasecmp (hostname, IDNA_ACE_PREFIX, IDNA_ACE_PREFIX_LEN))
751         return TRUE;
752       hostname = idna_end_of_label (hostname);
753       if (*hostname)
754         hostname = g_utf8_next_char (hostname);
755       if (!*hostname)
756         return FALSE;
757     }
758 }
759
760 /**
761  * g_hostname_is_ip_address:
762  * @hostname: a hostname (or IP address in string form)
763  *
764  * Tests if @hostname is the string form of an IPv4 or IPv6 address.
765  * (Eg, "192.168.0.1".)
766  *
767  * Since 2.66, IPv6 addresses with a zone-id are accepted (RFC6874).
768  *
769  * Returns: %TRUE if @hostname is an IP address
770  *
771  * Since: 2.22
772  **/
773 gboolean
774 g_hostname_is_ip_address (const gchar *hostname)
775 {
776   gchar *p, *end;
777   gint nsegments, octet;
778
779   /* On Linux we could implement this using inet_pton, but the Windows
780    * equivalent of that requires linking against winsock, so we just
781    * figure this out ourselves. Tested by tests/hostutils.c.
782    */
783
784   p = (char *)hostname;
785
786   if (strchr (p, ':'))
787     {
788       gboolean skipped;
789
790       /* If it contains a ':', it's an IPv6 address (assuming it's an
791        * IP address at all). This consists of eight ':'-separated
792        * segments, each containing a 1-4 digit hex number, except that
793        * optionally: (a) the last two segments can be replaced by an
794        * IPv4 address, and (b) a single span of 1 to 8 "0000" segments
795        * can be replaced with just "::".
796        */
797
798       nsegments = 0;
799       skipped = FALSE;
800       while (*p && *p != '%' && nsegments < 8)
801         {
802           /* Each segment after the first must be preceded by a ':'.
803            * (We also handle half of the "string starts with ::" case
804            * here.)
805            */
806           if (p != (char *)hostname || (p[0] == ':' && p[1] == ':'))
807             {
808               if (*p != ':')
809                 return FALSE;
810               p++;
811             }
812
813           /* If there's another ':', it means we're skipping some segments */
814           if (*p == ':' && !skipped)
815             {
816               skipped = TRUE;
817               nsegments++;
818
819               /* Handle the "string ends with ::" case */
820               if (!p[1])
821                 p++;
822
823               continue;
824             }
825
826           /* Read the segment, make sure it's valid. */
827           for (end = p; g_ascii_isxdigit (*end); end++)
828             ;
829           if (end == p || end > p + 4)
830             return FALSE;
831
832           if (*end == '.')
833             {
834               if ((nsegments == 6 && !skipped) || (nsegments <= 6 && skipped))
835                 goto parse_ipv4;
836               else
837                 return FALSE;
838             }
839
840           nsegments++;
841           p = end;
842         }
843
844       return (!*p || (p[0] == '%' && p[1])) && (nsegments == 8 || skipped);
845     }
846
847  parse_ipv4:
848
849   /* Parse IPv4: N.N.N.N, where each N <= 255 and doesn't have leading 0s. */
850   for (nsegments = 0; nsegments < 4; nsegments++)
851     {
852       if (nsegments != 0)
853         {
854           if (*p != '.')
855             return FALSE;
856           p++;
857         }
858
859       /* Check the segment; a little tricker than the IPv6 case since
860        * we can't allow extra leading 0s, and we can't assume that all
861        * strings of valid length are within range.
862        */
863       octet = 0;
864       if (*p == '0')
865         end = p + 1;
866       else
867         {
868           for (end = p; g_ascii_isdigit (*end); end++)
869             {
870               octet = 10 * octet + (*end - '0');
871
872               if (octet > 255)
873                 break;
874             }
875         }
876       if (end == p || end > p + 3 || octet > 255)
877         return FALSE;
878
879       p = end;
880     }
881
882   /* If there's nothing left to parse, then it's ok. */
883   return !*p;
884 }