1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-path-map.c: URI path prefix-matcher
4  *
5  * Copyright (C) 2007 Novell, Inc.
6  */
7 
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11 
12 #include <string.h>
13 
14 #include "soup-path-map.h"
15 
16 /* This could be replaced with something more clever, like a Patricia
17  * trie, but it's probably not worth it since the total number of
18  * mappings is likely to always be small. So we keep an array of
19  * paths, sorted by decreasing length. (The first prefix match will
20  * therefore be the longest.)
21  */
22 
23 typedef struct {
24 	char     *path;
25 	int       len;
26 	gpointer  data;
27 } SoupPathMapping;
28 
29 struct SoupPathMap {
30 	GArray *mappings;
31 	GDestroyNotify free_func;
32 };
33 
34 /**
35  * soup_path_map_new:
36  * @data_free_func: function to use to free data added with
37  * soup_path_map_add().
38  *
39  * Creates a new %SoupPathMap.
40  *
41  * Return value: the new %SoupPathMap
42  **/
43 SoupPathMap *
soup_path_map_new(GDestroyNotify data_free_func)44 soup_path_map_new (GDestroyNotify data_free_func)
45 {
46 	SoupPathMap *map;
47 
48 	map = g_slice_new0 (SoupPathMap);
49 	map->mappings = g_array_new (FALSE, FALSE, sizeof (SoupPathMapping));
50 	map->free_func = data_free_func;
51 
52 	return map;
53 }
54 
55 /**
56  * soup_path_map_free:
57  * @map: a %SoupPathMap
58  *
59  * Frees @map and all data stored in it.
60  **/
61 void
soup_path_map_free(SoupPathMap * map)62 soup_path_map_free (SoupPathMap *map)
63 {
64 	SoupPathMapping *mappings = (SoupPathMapping *)map->mappings->data;
65 	guint i;
66 
67 	for (i = 0; i < map->mappings->len; i++) {
68 		g_free (mappings[i].path);
69 		if (map->free_func)
70 			map->free_func (mappings[i].data);
71 	}
72 	g_array_free (map->mappings, TRUE);
73 
74 	g_slice_free (SoupPathMap, map);
75 }
76 
77 /* Scan @map looking for @path or one of its ancestors.
78  * Sets *@match to the index of a match, or -1 if no match is found.
79  * Sets *@insert to the index to insert @path at if a new mapping is
80  * desired. Returns %TRUE if *@match is an exact match.
81  */
82 static gboolean
mapping_lookup(SoupPathMap * map,const char * path,int * match,int * insert)83 mapping_lookup (SoupPathMap *map, const char *path, int *match, int *insert)
84 {
85 	SoupPathMapping *mappings = (SoupPathMapping *)map->mappings->data;
86 	guint i;
87 	int path_len;
88 	gboolean exact = FALSE;
89 
90 	*match = -1;
91 
92 	path_len = strcspn (path, "?");
93 	for (i = 0; i < map->mappings->len; i++) {
94 		if (mappings[i].len > path_len)
95 			continue;
96 
97 		if (insert && mappings[i].len < path_len) {
98 			*insert = i;
99 			/* Clear insert so we don't try to set it again */
100 			insert = NULL;
101 		}
102 
103 		if (!strncmp (mappings[i].path, path, mappings[i].len)) {
104 			*match = i;
105 			if (path_len == mappings[i].len)
106 				exact = TRUE;
107 			if (!insert)
108 				return exact;
109 		}
110 	}
111 
112 	if (insert)
113 		*insert = i;
114 	return exact;
115 }
116 
117 /**
118  * soup_path_map_add:
119  * @map: a %SoupPathMap
120  * @path: the path
121  * @data: the data
122  *
123  * Adds @data to @map at @path. If there was already data at @path it
124  * will be freed.
125  **/
126 void
soup_path_map_add(SoupPathMap * map,const char * path,gpointer data)127 soup_path_map_add (SoupPathMap *map, const char *path, gpointer data)
128 {
129 	SoupPathMapping *mappings = (SoupPathMapping *)map->mappings->data;
130 	int match, insert;
131 
132 	if (mapping_lookup (map, path, &match, &insert)) {
133 		if (map->free_func)
134 			map->free_func (mappings[match].data);
135 		mappings[match].data = data;
136 	} else {
137 		SoupPathMapping mapping;
138 
139 		mapping.path = g_strdup (path);
140 		mapping.len = strlen (path);
141 		mapping.data = data;
142 		g_array_insert_val (map->mappings, insert, mapping);
143 	}
144 }
145 
146 /**
147  * soup_path_map_remove:
148  * @map: a %SoupPathMap
149  * @path: the path
150  *
151  * Removes @data from @map at @path. (This must be called with the same
152  * path the data was originally added with, not a subdirectory.)
153  **/
154 void
soup_path_map_remove(SoupPathMap * map,const char * path)155 soup_path_map_remove (SoupPathMap *map, const char *path)
156 {
157 	SoupPathMapping *mappings = (SoupPathMapping *)map->mappings->data;
158 	int match;
159 
160 	if (!mapping_lookup (map, path, &match, NULL))
161 		return;
162 
163 	if (map->free_func)
164 		map->free_func (mappings[match].data);
165 	g_free (mappings[match].path);
166 	g_array_remove_index (map->mappings, match);
167 }
168 
169 /**
170  * soup_path_map_lookup:
171  * @map: a %SoupPathMap
172  * @path: the path
173  *
174  * Finds the data associated with @path in @map. If there is no data
175  * specifically associated with @path, it will return the data for the
176  * closest parent directory of @path that has data associated with it.
177  *
178  * Return value: (nullable): the data set with soup_path_map_add(), or
179  * %NULL if no data could be found for @path or any of its ancestors.
180  **/
181 gpointer
soup_path_map_lookup(SoupPathMap * map,const char * path)182 soup_path_map_lookup (SoupPathMap *map, const char *path)
183 {
184 	SoupPathMapping *mappings = (SoupPathMapping *)map->mappings->data;
185 	int match;
186 
187 	mapping_lookup (map, path, &match, NULL);
188 	if (match == -1)
189 		return NULL;
190 	else
191 		return mappings[match].data;
192 }
193