Remove build warning
[platform/upstream/libsoup.git] / libsoup / soup-tld.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-tld.c
4  *
5  * Copyright (C) 2012 Igalia S.L.
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #include <string.h>
13
14 #include <glib/gi18n-lib.h>
15
16 #include "soup-tld.h"
17 #include "soup.h"
18 #include "soup-tld-private.h"
19
20 /**
21  * SECTION:soup-tld
22  * @short_description: Top-Level Domain Utilities
23  *
24  * These functions can be used to parse hostnames to attempt to determine
25  * what part of the name belongs to the domain owner, and what part is
26  * simply a "public suffix" such as ".com".
27  */
28
29 static void soup_tld_ensure_rules_hash_table (void);
30 static const char *soup_tld_get_base_domain_internal (const char *hostname,
31                                                       guint       additional_domains,
32                                                       GError    **error);
33
34 static GHashTable *rules = NULL;
35 static SoupTLDEntry tld_entries[] = {
36 #include "tld_data.inc"
37 };
38
39 /* Stores the entries data in a hash table to ease and speed up
40  * searches.
41  */
42 static void
43 soup_tld_ensure_rules_hash_table (void)
44 {
45         static gsize init = 0;
46
47         if (g_once_init_enter (&init)) {
48                 int i;
49
50                 rules = g_hash_table_new (g_str_hash, g_str_equal);
51                 for (i = 0; i < G_N_ELEMENTS (tld_entries); ++i)
52                         g_hash_table_insert (rules, tld_entries[i].domain,
53                                              &(tld_entries[i].flags));
54                 g_once_init_leave (&init, 1);
55         }
56 }
57
58 /**
59  * soup_tld_get_base_domain:
60  * @hostname: a hostname
61  * @error: return location for a #GError, or %NULL to ignore
62  *   errors. See #SoupTLDError for the available error codes
63  *
64  * Finds the base domain for a given @hostname. The base domain is
65  * composed by the top level domain (such as .org, .com, .co.uk, etc)
66  * plus the second level domain, for example for myhost.mydomain.com
67  * it will return mydomain.com.
68  *
69  * Note that %NULL will be returned for private URLs (those not ending
70  * with any well known TLD) because choosing a base domain for them
71  * would be totally arbitrary.
72  *
73  * Prior to libsoup 2.46, this function required that @hostname be in
74  * UTF-8 if it was an IDN. From 2.46 on, the name can be in either
75  * UTF-8 or ASCII format (and the return value will be in the same
76  * format).
77  *
78  * Returns: a pointer to the start of the base domain in @hostname. If
79  * an error occurs, %NULL will be returned and @error set.
80  *
81  * Since: 2.40
82  **/
83 const char *
84 soup_tld_get_base_domain (const char *hostname, GError **error)
85 {
86         g_return_val_if_fail (hostname, NULL);
87
88         return soup_tld_get_base_domain_internal (hostname, 1, error);
89 }
90
91 /**
92  * soup_tld_domain_is_public_suffix:
93  * @domain: a domain name
94  *
95  * Looks whether the @domain passed as argument is a public domain
96  * suffix (.org, .com, .co.uk, etc) or not.
97  *
98  * Prior to libsoup 2.46, this function required that @domain be in
99  * UTF-8 if it was an IDN. From 2.46 on, the name can be in either
100  * UTF-8 or ASCII format (and the return value will be in the same
101  * format).
102  *
103  * Returns: %TRUE if it is a public domain, %FALSE otherwise.
104  *
105  * Since: 2.40
106  **/
107 gboolean
108 soup_tld_domain_is_public_suffix (const char *domain)
109 {
110         const char *base_domain;
111         GError *error = NULL;
112
113         g_return_val_if_fail (domain, FALSE);
114
115         /* Skip the leading '.' if present */
116         if (*domain == '.' && !*(++domain))
117                 g_return_val_if_reached (FALSE);
118
119         base_domain = soup_tld_get_base_domain_internal (domain, 0, &error);
120         if (g_strcmp0 (domain, base_domain)) {
121                 g_clear_error (&error);
122                 return FALSE;
123         }
124
125         if (g_error_matches (error, SOUP_TLD_ERROR, SOUP_TLD_ERROR_NO_BASE_DOMAIN)) {
126                 g_error_free (error);
127                 return FALSE;
128         }
129
130         if (g_error_matches (error, SOUP_TLD_ERROR, SOUP_TLD_ERROR_IS_IP_ADDRESS) ||
131             g_error_matches (error, SOUP_TLD_ERROR, SOUP_TLD_ERROR_INVALID_HOSTNAME)) {
132                 g_error_free (error);
133                 g_return_val_if_reached (FALSE);
134         }
135
136         g_clear_error (&error);
137
138         return TRUE;
139 }
140
141 /**
142  * SOUP_TLD_ERROR:
143  *
144  * The #GError domain for soup-tld-related errors.
145  *
146  * Since: 2.40
147  */
148 /**
149  * SoupTLDError:
150  * @SOUP_TLD_ERROR_INVALID_HOSTNAME: A hostname was syntactically
151  *   invalid.
152  * @SOUP_TLD_ERROR_IS_IP_ADDRESS: The passed-in "hostname" was
153  *   actually an IP address (and thus has no base domain or
154  *   public suffix).
155  * @SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS: The passed-in hostname
156  *   did not have enough components. Eg, calling
157  *   soup_tld_get_base_domain() on <literal>"co.uk"</literal>.
158  * @SOUP_TLD_ERROR_NO_BASE_DOMAIN: The passed-in hostname has
159  *   no recognized public suffix.
160  *
161  * Error codes for %SOUP_TLD_ERROR.
162  *
163  * Since: 2.40
164  */
165
166 GQuark
167 soup_tld_error_quark (void)
168 {
169         static GQuark error;
170         if (!error)
171                 error = g_quark_from_static_string ("soup_tld_error_quark");
172         return error;
173 }
174
175 static const char *
176 soup_tld_get_base_domain_internal (const char *hostname, guint additional_domains, GError **error)
177 {
178         char *prev_domain, *cur_domain, *next_dot;
179         gint add_domains;
180         const char *orig_hostname = NULL, *tld;
181         char *utf8_hostname = NULL;
182
183         soup_tld_ensure_rules_hash_table ();
184
185         if (g_hostname_is_ip_address (hostname)) {
186                 g_set_error_literal (error, SOUP_TLD_ERROR,
187                                      SOUP_TLD_ERROR_IS_IP_ADDRESS,
188                                      _("Hostname is an IP address"));
189                 return NULL;
190         }
191
192         if (g_hostname_is_ascii_encoded (hostname)) {
193                 orig_hostname = hostname;
194                 hostname = utf8_hostname = g_hostname_to_unicode (hostname);
195                 if (!hostname) {
196                         g_set_error_literal (error, SOUP_TLD_ERROR,
197                                              SOUP_TLD_ERROR_INVALID_HOSTNAME,
198                                              _("Invalid hostname"));
199                         return NULL;
200                 }
201         }
202
203         cur_domain = (char *) hostname;
204         tld = cur_domain;
205         prev_domain = NULL;
206         /* Process matching rules from longest to shortest. Logic
207          * based on Mozilla's implementation of nsEffectiveTLDService.
208          */
209         while (TRUE) {
210                 char *orig_domain;
211                 gboolean domain_found;
212                 int *flags;
213
214                 /* Valid hostnames neither start with a dot nor have more than one
215                  * dot together.
216                  */
217                 if (*cur_domain == '.') {
218                         g_set_error_literal (error, SOUP_TLD_ERROR,
219                                              SOUP_TLD_ERROR_INVALID_HOSTNAME,
220                                              _("Invalid hostname"));
221                         g_free (utf8_hostname);
222                         return NULL;
223                 }
224
225                 next_dot = strchr (cur_domain, '.');
226                 domain_found = g_hash_table_lookup_extended (rules, cur_domain, (gpointer *) &orig_domain, (gpointer *) &flags);
227                 /* We compare the keys just to be sure that we haven't hit a collision */
228                 if (domain_found && !strncmp (orig_domain, cur_domain, strlen (orig_domain))) {
229                         if (*flags & SOUP_TLD_RULE_MATCH_ALL) {
230                                 /* If we match a *. rule and there were no previous exceptions
231                                  * nor previous domains then treat it as an exact match.
232                                  */
233                                 tld = prev_domain ? prev_domain : cur_domain;
234                                 break;
235                         } else if (*flags == SOUP_TLD_RULE_NORMAL) {
236                                 tld = cur_domain;
237                                 break;
238                         } else if (*flags & SOUP_TLD_RULE_EXCEPTION) {
239                                 tld = next_dot + 1;
240                                 break;
241                         }
242                 }
243
244                 /* If we hit the top and haven't matched yet, then it
245                  * has no public suffix.
246                  */
247                 if (!next_dot) {
248                         g_set_error_literal (error, SOUP_TLD_ERROR,
249                                              SOUP_TLD_ERROR_NO_BASE_DOMAIN,
250                                              _("Hostname has no base domain"));
251                         g_free (utf8_hostname);
252                         return NULL;
253                 }
254
255                 prev_domain = cur_domain;
256                 cur_domain = next_dot + 1;
257         }
258
259         if (orig_hostname) {
260                 int dots;
261                 const char *p;
262
263                 /* Count the number of dots that appear after tld in
264                  * utf8_hostname, and then find the corresponding spot
265                  * in orig_hostname;
266                  */
267                 for (p = tld, dots = 0; *p; p++) {
268                         if (*p == '.')
269                                 dots++;
270                 }
271
272                 for (p = orig_hostname + strlen (orig_hostname); p > orig_hostname; p--) {
273                         if (*(p - 1) == '.') {
274                                 if (dots)
275                                         dots--;
276                                 else
277                                         break;
278                         }
279                 }
280                 /* It's not possible for utf8_hostname to have had
281                  * more dots than orig_hostname.
282                  */
283                 g_assert (dots == 0);
284
285                 tld = p;
286                 g_free (utf8_hostname);
287                 hostname = orig_hostname;
288         }
289
290         /* Include the additional number of domains requested. */
291         add_domains = additional_domains;
292         while (tld != hostname) {
293                 if (*(--tld) == '.' && (!(add_domains--))) {
294                         ++add_domains;
295                         ++tld;
296                         break;
297                 }
298         }
299
300         /* If additional_domains > 0 then we haven't found enough additional domains. */
301         if (add_domains) {
302                 g_set_error_literal (error, SOUP_TLD_ERROR,
303                                      SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS,
304                                      _("Not enough domains"));
305                 return NULL;
306         }
307
308         return tld;
309 }