]> git.proxmox.com Git - grub2.git/blob - gfxmenu/gui_string_util.c
merge mainline into rescue-efi
[grub2.git] / gfxmenu / gui_string_util.c
1 /* gui_string_util.c - String utilities used by the GUI system. */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2008,2009 Free Software Foundation, Inc.
5 *
6 * GRUB is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <grub/gui_string_util.h>
21 #include <grub/types.h>
22 #include <grub/misc.h>
23 #include <grub/mm.h>
24
25 /* Create a new NUL-terminated string on the heap as a substring of BUF.
26 The range of buf included is the half-open interval [START,END).
27 The index START is inclusive, END is exclusive. */
28 char *
29 grub_new_substring (const char *buf,
30 grub_size_t start, grub_size_t end)
31 {
32 if (end < start)
33 return 0;
34 grub_size_t len = end - start;
35 char *s = grub_malloc (len + 1);
36 if (! s)
37 return 0;
38 grub_memcpy (s, buf + start, len);
39 s[len] = '\0';
40 return s;
41 }
42
43 /* Eliminate "." and ".." path elements from PATH. A new heap-allocated
44 string is returned. */
45 static char *
46 canonicalize_path (const char *path)
47 {
48 int i;
49 const char *p;
50 char *newpath = 0;
51
52 /* Count the path components in path. */
53 int components = 1;
54 for (p = path; *p; p++)
55 if (*p == '/')
56 components++;
57
58 char **path_array = grub_malloc (components * sizeof (*path_array));
59 if (! path_array)
60 return 0;
61
62 /* Initialize array elements to NULL pointers; in case once of the
63 allocations fails, the cleanup code can just call grub_free() for all
64 pointers in the array. */
65 for (i = 0; i < components; i++)
66 path_array[i] = 0;
67
68 /* Parse the path into path_array. */
69 p = path;
70 for (i = 0; i < components && p; i++)
71 {
72 /* Find the end of the path element. */
73 const char *end = grub_strchr (p, '/');
74 if (!end)
75 end = p + grub_strlen (p);
76
77 /* Copy the element. */
78 path_array[i] = grub_new_substring (p, 0, end - p);
79 if (! path_array[i])
80 goto cleanup;
81
82 /* Advance p to point to the start of the next element, or NULL. */
83 if (*end)
84 p = end + 1;
85 else
86 p = 0;
87 }
88
89 /* Eliminate '.' and '..' elements from the path array. */
90 int newpath_length = 0;
91 for (i = components - 1; i >= 0; --i)
92 {
93 if (! grub_strcmp (path_array[i], "."))
94 {
95 grub_free (path_array[i]);
96 path_array[i] = 0;
97 }
98 else if (! grub_strcmp (path_array[i], "..")
99 && i > 0)
100 {
101 /* Delete the '..' and the prior path element. */
102 grub_free (path_array[i]);
103 path_array[i] = 0;
104 --i;
105 grub_free (path_array[i]);
106 path_array[i] = 0;
107 }
108 else
109 {
110 newpath_length += grub_strlen (path_array[i]) + 1;
111 }
112 }
113
114 /* Construct a new path string. */
115 newpath = grub_malloc (newpath_length + 1);
116 if (! newpath)
117 goto cleanup;
118
119 newpath[0] = '\0';
120 char *newpath_end = newpath;
121 int first = 1;
122 for (i = 0; i < components; i++)
123 {
124 char *element = path_array[i];
125 if (element)
126 {
127 /* For all components but the first, prefix with a slash. */
128 if (! first)
129 newpath_end = grub_stpcpy (newpath_end, "/");
130 newpath_end = grub_stpcpy (newpath_end, element);
131 first = 0;
132 }
133 }
134
135 cleanup:
136 for (i = 0; i < components; i++)
137 grub_free (path_array[i]);
138 grub_free (path_array);
139
140 return newpath;
141 }
142
143 /* Return a new heap-allocated string representing to absolute path
144 to the file referred to by PATH. If PATH is an absolute path, then
145 the returned path is a copy of PATH. If PATH is a relative path, then
146 BASE is with PATH used to construct the absolute path. */
147 char *
148 grub_resolve_relative_path (const char *base, const char *path)
149 {
150 char *abspath;
151 char *canonpath;
152 char *p;
153 grub_size_t l;
154
155 /* If PATH is an absolute path, then just use it as is. */
156 if (path[0] == '/' || path[0] == '(')
157 return canonicalize_path (path);
158
159 abspath = grub_malloc (grub_strlen (base) + grub_strlen (path) + 3);
160 if (! abspath)
161 return 0;
162
163 /* Concatenate BASE and PATH. */
164 p = grub_stpcpy (abspath, base);
165 l = grub_strlen (abspath);
166 if (l == 0 || abspath[l-1] != '/')
167 {
168 *p = '/';
169 p++;
170 *p = 0;
171 }
172 grub_stpcpy (p, path);
173
174 canonpath = canonicalize_path (abspath);
175 if (! canonpath)
176 return abspath;
177
178 grub_free (abspath);
179 return canonpath;
180 }
181
182 /* Get the path of the directory where the file at FILE_PATH is located.
183 FILE_PATH should refer to a file, not a directory. The returned path
184 includes a trailing slash.
185 This does not handle GRUB "(hd0,0)" paths properly yet since it only
186 looks at slashes. */
187 char *
188 grub_get_dirname (const char *file_path)
189 {
190 int i;
191 int last_slash;
192
193 last_slash = -1;
194 for (i = grub_strlen (file_path) - 1; i >= 0; --i)
195 {
196 if (file_path[i] == '/')
197 {
198 last_slash = i;
199 break;
200 }
201 }
202 if (last_slash == -1)
203 return grub_strdup ("/");
204
205 return grub_new_substring (file_path, 0, last_slash + 1);
206 }
207
208 static __inline int
209 my_isxdigit (char c)
210 {
211 return ((c >= '0' && c <= '9')
212 || (c >= 'a' && c <= 'f')
213 || (c >= 'A' && c <= 'F'));
214 }
215
216 static int
217 parse_hex_color_component (const char *s, unsigned start, unsigned end)
218 {
219 unsigned len;
220 char buf[3];
221
222 len = end - start;
223 /* Check the limits so we don't overrun the buffer. */
224 if (len < 1 || len > 2)
225 return 0;
226
227 if (len == 1)
228 {
229 buf[0] = s[start]; /* Get the first and only hex digit. */
230 buf[1] = buf[0]; /* Duplicate the hex digit. */
231 }
232 else if (len == 2)
233 {
234 buf[0] = s[start];
235 buf[1] = s[start + 1];
236 }
237
238 buf[2] = '\0';
239
240 return grub_strtoul (buf, 0, 16);
241 }
242
243 /* Parse a color string of the form "r, g, b", "#RGB", "#RGBA",
244 "#RRGGBB", or "#RRGGBBAA". */
245 grub_err_t
246 grub_gui_parse_color (const char *s, grub_gui_color_t *color)
247 {
248 grub_gui_color_t c;
249
250 /* Skip whitespace. */
251 while (*s && grub_isspace (*s))
252 s++;
253
254 if (*s == '#')
255 {
256 /* HTML-style. Number if hex digits:
257 [6] #RRGGBB [3] #RGB
258 [8] #RRGGBBAA [4] #RGBA */
259
260 s++; /* Skip the '#'. */
261 /* Count the hexits to determine the format. */
262 int hexits = 0;
263 const char *end = s;
264 while (my_isxdigit (*end))
265 {
266 end++;
267 hexits++;
268 }
269
270 /* Parse the color components based on the format. */
271 if (hexits == 3 || hexits == 4)
272 {
273 c.red = parse_hex_color_component (s, 0, 1);
274 c.green = parse_hex_color_component (s, 1, 2);
275 c.blue = parse_hex_color_component (s, 2, 3);
276 if (hexits == 4)
277 c.alpha = parse_hex_color_component (s, 3, 4);
278 else
279 c.alpha = 255;
280 }
281 else if (hexits == 6 || hexits == 8)
282 {
283 c.red = parse_hex_color_component (s, 0, 2);
284 c.green = parse_hex_color_component (s, 2, 4);
285 c.blue = parse_hex_color_component (s, 4, 6);
286 if (hexits == 8)
287 c.alpha = parse_hex_color_component (s, 6, 8);
288 else
289 c.alpha = 255;
290 }
291 else
292 return grub_error (GRUB_ERR_BAD_ARGUMENT,
293 "invalid HTML-type color string `%s'", s);
294 }
295 else if (grub_isdigit (*s))
296 {
297 /* Comma separated decimal values. */
298 c.red = grub_strtoul (s, 0, 0);
299 if ((s = grub_strchr (s, ',')) == 0)
300 return grub_error (GRUB_ERR_BAD_ARGUMENT,
301 "missing 1st comma separator in color `%s'", s);
302 s++;
303 c.green = grub_strtoul (s, 0, 0);
304 if ((s = grub_strchr (s, ',')) == 0)
305 return grub_error (GRUB_ERR_BAD_ARGUMENT,
306 "missing 2nd comma separator in color `%s'", s);
307 s++;
308 c.blue = grub_strtoul (s, 0, 0);
309 if ((s = grub_strchr (s, ',')) == 0)
310 c.alpha = 255;
311 else
312 {
313 s++;
314 c.alpha = grub_strtoul (s, 0, 0);
315 }
316 }
317 else
318 {
319 if (! grub_gui_get_named_color (s, &c))
320 return grub_error (GRUB_ERR_BAD_ARGUMENT,
321 "invalid named color `%s'", s);
322 }
323
324 if (grub_errno == GRUB_ERR_NONE)
325 *color = c;
326 return grub_errno;
327 }