1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * Copyright (C) 2009 Shaun McCance <shaunm@gnome.org>
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.
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.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
20 * Author: Shaun McCance <shaunm@gnome.org>
34 #include "yelp-debug.h"
35 #include "yelp-settings.h"
37 static void yelp_uri_class_init (YelpUriClass
*klass
);
38 static void yelp_uri_init (YelpUri
*uri
);
39 static void yelp_uri_dispose (GObject
*object
);
40 static void yelp_uri_finalize (GObject
*object
);
42 static void resolve_start (YelpUri
*uri
);
43 static void resolve_sync (YelpUri
*uri
);
44 static void resolve_async (YelpUri
*uri
);
45 static gboolean
resolve_final (YelpUri
*uri
);
47 static void resolve_file_uri (YelpUri
*uri
);
48 static void resolve_file_path (YelpUri
*uri
);
49 static void resolve_data_dirs (YelpUri
*uri
,
54 static void resolve_ghelp_uri (YelpUri
*uri
);
55 static void resolve_help_uri (YelpUri
*uri
);
56 static void resolve_help_list_uri (YelpUri
*uri
);
57 static void resolve_man_uri (YelpUri
*uri
);
58 static void resolve_info_uri (YelpUri
*uri
);
59 static void resolve_xref_uri (YelpUri
*uri
);
60 static void resolve_page_and_frag (YelpUri
*uri
,
62 static void resolve_gfile (YelpUri
*uri
,
65 static gboolean
is_man_path (const gchar
*uri
,
66 const gchar
*encoding
);
68 G_DEFINE_TYPE (YelpUri
, yelp_uri
, G_TYPE_OBJECT
);
69 #define GET_PRIV(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), YELP_TYPE_URI, YelpUriPrivate))
71 typedef struct _YelpUriPrivate YelpUriPrivate
;
72 struct _YelpUriPrivate
{
75 YelpUriDocumentType doctype
;
76 YelpUriDocumentType tmptype
;
97 static guint uri_signals
[LAST_SIGNAL
] = {0,};
99 /******************************************************************************/
101 static const gchar
*mancats
[] = {
103 "1", "1p", "1g", "1t", "1x", "1ssl", "1m",
105 "3", "3o", "3t", "3p", "3blt", "3nas", "3form", "3menu", "3tiff", "3ssl", "3readline",
106 "3ncurses", "3curses", "3f", "3pm", "3perl", "3qt", "3x", "3X11",
108 "5", "5snmp", "5x", "5ssl",
110 "7", "7gcc", "7x", "7ssl",
111 "8", "8l", "9", "0p",
115 static const gchar
*infosuffix
[] = {
117 ".info.gz", ".info.bz2", ".info.lzma",
118 ".gz", ".bz2", ".lzma",
122 static const gchar default_info_path
[] =
123 "/usr/info:/usr/share/info:/usr/local/info:/usr/local/share/info";
125 /******************************************************************************/
128 yelp_uri_class_init (YelpUriClass
*klass
)
130 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
132 object_class
->dispose
= yelp_uri_dispose
;
133 object_class
->finalize
= yelp_uri_finalize
;
135 uri_signals
[RESOLVED
] =
136 g_signal_new ("resolved",
137 G_OBJECT_CLASS_TYPE (klass
),
140 g_cclosure_marshal_VOID__VOID
,
143 g_type_class_add_private (klass
, sizeof (YelpUriPrivate
));
147 yelp_uri_init (YelpUri
*uri
)
149 YelpUriPrivate
*priv
= GET_PRIV (uri
);
151 priv
->query
= g_hash_table_new_full (g_str_hash
, g_str_equal
, g_free
, g_free
);
157 yelp_uri_dispose (GObject
*object
)
159 YelpUriPrivate
*priv
= GET_PRIV (object
);
162 g_object_unref (priv
->gfile
);
166 if (priv
->res_base
) {
167 g_object_unref (priv
->res_base
);
168 priv
->res_base
= NULL
;
172 g_hash_table_destroy (priv
->query
);
176 G_OBJECT_CLASS (yelp_uri_parent_class
)->dispose (object
);
180 yelp_uri_finalize (GObject
*object
)
182 YelpUriPrivate
*priv
= GET_PRIV (object
);
184 g_free (priv
->docuri
);
185 g_free (priv
->fulluri
);
186 g_strfreev (priv
->search_path
);
187 g_free (priv
->page_id
);
188 g_free (priv
->frag_id
);
189 g_free (priv
->res_arg
);
191 G_OBJECT_CLASS (yelp_uri_parent_class
)->finalize (object
);
194 /******************************************************************************/
197 yelp_uri_new (const gchar
*arg
)
199 return yelp_uri_new_relative (NULL
, arg
);
203 yelp_uri_new_relative (YelpUri
*base
, const gchar
*arg
)
206 YelpUriPrivate
*priv
;
208 uri
= (YelpUri
*) g_object_new (YELP_TYPE_URI
, NULL
);
210 priv
= GET_PRIV (uri
);
211 priv
->doctype
= YELP_URI_DOCUMENT_TYPE_UNRESOLVED
;
213 priv
->res_base
= g_object_ref (base
);
214 priv
->res_arg
= g_strdup (arg
);
220 yelp_uri_new_search (YelpUri
*base
,
224 YelpUriPrivate
*priv
;
227 uri
= (YelpUri
*) g_object_new (YELP_TYPE_URI
, NULL
);
229 priv
= GET_PRIV (uri
);
230 priv
->doctype
= YELP_URI_DOCUMENT_TYPE_UNRESOLVED
;
232 priv
->res_base
= g_object_ref (base
);
233 tmp
= g_uri_escape_string (text
, NULL
, FALSE
);
234 priv
->res_arg
= g_strconcat("xref:search=", tmp
, NULL
);
240 /******************************************************************************/
243 yelp_uri_resolve (YelpUri
*uri
)
245 YelpUriPrivate
*priv
= GET_PRIV (uri
);
247 if (priv
->res_base
&& !yelp_uri_is_resolved (priv
->res_base
)) {
248 g_signal_connect_swapped (priv
->res_base
, "resolved",
249 G_CALLBACK (resolve_start
),
251 yelp_uri_resolve (priv
->res_base
);
259 yelp_uri_resolve_sync (YelpUri
*uri
)
261 YelpUriPrivate
*priv
= GET_PRIV (uri
);
263 if (priv
->doctype
!= YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
267 yelp_uri_resolve_sync (priv
->res_base
);
274 /* We want code to be able to do something like this:
276 * if (yelp_uri_get_document_type (uri) != YELP_URI_DOCUMENT_TYPE_UNRESOLVED) {
277 * g_signal_connect (uri, "resolve", callback, data);
278 * yelp_uri_resolve (uri);
281 * Resolving happens in a separate thread, though, so if that thread can change
282 * the document type, we have a race condition. So here's the rules we play by:
284 * 1) None of the getters except the document type getter can return real data
285 * while the URI is unresolved. They all do a resolved check first, and
286 * return NULL if the URI is not resolved.
288 * 2) The threaded resolver functions can modify anything but the document
289 * type. They are the only things that are allowed to modify that data.
291 * 3) The resolver thread is not allowed to modify the document type. When
292 * it's done, it queues an async function to set the document type and
293 * emit "resolved" in the main thread.
295 * 4) Once a URI is resolved, it is immutable.
298 resolve_start (YelpUri
*uri
)
300 YelpUriPrivate
*priv
= GET_PRIV (uri
);
302 if (priv
->resolver
== NULL
) {
304 priv
->resolver
= g_thread_create ((GThreadFunc
) resolve_async
,
310 resolve_sync (YelpUri
*uri
)
313 YelpUriPrivate
*priv
= GET_PRIV (uri
);
315 if (g_str_has_prefix (priv
->res_arg
, "ghelp:")
316 || g_str_has_prefix (priv
->res_arg
, "gnome-help:")) {
317 resolve_ghelp_uri (uri
);
319 else if (g_str_has_prefix (priv
->res_arg
, "help:")) {
320 resolve_help_uri (uri
);
322 else if (g_str_has_prefix (priv
->res_arg
, "help-list:")) {
323 resolve_help_list_uri (uri
);
325 else if (g_str_has_prefix (priv
->res_arg
, "file:")) {
326 resolve_file_uri (uri
);
328 else if (g_str_has_prefix (priv
->res_arg
, "man:")) {
329 resolve_man_uri (uri
);
331 else if (g_str_has_prefix (priv
->res_arg
, "info:")) {
332 resolve_info_uri (uri
);
334 else if (g_str_has_prefix (priv
->res_arg
, "xref:")) {
335 YelpUriPrivate
*base_priv
;
336 if (priv
->res_base
== NULL
) {
337 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_ERROR
;
340 base_priv
= GET_PRIV (priv
->res_base
);
341 switch (base_priv
->doctype
) {
342 case YELP_URI_DOCUMENT_TYPE_UNRESOLVED
:
344 case YELP_URI_DOCUMENT_TYPE_DOCBOOK
:
345 case YELP_URI_DOCUMENT_TYPE_MALLARD
:
346 case YELP_URI_DOCUMENT_TYPE_INFO
:
347 resolve_xref_uri (uri
);
349 case YELP_URI_DOCUMENT_TYPE_MAN
: {
350 gchar
*tmp
= g_strconcat ("man:", priv
->res_arg
+ 5, NULL
);
351 g_free (priv
->res_arg
);
353 resolve_man_uri (uri
);
356 case YELP_URI_DOCUMENT_TYPE_TEXT
:
357 case YELP_URI_DOCUMENT_TYPE_HTML
:
358 case YELP_URI_DOCUMENT_TYPE_XHTML
:
359 resolve_file_path (uri
);
361 case YELP_URI_DOCUMENT_TYPE_HELP_LIST
:
362 /* FIXME: what do we do? */
364 case YELP_URI_DOCUMENT_TYPE_NOT_FOUND
:
365 case YELP_URI_DOCUMENT_TYPE_EXTERNAL
:
366 case YELP_URI_DOCUMENT_TYPE_ERROR
:
370 else if (strchr (priv
->res_arg
, ':')) {
371 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_EXTERNAL
;
374 resolve_file_path (uri
);
377 /* We _always_ want to have a non-null fulluri, so check for it
378 * having been set here and, if we can't think of something
379 * better, set it to res_arg. */
380 if (!priv
->fulluri
) {
381 priv
->fulluri
= g_strdup (priv
->res_arg
);
386 resolve_async (YelpUri
*uri
)
389 g_idle_add ((GSourceFunc
) resolve_final
, uri
);
393 resolve_final (YelpUri
*uri
)
395 YelpUriPrivate
*priv
= GET_PRIV (uri
);
397 priv
->resolver
= NULL
;
399 if (priv
->tmptype
!= YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
400 priv
->doctype
= priv
->tmptype
;
402 priv
->doctype
= YELP_URI_DOCUMENT_TYPE_ERROR
;
404 if (priv
->res_base
) {
405 g_object_unref (priv
->res_base
);
406 priv
->res_base
= NULL
;
410 g_free (priv
->res_arg
);
411 priv
->res_arg
= NULL
;
414 g_signal_emit (uri
, uri_signals
[RESOLVED
], 0);
415 g_object_unref (uri
);
419 /******************************************************************************/
422 yelp_uri_is_resolved (YelpUri
*uri
)
424 YelpUriPrivate
*priv
= GET_PRIV (uri
);
425 return priv
->doctype
!= YELP_URI_DOCUMENT_TYPE_UNRESOLVED
;
429 yelp_uri_get_document_type (YelpUri
*uri
)
431 YelpUriPrivate
*priv
= GET_PRIV (uri
);
432 return priv
->doctype
;
436 yelp_uri_get_document_uri (YelpUri
*uri
)
438 YelpUriPrivate
*priv
= GET_PRIV (uri
);
439 if (priv
->doctype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
442 /* There's some client code where it makes sense to want a
443 * document uri, whether or not it conforms to a scheme we really
444 * understand. For example, we might want to look up whether the
445 * given page is currently being visited. */
446 if ((!priv
->docuri
) && priv
->fulluri
) {
447 return g_strdup (priv
->fulluri
);
450 return g_strdup (priv
->docuri
);
454 yelp_uri_get_canonical_uri (YelpUri
*uri
)
456 YelpUriPrivate
*priv
= GET_PRIV (uri
);
457 if (priv
->doctype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
459 return g_strdup (priv
->fulluri
);
463 yelp_uri_get_file (YelpUri
*uri
)
465 YelpUriPrivate
*priv
= GET_PRIV (uri
);
466 if (priv
->doctype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
468 return priv
->gfile
? g_object_ref (priv
->gfile
) : NULL
;
472 yelp_uri_get_search_path (YelpUri
*uri
)
474 YelpUriPrivate
*priv
= GET_PRIV (uri
);
475 if (priv
->doctype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
477 return g_strdupv (priv
->search_path
);
481 yelp_uri_get_page_id (YelpUri
*uri
)
483 YelpUriPrivate
*priv
= GET_PRIV (uri
);
484 if (priv
->doctype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
486 return g_strdup (priv
->page_id
);
490 yelp_uri_get_frag_id (YelpUri
*uri
)
492 YelpUriPrivate
*priv
= GET_PRIV (uri
);
493 if (priv
->doctype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
495 return g_strdup (priv
->frag_id
);
499 yelp_uri_get_query (YelpUri
*uri
,
502 YelpUriPrivate
*priv
= GET_PRIV (uri
);
503 const gchar
*ret
= g_hash_table_lookup (priv
->query
, key
);
505 return g_strdup (ret
);
510 /******************************************************************************/
513 yelp_uri_locate_file_uri (YelpUri
*uri
,
514 const gchar
*filename
)
516 YelpUriPrivate
*priv
= GET_PRIV (uri
);
519 gchar
*returi
= NULL
;
521 for (i
= 0; priv
->search_path
[i
] != NULL
; i
++) {
522 fullpath
= g_strconcat (priv
->search_path
[i
],
526 if (g_file_test (fullpath
, G_FILE_TEST_EXISTS
)) {
527 gfile
= g_file_new_for_path (fullpath
);
528 returi
= g_file_get_uri (gfile
);
529 g_object_unref (gfile
);
538 /******************************************************************************/
541 resolve_file_uri (YelpUri
*uri
)
543 YelpUriPrivate
*priv
= GET_PRIV (uri
);
545 const gchar
*hash
= strchr (priv
->res_arg
, '#');
548 uristr
= g_strndup (priv
->res_arg
, hash
- priv
->res_arg
);
552 uristr
= priv
->res_arg
;
554 priv
->gfile
= g_file_new_for_uri (uristr
);
556 resolve_gfile (uri
, hash
);
560 resolve_file_path (YelpUri
*uri
)
562 YelpUriPrivate
*base_priv
= NULL
;
563 YelpUriPrivate
*priv
= GET_PRIV (uri
);
567 /* Treat xref: URIs like relative file paths */
568 if (g_str_has_prefix (priv
->res_arg
, "xref:")) {
569 gchar
*tmp
= g_strdup (priv
->res_arg
+ 5);
570 g_free (priv
->res_arg
);
575 base_priv
= GET_PRIV (priv
->res_base
);
577 hash
= strchr (priv
->res_arg
, '#');
579 path
= g_strndup (priv
->res_arg
, hash
- priv
->res_arg
);
583 path
= priv
->res_arg
;
585 if (priv
->res_arg
[0] == '/') {
586 priv
->gfile
= g_file_new_for_path (path
);
588 else if (base_priv
&& base_priv
->gfile
) {
590 info
= g_file_query_info (base_priv
->gfile
,
591 G_FILE_ATTRIBUTE_STANDARD_TYPE
,
592 G_FILE_QUERY_INFO_NONE
,
594 if (g_file_info_get_file_type (info
) == G_FILE_TYPE_REGULAR
) {
595 GFile
*parent
= g_file_get_parent (base_priv
->gfile
);
596 priv
->gfile
= g_file_resolve_relative_path (parent
, path
);
597 g_object_unref (parent
);
600 priv
->gfile
= g_file_resolve_relative_path (base_priv
->gfile
, path
);
606 cur
= g_get_current_dir ();
607 curfile
= g_file_new_for_path (cur
);
608 priv
->gfile
= g_file_resolve_relative_path (curfile
, path
);
609 g_object_unref (curfile
);
613 resolve_gfile (uri
, hash
);
617 resolve_data_dirs (YelpUri
*ret
,
623 const gchar
* const *sdatadirs
= g_get_system_data_dirs ();
624 const gchar
* const *langs
= g_get_language_names ();
625 /* The strings are still owned by GLib; we just own the array. */
627 YelpUriPrivate
*priv
= GET_PRIV (ret
);
628 gchar
*filename
= NULL
;
629 gchar
**searchpath
= NULL
;
630 gint searchi
, searchmax
;
631 gint datadir_i
, lang_i
;
633 datadirs
= g_new0 (gchar
*, g_strv_length ((gchar
**) sdatadirs
) + 2);
634 datadirs
[0] = (gchar
*) g_get_user_data_dir ();
635 for (datadir_i
= 0; sdatadirs
[datadir_i
]; datadir_i
++)
636 datadirs
[datadir_i
+ 1] = (gchar
*) sdatadirs
[datadir_i
];
640 searchpath
= g_new0 (gchar
*, 10);
642 for (datadir_i
= 0; datadirs
[datadir_i
]; datadir_i
++) {
643 for (lang_i
= 0; langs
[lang_i
]; lang_i
++) {
644 gchar
*helpdir
= g_build_filename (datadirs
[datadir_i
],
646 langfirst
? langs
[lang_i
] : docid
,
647 langfirst
? docid
: langs
[lang_i
],
649 if (!g_file_test (helpdir
, G_FILE_TEST_IS_DIR
)) {
654 if (searchi
+ 1 >= searchmax
) {
656 searchpath
= g_renew (gchar
*, searchpath
, searchmax
);
658 searchpath
[searchi
] = helpdir
;
659 searchpath
[++searchi
] = NULL
;
661 if (priv
->tmptype
!= YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
662 /* We've already found it. We're just adding to the search path now. */
665 filename
= g_strdup_printf ("%s/index.page", helpdir
);
666 if (g_file_test (filename
, G_FILE_TEST_IS_REGULAR
)) {
667 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MALLARD
;
669 filename
= g_strdup (helpdir
);
675 filename
= g_strdup_printf ("%s/index.docbook", helpdir
);
676 if (g_file_test (filename
, G_FILE_TEST_IS_REGULAR
)) {
677 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_DOCBOOK
;
683 filename
= g_strdup_printf ("%s/%s.xml", helpdir
, pageid
);
684 if (g_file_test (filename
, G_FILE_TEST_IS_REGULAR
)) {
685 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_DOCBOOK
;
691 filename
= g_strdup_printf ("%s/%s.html", helpdir
, pageid
);
692 if (g_file_test (filename
, G_FILE_TEST_IS_REGULAR
)) {
693 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_HTML
;
698 filename
= g_strdup_printf ("%s/%s.xhtml", helpdir
, pageid
);
699 if (g_file_test (filename
, G_FILE_TEST_IS_REGULAR
)) {
700 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_XHTML
;
704 } /* end for langs */
705 } /* end for datadirs */
708 if (priv
->tmptype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
) {
709 g_strfreev (searchpath
);
710 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_NOT_FOUND
;
713 priv
->gfile
= g_file_new_for_path (filename
);
714 priv
->search_path
= searchpath
;
719 resolve_ghelp_uri (YelpUri
*uri
)
721 /* ghelp:/path/to/file
722 * ghelp:document[/file][?page][#frag]
724 YelpUriPrivate
*priv
= GET_PRIV (uri
);
725 gchar
*document
, *slash
, *query
, *hash
;
726 gchar
*colon
, *c
; /* do not free */
728 colon
= strchr (priv
->res_arg
, ':');
730 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_ERROR
;
734 slash
= query
= hash
= NULL
;
735 for (c
= colon
; *c
!= '\0'; c
++) {
736 if (*c
== '#' && hash
== NULL
)
738 else if (*c
== '?' && query
== NULL
&& hash
== NULL
)
740 else if (*c
== '/' && slash
== NULL
&& query
== NULL
&& hash
== NULL
)
744 if (slash
|| query
|| hash
)
745 document
= g_strndup (colon
+ 1,
746 (slash
? slash
: (query
? query
: hash
)) - colon
- 1);
748 document
= g_strdup (colon
+ 1);
750 if (slash
&& (query
|| hash
))
751 slash
= g_strndup (slash
+ 1,
752 (query
? query
: hash
) - slash
- 1);
754 slash
= g_strdup (slash
+ 1);
757 query
= g_strndup (query
+ 1,
760 query
= g_strdup (query
+ 1);
763 hash
= g_strdup (hash
+ 1);
765 if (*(colon
+ 1) == '/') {
767 newuri
= g_strdup_printf ("file:/%s", slash
);
768 g_free (priv
->res_arg
);
769 priv
->res_arg
= newuri
;
770 resolve_file_uri (uri
);
773 resolve_data_dirs (uri
, "gnome/help", document
, slash
? slash
: document
, FALSE
);
777 priv
->page_id
= query
;
778 priv
->frag_id
= hash
;
781 priv
->page_id
= query
;
782 if (priv
->tmptype
!= YELP_URI_DOCUMENT_TYPE_MALLARD
)
783 priv
->frag_id
= g_strdup (query
);
786 priv
->page_id
= hash
;
787 priv
->frag_id
= g_strdup (hash
);
790 if (priv
->frag_id
&& g_str_has_prefix (priv
->frag_id
, "search=")) {
791 g_free (priv
->frag_id
);
792 priv
->frag_id
= NULL
;
795 priv
->docuri
= g_strconcat ("ghelp:", document
,
799 priv
->fulluri
= g_strconcat (priv
->docuri
,
800 priv
->page_id
? "?" : "",
801 priv
->page_id
? priv
->page_id
: "",
802 priv
->frag_id
? "#" : "",
803 priv
->frag_id
? priv
->frag_id
: "",
812 resolve_help_uri (YelpUri
*uri
)
814 /* help:document[/page][?query][#frag]
816 YelpUriPrivate
*priv
= GET_PRIV (uri
);
817 gchar
*document
, *slash
, *query
, *hash
;
818 gchar
*colon
, *c
; /* do not free */
820 colon
= strchr (priv
->res_arg
, ':');
822 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_ERROR
;
826 slash
= query
= hash
= NULL
;
827 for (c
= colon
; *c
!= '\0'; c
++) {
828 if (*c
== '#' && hash
== NULL
)
830 else if (*c
== '?' && query
== NULL
&& hash
== NULL
)
832 else if (*c
== '/' && slash
== NULL
&& query
== NULL
&& hash
== NULL
)
836 if (slash
|| query
|| hash
)
837 document
= g_strndup (colon
+ 1,
838 (slash
? slash
: (query
? query
: hash
)) - colon
- 1);
840 document
= g_strdup (colon
+ 1);
842 if (slash
&& (query
|| hash
))
843 slash
= g_strndup (slash
+ 1,
844 (query
? query
: hash
) - slash
- 1);
846 slash
= g_strdup (slash
+ 1);
849 query
= g_strndup (query
+ 1,
852 query
= g_strdup (query
+ 1);
855 gchar
**keyvals
= g_strsplit (query
, "&", 0);
858 for (i
= 0; keyvals
[i
]; i
++) {
860 val
= strchr (keyvals
[i
], '=');
863 key
= g_uri_unescape_segment (keyvals
[i
], val
, NULL
);
864 val
= g_uri_unescape_string (val
+ 1, NULL
);
866 g_hash_table_insert (priv
->query
, key
, val
);
869 g_strfreev (keyvals
);
873 hash
= g_strdup (hash
+ 1);
875 priv
->page_id
= (slash
? slash
: g_strdup ("index"));
876 resolve_data_dirs (uri
, "help", document
, priv
->page_id
, TRUE
);
879 priv
->frag_id
= hash
;
880 if (priv
->frag_id
&& g_str_has_prefix (priv
->frag_id
, "search=")) {
881 g_free (priv
->frag_id
);
882 priv
->frag_id
= NULL
;
885 priv
->docuri
= g_strconcat ("help:", document
, NULL
);
887 priv
->fulluri
= g_strconcat (priv
->docuri
,
888 priv
->page_id
? "/" : "",
889 priv
->page_id
? priv
->page_id
: "",
892 priv
->frag_id
? "#" : "",
893 priv
->frag_id
? priv
->frag_id
: "",
902 resolve_help_list_uri (YelpUri
*uri
)
904 YelpUriPrivate
*priv
= GET_PRIV (uri
);
905 priv
->docuri
= g_strdup ("help-list:");
906 priv
->fulluri
= g_strdup (priv
->res_arg
);
907 priv
->page_id
= g_strdup ("index");
908 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_HELP_LIST
;
912 Resolve a manual file's path using 'man -w'. section may be NULL,
913 otherwise should be the section of the manual (ie should have dealt
914 with empty strings before calling this!) Returns NULL if the file
918 find_man_path (gchar
* name
, gchar
* section
)
920 gchar
* argv
[] = { "man", "-w", NULL
, NULL
, NULL
};
921 gchar
*ystdout
= NULL
;
924 GError
*error
= NULL
;
926 /* Syntax for man is "man -w <section> <name>", possibly omitting
935 if (!g_spawn_sync (NULL
, argv
, NULL
,
936 G_SPAWN_SEARCH_PATH
| G_SPAWN_STDERR_TO_DEV_NULL
,
938 &ystdout
, NULL
, &status
, &error
)) {
939 g_warning ("Couldn't find path for %s(%s). Error: %s",
940 name
, section
, error
->message
);
941 g_error_free (error
);
945 lines
= g_strsplit (ystdout
, "\n", 2);
947 ystdout
= g_strdup (lines
[0]);
958 resolve_man_uri (YelpUri
*uri
)
960 YelpUriPrivate
*priv
= GET_PRIV (uri
);
967 /* Search via regular expressions for name, name(section) and
968 * name.section (assuming that name doesn't contain forward
969 * slashes or other nasties)
971 * If these don't match, assume that we were given a filename
972 * (absolute iff it starts with a /).
974 static GRegex
* man_not_path
= NULL
;
975 GError
*error
= NULL
;
976 GMatchInfo
*match_info
= NULL
;
977 gchar
*name
, *section
, *hash
;
981 /* Match group 1 should contain the name; then one of groups 3
982 * and 4 will contain the section if there was one. Group 6
983 * will contain any hash fragment. */
984 man_not_path
= g_regex_new ("man:((?:[^ /.()#]|\\.(?=[^0-9]))+)"
985 "(\\(([0-9A-Za-z]+)\\)|\\.([0-9A-Za-z]+)|)"
989 g_error ("Error with regex in man uri: %s\n",
994 if (!g_regex_match (man_not_path
, priv
->res_arg
,
996 /* The regexp didn't match, so treat as a file name. */
998 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MAN
;
999 newuri
= g_strdup_printf ("file:%s", priv
->res_arg
+ 4);
1000 g_free (priv
->res_arg
);
1001 priv
->res_arg
= newuri
;
1002 resolve_file_uri (uri
);
1005 /* The regexp matched, so we've got a name/section pair that
1006 * needs resolving. */
1007 name
= g_match_info_fetch (match_info
, 1);
1008 section
= g_match_info_fetch (match_info
, 3);
1009 hash
= g_match_info_fetch (match_info
, 6);
1011 g_error ("Error matching strings in man uri '%s'",
1014 if ((!section
) || (section
[0] == '\0')) {
1015 section
= g_match_info_fetch (match_info
, 4);
1017 if (section
&& section
[0] == '\0') section
= NULL
;
1019 path
= find_man_path (name
, section
);
1022 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_NOT_FOUND
;
1025 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MAN
;
1026 priv
->gfile
= g_file_new_for_path (path
);
1027 priv
->docuri
= g_strdup ("man:");
1028 priv
->fulluri
= g_strconcat ("man:", name
, ".", section
, NULL
);
1029 priv
->page_id
= g_strconcat (name
, ".", section
, NULL
);
1030 resolve_gfile (uri
, NULL
);
1032 if (hash
&& hash
[0] != '\0')
1033 resolve_page_and_frag (uri
, hash
+ 1);
1036 g_match_info_free (match_info
);
1041 resolve_info_uri (YelpUri
*uri
)
1043 YelpUriPrivate
*priv
= GET_PRIV (uri
);
1044 /* info:/path/to/file
1050 static gchar
**infopath
= NULL
;
1053 gchar
*fullpath
= NULL
;
1056 gint infopath_i
, suffix_i
;
1058 if (g_str_has_prefix (priv
->res_arg
, "info:/")) {
1060 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_INFO
;
1061 newuri
= g_strdup_printf ("file:%s", priv
->res_arg
+ 5);
1062 g_free (priv
->res_arg
);
1063 priv
->res_arg
= newuri
;
1064 resolve_file_uri (uri
);
1069 /* Initialize infopath only once */
1071 /* Use the same logic as the info program. If INFOPATH is not
1072 specified, use the default. If it is specified, just use it
1073 unless it ends with a colon, in which case we add the
1074 default as a suffix.
1076 const gchar
*env
= g_getenv ("INFOPATH");
1078 if (!env
|| env
[0] == '\0')
1079 paths
= g_strdup (default_info_path
);
1080 else if (env
[strlen (env
)-1] == ':')
1081 paths
= g_strconcat (env
, default_info_path
, NULL
);
1083 paths
= g_strdup (env
);
1085 infopath
= g_strsplit (paths
, ":", 0);
1090 colon
= strchr (priv
->res_arg
, ':');
1094 colon
= (gchar
*) priv
->res_arg
;
1096 if (colon
[0] == '(') {
1097 const gchar
*rbrace
= strchr (colon
, ')');
1099 name
= g_strndup (colon
+ 1, rbrace
- colon
- 1);
1100 sect
= g_strdup (rbrace
+ 1);
1104 const gchar
*hash
= strchr (colon
, '#');
1106 name
= g_strndup (colon
, hash
- colon
);
1107 sect
= g_strdup (hash
+ 1);
1110 name
= g_strdup (colon
);
1115 for (infopath_i
= 0; infopath
[infopath_i
]; infopath_i
++) {
1116 if (!g_file_test (infopath
[infopath_i
], G_FILE_TEST_IS_DIR
))
1118 for (suffix_i
= 0; infosuffix
[suffix_i
]; suffix_i
++) {
1119 fullpath
= g_strconcat (infopath
[infopath_i
], "/",
1120 name
, infosuffix
[suffix_i
], NULL
);
1121 if (g_file_test (fullpath
, G_FILE_TEST_IS_REGULAR
))
1126 if (fullpath
!= NULL
)
1131 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_INFO
;
1132 priv
->gfile
= g_file_new_for_path (fullpath
);
1133 priv
->docuri
= g_strconcat ("info:", name
, NULL
);
1135 priv
->fulluri
= g_strconcat ("info:", name
, "#", sect
, NULL
);
1136 priv
->page_id
= g_strdup (sect
);
1137 priv
->frag_id
= sect
;
1138 sect
= NULL
; /* steal memory */
1141 priv
->fulluri
= g_strdup (priv
->docuri
);
1142 resolve_gfile (uri
, NULL
);
1144 gchar
*res_arg
= priv
->res_arg
;
1145 priv
->res_arg
= g_strconcat ("man:", name
, NULL
);
1146 resolve_man_uri (uri
);
1147 if (priv
->tmptype
== YELP_URI_DOCUMENT_TYPE_MAN
) {
1148 g_free (priv
->res_arg
);
1149 priv
->res_arg
= res_arg
;
1153 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_NOT_FOUND
;
1162 resolve_xref_uri (YelpUri
*uri
)
1164 YelpUriPrivate
*priv
= GET_PRIV (uri
);
1165 const gchar
*arg
= priv
->res_arg
+ 5;
1166 YelpUriPrivate
*base_priv
= GET_PRIV (priv
->res_base
);
1168 priv
->tmptype
= base_priv
->doctype
;
1169 priv
->gfile
= g_object_ref (base_priv
->gfile
);
1170 priv
->search_path
= g_strdupv (base_priv
->search_path
);
1171 priv
->docuri
= g_strdup (base_priv
->docuri
);
1173 if (arg
[0] == '#') {
1174 priv
->page_id
= g_strdup (base_priv
->page_id
);
1175 priv
->frag_id
= g_strdup (arg
+ 1);
1178 gchar
*hash
= strchr (arg
, '#');
1180 priv
->page_id
= g_strndup (arg
, hash
- arg
);
1181 priv
->frag_id
= g_strdup (hash
+ 1);
1184 priv
->page_id
= g_strdup (arg
);
1185 priv
->frag_id
= NULL
;
1188 if (priv
->page_id
&& priv
->page_id
[0] == '\0') {
1189 g_free (priv
->page_id
);
1190 priv
->page_id
= NULL
;
1193 if (priv
->page_id
&&
1194 g_str_has_prefix (priv
->docuri
, "info:")) {
1196 Special characters get url-encoded when they get clicked on
1197 as links. Info files, at least, don't want that so decode
1200 gchar
* tmp
= priv
->page_id
;
1201 priv
->page_id
= g_uri_unescape_string (tmp
, NULL
);
1205 if (g_str_has_prefix (priv
->docuri
, "ghelp:"))
1206 priv
->fulluri
= g_strconcat (priv
->docuri
,
1207 priv
->page_id
? "?" : "",
1208 priv
->page_id
? priv
->page_id
: "",
1209 priv
->frag_id
? "#" : "",
1210 priv
->frag_id
? priv
->frag_id
: "",
1212 else if (g_str_has_prefix (priv
->docuri
, "help:"))
1213 priv
->fulluri
= g_strconcat (priv
->docuri
,
1214 priv
->page_id
? "/" : "",
1215 priv
->page_id
? priv
->page_id
: "",
1216 priv
->frag_id
? "#" : "",
1217 priv
->frag_id
? priv
->frag_id
: "",
1219 else if (g_str_has_prefix (priv
->docuri
, "file:") ||
1220 g_str_has_prefix (priv
->docuri
, "info:") )
1221 priv
->fulluri
= g_strconcat (priv
->docuri
,
1222 (priv
->page_id
|| priv
->frag_id
) ? "#" : "",
1223 priv
->page_id
? priv
->page_id
: "",
1224 priv
->frag_id
? "#" : "",
1228 /* FIXME: other URI schemes */
1229 priv
->fulluri
= g_strconcat (priv
->docuri
,
1230 priv
->page_id
? "#" : "",
1236 resolve_page_and_frag (YelpUri
*uri
, const gchar
*arg
)
1238 YelpUriPrivate
*priv
= GET_PRIV (uri
);
1241 if (!arg
|| arg
[0] == '\0')
1244 hash
= strchr (arg
, '#');
1246 priv
->page_id
= g_strndup (arg
, hash
- arg
);
1247 priv
->frag_id
= g_strdup (hash
+ 1);
1249 priv
->page_id
= g_strdup (arg
);
1250 priv
->frag_id
= g_strdup (arg
);
1256 resolve_gfile (YelpUri
*uri
, const gchar
*hash
)
1258 YelpUriPrivate
*priv
= GET_PRIV (uri
);
1260 GError
*error
= NULL
;
1262 info
= g_file_query_info (priv
->gfile
,
1263 G_FILE_ATTRIBUTE_STANDARD_TYPE
","
1264 G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE
,
1265 G_FILE_QUERY_INFO_NONE
,
1268 if (g_error_matches (error
, G_IO_ERROR
, G_IO_ERROR_NOT_FOUND
)) {
1269 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_NOT_FOUND
;
1272 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_ERROR
;
1273 g_error_free (error
);
1277 if (priv
->search_path
== NULL
) {
1278 if (g_file_info_get_attribute_uint32 (info
, G_FILE_ATTRIBUTE_STANDARD_TYPE
) ==
1279 G_FILE_TYPE_DIRECTORY
) {
1280 priv
->search_path
= g_new0 (gchar
*, 2);
1281 priv
->search_path
[0] = g_file_get_path (priv
->gfile
);
1283 GFile
*parent
= g_file_get_parent (priv
->gfile
);
1284 priv
->search_path
= g_new0 (gchar
*, 2);
1285 priv
->search_path
[0] = g_file_get_path (parent
);
1286 g_object_unref (parent
);
1290 if (priv
->tmptype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
) {
1291 gchar
**splithash
= NULL
;
1293 splithash
= g_strsplit (hash
, "#", 2);
1294 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_EXTERNAL
;
1296 if (g_file_info_get_attribute_uint32 (info
, G_FILE_ATTRIBUTE_STANDARD_TYPE
) ==
1297 G_FILE_TYPE_DIRECTORY
) {
1298 GFile
*child
= g_file_get_child (priv
->gfile
, "index.page");
1299 if (g_file_query_exists (child
, NULL
)) {
1300 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MALLARD
;
1302 if (priv
->page_id
== NULL
)
1303 priv
->page_id
= g_strdup (splithash
[0]);
1304 if (priv
->frag_id
== NULL
&& splithash
[0])
1305 priv
->frag_id
= g_strdup (splithash
[1]);
1308 else if (yelp_settings_get_editor_mode (yelp_settings_get_default ())) {
1309 g_object_unref (child
);
1310 child
= g_file_get_child (priv
->gfile
, "index.page.stub");
1311 if (g_file_query_exists (child
, NULL
)) {
1312 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MALLARD
;
1314 if (priv
->page_id
== NULL
)
1315 priv
->page_id
= g_strdup (splithash
[0]);
1316 if (priv
->frag_id
== NULL
&& splithash
[0])
1317 priv
->frag_id
= g_strdup (splithash
[1]);
1321 g_object_unref (child
);
1325 const gchar
*mime_type
= g_file_info_get_attribute_string (info
,
1326 G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE
);
1327 basename
= g_file_get_basename (priv
->gfile
);
1328 if (g_str_has_suffix (basename
, ".page")) {
1330 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MALLARD
;
1332 priv
->gfile
= g_file_get_parent (old
);
1333 if (priv
->page_id
== NULL
) {
1334 /* File names aren't really page IDs, so we stick an illegal character
1335 on the beginning so it can't possibly be confused with a real page
1336 ID. Then we let YelpMallardDocument map file names to pages IDs.
1338 gchar
*tmp
= g_file_get_basename (old
);
1339 priv
->page_id
= g_strconcat (G_DIR_SEPARATOR_S
, tmp
, NULL
);
1342 if (priv
->frag_id
== NULL
)
1343 priv
->frag_id
= g_strdup (hash
);
1344 g_object_unref (old
);
1346 else if (g_str_equal (mime_type
, "text/xml") ||
1347 g_str_equal (mime_type
, "application/docbook+xml") ||
1348 g_str_equal (mime_type
, "application/xml")) {
1349 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_DOCBOOK
;
1351 if (priv
->page_id
== NULL
)
1352 priv
->page_id
= g_strdup (splithash
[0]);
1353 if (priv
->frag_id
== NULL
&& splithash
[0])
1354 priv
->frag_id
= g_strdup (splithash
[1]);
1357 else if (g_str_equal (mime_type
, "text/html") ||
1358 g_str_equal (mime_type
, "application/xhtml+xml")) {
1359 GFile
*parent
= g_file_get_parent (priv
->gfile
);
1360 priv
->docuri
= g_file_get_uri (parent
);
1361 g_object_unref (parent
);
1362 priv
->tmptype
= mime_type
[0] == 't' ? YELP_URI_DOCUMENT_TYPE_HTML
: YELP_URI_DOCUMENT_TYPE_XHTML
;
1363 if (priv
->page_id
== NULL
)
1364 priv
->page_id
= g_file_get_basename (priv
->gfile
);
1365 if (priv
->frag_id
== NULL
)
1366 priv
->frag_id
= g_strdup (hash
);
1367 if (priv
->fulluri
== NULL
) {
1369 fulluri
= g_file_get_uri (priv
->gfile
);
1370 priv
->fulluri
= g_strconcat (fulluri
,
1371 priv
->frag_id
? "#" : NULL
,
1377 else if (g_str_equal (mime_type
, "application/x-gzip")) {
1378 if (g_str_has_suffix (basename
, ".info.gz"))
1379 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_INFO
;
1380 else if (is_man_path (basename
, "gz"))
1381 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MAN
;
1383 else if (g_str_equal (mime_type
, "application/x-bzip")) {
1384 if (g_str_has_suffix (basename
, ".info.bz2"))
1385 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_INFO
;
1386 else if (is_man_path (basename
, "bz2"))
1387 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MAN
;
1389 else if (g_str_equal (mime_type
, "application/x-lzma")) {
1390 if (g_str_has_suffix (basename
, ".info.lzma"))
1391 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_INFO
;
1392 else if (is_man_path (basename
, "lzma"))
1393 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MAN
;
1395 else if (g_str_equal (mime_type
, "application/octet-stream")) {
1396 if (g_str_has_suffix (basename
, ".info"))
1397 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_INFO
;
1398 else if (is_man_path (basename
, NULL
))
1399 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MAN
;
1401 else if (g_str_equal (mime_type
, "text/plain")) {
1402 if (g_str_has_suffix (basename
, ".info"))
1403 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_INFO
;
1404 else if (is_man_path (basename
, NULL
))
1405 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MAN
;
1407 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_TEXT
;
1408 if (priv
->frag_id
== NULL
)
1409 priv
->frag_id
= g_strdup (hash
);
1411 else if (g_str_equal (mime_type
, "text/x-readme")) {
1412 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_TEXT
;
1415 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_EXTERNAL
;
1419 g_strfreev (splithash
);
1422 if (priv
->docuri
== NULL
)
1423 priv
->docuri
= g_file_get_uri (priv
->gfile
);
1425 if (priv
->fulluri
== NULL
)
1426 priv
->fulluri
= g_strconcat (priv
->docuri
,
1427 (priv
->page_id
|| priv
->frag_id
) ? "#" : NULL
,
1428 priv
->page_id
? priv
->page_id
: "",
1429 priv
->frag_id
? "#" : NULL
,
1430 priv
->frag_id
? priv
->frag_id
: NULL
,
1433 g_object_unref (info
);
1437 is_man_path (const gchar
*path
, const gchar
*encoding
)
1439 gchar
**iter
= (gchar
**) mancats
;
1441 if (encoding
&& *encoding
) {
1442 while (iter
&& *iter
) {
1443 gchar
*ending
= g_strdup_printf ("%s.%s", *iter
, encoding
);
1444 if (g_str_has_suffix (path
, ending
)) {
1452 while (iter
&& *iter
) {
1453 if (g_str_has_suffix (path
, *iter
)) {