2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 * See the COPYING file for license information.
18 * Guillaume Chazarain <guichaz@yahoo.fr>
21 /*******************************************
22 * UTF-8, filenames and mnemonics handling *
23 *******************************************/
25 #include <string.h> /* strstr(), strchr(), memcpy(), strcpy() */
26 #include <stdlib.h> /* getenv() */
29 #include "str_utils.h"
31 /* If you can't stand string manipulations in C, just don't look there */
33 gint
common_prefix_length(const gchar
* str1
, const gchar
* str2
)
35 const gchar
*orig_str1
= str1
;
37 if (NOT_ALIGNED(str1
) == NOT_ALIGNED(str2
)) {
38 /* We can align both pointers. */
39 while (NOT_ALIGNED(str1
) && *str1
== *str2
&& *str1
!= '\0') {
44 if (*str1
!= '\0' && *str1
== *str2
) {
45 /* Both pointers are now aligned. */
46 gulong
*long_ptr1
= (gulong
*) str1
, *long_ptr2
= (gulong
*) str2
;
48 /* Skip identical blocks in the paths. */
49 while (!HAS_ZERO(*long_ptr1
) && *long_ptr1
== *long_ptr2
) {
54 str1
= (const gchar
*) long_ptr1
;
55 str2
= (const gchar
*) long_ptr2
;
59 /* Skip identical characters in the paths. */
60 while (*str1
!= '\0' && *str1
== *str2
) {
65 return str1
- orig_str1
;
68 #define CHECK_BYTE(ptr) \
79 static gboolean
str_is_ascii(const gchar
* str
)
81 gulong
*long_ptr
, long_int
;
90 if (!NOT_ALIGNED(str
))
97 long_ptr
= (gulong
*) str
;
100 long_int
= *long_ptr
;
101 if (HAS_ZERO(long_int
)) {
102 /* A '\0' has been detected. */
103 const gchar
*char_ptr
= (const gchar
*) long_ptr
;
104 CHECK_BYTE(char_ptr
);
105 CHECK_BYTE(char_ptr
);
106 CHECK_BYTE(char_ptr
);
107 CHECK_BYTE(char_ptr
);
108 if (sizeof(gulong
) > 4) {
110 CHECK_BYTE(char_ptr
);
111 CHECK_BYTE(char_ptr
);
112 CHECK_BYTE(char_ptr
);
113 CHECK_BYTE(char_ptr
);
115 } else if (long_int
& LONG_MASK(0x80808080L
, 0x8080808080808080L
))
124 static gboolean
have_broken_filenames(void)
126 static gboolean initialized
= FALSE
;
127 static gboolean broken
;
132 broken
= getenv("G_BROKEN_FILENAMES") != NULL
;
138 /* The returned string should not be freed. */
139 const gchar
*filename_to_utf8(const gchar
* str
)
141 static GStaticPrivate result_key
= G_STATIC_PRIVATE_INIT
;
145 if (!have_broken_filenames() || g_get_charset(NULL
) || str_is_ascii(str
))
148 result
= g_static_private_get(&result_key
);
149 if (result
== NULL
) {
150 /* First time in this thread. */
151 result
= g_new(gchar
*, 1);
152 g_static_private_set(&result_key
, result
, g_free
);
158 *result
= g_filename_to_utf8(str
, -1, NULL
, NULL
, &err
);
160 g_printerr("%s\n", err
->message
);
171 static gboolean
starts_dotslash(const gchar
* str
)
173 return str
[0] == '.' && str
[1] == '/';
176 static gboolean
is_clean(const gchar
* filename
)
178 if (filename
[0] != '/' && !starts_dotslash(filename
))
181 return strstr(filename
, "//") == NULL
&& strstr(filename
, "/./") == NULL
;
184 static gint
count_errors(gchar
* str
, gint
* len
)
187 gchar
*prev_slash
= str
, *next_slash
= NULL
;
190 * "path1/./path2" is replaced with "path1///path2".
191 * "./" and "//" are counted.
193 while ((next_slash
= strchr(prev_slash
+ !!next_slash
, '/')) != NULL
) {
194 *len
+= next_slash
- prev_slash
;
196 switch (next_slash
[1]) {
198 if (next_slash
[2] == '/') {
208 prev_slash
= next_slash
;
211 *len
+= strlen(prev_slash
);
215 static gchar
*remove_double_slash(gchar
* str
, gint new_len
)
217 gchar
*new, *new_ptr
;
218 gchar
*prev_slash
= str
, *next_slash
= NULL
;
220 if (str
[0] == '/' || starts_dotslash(str
)) {
221 new = g_new(gchar
, new_len
);
224 new = g_new(gchar
, new_len
+ 2);
230 while ((next_slash
= strchr(prev_slash
+ !!next_slash
, '/')) != NULL
) {
231 memcpy(new_ptr
, prev_slash
, next_slash
- prev_slash
);
232 new_ptr
+= next_slash
- prev_slash
;
234 while (next_slash
[1] == '/')
237 prev_slash
= next_slash
;
240 strcpy(new_ptr
, prev_slash
);
245 gchar
*clean_filename(const gchar
* filename
)
247 gchar
*orig_copy
, *copy
, *new;
248 gint count
= 0, len
= 0;
249 gboolean absolute
, finished
= FALSE
;
251 if (is_clean(filename
))
252 return g_strdup(filename
);
254 /* We work on a copy as we may modify it. */
255 orig_copy
= copy
= g_strdup(filename
);
257 absolute
= (copy
[0] == '/');
258 while (finished
== FALSE
) {
277 /* We keep the last leading '/' for absolute filenames. */
279 else if (copy
- orig_copy
>= 2) {
280 /* We add a './' for relative filenames. */
286 /* Count the '//' and '/./'. */
287 count
= count_errors(copy
, &len
);
290 /* The filename was clean. */
293 if (orig_copy
!= copy
)
294 /* The filename started with "//". */
295 g_memmove(orig_copy
, copy
, len
+ 1);
300 if (starts_dotslash(copy
)) {
301 if (orig_copy
!= copy
)
302 g_memmove(orig_copy
, copy
, len
+ 1);
305 /* The relative filename just lacked the "./" in the beginning. */
306 new = g_strconcat("./", copy
, NULL
);
313 /* We now have to remove the "//". */
314 new = remove_double_slash(copy
, len
- count
+ 1);