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