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
;
95 static guint uri_signals
[LAST_SIGNAL
] = {0,};
97 /******************************************************************************/
99 static const gchar
*mancats
[] = {
101 "1", "1p", "1g", "1t", "1x", "1ssl", "1m",
103 "3", "3o", "3t", "3p", "3blt", "3nas", "3form", "3menu", "3tiff", "3ssl", "3readline",
104 "3ncurses", "3curses", "3f", "3pm", "3perl", "3qt", "3x", "3X11",
106 "5", "5snmp", "5x", "5ssl",
108 "7", "7gcc", "7x", "7ssl",
109 "8", "8l", "9", "0p",
113 static const gchar
*infosuffix
[] = {
115 ".info.gz", ".info.bz2", ".info.lzma",
116 ".gz", ".bz2", ".lzma",
120 static const gchar default_info_path
[] =
121 "/usr/info:/usr/share/info:/usr/local/info:/usr/local/share/info";
123 /******************************************************************************/
126 yelp_uri_class_init (YelpUriClass
*klass
)
128 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
130 object_class
->dispose
= yelp_uri_dispose
;
131 object_class
->finalize
= yelp_uri_finalize
;
133 uri_signals
[RESOLVED
] =
134 g_signal_new ("resolved",
135 G_OBJECT_CLASS_TYPE (klass
),
138 g_cclosure_marshal_VOID__VOID
,
141 g_type_class_add_private (klass
, sizeof (YelpUriPrivate
));
145 yelp_uri_init (YelpUri
*uri
)
151 yelp_uri_dispose (GObject
*object
)
153 YelpUriPrivate
*priv
= GET_PRIV (object
);
156 g_object_unref (priv
->gfile
);
160 if (priv
->res_base
) {
161 g_object_unref (priv
->res_base
);
162 priv
->res_base
= NULL
;
165 G_OBJECT_CLASS (yelp_uri_parent_class
)->dispose (object
);
169 yelp_uri_finalize (GObject
*object
)
171 YelpUriPrivate
*priv
= GET_PRIV (object
);
173 g_free (priv
->docuri
);
174 g_free (priv
->fulluri
);
175 g_strfreev (priv
->search_path
);
176 g_free (priv
->page_id
);
177 g_free (priv
->frag_id
);
178 g_free (priv
->res_arg
);
180 G_OBJECT_CLASS (yelp_uri_parent_class
)->finalize (object
);
183 /******************************************************************************/
186 yelp_uri_new (const gchar
*arg
)
188 return yelp_uri_new_relative (NULL
, arg
);
192 yelp_uri_new_relative (YelpUri
*base
, const gchar
*arg
)
195 YelpUriPrivate
*priv
;
197 uri
= (YelpUri
*) g_object_new (YELP_TYPE_URI
, NULL
);
199 priv
= GET_PRIV (uri
);
200 priv
->doctype
= YELP_URI_DOCUMENT_TYPE_UNRESOLVED
;
202 priv
->res_base
= g_object_ref (base
);
203 priv
->res_arg
= g_strdup (arg
);
209 yelp_uri_new_search (YelpUri
*base
,
213 YelpUriPrivate
*priv
;
216 uri
= (YelpUri
*) g_object_new (YELP_TYPE_URI
, NULL
);
218 priv
= GET_PRIV (uri
);
219 priv
->doctype
= YELP_URI_DOCUMENT_TYPE_UNRESOLVED
;
221 priv
->res_base
= g_object_ref (base
);
222 tmp
= g_uri_escape_string (text
, NULL
, FALSE
);
223 priv
->res_arg
= g_strconcat("xref:search=", tmp
, NULL
);
229 /******************************************************************************/
232 yelp_uri_resolve (YelpUri
*uri
)
234 YelpUriPrivate
*priv
= GET_PRIV (uri
);
236 if (priv
->res_base
&& !yelp_uri_is_resolved (priv
->res_base
)) {
237 g_signal_connect_swapped (priv
->res_base
, "resolved",
238 G_CALLBACK (resolve_start
),
240 yelp_uri_resolve (priv
->res_base
);
248 yelp_uri_resolve_sync (YelpUri
*uri
)
250 YelpUriPrivate
*priv
= GET_PRIV (uri
);
252 if (priv
->doctype
!= YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
256 yelp_uri_resolve_sync (priv
->res_base
);
263 /* We want code to be able to do something like this:
265 * if (yelp_uri_get_document_type (uri) != YELP_URI_DOCUMENT_TYPE_UNRESOLVED) {
266 * g_signal_connect (uri, "resolve", callback, data);
267 * yelp_uri_resolve (uri);
270 * Resolving happens in a separate thread, though, so if that thread can change
271 * the document type, we have a race condition. So here's the rules we play by:
273 * 1) None of the getters except the document type getter can return real data
274 * while the URI is unresolved. They all do a resolved check first, and
275 * return NULL if the URI is not resolved.
277 * 2) The threaded resolver functions can modify anything but the document
278 * type. They are the only things that are allowed to modify that data.
280 * 3) The resolver thread is not allowed to modify the document type. When
281 * it's done, it queues an async function to set the document type and
282 * emit "resolved" in the main thread.
284 * 4) Once a URI is resolved, it is immutable.
287 resolve_start (YelpUri
*uri
)
289 YelpUriPrivate
*priv
= GET_PRIV (uri
);
291 if (priv
->resolver
== NULL
) {
293 priv
->resolver
= g_thread_create ((GThreadFunc
) resolve_async
,
299 resolve_sync (YelpUri
*uri
)
302 YelpUriPrivate
*priv
= GET_PRIV (uri
);
304 if (g_str_has_prefix (priv
->res_arg
, "ghelp:")
305 || g_str_has_prefix (priv
->res_arg
, "gnome-help:")) {
306 resolve_ghelp_uri (uri
);
308 else if (g_str_has_prefix (priv
->res_arg
, "help:")) {
309 resolve_help_uri (uri
);
311 else if (g_str_has_prefix (priv
->res_arg
, "help-list:")) {
312 resolve_help_list_uri (uri
);
314 else if (g_str_has_prefix (priv
->res_arg
, "file:")) {
315 resolve_file_uri (uri
);
317 else if (g_str_has_prefix (priv
->res_arg
, "man:")) {
318 resolve_man_uri (uri
);
320 else if (g_str_has_prefix (priv
->res_arg
, "info:")) {
321 resolve_info_uri (uri
);
323 else if (g_str_has_prefix (priv
->res_arg
, "xref:")) {
324 YelpUriPrivate
*base_priv
;
325 if (priv
->res_base
== NULL
) {
326 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_ERROR
;
329 base_priv
= GET_PRIV (priv
->res_base
);
330 switch (base_priv
->doctype
) {
331 case YELP_URI_DOCUMENT_TYPE_UNRESOLVED
:
333 case YELP_URI_DOCUMENT_TYPE_DOCBOOK
:
334 case YELP_URI_DOCUMENT_TYPE_MALLARD
:
335 case YELP_URI_DOCUMENT_TYPE_INFO
:
336 resolve_xref_uri (uri
);
338 case YELP_URI_DOCUMENT_TYPE_MAN
: {
339 gchar
*tmp
= g_strconcat ("man:", priv
->res_arg
+ 5, NULL
);
340 g_free (priv
->res_arg
);
342 resolve_man_uri (uri
);
345 case YELP_URI_DOCUMENT_TYPE_TEXT
:
346 case YELP_URI_DOCUMENT_TYPE_HTML
:
347 case YELP_URI_DOCUMENT_TYPE_XHTML
:
348 resolve_file_path (uri
);
350 case YELP_URI_DOCUMENT_TYPE_HELP_LIST
:
351 /* FIXME: what do we do? */
353 case YELP_URI_DOCUMENT_TYPE_NOT_FOUND
:
354 case YELP_URI_DOCUMENT_TYPE_EXTERNAL
:
355 case YELP_URI_DOCUMENT_TYPE_ERROR
:
359 else if (strchr (priv
->res_arg
, ':')) {
360 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_EXTERNAL
;
363 resolve_file_path (uri
);
366 /* We _always_ want to have a non-null fulluri, so check for it
367 * having been set here and, if we can't think of something
368 * better, set it to res_arg. */
369 if (!priv
->fulluri
) {
370 priv
->fulluri
= g_strdup (priv
->res_arg
);
375 resolve_async (YelpUri
*uri
)
378 g_idle_add ((GSourceFunc
) resolve_final
, uri
);
382 resolve_final (YelpUri
*uri
)
384 YelpUriPrivate
*priv
= GET_PRIV (uri
);
386 priv
->resolver
= NULL
;
388 if (priv
->tmptype
!= YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
389 priv
->doctype
= priv
->tmptype
;
391 priv
->doctype
= YELP_URI_DOCUMENT_TYPE_ERROR
;
393 if (priv
->res_base
) {
394 g_object_unref (priv
->res_base
);
395 priv
->res_base
= NULL
;
399 g_free (priv
->res_arg
);
400 priv
->res_arg
= NULL
;
403 g_signal_emit (uri
, uri_signals
[RESOLVED
], 0);
404 g_object_unref (uri
);
408 /******************************************************************************/
411 yelp_uri_is_resolved (YelpUri
*uri
)
413 YelpUriPrivate
*priv
= GET_PRIV (uri
);
414 return priv
->doctype
!= YELP_URI_DOCUMENT_TYPE_UNRESOLVED
;
418 yelp_uri_get_document_type (YelpUri
*uri
)
420 YelpUriPrivate
*priv
= GET_PRIV (uri
);
421 return priv
->doctype
;
425 yelp_uri_get_document_uri (YelpUri
*uri
)
427 YelpUriPrivate
*priv
= GET_PRIV (uri
);
428 if (priv
->doctype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
431 /* There's some client code where it makes sense to want a
432 * document uri, whether or not it conforms to a scheme we really
433 * understand. For example, we might want to look up whether the
434 * given page is currently being visited. */
435 if ((!priv
->docuri
) && priv
->fulluri
) {
436 return g_strdup (priv
->fulluri
);
439 return g_strdup (priv
->docuri
);
443 yelp_uri_get_canonical_uri (YelpUri
*uri
)
445 YelpUriPrivate
*priv
= GET_PRIV (uri
);
446 if (priv
->doctype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
448 return g_strdup (priv
->fulluri
);
452 yelp_uri_get_file (YelpUri
*uri
)
454 YelpUriPrivate
*priv
= GET_PRIV (uri
);
455 if (priv
->doctype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
457 return priv
->gfile
? g_object_ref (priv
->gfile
) : NULL
;
461 yelp_uri_get_search_path (YelpUri
*uri
)
463 YelpUriPrivate
*priv
= GET_PRIV (uri
);
464 if (priv
->doctype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
466 return g_strdupv (priv
->search_path
);
470 yelp_uri_get_page_id (YelpUri
*uri
)
472 YelpUriPrivate
*priv
= GET_PRIV (uri
);
473 if (priv
->doctype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
475 return g_strdup (priv
->page_id
);
479 yelp_uri_get_frag_id (YelpUri
*uri
)
481 YelpUriPrivate
*priv
= GET_PRIV (uri
);
482 if (priv
->doctype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
484 return g_strdup (priv
->frag_id
);
487 /******************************************************************************/
490 yelp_uri_locate_file_uri (YelpUri
*uri
,
491 const gchar
*filename
)
493 YelpUriPrivate
*priv
= GET_PRIV (uri
);
496 gchar
*returi
= NULL
;
498 for (i
= 0; priv
->search_path
[i
] != NULL
; i
++) {
499 fullpath
= g_strconcat (priv
->search_path
[i
],
503 if (g_file_test (fullpath
, G_FILE_TEST_EXISTS
)) {
504 gfile
= g_file_new_for_path (fullpath
);
505 returi
= g_file_get_uri (gfile
);
506 g_object_unref (gfile
);
515 /******************************************************************************/
518 resolve_file_uri (YelpUri
*uri
)
520 YelpUriPrivate
*priv
= GET_PRIV (uri
);
522 const gchar
*hash
= strchr (priv
->res_arg
, '#');
525 uristr
= g_strndup (priv
->res_arg
, hash
- priv
->res_arg
);
529 uristr
= priv
->res_arg
;
531 priv
->gfile
= g_file_new_for_uri (uristr
);
533 resolve_gfile (uri
, hash
);
537 resolve_file_path (YelpUri
*uri
)
539 YelpUriPrivate
*base_priv
= NULL
;
540 YelpUriPrivate
*priv
= GET_PRIV (uri
);
542 const gchar
*hash
= strchr (priv
->res_arg
, '#');
544 /* Treat xref: URIs like relative file paths */
545 if (g_str_has_prefix (priv
->res_arg
, "xref:")) {
546 gchar
*tmp
= g_strdup (priv
->res_arg
+ 5);
547 g_free (priv
->res_arg
);
552 base_priv
= GET_PRIV (priv
->res_base
);
555 path
= g_strndup (priv
->res_arg
, hash
- priv
->res_arg
);
559 path
= priv
->res_arg
;
561 if (priv
->res_arg
[0] == '/') {
562 priv
->gfile
= g_file_new_for_path (path
);
564 else if (base_priv
&& base_priv
->gfile
) {
566 info
= g_file_query_info (base_priv
->gfile
,
567 G_FILE_ATTRIBUTE_STANDARD_TYPE
,
568 G_FILE_QUERY_INFO_NONE
,
570 if (g_file_info_get_file_type (info
) == G_FILE_TYPE_REGULAR
) {
571 GFile
*parent
= g_file_get_parent (base_priv
->gfile
);
572 priv
->gfile
= g_file_resolve_relative_path (parent
, path
);
573 g_object_unref (parent
);
576 priv
->gfile
= g_file_resolve_relative_path (base_priv
->gfile
, path
);
582 cur
= g_get_current_dir ();
583 curfile
= g_file_new_for_path (cur
);
584 priv
->gfile
= g_file_resolve_relative_path (curfile
, path
);
585 g_object_unref (curfile
);
589 resolve_gfile (uri
, hash
);
593 resolve_data_dirs (YelpUri
*ret
,
599 const gchar
* const *sdatadirs
= g_get_system_data_dirs ();
600 const gchar
* const *langs
= g_get_language_names ();
601 /* The strings are still owned by GLib; we just own the array. */
603 YelpUriPrivate
*priv
= GET_PRIV (ret
);
604 gchar
*filename
= NULL
;
605 gchar
**searchpath
= NULL
;
606 gint searchi
, searchmax
;
607 gint datadir_i
, lang_i
;
609 datadirs
= g_new0 (gchar
*, g_strv_length ((gchar
**) sdatadirs
) + 2);
610 datadirs
[0] = (gchar
*) g_get_user_data_dir ();
611 for (datadir_i
= 0; sdatadirs
[datadir_i
]; datadir_i
++)
612 datadirs
[datadir_i
+ 1] = (gchar
*) sdatadirs
[datadir_i
];
616 searchpath
= g_new0 (gchar
*, 10);
618 for (datadir_i
= 0; datadirs
[datadir_i
]; datadir_i
++) {
619 for (lang_i
= 0; langs
[lang_i
]; lang_i
++) {
620 gchar
*helpdir
= g_build_filename (datadirs
[datadir_i
],
622 langfirst
? langs
[lang_i
] : docid
,
623 langfirst
? docid
: langs
[lang_i
],
625 if (!g_file_test (helpdir
, G_FILE_TEST_IS_DIR
)) {
630 if (searchi
+ 1 >= searchmax
) {
632 searchpath
= g_renew (gchar
*, searchpath
, searchmax
);
634 searchpath
[searchi
] = helpdir
;
635 searchpath
[++searchi
] = NULL
;
637 if (priv
->tmptype
!= YELP_URI_DOCUMENT_TYPE_UNRESOLVED
)
638 /* We've already found it. We're just adding to the search path now. */
641 filename
= g_strdup_printf ("%s/index.page", helpdir
);
642 if (g_file_test (filename
, G_FILE_TEST_IS_REGULAR
)) {
643 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MALLARD
;
645 filename
= g_strdup (helpdir
);
651 filename
= g_strdup_printf ("%s/index.docbook", helpdir
);
652 if (g_file_test (filename
, G_FILE_TEST_IS_REGULAR
)) {
653 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_DOCBOOK
;
659 filename
= g_strdup_printf ("%s/%s.xml", helpdir
, pageid
);
660 if (g_file_test (filename
, G_FILE_TEST_IS_REGULAR
)) {
661 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_DOCBOOK
;
667 filename
= g_strdup_printf ("%s/%s.html", helpdir
, pageid
);
668 if (g_file_test (filename
, G_FILE_TEST_IS_REGULAR
)) {
669 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_HTML
;
674 filename
= g_strdup_printf ("%s/%s.xhtml", helpdir
, pageid
);
675 if (g_file_test (filename
, G_FILE_TEST_IS_REGULAR
)) {
676 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_XHTML
;
680 } /* end for langs */
681 } /* end for datadirs */
684 if (priv
->tmptype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
) {
685 g_strfreev (searchpath
);
686 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_NOT_FOUND
;
689 priv
->gfile
= g_file_new_for_path (filename
);
690 priv
->search_path
= searchpath
;
695 resolve_ghelp_uri (YelpUri
*uri
)
697 /* ghelp:/path/to/file
698 * ghelp:document[/file][?page][#frag]
700 YelpUriPrivate
*priv
= GET_PRIV (uri
);
701 gchar
*document
, *slash
, *query
, *hash
;
702 gchar
*colon
, *c
; /* do not free */
704 colon
= strchr (priv
->res_arg
, ':');
706 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_ERROR
;
710 slash
= query
= hash
= NULL
;
711 for (c
= colon
; *c
!= '\0'; c
++) {
712 if (*c
== '#' && hash
== NULL
)
714 else if (*c
== '?' && query
== NULL
&& hash
== NULL
)
716 else if (*c
== '/' && slash
== NULL
&& query
== NULL
&& hash
== NULL
)
720 if (slash
|| query
|| hash
)
721 document
= g_strndup (colon
+ 1,
722 (slash
? slash
: (query
? query
: hash
)) - colon
- 1);
724 document
= g_strdup (colon
+ 1);
726 if (slash
&& (query
|| hash
))
727 slash
= g_strndup (slash
+ 1,
728 (query
? query
: hash
) - slash
- 1);
730 slash
= g_strdup (slash
+ 1);
733 query
= g_strndup (query
+ 1,
736 query
= g_strdup (query
+ 1);
739 hash
= g_strdup (hash
+ 1);
741 if (*(colon
+ 1) == '/') {
743 newuri
= g_strdup_printf ("file:/%s", slash
);
744 g_free (priv
->res_arg
);
745 priv
->res_arg
= newuri
;
746 resolve_file_uri (uri
);
749 resolve_data_dirs (uri
, "gnome/help", document
, slash
? slash
: document
, FALSE
);
753 priv
->page_id
= query
;
754 priv
->frag_id
= hash
;
757 priv
->page_id
= query
;
758 if (priv
->tmptype
!= YELP_URI_DOCUMENT_TYPE_MALLARD
)
759 priv
->frag_id
= g_strdup (query
);
762 priv
->page_id
= hash
;
763 priv
->frag_id
= g_strdup (hash
);
766 if (priv
->frag_id
&& g_str_has_prefix (priv
->frag_id
, "search=")) {
767 g_free (priv
->frag_id
);
768 priv
->frag_id
= NULL
;
771 priv
->docuri
= g_strconcat ("ghelp:", document
,
775 priv
->fulluri
= g_strconcat (priv
->docuri
,
776 priv
->page_id
? "?" : "",
777 priv
->page_id
? priv
->page_id
: "",
778 priv
->frag_id
? "#" : "",
779 priv
->frag_id
? priv
->frag_id
: "",
788 resolve_help_uri (YelpUri
*uri
)
790 /* help:document[/page][?query][#frag]
792 YelpUriPrivate
*priv
= GET_PRIV (uri
);
793 gchar
*document
, *slash
, *query
, *hash
;
794 gchar
*colon
, *c
; /* do not free */
796 colon
= strchr (priv
->res_arg
, ':');
798 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_ERROR
;
802 slash
= query
= hash
= NULL
;
803 for (c
= colon
; *c
!= '\0'; c
++) {
804 if (*c
== '#' && hash
== NULL
)
806 else if (*c
== '?' && query
== NULL
&& hash
== NULL
)
808 else if (*c
== '/' && slash
== NULL
&& query
== NULL
&& hash
== NULL
)
812 if (slash
|| query
|| hash
)
813 document
= g_strndup (colon
+ 1,
814 (slash
? slash
: (query
? query
: hash
)) - colon
- 1);
816 document
= g_strdup (colon
+ 1);
818 if (slash
&& (query
|| hash
))
819 slash
= g_strndup (slash
+ 1,
820 (query
? query
: hash
) - slash
- 1);
822 slash
= g_strdup (slash
+ 1);
825 query
= g_strndup (query
+ 1,
828 query
= g_strdup (query
+ 1);
831 hash
= g_strdup (hash
+ 1);
833 priv
->page_id
= (slash
? slash
: g_strdup ("index"));
834 resolve_data_dirs (uri
, "help", document
, priv
->page_id
, TRUE
);
837 priv
->frag_id
= hash
;
838 if (priv
->frag_id
&& g_str_has_prefix (priv
->frag_id
, "search=")) {
839 g_free (priv
->frag_id
);
840 priv
->frag_id
= NULL
;
843 priv
->docuri
= g_strconcat ("help:", document
, NULL
);
845 priv
->fulluri
= g_strconcat (priv
->docuri
,
846 priv
->page_id
? "/" : "",
847 priv
->page_id
? priv
->page_id
: "",
850 priv
->frag_id
? "#" : "",
851 priv
->frag_id
? priv
->frag_id
: "",
860 resolve_help_list_uri (YelpUri
*uri
)
862 YelpUriPrivate
*priv
= GET_PRIV (uri
);
863 priv
->docuri
= g_strdup ("help-list:");
864 priv
->fulluri
= g_strdup (priv
->res_arg
);
865 priv
->page_id
= g_strdup ("index");
866 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_HELP_LIST
;
870 Resolve a manual file's path using 'man -w'. section may be NULL,
871 otherwise should be the section of the manual (ie should have dealt
872 with empty strings before calling this!) Returns NULL if the file
876 find_man_path (gchar
* name
, gchar
* section
)
878 gchar
* argv
[] = { "man", "-w", NULL
, NULL
, NULL
};
879 gchar
*stdout
= NULL
;
882 GError
*error
= NULL
;
884 /* Syntax for man is "man -w <section> <name>", possibly omitting
893 if (!g_spawn_sync (NULL
, argv
, NULL
,
894 G_SPAWN_SEARCH_PATH
| G_SPAWN_STDERR_TO_DEV_NULL
,
896 &stdout
, NULL
, &status
, &error
)) {
897 g_warning ("Couldn't find path for %s(%s). Error: %s",
898 name
, section
, error
->message
);
899 g_error_free (error
);
903 lines
= g_strsplit (stdout
, "\n", 2);
905 stdout
= g_strdup (lines
[0]);
916 resolve_man_uri (YelpUri
*uri
)
918 YelpUriPrivate
*priv
= GET_PRIV (uri
);
925 /* Search via regular expressions for name, name(section) and
926 * name.section (assuming that name doesn't contain forward
927 * slashes or other nasties)
929 * If these don't match, assume that we were given a filename
930 * (absolute iff it starts with a /).
932 static GRegex
* man_not_path
= NULL
;
933 GError
*error
= NULL
;
934 GMatchInfo
*match_info
= NULL
;
935 gchar
*name
, *section
, *hash
;
939 /* Match group 1 should contain the name; then one of groups 3
940 * and 4 will contain the section if there was one. Group 6
941 * will contain any hash fragment. */
942 man_not_path
= g_regex_new ("man:([^ /.()#]+)"
943 "(\\(([0-9A-Za-z]+)\\)|\\.([0-9A-Za-z]+)|)"
947 g_error ("Error with regex in man uri: %s\n",
952 if (!g_regex_match (man_not_path
, priv
->res_arg
,
954 /* The regexp didn't match, so treat as a file name. */
956 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MAN
;
957 newuri
= g_strdup_printf ("file:%s", priv
->res_arg
+ 4);
958 g_free (priv
->res_arg
);
959 priv
->res_arg
= newuri
;
960 resolve_file_uri (uri
);
963 /* The regexp matched, so we've got a name/section pair that
964 * needs resolving. */
965 name
= g_match_info_fetch (match_info
, 1);
966 section
= g_match_info_fetch (match_info
, 3);
967 hash
= g_match_info_fetch (match_info
, 6);
969 g_error ("Error matching strings in man uri '%s'",
972 if ((!section
) || (section
[0] == '\0')) {
973 section
= g_match_info_fetch (match_info
, 4);
975 if (section
&& section
[0] == '\0') section
= NULL
;
977 path
= find_man_path (name
, section
);
980 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_NOT_FOUND
;
983 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MAN
;
984 priv
->gfile
= g_file_new_for_path (path
);
985 priv
->docuri
= g_strdup ("man:");
986 priv
->fulluri
= g_strconcat ("man:", name
, ".", section
, NULL
);
987 priv
->page_id
= g_strconcat (name
, ".", section
, NULL
);
988 resolve_gfile (uri
, NULL
);
990 if (hash
&& hash
[0] != '\0')
991 resolve_page_and_frag (uri
, hash
+ 1);
994 g_match_info_free (match_info
);
999 Return 1 if ch is a number from 0 to 9 or a letter a-f or A-F and 0
1000 otherwise. This is sort of not utf8-safe, but since we are only
1001 looking for 7-bit things, it doesn't matter.
1006 if (((48 <= ch
) && (ch
<= 57)) ||
1007 ((65 <= ch
) && (ch
<= 70)) ||
1008 ((97 <= ch
) && (ch
<= 102)))
1014 Return a newly allocated string, where %ab for a,b in [0, f] is
1015 replaced by the character it represents.
1018 decode_url (const gchar
*url
)
1020 if (!url
) return NULL
;
1022 unsigned int len
= strlen (url
);
1024 gchar
*ret
= g_new (gchar
, len
+ 1);
1025 const gchar
*ptr
= url
, *end
= url
+ len
;
1026 gchar
*retptr
= ret
, *tmp
;
1029 if (*ptr
== '%' && is_hex(*(ptr
+ 1)) && is_hex(*(ptr
+ 2))) {
1031 *(retptr
+1) = *(ptr
+2);
1034 sscanf (retptr
, "%x", &hex
);
1036 if (hex
< 0 || hex
> 127) {
1037 g_warning ("Skipping non-7-bit character.");
1041 *retptr
= (gchar
)hex
;
1047 tmp
= g_utf8_next_char(ptr
);
1048 memcpy (retptr
, ptr
, (tmp
-ptr
));
1059 resolve_info_uri (YelpUri
*uri
)
1061 YelpUriPrivate
*priv
= GET_PRIV (uri
);
1062 /* info:/path/to/file
1068 static gchar
**infopath
= NULL
;
1071 gchar
*fullpath
= NULL
;
1074 gint infopath_i
, suffix_i
;
1076 if (g_str_has_prefix (priv
->res_arg
, "info:/")) {
1078 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_INFO
;
1079 newuri
= g_strdup_printf ("file:%s", priv
->res_arg
+ 4);
1080 g_free (priv
->res_arg
);
1081 priv
->res_arg
= newuri
;
1082 resolve_file_uri (uri
);
1087 /* Initialize infopath only once */
1089 /* Use the same logic as the info program. If INFOPATH is not
1090 specified, use the default. If it is specified, just use it
1091 unless it ends with a colon, in which case we add the
1092 default as a suffix.
1094 const gchar
*env
= g_getenv ("INFOPATH");
1096 if (!env
|| env
[0] == '\0')
1097 paths
= g_strdup (default_info_path
);
1098 else if (env
[strlen (env
)-1] == ':')
1099 paths
= g_strconcat (env
, default_info_path
, NULL
);
1101 paths
= g_strdup (env
);
1103 infopath
= g_strsplit (paths
, ":", 0);
1108 colon
= strchr (priv
->res_arg
, ':');
1112 colon
= (gchar
*) priv
->res_arg
;
1114 if (colon
[0] == '(') {
1115 const gchar
*rbrace
= strchr (colon
, ')');
1117 name
= g_strndup (colon
+ 1, rbrace
- colon
- 1);
1118 sect
= g_strdup (rbrace
+ 1);
1122 const gchar
*hash
= strchr (colon
, '#');
1124 name
= g_strndup (colon
, hash
- colon
);
1125 sect
= g_strdup (hash
+ 1);
1128 name
= g_strdup (colon
);
1133 for (infopath_i
= 0; infopath
[infopath_i
]; infopath_i
++) {
1134 if (!g_file_test (infopath
[infopath_i
], G_FILE_TEST_IS_DIR
))
1136 for (suffix_i
= 0; infosuffix
[suffix_i
]; suffix_i
++) {
1137 fullpath
= g_strconcat (infopath
[infopath_i
], "/",
1138 name
, infosuffix
[suffix_i
], NULL
);
1139 if (g_file_test (fullpath
, G_FILE_TEST_IS_REGULAR
))
1144 if (fullpath
!= NULL
)
1149 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_INFO
;
1150 priv
->gfile
= g_file_new_for_path (fullpath
);
1151 priv
->docuri
= g_strconcat ("info:", name
, NULL
);
1153 priv
->fulluri
= g_strconcat ("info:", name
, "#", sect
, NULL
);
1154 priv
->page_id
= g_strdup (sect
);
1155 priv
->frag_id
= sect
;
1156 sect
= NULL
; /* steal memory */
1159 priv
->fulluri
= g_strdup (priv
->docuri
);
1160 resolve_gfile (uri
, NULL
);
1162 gchar
*res_arg
= priv
->res_arg
;
1163 priv
->res_arg
= g_strconcat ("man:", name
, NULL
);
1164 resolve_man_uri (uri
);
1165 if (priv
->tmptype
== YELP_URI_DOCUMENT_TYPE_MAN
) {
1166 g_free (priv
->res_arg
);
1167 priv
->res_arg
= res_arg
;
1171 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_NOT_FOUND
;
1180 resolve_xref_uri (YelpUri
*uri
)
1182 YelpUriPrivate
*priv
= GET_PRIV (uri
);
1183 const gchar
*arg
= priv
->res_arg
+ 5;
1184 YelpUriPrivate
*base_priv
= GET_PRIV (priv
->res_base
);
1186 priv
->tmptype
= base_priv
->doctype
;
1187 priv
->gfile
= g_object_ref (base_priv
->gfile
);
1188 priv
->search_path
= g_strdupv (base_priv
->search_path
);
1189 priv
->docuri
= g_strdup (base_priv
->docuri
);
1191 if (arg
[0] == '#') {
1192 priv
->page_id
= g_strdup (base_priv
->page_id
);
1193 priv
->frag_id
= g_strdup (arg
+ 1);
1196 gchar
*hash
= strchr (arg
, '#');
1198 priv
->page_id
= g_strndup (arg
, hash
- arg
);
1199 priv
->frag_id
= g_strdup (hash
+ 1);
1202 priv
->page_id
= g_strdup (arg
);
1203 priv
->frag_id
= NULL
;
1206 if (priv
->page_id
&& priv
->page_id
[0] == '\0') {
1207 g_free (priv
->page_id
);
1208 priv
->page_id
= NULL
;
1211 if (priv
->page_id
&&
1212 g_str_has_prefix (priv
->docuri
, "info:")) {
1214 Special characters get url-encoded when they get clicked on
1215 as links. Info files, at least, don't want that so decode
1218 gchar
* tmp
= priv
->page_id
;
1219 priv
->page_id
= decode_url (priv
->page_id
);
1223 if (g_str_has_prefix (priv
->docuri
, "ghelp:"))
1224 priv
->fulluri
= g_strconcat (priv
->docuri
,
1225 priv
->page_id
? "?" : "",
1226 priv
->page_id
? priv
->page_id
: "",
1227 priv
->frag_id
? "#" : "",
1228 priv
->frag_id
? priv
->frag_id
: "",
1230 else if (g_str_has_prefix (priv
->docuri
, "file:") ||
1231 g_str_has_prefix (priv
->docuri
, "info:") )
1232 priv
->fulluri
= g_strconcat (priv
->docuri
,
1233 (priv
->page_id
|| priv
->frag_id
) ? "#" : "",
1234 priv
->page_id
? priv
->page_id
: "",
1235 priv
->frag_id
? "#" : "",
1239 /* FIXME: other URI schemes */
1240 priv
->fulluri
= g_strconcat (priv
->docuri
,
1241 priv
->page_id
? "#" : "",
1247 resolve_page_and_frag (YelpUri
*uri
, const gchar
*arg
)
1249 YelpUriPrivate
*priv
= GET_PRIV (uri
);
1252 if (!arg
|| arg
[0] == '\0')
1255 hash
= strchr (arg
, '#');
1257 priv
->page_id
= g_strndup (arg
, hash
- arg
);
1258 priv
->frag_id
= g_strdup (hash
+ 1);
1260 priv
->page_id
= g_strdup (arg
);
1261 priv
->frag_id
= g_strdup (arg
);
1267 resolve_gfile (YelpUri
*uri
, const gchar
*hash
)
1269 YelpUriPrivate
*priv
= GET_PRIV (uri
);
1271 GError
*error
= NULL
;
1273 info
= g_file_query_info (priv
->gfile
,
1274 G_FILE_ATTRIBUTE_STANDARD_TYPE
","
1275 G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE
,
1276 G_FILE_QUERY_INFO_NONE
,
1279 if (g_error_matches (error
, G_IO_ERROR
, G_IO_ERROR_NOT_FOUND
)) {
1280 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_NOT_FOUND
;
1283 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_ERROR
;
1284 g_error_free (error
);
1288 if (priv
->search_path
== NULL
) {
1289 if (g_file_info_get_attribute_uint32 (info
, G_FILE_ATTRIBUTE_STANDARD_TYPE
) ==
1290 G_FILE_TYPE_DIRECTORY
) {
1291 priv
->search_path
= g_new0 (gchar
*, 2);
1292 priv
->search_path
[0] = g_file_get_path (priv
->gfile
);
1294 GFile
*parent
= g_file_get_parent (priv
->gfile
);
1295 priv
->search_path
= g_new0 (gchar
*, 2);
1296 priv
->search_path
[0] = g_file_get_path (parent
);
1297 g_object_unref (parent
);
1301 if (priv
->tmptype
== YELP_URI_DOCUMENT_TYPE_UNRESOLVED
) {
1302 gchar
**splithash
= NULL
;
1304 splithash
= g_strsplit (hash
, "#", 2);
1305 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_EXTERNAL
;
1307 if (g_file_info_get_attribute_uint32 (info
, G_FILE_ATTRIBUTE_STANDARD_TYPE
) ==
1308 G_FILE_TYPE_DIRECTORY
) {
1309 GFile
*child
= g_file_get_child (priv
->gfile
, "index.page");
1310 if (g_file_query_exists (child
, NULL
)) {
1311 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MALLARD
;
1313 if (priv
->page_id
== NULL
)
1314 priv
->page_id
= g_strdup (splithash
[0]);
1315 if (priv
->frag_id
== NULL
&& splithash
[0])
1316 priv
->frag_id
= g_strdup (splithash
[1]);
1319 else if (yelp_settings_get_editor_mode (yelp_settings_get_default ())) {
1320 g_object_unref (child
);
1321 child
= g_file_get_child (priv
->gfile
, "index.page.stub");
1322 if (g_file_query_exists (child
, NULL
)) {
1323 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MALLARD
;
1325 if (priv
->page_id
== NULL
)
1326 priv
->page_id
= g_strdup (splithash
[0]);
1327 if (priv
->frag_id
== NULL
&& splithash
[0])
1328 priv
->frag_id
= g_strdup (splithash
[1]);
1332 g_object_unref (child
);
1336 const gchar
*mime_type
= g_file_info_get_attribute_string (info
,
1337 G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE
);
1338 basename
= g_file_get_basename (priv
->gfile
);
1339 if (g_str_has_suffix (basename
, ".page")) {
1341 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MALLARD
;
1343 priv
->gfile
= g_file_get_parent (old
);
1344 if (priv
->page_id
== NULL
) {
1345 /* File names aren't really page IDs, so we stick an illegal character
1346 on the beginning so it can't possibly be confused with a real page
1347 ID. Then we let YelpMallardDocument map file names to pages IDs.
1349 gchar
*tmp
= g_file_get_basename (old
);
1350 priv
->page_id
= g_strconcat (G_DIR_SEPARATOR_S
, tmp
, NULL
);
1353 if (priv
->frag_id
== NULL
)
1354 priv
->frag_id
= g_strdup (hash
);
1355 g_object_unref (old
);
1357 else if (g_str_equal (mime_type
, "text/xml") ||
1358 g_str_equal (mime_type
, "application/docbook+xml") ||
1359 g_str_equal (mime_type
, "application/xml")) {
1360 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_DOCBOOK
;
1362 if (priv
->page_id
== NULL
)
1363 priv
->page_id
= g_strdup (splithash
[0]);
1364 if (priv
->frag_id
== NULL
&& splithash
[0])
1365 priv
->frag_id
= g_strdup (splithash
[1]);
1368 else if (g_str_equal (mime_type
, "text/html") ||
1369 g_str_equal (mime_type
, "application/xhtml+xml")) {
1370 GFile
*parent
= g_file_get_parent (priv
->gfile
);
1371 priv
->docuri
= g_file_get_uri (parent
);
1372 g_object_unref (parent
);
1373 priv
->tmptype
= mime_type
[0] == 't' ? YELP_URI_DOCUMENT_TYPE_HTML
: YELP_URI_DOCUMENT_TYPE_XHTML
;
1374 if (priv
->page_id
== NULL
)
1375 priv
->page_id
= g_file_get_basename (priv
->gfile
);
1376 if (priv
->frag_id
== NULL
)
1377 priv
->frag_id
= g_strdup (hash
);
1378 if (priv
->fulluri
== NULL
) {
1380 fulluri
= g_file_get_uri (priv
->gfile
);
1381 priv
->fulluri
= g_strconcat (fulluri
,
1382 priv
->frag_id
? "#" : NULL
,
1388 else if (g_str_equal (mime_type
, "application/x-gzip")) {
1389 if (g_str_has_suffix (basename
, ".info.gz"))
1390 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_INFO
;
1391 else if (is_man_path (basename
, "gz"))
1392 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MAN
;
1394 else if (g_str_equal (mime_type
, "application/x-bzip")) {
1395 if (g_str_has_suffix (basename
, ".info.bz2"))
1396 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_INFO
;
1397 else if (is_man_path (basename
, "bz2"))
1398 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MAN
;
1400 else if (g_str_equal (mime_type
, "application/x-lzma")) {
1401 if (g_str_has_suffix (basename
, ".info.lzma"))
1402 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_INFO
;
1403 else if (is_man_path (basename
, "lzma"))
1404 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MAN
;
1406 else if (g_str_equal (mime_type
, "application/octet-stream")) {
1407 if (g_str_has_suffix (basename
, ".info"))
1408 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_INFO
;
1409 else if (is_man_path (basename
, NULL
))
1410 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MAN
;
1412 else if (g_str_equal (mime_type
, "text/plain")) {
1413 if (g_str_has_suffix (basename
, ".info"))
1414 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_INFO
;
1415 else if (is_man_path (basename
, NULL
))
1416 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_MAN
;
1418 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_TEXT
;
1419 if (priv
->frag_id
== NULL
)
1420 priv
->frag_id
= g_strdup (hash
);
1422 else if (g_str_equal (mime_type
, "text/x-readme")) {
1423 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_TEXT
;
1426 priv
->tmptype
= YELP_URI_DOCUMENT_TYPE_EXTERNAL
;
1430 g_strfreev (splithash
);
1433 if (priv
->docuri
== NULL
)
1434 priv
->docuri
= g_file_get_uri (priv
->gfile
);
1436 if (priv
->fulluri
== NULL
)
1437 priv
->fulluri
= g_strconcat (priv
->docuri
,
1438 (priv
->page_id
|| priv
->frag_id
) ? "#" : NULL
,
1439 priv
->page_id
? priv
->page_id
: "",
1440 priv
->frag_id
? "#" : NULL
,
1441 priv
->frag_id
? priv
->frag_id
: NULL
,
1444 g_object_unref (info
);
1448 is_man_path (const gchar
*path
, const gchar
*encoding
)
1450 gchar
**iter
= (gchar
**) mancats
;
1452 if (encoding
&& *encoding
) {
1453 while (iter
&& *iter
) {
1454 gchar
*ending
= g_strdup_printf ("%s.%s", *iter
, encoding
);
1455 if (g_str_has_suffix (path
, ending
)) {
1463 while (iter
&& *iter
) {
1464 if (g_str_has_suffix (path
, *iter
)) {