1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * Copyright (C) 2009-2020 Shaun McCance  <shaunm@gnome.org>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
17  *
18  * Author: Shaun McCance  <shaunm@gnome.org>
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #include <string.h>
26 #include <stdio.h>
27 
28 #include <glib.h>
29 #include <gio/gio.h>
30 
31 #include "yelp-uri.h"
32 #include "yelp-settings.h"
33 
34 static void           yelp_uri_dispose           (GObject        *object);
35 static void           yelp_uri_finalize          (GObject        *object);
36 
37 static void           resolve_start              (YelpUri        *uri);
38 static void           resolve_sync               (YelpUri        *uri);
39 static void           resolve_async              (YelpUri        *uri);
40 static gboolean       resolve_final              (YelpUri        *uri);
41 
42 static void           resolve_file_uri           (YelpUri        *uri);
43 static void           resolve_file_path          (YelpUri        *uri);
44 static void           resolve_data_dirs          (YelpUri        *uri,
45                                                   const gchar    *subdir,
46                                                   const gchar    *docid,
47                                                   const gchar    *pageid,
48                                                   gboolean        langfirst);
49 static void           resolve_ghelp_uri          (YelpUri        *uri);
50 static void           resolve_help_uri           (YelpUri        *uri);
51 static void           resolve_help_list_uri      (YelpUri        *uri);
52 static void           resolve_man_uri            (YelpUri        *uri);
53 static void           resolve_info_uri           (YelpUri        *uri);
54 static void           resolve_xref_uri           (YelpUri        *uri);
55 static void           resolve_page_and_frag      (YelpUri        *uri,
56                                                   const gchar    *arg);
57 static void           resolve_gfile              (YelpUri        *uri,
58                                                   const gchar    *query,
59                                                   const gchar    *hash);
60 
61 static gboolean       is_man_path                (const gchar    *uri,
62                                                   const gchar    *encoding);
63 
64 typedef struct _YelpUriPrivate YelpUriPrivate;
65 struct _YelpUriPrivate {
66     GThread              *resolver;
67 
68     YelpUriDocumentType   doctype;
69     YelpUriDocumentType   tmptype;
70 
71     gchar                *docuri;
72     gchar                *fulluri;
73     GFile                *gfile;
74 
75     gchar               **search_path;
76     gchar                *page_id;
77     gchar                *frag_id;
78 
79     GHashTable           *query;
80 
81     /* Unresolved */
82     YelpUri              *res_base;
83     gchar                *res_arg;
84 };
85 
86 enum {
87     RESOLVED,
88     LAST_SIGNAL
89 };
90 static guint uri_signals[LAST_SIGNAL] = {0,};
91 
92 G_DEFINE_TYPE_WITH_PRIVATE (YelpUri, yelp_uri, G_TYPE_OBJECT)
93 
94 /******************************************************************************/
95 
96 static const gchar *mancats[] = {
97     "0p",
98     "1", "1p", "1g", "1t", "1x", "1ssl", "1m",
99     "2",
100     "3", "3o", "3t", "3p", "3blt", "3nas", "3form", "3menu", "3tiff", "3ssl", "3readline",
101     "3ncurses", "3curses", "3f", "3pm", "3perl", "3qt", "3x", "3X11",
102     "4", "4x",
103     "5", "5snmp", "5x", "5ssl",
104     "6", "6x",
105     "7", "7gcc", "7x", "7ssl",
106     "8", "8l", "9", "0p",
107     NULL
108 };
109 
110 static const gchar *infosuffix[] = {
111     ".info",
112     ".info.gz", ".info.bz2", ".info.lzma",
113     ".gz", ".bz2", ".lzma",
114     NULL
115 };
116 
117 static const gchar default_info_path[] =
118     "/usr/info:/usr/share/info:/usr/local/info:/usr/local/share/info";
119 
120 /******************************************************************************/
121 
122 static void
yelp_uri_class_init(YelpUriClass * klass)123 yelp_uri_class_init (YelpUriClass *klass)
124 {
125     GObjectClass *object_class = G_OBJECT_CLASS (klass);
126 
127     object_class->dispose  = yelp_uri_dispose;
128     object_class->finalize = yelp_uri_finalize;
129 
130     uri_signals[RESOLVED] =
131         g_signal_new ("resolved",
132                       G_OBJECT_CLASS_TYPE (klass),
133                       G_SIGNAL_RUN_LAST,
134                       0, NULL, NULL,
135                       g_cclosure_marshal_VOID__VOID,
136                       G_TYPE_NONE, 0);
137 }
138 
139 static void
yelp_uri_init(YelpUri * uri)140 yelp_uri_init (YelpUri *uri)
141 {
142     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
143 
144     priv->query = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
145 
146     return;
147 }
148 
149 static void
yelp_uri_dispose(GObject * object)150 yelp_uri_dispose (GObject *object)
151 {
152     YelpUriPrivate *priv = yelp_uri_get_instance_private (YELP_URI (object));
153 
154     if (priv->gfile) {
155         g_object_unref (priv->gfile);
156         priv->gfile = NULL;
157     }
158 
159     if (priv->res_base) {
160         g_object_unref (priv->res_base);
161         priv->res_base = NULL;
162     }
163 
164     if (priv->query) {
165         g_hash_table_destroy (priv->query);
166         priv->query = NULL;
167     }
168 
169     G_OBJECT_CLASS (yelp_uri_parent_class)->dispose (object);
170 }
171 
172 static void
yelp_uri_finalize(GObject * object)173 yelp_uri_finalize (GObject *object)
174 {
175     YelpUriPrivate *priv = yelp_uri_get_instance_private (YELP_URI (object));
176 
177     g_free (priv->docuri);
178     g_free (priv->fulluri);
179     g_strfreev (priv->search_path);
180     g_free (priv->page_id);
181     g_free (priv->frag_id);
182     g_free (priv->res_arg);
183 
184     G_OBJECT_CLASS (yelp_uri_parent_class)->finalize (object);
185 }
186 
187 /******************************************************************************/
188 
189 YelpUri *
yelp_uri_new(const gchar * arg)190 yelp_uri_new (const gchar *arg)
191 {
192     return yelp_uri_new_relative (NULL, arg);
193 }
194 
195 YelpUri *
yelp_uri_new_relative(YelpUri * base,const gchar * arg)196 yelp_uri_new_relative (YelpUri *base, const gchar *arg)
197 {
198     YelpUri *uri;
199     YelpUriPrivate *priv;
200 
201     uri = (YelpUri *) g_object_new (YELP_TYPE_URI, NULL);
202 
203     priv = yelp_uri_get_instance_private (uri);
204     priv->doctype = YELP_URI_DOCUMENT_TYPE_UNRESOLVED;
205     if (base)
206         priv->res_base = g_object_ref (base);
207     priv->res_arg = g_strdup (arg);
208 
209     return uri;
210 }
211 
212 YelpUri *
yelp_uri_new_search(YelpUri * base,const gchar * text)213 yelp_uri_new_search (YelpUri      *base,
214                      const gchar  *text)
215 {
216     YelpUri *uri;
217     YelpUriPrivate *priv;
218     gchar *tmp;
219 
220     uri = (YelpUri *) g_object_new (YELP_TYPE_URI, NULL);
221 
222     priv = yelp_uri_get_instance_private (uri);
223     priv->doctype = YELP_URI_DOCUMENT_TYPE_UNRESOLVED;
224     if (base)
225         priv->res_base = g_object_ref (base);
226     tmp = g_uri_escape_string (text, NULL, FALSE);
227     priv->res_arg = g_strconcat("xref:search=", tmp, NULL);
228     g_free (tmp);
229 
230     return uri;
231 }
232 
233 /******************************************************************************/
234 
235 void
yelp_uri_resolve(YelpUri * uri)236 yelp_uri_resolve (YelpUri *uri)
237 {
238     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
239 
240     if (priv->res_base && !yelp_uri_is_resolved (priv->res_base)) {
241         g_signal_connect_swapped (priv->res_base, "resolved",
242                                   G_CALLBACK (resolve_start),
243                                   uri);
244         yelp_uri_resolve (priv->res_base);
245     }
246     else {
247         resolve_start (uri);
248     }
249 }
250 
251 void
yelp_uri_resolve_sync(YelpUri * uri)252 yelp_uri_resolve_sync (YelpUri *uri)
253 {
254     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
255 
256     if (priv->doctype != YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
257         return;
258 
259     if (priv->res_base)
260         yelp_uri_resolve_sync (priv->res_base);
261 
262     g_object_ref (uri);
263     resolve_sync (uri);
264     resolve_final (uri);
265 }
266 
267 /* We want code to be able to do something like this:
268  *
269  *   if (yelp_uri_get_document_type (uri) != YELP_URI_DOCUMENT_TYPE_UNRESOLVED) {
270  *     g_signal_connect (uri, "resolve", callback, data);
271  *     yelp_uri_resolve (uri);
272  *   }
273  *
274  * Resolving happens in a separate thread, though, so if that thread can change
275  * the document type, we have a race condition.  So here's the rules we play by:
276  *
277  * 1) None of the getters except the document type getter can return real data
278  *    while the URI is unresolved.  They all do a resolved check first, and
279  *    return NULL if the URI is not resolved.
280  *
281  * 2) The threaded resolver functions can modify anything but the document
282  *    type.  They are the only things that are allowed to modify that data.
283  *
284  * 3) The resolver thread is not allowed to modify the document type.  When
285  *    it's done, it queues an async function to set the document type and
286  *    emit "resolved" in the main thread.
287  *
288  * 4) Once a URI is resolved, it is immutable.
289  */
290 static void
resolve_start(YelpUri * uri)291 resolve_start (YelpUri *uri)
292 {
293     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
294 
295     if (priv->resolver == NULL) {
296         g_object_ref (uri);
297         priv->resolver = g_thread_new ("uri-resolve",
298                                        (GThreadFunc)(GCallback) resolve_async,
299                                        uri);
300     }
301 }
302 
303 static void
resolve_sync(YelpUri * uri)304 resolve_sync (YelpUri *uri)
305 {
306     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
307 
308     if (g_str_has_prefix (priv->res_arg, "ghelp:")
309         || g_str_has_prefix (priv->res_arg, "gnome-help:")) {
310         resolve_ghelp_uri (uri);
311     }
312     else if (g_str_has_prefix (priv->res_arg, "help:")) {
313         resolve_help_uri (uri);
314     }
315     else if (g_str_has_prefix (priv->res_arg, "help-list:")) {
316         resolve_help_list_uri (uri);
317     }
318     else if (g_str_has_prefix (priv->res_arg, "file:")) {
319         resolve_file_uri (uri);
320     }
321     else if (g_str_has_prefix (priv->res_arg, "man:")) {
322         resolve_man_uri (uri);
323     }
324     else if (g_str_has_prefix (priv->res_arg, "info:")) {
325         resolve_info_uri (uri);
326     }
327     else if (g_str_has_prefix (priv->res_arg, "xref:")) {
328         YelpUriPrivate *base_priv;
329         if (priv->res_base == NULL) {
330             priv->tmptype = YELP_URI_DOCUMENT_TYPE_ERROR;
331             return;
332         }
333         base_priv = yelp_uri_get_instance_private (priv->res_base);
334         switch (base_priv->doctype) {
335         case YELP_URI_DOCUMENT_TYPE_UNRESOLVED:
336             break;
337         case YELP_URI_DOCUMENT_TYPE_DOCBOOK:
338         case YELP_URI_DOCUMENT_TYPE_MALLARD:
339         case YELP_URI_DOCUMENT_TYPE_INFO:
340             resolve_xref_uri (uri);
341             break;
342         case YELP_URI_DOCUMENT_TYPE_MAN: {
343             gchar *tmp = g_strconcat ("man:", priv->res_arg + 5, NULL);
344             g_free (priv->res_arg);
345             priv->res_arg = tmp;
346             resolve_man_uri (uri);
347             break;
348         }
349         case YELP_URI_DOCUMENT_TYPE_TEXT:
350         case YELP_URI_DOCUMENT_TYPE_HTML:
351         case YELP_URI_DOCUMENT_TYPE_XHTML:
352             resolve_file_path (uri);
353             break;
354         case YELP_URI_DOCUMENT_TYPE_HELP_LIST:
355             /* FIXME: what do we do? */
356             break;
357         case YELP_URI_DOCUMENT_TYPE_NOT_FOUND:
358         case YELP_URI_DOCUMENT_TYPE_EXTERNAL:
359         case YELP_URI_DOCUMENT_TYPE_ERROR:
360             break;
361         default:
362             g_assert_not_reached ();
363             break;
364         }
365     }
366     else if (strchr (priv->res_arg, ':')) {
367         priv->tmptype = YELP_URI_DOCUMENT_TYPE_EXTERNAL;
368     }
369     else {
370         resolve_file_path (uri);
371     }
372 
373     /* We _always_ want to have a non-null fulluri, so check for it
374      * having been set here and, if we can't think of something
375      * better, set it to res_arg. */
376     if (!priv->fulluri) {
377         priv->fulluri = g_strdup (priv->res_arg);
378     }
379 }
380 
381 static void
resolve_async(YelpUri * uri)382 resolve_async (YelpUri *uri)
383 {
384     resolve_sync (uri);
385     g_idle_add ((GSourceFunc) resolve_final, uri);
386 }
387 
388 static gboolean
resolve_final(YelpUri * uri)389 resolve_final (YelpUri *uri)
390 {
391     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
392 
393     priv->resolver = NULL;
394 
395     if (priv->tmptype != YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
396         priv->doctype = priv->tmptype;
397     else
398         priv->doctype = YELP_URI_DOCUMENT_TYPE_ERROR;
399 
400     if (priv->res_base) {
401         g_object_unref (priv->res_base);
402         priv->res_base = NULL;
403     }
404 
405     if (priv->res_arg) {
406         g_free (priv->res_arg);
407         priv->res_arg = NULL;
408     }
409 
410     g_signal_emit (uri, uri_signals[RESOLVED], 0);
411     g_object_unref (uri);
412     return FALSE;
413 }
414 
415 /******************************************************************************/
416 
417 gboolean
yelp_uri_is_resolved(YelpUri * uri)418 yelp_uri_is_resolved (YelpUri *uri)
419 {
420     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
421     return priv->doctype != YELP_URI_DOCUMENT_TYPE_UNRESOLVED;
422 }
423 
424 YelpUriDocumentType
yelp_uri_get_document_type(YelpUri * uri)425 yelp_uri_get_document_type (YelpUri *uri)
426 {
427     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
428     return priv->doctype;
429 }
430 
431 gchar *
yelp_uri_get_document_uri(YelpUri * uri)432 yelp_uri_get_document_uri (YelpUri *uri)
433 {
434     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
435     if (priv->doctype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
436         return NULL;
437 
438     /* There's some client code where it makes sense to want a
439      * document uri, whether or not it conforms to a scheme we really
440      * understand. For example, we might want to look up whether the
441      * given page is currently being visited. */
442     if ((!priv->docuri) && priv->fulluri) {
443         return g_strdup (priv->fulluri);
444     }
445 
446     return g_strdup (priv->docuri);
447 }
448 
449 gchar *
yelp_uri_get_canonical_uri(YelpUri * uri)450 yelp_uri_get_canonical_uri (YelpUri *uri)
451 {
452     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
453     if (priv->doctype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
454         return NULL;
455     return g_strdup (priv->fulluri);
456 }
457 
458 GFile *
yelp_uri_get_file(YelpUri * uri)459 yelp_uri_get_file (YelpUri *uri)
460 {
461     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
462     if (priv->doctype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
463         return NULL;
464     return priv->gfile ? g_object_ref (priv->gfile) : NULL;
465 }
466 
467 gchar **
yelp_uri_get_search_path(YelpUri * uri)468 yelp_uri_get_search_path (YelpUri *uri)
469 {
470     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
471     if (priv->doctype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
472         return NULL;
473     return g_strdupv (priv->search_path);
474 }
475 
476 gchar *
yelp_uri_get_page_id(YelpUri * uri)477 yelp_uri_get_page_id (YelpUri *uri)
478 {
479     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
480     if (priv->doctype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
481         return NULL;
482     return g_strdup (priv->page_id);
483 }
484 
485 gchar *
yelp_uri_get_frag_id(YelpUri * uri)486 yelp_uri_get_frag_id (YelpUri *uri)
487 {
488     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
489     if (priv->doctype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
490         return NULL;
491     return g_strdup (priv->frag_id);
492 }
493 
494 gchar *
yelp_uri_get_query(YelpUri * uri,const gchar * key)495 yelp_uri_get_query (YelpUri      *uri,
496                     const gchar  *key)
497 {
498     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
499     const gchar *ret = g_hash_table_lookup (priv->query, key);
500     if (ret)
501         return g_strdup (ret);
502     else
503         return NULL;
504 }
505 
506 /******************************************************************************/
507 
508 gchar *
yelp_uri_locate_file_uri(YelpUri * uri,const gchar * filename)509 yelp_uri_locate_file_uri (YelpUri     *uri,
510                           const gchar *filename)
511 {
512     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
513     GFile *gfile;
514     gchar *fullpath;
515     gchar *returi = NULL;
516     gint i;
517 
518     if (g_path_is_absolute (filename)) {
519         if (g_file_test (filename, G_FILE_TEST_EXISTS))
520             return g_filename_to_uri (filename, NULL, NULL);
521         return NULL;
522     }
523 
524     for (i = 0; priv->search_path[i] != NULL; i++) {
525         fullpath = g_strconcat (priv->search_path[i],
526                                 G_DIR_SEPARATOR_S,
527                                 filename,
528                                 NULL);
529         if (g_file_test (fullpath, G_FILE_TEST_EXISTS)) {
530             gfile = g_file_new_for_path (fullpath);
531             returi = g_file_get_uri (gfile);
532             g_object_unref (gfile);
533         }
534         g_free (fullpath);
535         if (returi)
536             break;
537     }
538     return returi;
539 }
540 
541 /******************************************************************************/
542 
543 static void
resolve_file_uri(YelpUri * uri)544 resolve_file_uri (YelpUri *uri)
545 {
546     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
547     gchar *uristr;
548     const gchar *hash = strchr (priv->res_arg, '#');
549 
550     if (hash) {
551         uristr = g_strndup (priv->res_arg, hash - priv->res_arg);
552         hash++;
553     }
554     else
555         uristr = priv->res_arg;
556 
557     priv->gfile = g_file_new_for_uri (uristr);
558 
559     resolve_gfile (uri, NULL, hash);
560 }
561 
562 static void
resolve_file_path(YelpUri * uri)563 resolve_file_path (YelpUri *uri)
564 {
565     YelpUriPrivate *base_priv = NULL;
566     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
567     gchar *path;
568     const gchar *hash;
569 
570     /* Treat xref: URIs like relative file paths */
571     if (g_str_has_prefix (priv->res_arg, "xref:")) {
572         gchar *tmp = g_strdup (priv->res_arg + 5);
573         g_free (priv->res_arg);
574         priv->res_arg = tmp;
575     }
576 
577     if (priv->res_base)
578         base_priv = yelp_uri_get_instance_private (priv->res_base);
579 
580     hash = strchr (priv->res_arg, '#');
581     if (hash) {
582         path = g_strndup (priv->res_arg, hash - priv->res_arg);
583         hash++;
584     }
585     else
586         path = priv->res_arg;
587 
588     if (priv->res_arg[0] == '/') {
589         priv->gfile = g_file_new_for_path (path);
590     }
591     else if (base_priv && base_priv->gfile) {
592         GFileInfo *info;
593         info = g_file_query_info (base_priv->gfile,
594                                   G_FILE_ATTRIBUTE_STANDARD_TYPE,
595                                   G_FILE_QUERY_INFO_NONE,
596                                   NULL, NULL);
597         if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR) {
598             GFile *parent = g_file_get_parent (base_priv->gfile);
599             priv->gfile = g_file_resolve_relative_path (parent, path);
600             g_object_unref (parent);
601         }
602         else {
603             priv->gfile = g_file_resolve_relative_path (base_priv->gfile, path);
604         }
605 
606         g_object_unref (info);
607     }
608     else {
609         gchar *cur;
610         GFile *curfile;
611         cur = g_get_current_dir ();
612         curfile = g_file_new_for_path (cur);
613         priv->gfile = g_file_resolve_relative_path (curfile, path);
614         g_object_unref (curfile);
615         g_free (cur);
616     }
617 
618     resolve_gfile (uri, NULL, hash);
619 }
620 
621 static void
resolve_data_dirs(YelpUri * ret,const gchar * subdir,const gchar * docid,const gchar * pageid,gboolean langfirst)622 resolve_data_dirs (YelpUri      *ret,
623                    const gchar  *subdir,
624                    const gchar  *docid,
625                    const gchar  *pageid,
626                    gboolean      langfirst)
627 {
628     const gchar * const *sdatadirs = g_get_system_data_dirs ();
629     const gchar * const *langs = g_get_language_names ();
630     /* The strings are still owned by GLib; we just own the array. */
631     gchar **datadirs;
632     YelpUriPrivate *priv = yelp_uri_get_instance_private (ret);
633     gchar *filename = NULL;
634     gchar **searchpath = NULL;
635     gint searchi, searchmax;
636     gint datadir_i, lang_i;
637 
638     datadirs = g_new0 (gchar *, g_strv_length ((gchar **) sdatadirs) + 2);
639     datadirs[0] = (gchar *) g_get_user_data_dir ();
640     for (datadir_i = 0; sdatadirs[datadir_i]; datadir_i++)
641         datadirs[datadir_i + 1] = (gchar *) sdatadirs[datadir_i];
642 
643     searchi = 0;
644     searchmax = 10;
645     searchpath = g_new0 (gchar *, 10);
646 
647     for (datadir_i = 0; datadirs[datadir_i]; datadir_i++) {
648         for (lang_i = 0; langs[lang_i]; lang_i++) {
649             gchar *helpdir = g_build_filename (datadirs[datadir_i],
650                                                subdir,
651                                                langfirst ? langs[lang_i] : docid,
652                                                langfirst ? docid : langs[lang_i],
653                                                NULL);
654             if (!g_file_test (helpdir, G_FILE_TEST_IS_DIR)) {
655                 g_free (helpdir);
656                 continue;
657             }
658 
659             if (searchi + 1 >= searchmax) {
660                 searchmax += 5;
661                 searchpath = g_renew (gchar *, searchpath, searchmax);
662             }
663             searchpath[searchi] = helpdir;
664             searchpath[++searchi] = NULL;
665 
666             if (priv->tmptype != YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
667                 /* We've already found it.  We're just adding to the search path now. */
668                 continue;
669 
670             filename = g_strdup_printf ("%s/index.page", helpdir);
671             if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) {
672                 priv->tmptype = YELP_URI_DOCUMENT_TYPE_MALLARD;
673                 g_free (filename);
674                 filename = g_strdup (helpdir);
675                 continue;
676             }
677             g_free (filename);
678 
679             if (langfirst) {
680                 filename = g_strdup_printf ("%s/index.docbook", helpdir);
681                 if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) {
682                     priv->tmptype = YELP_URI_DOCUMENT_TYPE_DOCBOOK;
683                     continue;
684                 }
685                 g_free (filename);
686             }
687             else {
688                 filename = g_strdup_printf ("%s/%s.xml", helpdir, pageid);
689                 if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) {
690                     priv->tmptype = YELP_URI_DOCUMENT_TYPE_DOCBOOK;
691                     continue;
692                 }
693                 g_free (filename);
694             }
695 
696             filename = g_strdup_printf ("%s/%s.html", helpdir, pageid);
697             if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) {
698                 priv->tmptype = YELP_URI_DOCUMENT_TYPE_HTML;
699                 continue;
700             }
701             g_free (filename);
702 
703             filename = g_strdup_printf ("%s/%s.xhtml", helpdir, pageid);
704             if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) {
705                 priv->tmptype = YELP_URI_DOCUMENT_TYPE_XHTML;
706                 continue;
707             }
708             g_free (filename);
709         } /* end for langs */
710     } /* end for datadirs */
711 
712     g_free (datadirs);
713     if (priv->tmptype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED) {
714         g_strfreev (searchpath);
715         priv->tmptype = YELP_URI_DOCUMENT_TYPE_NOT_FOUND;
716     }
717     else {
718         priv->gfile = g_file_new_for_path (filename);
719         priv->search_path = searchpath;
720         g_free (filename);
721     }
722 }
723 
724 static void
build_ghelp_fulluri(YelpUri * uri)725 build_ghelp_fulluri (YelpUri *uri)
726 {
727     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
728 
729     g_assert (priv->tmptype != YELP_URI_DOCUMENT_TYPE_UNRESOLVED);
730     g_assert (priv->docuri != NULL);
731     priv->fulluri = g_strconcat (priv->docuri,
732                                  priv->tmptype == YELP_URI_DOCUMENT_TYPE_MALLARD ? "/" : "",
733                                  priv->page_id ? "?" : "",
734                                  priv->page_id ? priv->page_id : "",
735                                  priv->frag_id ? "#" : "",
736                                  priv->frag_id ? priv->frag_id : "",
737                                  NULL);
738 }
739 
740 static void
resolve_ghelp_uri(YelpUri * uri)741 resolve_ghelp_uri (YelpUri *uri)
742 {
743     /* ghelp:/path/to/file
744      * ghelp:document[/file][?page][#frag]
745      */
746     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
747     gchar *document, *slash, *query, *hash;
748     gchar *colon, *c; /* do not free */
749 
750     colon = strchr (priv->res_arg, ':');
751     if (!colon) {
752         priv->tmptype = YELP_URI_DOCUMENT_TYPE_ERROR;
753         return;
754     }
755 
756     slash = query = hash = NULL;
757     for (c = colon; *c != '\0'; c++) {
758         if (*c == '#' && hash == NULL)
759             hash = c;
760         else if (*c == '?' && query == NULL && hash == NULL)
761             query = c;
762         else if (*c == '/' && slash == NULL && query == NULL && hash == NULL)
763             slash = c;
764     }
765 
766     if (slash || query || hash)
767         document = g_strndup (colon + 1,
768                               (slash ? slash : (query ? query : hash)) - colon - 1);
769     else
770         document = g_strdup (colon + 1);
771 
772     if (slash && (query || hash))
773         slash = g_strndup (slash + 1,
774                            (query ? query : hash) - slash - 1);
775     else if (slash)
776         slash = g_strdup (slash + 1);
777 
778     if (query && hash)
779         query = g_strndup (query + 1,
780                            hash - query - 1);
781     else if (query)
782         query = g_strdup (query + 1);
783 
784     if (hash)
785         hash = g_strdup (hash + 1);
786 
787     if (*(colon + 1) == '/') {
788         gchar *path;
789 
790         path = g_build_filename ("/", slash, NULL);
791         if (g_file_test (path, G_FILE_TEST_EXISTS)) {
792             priv->gfile = g_file_new_for_path (path);
793             resolve_gfile (uri, query, hash);
794         } else {
795             gchar *dirname = g_path_get_dirname (path);
796             gchar *basename = g_path_get_basename (path);
797 
798             priv->gfile = g_file_new_for_path (dirname);
799             g_free (dirname);
800             resolve_gfile (uri, basename, hash);
801             g_free (basename);
802         }
803         g_free (path);
804         g_free (slash);
805         g_free (query);
806         g_free (hash);
807         g_free (document);
808 
809         return;
810     }
811 
812     resolve_data_dirs (uri, "gnome/help", document, slash ? slash : document, FALSE);
813 
814     if (query && hash) {
815         priv->page_id = query;
816         priv->frag_id = hash;
817     }
818     else if (query) {
819         priv->page_id = query;
820         if (priv->tmptype != YELP_URI_DOCUMENT_TYPE_MALLARD)
821             priv->frag_id = g_strdup (query);
822     }
823     else if (hash) {
824         priv->page_id = hash;
825         priv->frag_id = g_strdup (hash);
826     }
827 
828     if (priv->frag_id && g_str_has_prefix (priv->frag_id, "search=")) {
829         g_free (priv->frag_id);
830         priv->frag_id = NULL;
831     }
832 
833     priv->docuri = g_strconcat ("ghelp:", document,
834                                 slash ? "/" : NULL,
835                                 slash, NULL);
836 
837     build_ghelp_fulluri (uri);
838 
839     g_free (document);
840     g_free (slash);
841     return;
842 }
843 
844 static void
resolve_help_uri(YelpUri * uri)845 resolve_help_uri (YelpUri *uri)
846 {
847     /* help:document[/page][?query][#frag]
848      */
849     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
850     gchar *document, *slash, *query, *hash;
851     gchar *colon, *c; /* do not free */
852 
853     colon = strchr (priv->res_arg, ':');
854     if (!colon) {
855         priv->tmptype = YELP_URI_DOCUMENT_TYPE_ERROR;
856         return;
857     }
858 
859     slash = query = hash = NULL;
860     for (c = colon; *c != '\0'; c++) {
861         if (*c == '#' && hash == NULL)
862             hash = c;
863         else if (*c == '?' && query == NULL && hash == NULL)
864             query = c;
865         else if (*c == '/' && slash == NULL && query == NULL && hash == NULL)
866             slash = c;
867     }
868 
869     if (slash || query || hash)
870         document = g_strndup (colon + 1,
871                               (slash ? slash : (query ? query : hash)) - colon - 1);
872     else
873         document = g_strdup (colon + 1);
874 
875     if (slash && (query || hash))
876         slash = g_strndup (slash + 1,
877                            (query ? query : hash) - slash - 1);
878     else if (slash)
879         slash = g_strdup (slash + 1);
880 
881     if (query && hash)
882         query = g_strndup (query + 1,
883                            hash - query - 1);
884     else if (query)
885         query = g_strdup (query + 1);
886 
887     if (query) {
888         gchar **keyvals = g_strsplit (query, "&", 0);
889         gint i;
890 
891         for (i = 0; keyvals[i]; i++) {
892             gchar *key, *val;
893             val = strchr (keyvals[i], '=');
894             if (val == NULL)
895                 continue;
896             key = g_uri_unescape_segment (keyvals[i], val, NULL);
897             val = g_uri_unescape_string (val + 1, NULL);
898 
899             g_hash_table_insert (priv->query, key, val);
900         }
901 
902         g_strfreev (keyvals);
903     }
904 
905     if (hash)
906         hash = g_strdup (hash + 1);
907 
908     priv->page_id = (slash ? slash : g_strdup ("index"));
909     resolve_data_dirs (uri, "help", document, priv->page_id, TRUE);
910 
911     if (hash)
912         priv->frag_id = hash;
913     if (priv->frag_id && g_str_has_prefix (priv->frag_id, "search=")) {
914         g_free (priv->frag_id);
915         priv->frag_id = NULL;
916     }
917 
918     priv->docuri = g_strconcat ("help:", document, NULL);
919 
920     priv->fulluri = g_strconcat (priv->docuri,
921                                  priv->page_id ? "/" : "",
922                                  priv->page_id ? priv->page_id : "",
923                                  query ? "?" : "",
924                                  query ? query : "",
925                                  priv->frag_id ? "#" : "",
926                                  priv->frag_id ? priv->frag_id : "",
927                                  NULL);
928 
929     g_free (query);
930     g_free (document);
931     return;
932 }
933 
934 static void
resolve_help_list_uri(YelpUri * uri)935 resolve_help_list_uri (YelpUri *uri)
936 {
937     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
938     priv->docuri = g_strdup ("help-list:");
939     priv->fulluri = g_strdup (priv->res_arg);
940     priv->page_id = g_strdup ("index");
941     priv->tmptype = YELP_URI_DOCUMENT_TYPE_HELP_LIST;
942 }
943 
944 /*
945   Resolve a manual file's path using 'man -w'. section may be NULL,
946   otherwise should be the section of the manual (ie should have dealt
947   with empty strings before calling this!) Returns NULL if the file
948   can't be found.
949 */
950 static gchar*
find_man_path(gchar * name,gchar * section)951 find_man_path (gchar* name, gchar* section)
952 {
953     const gchar* argv[] = { "man", "-w", NULL, NULL, NULL };
954     gchar **my_argv;
955     gchar *ystdout = NULL;
956     gint status;
957     gchar **lines;
958     GError *error = NULL;
959 
960     /* Syntax for man is "man -w <section> <name>", possibly omitting
961        section */
962     if (section) {
963         argv[2] = section;
964         argv[3] = name;
965     } else {
966         argv[2] = name;
967     }
968 
969     /* g_strdupv() should accept a "const gchar **". */
970     my_argv = g_strdupv ((gchar **) argv);
971 
972     if (!g_spawn_sync (NULL, my_argv, NULL,
973                        G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL,
974                        NULL, NULL,
975                        &ystdout, NULL, &status, &error)) {
976         g_warning ("Couldn't find path for %s(%s). Error: %s",
977                    name, section, error->message);
978         g_error_free (error);
979     }
980 
981     g_strfreev (my_argv);
982 
983     if (status == 0) {
984         lines = g_strsplit (ystdout, "\n", 2);
985         g_free (ystdout);
986         ystdout = g_strdup (lines[0]);
987 
988         g_strfreev (lines);
989         return ystdout;
990     } else {
991         g_free (ystdout);
992         return NULL;
993     }
994 }
995 
996 static void
build_man_uris(YelpUri * uri,const char * name,const char * section)997 build_man_uris (YelpUri *uri, const char *name, const char *section)
998 {
999     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
1000 
1001     g_assert (priv->tmptype == YELP_URI_DOCUMENT_TYPE_MAN);
1002     priv->docuri = g_strdup ("man:");
1003     priv->fulluri = g_strconcat ("man:",  name,
1004                                  section ? "." : "",
1005                                  section ? section : "",
1006                                  NULL);
1007     priv->page_id = g_strconcat (name,
1008                                  section ? "." : "",
1009                                  section ? section : "",
1010                                  NULL);
1011 }
1012 
1013 static void
resolve_man_uri(YelpUri * uri)1014 resolve_man_uri (YelpUri *uri)
1015 {
1016     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
1017     /* man:/path/to/file
1018      * man:name(section)
1019      * man:name.section
1020      * man:name
1021      */
1022 
1023     /* Search via regular expressions for name, name(section) and
1024      * name.section (assuming that name doesn't contain forward
1025      * slashes or other nasties)
1026      *
1027      * If these don't match, assume that we were given a filename
1028      * (absolute iff it starts with a /).
1029      */
1030     static GRegex* man_not_path = NULL;
1031     GError *error = NULL;
1032     GMatchInfo *match_info = NULL;
1033     gchar *name, *section, *hash;
1034     gchar *path;
1035 
1036     if (!man_not_path) {
1037         /* Match group 1 should contain the name; then one of groups 3
1038          * and 4 will contain the section if there was one. Group 6
1039          * will contain any hash fragment. */
1040         man_not_path = g_regex_new ("man:((?:[^ /.()#]|\\.(?=[^0-9]))+)"
1041                                     "(\\(([0-9A-Za-z]+)\\)|\\.([0-9A-Za-z]+)|)"
1042                                     "(#([^/ ()]+))?",
1043                                     0, 0, &error);
1044         if (!man_not_path) {
1045             g_error ("Error with regex in man uri: %s\n",
1046                      error->message);
1047         }
1048     }
1049 
1050     if (!g_regex_match (man_not_path, priv->res_arg,
1051                         0, &match_info)) {
1052         /* The regexp didn't match, so treat as a file name. */
1053         guint i;
1054         static const char *man_suffixes[] = { "gz", "bz2", "lzma", NULL };
1055 
1056         priv->tmptype = YELP_URI_DOCUMENT_TYPE_MAN;
1057         priv->gfile = g_file_new_for_path (priv->res_arg + 4);
1058         name = g_file_get_basename (priv->gfile);
1059         for (i = 0; i < G_N_ELEMENTS (man_suffixes); i++) {
1060             if (is_man_path (name, man_suffixes[i])) {
1061                 if (man_suffixes[i])
1062                     name[strlen (name) - strlen (man_suffixes[i]) - 1] = '\0';
1063                 break;
1064             }
1065         }
1066         build_man_uris (uri, name, NULL);
1067     }
1068     else {
1069         /* The regexp matched, so we've got a name/section pair that
1070          * needs resolving. */
1071         name = g_match_info_fetch (match_info, 1);
1072         section = g_match_info_fetch (match_info, 3);
1073         hash = g_match_info_fetch (match_info, 6);
1074         if (!name) {
1075             g_error ("Error matching strings in man uri '%s'",
1076                      priv->res_arg);
1077         }
1078         if ((!section) || (section[0] == '\0')) {
1079             section = g_match_info_fetch (match_info, 4);
1080         }
1081         if (section && section[0] == '\0') section = NULL;
1082 
1083         path = find_man_path (name, section);
1084 
1085         if (!path) {
1086             priv->tmptype = YELP_URI_DOCUMENT_TYPE_NOT_FOUND;
1087             return;
1088         }
1089         priv->tmptype = YELP_URI_DOCUMENT_TYPE_MAN;
1090         priv->gfile = g_file_new_for_path (path);
1091         build_man_uris (uri, name, section);
1092 
1093         if (hash && hash[0] != '\0')
1094             resolve_page_and_frag (uri, hash + 1);
1095 
1096         g_free (path);
1097         g_match_info_free (match_info);
1098     }
1099 }
1100 
1101 static void
build_info_uris(YelpUri * uri,const char * name,const char * section)1102 build_info_uris (YelpUri *uri, const char *name, const char *section)
1103 {
1104     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
1105 
1106     g_assert (priv->tmptype == YELP_URI_DOCUMENT_TYPE_INFO);
1107     priv->docuri = g_strconcat ("info:", name, NULL);
1108     priv->fulluri = g_strconcat (priv->docuri,
1109                                  section ? "#" : "",
1110                                  section ? section : "",
1111                                  NULL);
1112     priv->page_id = g_strdup (section);
1113     priv->frag_id = g_strdup (section);
1114 }
1115 
1116 static void
resolve_info_uri(YelpUri * uri)1117 resolve_info_uri (YelpUri *uri)
1118 {
1119     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
1120     /* info:/path/to/file
1121      * info:name#node
1122      * info:name
1123      * info:(name)node
1124      * info:(name)
1125      */
1126     static gchar **infopath = NULL;
1127     gchar *name = NULL;
1128     gchar *sect = NULL;
1129     gchar *fullpath = NULL;
1130     /* do not free */
1131     gchar *colon;
1132     gint infopath_i, suffix_i;
1133 
1134     if (g_str_has_prefix (priv->res_arg, "info:/")) {
1135         const gchar *hash;
1136 
1137         priv->tmptype = YELP_URI_DOCUMENT_TYPE_INFO;
1138 
1139         hash = strchr (priv->res_arg + 5, '#');
1140         if (hash) {
1141             gchar *path;
1142 
1143             path = g_strndup (priv->res_arg + 5, hash - (priv->res_arg + 5));
1144             priv->gfile = g_file_new_for_path (path);
1145             g_free (path);
1146             sect = g_strdup (hash + 1);
1147         }
1148         else
1149             priv->gfile = g_file_new_for_path (priv->res_arg + 5);
1150 
1151         name = g_file_get_basename (priv->gfile);
1152         for (suffix_i = 0; infosuffix[suffix_i]; suffix_i++) {
1153             if (g_str_has_suffix (name, infosuffix[suffix_i])) {
1154                 name[strlen (name) - strlen (infosuffix[suffix_i])] = '\0';
1155                 break;
1156             }
1157         }
1158 
1159         build_info_uris (uri, name, sect);
1160         g_free (name);
1161         g_free (sect);
1162         return;
1163     }
1164 
1165     if (!infopath) {
1166         /* Initialize infopath only once */
1167 
1168         /* Use the same logic as the info program. If INFOPATH is not
1169            specified, use the default. If it is specified, just use it
1170            unless it ends with a colon, in which case we add the
1171            default as a suffix.
1172         */
1173         const gchar *env = g_getenv ("INFOPATH");
1174         gchar *paths;
1175         if (!env || env[0] == '\0')
1176             paths = g_strdup (default_info_path);
1177         else if (env[strlen (env)-1] == ':')
1178             paths = g_strconcat (env, default_info_path, NULL);
1179         else
1180             paths = g_strdup (env);
1181 
1182         infopath = g_strsplit (paths, ":", 0);
1183 
1184         g_free (paths);
1185     }
1186 
1187     colon = strchr (priv->res_arg, ':');
1188     if (colon)
1189         colon++;
1190     else
1191         colon = (gchar *) priv->res_arg;
1192 
1193     if (colon[0] == '(') {
1194         const gchar *rbrace = strchr (colon, ')');
1195         if (rbrace) {
1196             name = g_strndup (colon + 1, rbrace - colon - 1);
1197             sect = g_strdup (rbrace + 1);
1198         }
1199     }
1200     else {
1201         const gchar *hash = strchr (colon, '#');
1202         if (hash) {
1203             name = g_strndup (colon, hash - colon);
1204             sect = g_strdup (hash + 1);
1205         }
1206         else {
1207             name = g_strdup (colon);
1208             sect = NULL;
1209         }
1210     }
1211 
1212     for (infopath_i = 0; infopath[infopath_i]; infopath_i++) {
1213         if (!g_file_test (infopath[infopath_i], G_FILE_TEST_IS_DIR))
1214             continue;
1215         for (suffix_i = 0; infosuffix[suffix_i]; suffix_i++) {
1216             fullpath = g_strconcat (infopath[infopath_i], "/",
1217                                     name, infosuffix[suffix_i], NULL);
1218             if (g_file_test (fullpath, G_FILE_TEST_IS_REGULAR))
1219                 break;
1220             g_free (fullpath);
1221             fullpath = NULL;
1222         }
1223         if (fullpath != NULL)
1224             break;
1225     }
1226 
1227     if (fullpath) {
1228         priv->tmptype = YELP_URI_DOCUMENT_TYPE_INFO;
1229         priv->gfile = g_file_new_for_path (fullpath);
1230         build_info_uris (uri, name, sect);
1231     } else {
1232         gchar *res_arg = priv->res_arg;
1233         priv->res_arg = g_strconcat ("man:", name, NULL);
1234         resolve_man_uri (uri);
1235         if (priv->tmptype == YELP_URI_DOCUMENT_TYPE_MAN) {
1236             g_free (priv->res_arg);
1237             priv->res_arg = res_arg;
1238         }
1239         else {
1240             g_free (res_arg);
1241             priv->tmptype = YELP_URI_DOCUMENT_TYPE_NOT_FOUND;
1242         }
1243     }
1244     g_free (fullpath);
1245     g_free (name);
1246     g_free (sect);
1247 }
1248 
1249 static void
resolve_xref_uri(YelpUri * uri)1250 resolve_xref_uri (YelpUri *uri)
1251 {
1252     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
1253     const gchar *arg = priv->res_arg + 5;
1254     YelpUriPrivate *base_priv = yelp_uri_get_instance_private (priv->res_base);
1255 
1256     priv->tmptype = base_priv->doctype;
1257     priv->gfile = g_object_ref (base_priv->gfile);
1258     priv->search_path = g_strdupv (base_priv->search_path);
1259     priv->docuri = g_strdup (base_priv->docuri);
1260 
1261     if (arg[0] == '#') {
1262         priv->page_id = g_strdup (base_priv->page_id);
1263         priv->frag_id = g_strdup (arg + 1);
1264     }
1265     else {
1266         gchar *hash = strchr (arg, '#');
1267         if (hash) {
1268             priv->page_id = g_strndup (arg, hash - arg);
1269             priv->frag_id = g_strdup (hash + 1);
1270         }
1271         else {
1272             priv->page_id = g_strdup (arg);
1273             priv->frag_id = NULL;
1274         }
1275     }
1276     if (priv->page_id && priv->page_id[0] == '\0') {
1277         g_free (priv->page_id);
1278         if (g_str_has_prefix (priv->docuri, "help:"))
1279             priv->page_id = g_strdup ("index");
1280         else
1281             priv->page_id = NULL;
1282     }
1283 
1284     if (priv->page_id &&
1285         g_str_has_prefix (priv->docuri, "info:")) {
1286         /*
1287           Special characters get url-encoded when they get clicked on
1288           as links. Info files, at least, don't want that so decode
1289           the url again here.
1290          */
1291         gchar* tmp = priv->page_id;
1292         priv->page_id = g_uri_unescape_string (tmp, NULL);
1293         g_free (tmp);
1294     }
1295 
1296     if (g_str_has_prefix (priv->docuri, "ghelp:"))
1297         build_ghelp_fulluri (uri);
1298     else if (g_str_has_prefix (priv->docuri, "help:"))
1299         priv->fulluri = g_strconcat (priv->docuri,
1300                                      priv->page_id ? "/" : "",
1301                                      priv->page_id ? priv->page_id : "",
1302                                      priv->frag_id ? "#" : "",
1303                                      priv->frag_id ? priv->frag_id : "",
1304                                      NULL);
1305     else if (g_str_has_prefix (priv->docuri, "file:") ||
1306              g_str_has_prefix (priv->docuri, "info:") )
1307         priv->fulluri = g_strconcat (priv->docuri,
1308                                      (priv->page_id || priv->frag_id) ? "#" : "",
1309                                      priv->page_id ? priv->page_id : "",
1310                                      priv->frag_id ? "#" : "",
1311                                      priv->frag_id,
1312                                      NULL);
1313     else
1314         /* FIXME: other URI schemes */
1315         priv->fulluri = g_strconcat (priv->docuri,
1316                                      priv->page_id ? "#" : "",
1317                                      priv->page_id,
1318                                      NULL);
1319 }
1320 
1321 static void
resolve_page_and_frag(YelpUri * uri,const gchar * arg)1322 resolve_page_and_frag (YelpUri *uri, const gchar *arg)
1323 {
1324     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
1325     gchar *hash;
1326 
1327     if (!arg || arg[0] == '\0')
1328         return;
1329 
1330     hash = strchr (arg, '#');
1331     if (hash) {
1332         priv->page_id = g_strndup (arg, hash - arg);
1333         priv->frag_id = g_strdup (hash + 1);
1334     } else {
1335         priv->page_id = g_strdup (arg);
1336         priv->frag_id = g_strdup (arg);
1337     }
1338     return;
1339 }
1340 
1341 static void
resolve_gfile(YelpUri * uri,const gchar * query,const gchar * hash)1342 resolve_gfile (YelpUri *uri, const gchar *query, const gchar *hash)
1343 {
1344     YelpUriPrivate *priv = yelp_uri_get_instance_private (uri);
1345     GFileInfo *info;
1346     GError *error = NULL;
1347 
1348     info = g_file_query_info (priv->gfile,
1349                               G_FILE_ATTRIBUTE_STANDARD_TYPE ","
1350                               G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE,
1351                               G_FILE_QUERY_INFO_NONE,
1352                               NULL, &error);
1353     if (error) {
1354         if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
1355             priv->tmptype = YELP_URI_DOCUMENT_TYPE_NOT_FOUND;
1356         }
1357         else
1358             priv->tmptype = YELP_URI_DOCUMENT_TYPE_ERROR;
1359         g_error_free (error);
1360         return;
1361     }
1362 
1363     if (priv->search_path == NULL) {
1364         if (g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE) ==
1365             G_FILE_TYPE_DIRECTORY) {
1366             priv->search_path = g_new0 (gchar *, 2);
1367             priv->search_path[0] = g_file_get_path (priv->gfile);
1368         } else {
1369             GFile *parent = g_file_get_parent (priv->gfile);
1370             priv->search_path = g_new0 (gchar *, 2);
1371             priv->search_path[0] = g_file_get_path (parent);
1372             g_object_unref (parent);
1373         }
1374     }
1375 
1376     if (priv->tmptype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED) {
1377         priv->tmptype = YELP_URI_DOCUMENT_TYPE_EXTERNAL;
1378 
1379         if (g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE) ==
1380             G_FILE_TYPE_DIRECTORY) {
1381             GFile *child = g_file_get_child (priv->gfile, "index.page");
1382             if (g_file_query_exists (child, NULL)) {
1383                 char *path;
1384 
1385                 priv->tmptype = YELP_URI_DOCUMENT_TYPE_MALLARD;
1386                 if (priv->page_id == NULL)
1387                     priv->page_id = g_strdup (query);
1388                 if (priv->frag_id == NULL)
1389                     priv->frag_id = g_strdup (hash);
1390 
1391                 path = g_file_get_path (priv->gfile);
1392                 priv->docuri = g_strconcat ("ghelp:", path, NULL);
1393                 build_ghelp_fulluri (uri);
1394                 g_free (path);
1395             }
1396             else if (yelp_settings_get_editor_mode (yelp_settings_get_default ())) {
1397                 g_object_unref (child);
1398                 child = g_file_get_child (priv->gfile, "index.page.stub");
1399                 if (g_file_query_exists (child, NULL)) {
1400                     priv->tmptype = YELP_URI_DOCUMENT_TYPE_MALLARD;
1401                     if (priv->page_id == NULL)
1402                         priv->page_id = g_strdup (query);
1403                     if (priv->frag_id == NULL)
1404                         priv->frag_id = g_strdup (hash);
1405                 }
1406             }
1407             g_object_unref (child);
1408         }
1409         else {
1410             gchar *basename;
1411             const gchar *mime_type = g_file_info_get_attribute_string (info,
1412                                                                        G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE);
1413             basename = g_file_get_basename (priv->gfile);
1414             if (g_str_has_suffix (basename, ".page")) {
1415                 GFile *old;
1416                 char *path;
1417 
1418                 priv->tmptype = YELP_URI_DOCUMENT_TYPE_MALLARD;
1419                 old = priv->gfile;
1420                 priv->gfile = g_file_get_parent (old);
1421                 if (priv->page_id == NULL) {
1422                     /* File names aren't really page IDs, so we stick an illegal character
1423                        on the beginning so it can't possibly be confused with a real page
1424                        ID.  Then we let YelpMallardDocument map file names to pages IDs.
1425                      */
1426                     gchar *tmp = g_file_get_basename (old);
1427                     priv->page_id = g_strconcat (G_DIR_SEPARATOR_S, tmp, NULL);
1428                     g_free (tmp);
1429                 }
1430 
1431                 if (priv->frag_id == NULL)
1432                     priv->frag_id = g_strdup (hash);
1433                 path = g_file_get_path (priv->gfile);
1434                 priv->docuri = g_strconcat ("ghelp:", path, NULL);
1435                 build_ghelp_fulluri (uri);
1436                 g_free (path);
1437                 g_object_unref (old);
1438             }
1439             else if (g_str_equal (mime_type, "text/xml") ||
1440                      g_str_equal (mime_type, "application/docbook+xml") ||
1441                      g_str_equal (mime_type, "application/xml") ||
1442                      g_str_has_suffix (basename, ".docbook")) {
1443                 char *path;
1444 
1445                 priv->tmptype = YELP_URI_DOCUMENT_TYPE_DOCBOOK;
1446 
1447                 if (priv->page_id == NULL)
1448                     priv->page_id = g_strdup (query);
1449                 if (priv->frag_id == NULL)
1450                     priv->frag_id = g_strdup (hash);
1451 
1452                 path = g_file_get_path (priv->gfile);
1453                 priv->docuri = g_strconcat ("ghelp:", path, NULL);
1454                 build_ghelp_fulluri (uri);
1455                 g_free (path);
1456             }
1457             else if (g_str_equal (mime_type, "text/html") ||
1458                      g_str_equal (mime_type, "application/xhtml+xml")) {
1459                 GFile *parent = g_file_get_parent (priv->gfile);
1460                 priv->docuri = g_file_get_uri (parent);
1461                 g_object_unref (parent);
1462                 priv->tmptype = mime_type[0] == 't' ? YELP_URI_DOCUMENT_TYPE_HTML : YELP_URI_DOCUMENT_TYPE_XHTML;
1463                 if (priv->page_id == NULL)
1464                     priv->page_id = g_strdup (basename);
1465                 if (priv->frag_id == NULL)
1466                     priv->frag_id = g_strdup (hash);
1467                 if (priv->fulluri == NULL) {
1468                     gchar *fulluri;
1469                     fulluri = g_file_get_uri (priv->gfile);
1470                     priv->fulluri = g_strconcat (fulluri,
1471                                                  priv->frag_id ? "#" : NULL,
1472                                                  priv->frag_id,
1473                                                  NULL);
1474                     g_free (fulluri);
1475                 }
1476             }
1477             else if (g_str_equal (mime_type, "application/x-gzip") ||
1478                      g_str_equal (mime_type, "application/gzip")) {
1479                 if (g_str_has_suffix (basename, ".info.gz")) {
1480                     priv->tmptype = YELP_URI_DOCUMENT_TYPE_INFO;
1481                     basename[strlen (basename) - strlen (".info.gz")] = '\0';
1482                     build_info_uris (uri, basename, hash);
1483                 }
1484                 else if (is_man_path (basename, "gz")) {
1485                     priv->tmptype = YELP_URI_DOCUMENT_TYPE_MAN;
1486                     basename[strlen (basename) - strlen ("gz") - 1] = '\0';
1487                     build_man_uris (uri, basename, NULL);
1488                 }
1489             }
1490             else if (g_str_equal (mime_type, "application/x-bzip")) {
1491                 if (g_str_has_suffix (basename, ".info.bz2")) {
1492                     priv->tmptype = YELP_URI_DOCUMENT_TYPE_INFO;
1493                     basename[strlen (basename) - strlen (".info.bz2")] = '\0';
1494                     build_info_uris (uri, basename, hash);
1495                 }
1496                 else if (is_man_path (basename, "bz2")) {
1497                     priv->tmptype = YELP_URI_DOCUMENT_TYPE_MAN;
1498                     basename[strlen (basename) - strlen ("bz2") - 1] = '\0';
1499                     build_man_uris (uri, basename, NULL);
1500                 }
1501             }
1502             else if (g_str_equal (mime_type, "application/x-lzma")) {
1503                 if (g_str_has_suffix (basename, ".info.lzma")) {
1504                     priv->tmptype = YELP_URI_DOCUMENT_TYPE_INFO;
1505                     basename[strlen (basename) - strlen (".info.lzma")] = '\0';
1506                     build_info_uris (uri, basename, hash);
1507                 }
1508                 else if (is_man_path (basename, "lzma")) {
1509                     priv->tmptype = YELP_URI_DOCUMENT_TYPE_MAN;
1510                     basename[strlen (basename) - strlen ("lzma") - 1] = '\0';
1511                     build_man_uris (uri, basename, NULL);
1512                 }
1513             }
1514             else if (g_str_equal (mime_type, "application/octet-stream")) {
1515                 if (g_str_has_suffix (basename, ".info")) {
1516                     priv->tmptype = YELP_URI_DOCUMENT_TYPE_INFO;
1517                     basename[strlen (basename) - strlen (".info")] = '\0';
1518                     build_info_uris (uri, basename, hash);
1519                 }
1520                 else if (is_man_path (basename, NULL)) {
1521                     priv->tmptype = YELP_URI_DOCUMENT_TYPE_MAN;
1522                     build_man_uris (uri, basename, NULL);
1523                 }
1524             }
1525             else if (g_str_equal (mime_type, "text/plain")) {
1526                 if (g_str_has_suffix (basename, ".info")) {
1527                     priv->tmptype = YELP_URI_DOCUMENT_TYPE_INFO;
1528                     basename[strlen (basename) - strlen (".info")] = '\0';
1529                     build_info_uris (uri, basename, hash);
1530                 }
1531                 else if (is_man_path (basename, NULL)) {
1532                     priv->tmptype = YELP_URI_DOCUMENT_TYPE_MAN;
1533                     build_man_uris (uri, basename, NULL);
1534                 } else
1535                     priv->tmptype = YELP_URI_DOCUMENT_TYPE_TEXT;
1536                 if (priv->frag_id == NULL)
1537                     priv->frag_id = g_strdup (hash);
1538             }
1539             else if (g_str_equal (mime_type, "text/x-readme")) {
1540                 priv->tmptype = YELP_URI_DOCUMENT_TYPE_TEXT;
1541             }
1542             else {
1543                 priv->tmptype = YELP_URI_DOCUMENT_TYPE_EXTERNAL;
1544             }
1545             g_free (basename);
1546         }
1547     }
1548 
1549     if (priv->docuri == NULL)
1550         priv->docuri = g_file_get_uri (priv->gfile);
1551 
1552     if (priv->fulluri == NULL)
1553         priv->fulluri = g_strconcat (priv->docuri,
1554                                      (priv->page_id || priv->frag_id) ? "#" : NULL,
1555                                      priv->page_id ? priv->page_id : "",
1556                                      priv->frag_id ? "#" : NULL,
1557                                      priv->frag_id ? priv->frag_id : NULL,
1558                                      NULL);
1559 
1560     g_object_unref (info);
1561 }
1562 
1563 static gboolean
is_man_path(const gchar * path,const gchar * encoding)1564 is_man_path (const gchar *path, const gchar *encoding)
1565 {
1566     gchar **iter = (gchar **) mancats;
1567 
1568     if (encoding && *encoding) {
1569         while (iter && *iter) {
1570             gchar *ending = g_strdup_printf ("%s.%s", *iter, encoding);
1571             if (g_str_has_suffix (path, ending)) {
1572                 g_free (ending);
1573                 return TRUE;
1574             }
1575             g_free (ending);
1576             iter++;
1577         }
1578     } else {
1579         while (iter && *iter) {
1580             if (g_str_has_suffix (path, *iter)) {
1581                 return TRUE;
1582             }
1583             iter++;
1584         }
1585     }
1586     return FALSE;
1587 }
1588