1 /*         ______   ___    ___
2  *        /\  _  \ /\_ \  /\_ \
3  *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
4  *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
5  *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
6  *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
7  *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
8  *                                           /\____/
9  *                                           \_/__/
10  *
11  *      Filesystem path routines.
12  *
13  *      By Thomas Fjellstrom.
14  *
15  *      See LICENSE.txt for copyright information.
16  */
17 
18 /* Title: Filesystem path routines
19  */
20 
21 #include <stdio.h>
22 #include "allegro5/allegro.h"
23 #include "allegro5/internal/aintern.h"
24 #include "allegro5/internal/aintern_vector.h"
25 #include "allegro5/internal/aintern_path.h"
26 
27 
28 /* get_segment:
29  *  Return the i'th directory component of a path.
30  */
get_segment(const ALLEGRO_PATH * path,unsigned i)31 static ALLEGRO_USTR *get_segment(const ALLEGRO_PATH *path, unsigned i)
32 {
33    ALLEGRO_USTR **seg = _al_vector_ref(&path->segments, i);
34    return *seg;
35 }
36 
37 
38 /* get_segment_cstr:
39  *  Return the i'th directory component of a path as a C string.
40  */
get_segment_cstr(const ALLEGRO_PATH * path,unsigned i)41 static const char *get_segment_cstr(const ALLEGRO_PATH *path, unsigned i)
42 {
43    return al_cstr(get_segment(path, i));
44 }
45 
46 
47 /* replace_backslashes:
48  *  Replace backslashes by slashes.
49  */
replace_backslashes(ALLEGRO_USTR * path)50 static void replace_backslashes(ALLEGRO_USTR *path)
51 {
52    al_ustr_find_replace_cstr(path, 0, "\\", "/");
53 }
54 
55 
56 /* parse_path_string:
57  *
58  * Parse a path string according to the following grammar.  The last
59  * component, if it is not followed by a directory separator, is interpreted
60  * as the filename component, unless it is "." or "..".
61  *
62  * GRAMMAR
63  *
64  * path     ::=   "//" c+ "/" nonlast        [Windows only]
65  *            |   c ":" nonlast              [Windows only]
66  *            |   nonlast
67  *
68  * nonlast  ::=   c* "/" nonlast
69  *            |   last
70  *
71  * last     ::=   "."
72  *            |   ".."
73  *            |   filename
74  *
75  * filename ::=   c*                         [but not "." and ".."]
76  *
77  * c        ::=   any character but '/'
78  */
parse_path_string(const ALLEGRO_USTR * str,ALLEGRO_PATH * path)79 static bool parse_path_string(const ALLEGRO_USTR *str, ALLEGRO_PATH *path)
80 {
81    ALLEGRO_USTR_INFO    dot_info;
82    ALLEGRO_USTR_INFO    dotdot_info;
83    const ALLEGRO_USTR *  dot = al_ref_cstr(&dot_info, ".");
84    const ALLEGRO_USTR *  dotdot = al_ref_cstr(&dotdot_info, "..");
85 
86    ALLEGRO_USTR *piece = al_ustr_new("");
87    int pos = 0;
88    bool on_windows;
89 
90    /* We compile the drive handling code on non-Windows platforms to prevent
91     * it becoming broken.
92     */
93 #ifdef ALLEGRO_WINDOWS
94    on_windows = true;
95 #else
96    on_windows = false;
97 #endif
98    if (on_windows) {
99       /* UNC \\server\share name */
100       if (al_ustr_has_prefix_cstr(str, "//")) {
101          int slash = al_ustr_find_chr(str, 2, '/');
102          if (slash == -1 || slash == 2) {
103             /* Missing slash or server component is empty. */
104             goto Error;
105          }
106          al_ustr_assign_substr(path->drive, str, pos, slash);
107          // Note: The slash will be parsed again, so we end up with
108          // "//server/share" and not "//servershare"!
109          pos = slash;
110       }
111       else {
112          /* Drive letter. */
113          int colon = al_ustr_offset(str, 1);
114          if (colon > -1 && al_ustr_get(str, colon) == ':') {
115             /* Include the colon in the drive string. */
116             al_ustr_assign_substr(path->drive, str, 0, colon + 1);
117             pos = colon + 1;
118          }
119       }
120    }
121 
122    for (;;) {
123       int slash = al_ustr_find_chr(str, pos, '/');
124 
125       if (slash == -1) {
126          /* Last component. */
127          al_ustr_assign_substr(piece, str, pos, al_ustr_size(str));
128          if (al_ustr_equal(piece, dot) || al_ustr_equal(piece, dotdot)) {
129             al_append_path_component(path, al_cstr(piece));
130          }
131          else {
132             /* This might be an empty string, but that's okay. */
133             al_ustr_assign(path->filename, piece);
134          }
135          break;
136       }
137 
138       /* Non-last component. */
139       al_ustr_assign_substr(piece, str, pos, slash);
140       al_append_path_component(path, al_cstr(piece));
141       pos = slash + 1;
142    }
143 
144    al_ustr_free(piece);
145    return true;
146 
147 Error:
148 
149    al_ustr_free(piece);
150    return false;
151 }
152 
153 
154 /* Function: al_create_path
155  */
al_create_path(const char * str)156 ALLEGRO_PATH *al_create_path(const char *str)
157 {
158    ALLEGRO_PATH *path;
159 
160    path = al_malloc(sizeof(ALLEGRO_PATH));
161    if (!path)
162       return NULL;
163 
164    path->drive = al_ustr_new("");
165    path->filename = al_ustr_new("");
166    _al_vector_init(&path->segments, sizeof(ALLEGRO_USTR *));
167    path->basename = al_ustr_new("");
168    path->full_string = al_ustr_new("");
169 
170    if (str != NULL) {
171       ALLEGRO_USTR *copy = al_ustr_new(str);
172       replace_backslashes(copy);
173 
174       if (!parse_path_string(copy, path)) {
175          al_destroy_path(path);
176          path = NULL;
177       }
178 
179       al_ustr_free(copy);
180    }
181 
182    return path;
183 }
184 
185 
186 /* Function: al_create_path_for_directory
187  */
al_create_path_for_directory(const char * str)188 ALLEGRO_PATH *al_create_path_for_directory(const char *str)
189 {
190    ALLEGRO_PATH *path = al_create_path(str);
191    if (al_ustr_length(path->filename)) {
192       ALLEGRO_USTR *last = path->filename;
193       path->filename = al_ustr_new("");
194       al_append_path_component(path, al_cstr(last));
195       al_ustr_free(last);
196    }
197    return path;
198 }
199 
200 
201 /* Function: al_clone_path
202  */
al_clone_path(const ALLEGRO_PATH * path)203 ALLEGRO_PATH *al_clone_path(const ALLEGRO_PATH *path)
204 {
205    ALLEGRO_PATH *clone;
206    unsigned int i;
207 
208    ASSERT(path);
209 
210    clone = al_create_path(NULL);
211    if (!clone) {
212       return NULL;
213    }
214 
215    al_ustr_assign(clone->drive, path->drive);
216    al_ustr_assign(clone->filename, path->filename);
217 
218    for (i = 0; i < _al_vector_size(&path->segments); i++) {
219       ALLEGRO_USTR **slot = _al_vector_alloc_back(&clone->segments);
220       (*slot) = al_ustr_dup(get_segment(path, i));
221    }
222 
223    return clone;
224 }
225 
226 
227 /* Function: al_get_path_num_components
228  */
al_get_path_num_components(const ALLEGRO_PATH * path)229 int al_get_path_num_components(const ALLEGRO_PATH *path)
230 {
231    ASSERT(path);
232 
233    return _al_vector_size(&path->segments);
234 }
235 
236 
237 /* Function: al_get_path_component
238  */
al_get_path_component(const ALLEGRO_PATH * path,int i)239 const char *al_get_path_component(const ALLEGRO_PATH *path, int i)
240 {
241    ASSERT(path);
242    ASSERT(i < (int)_al_vector_size(&path->segments));
243 
244    if (i < 0)
245       i = _al_vector_size(&path->segments) + i;
246 
247    ASSERT(i >= 0);
248 
249    return get_segment_cstr(path, i);
250 }
251 
252 
253 /* Function: al_replace_path_component
254  */
al_replace_path_component(ALLEGRO_PATH * path,int i,const char * s)255 void al_replace_path_component(ALLEGRO_PATH *path, int i, const char *s)
256 {
257    ASSERT(path);
258    ASSERT(s);
259    ASSERT(i < (int)_al_vector_size(&path->segments));
260 
261    if (i < 0)
262       i = _al_vector_size(&path->segments) + i;
263 
264    ASSERT(i >= 0);
265 
266    al_ustr_assign_cstr(get_segment(path, i), s);
267 }
268 
269 
270 /* Function: al_remove_path_component
271  */
al_remove_path_component(ALLEGRO_PATH * path,int i)272 void al_remove_path_component(ALLEGRO_PATH *path, int i)
273 {
274    ASSERT(path);
275    ASSERT(i < (int)_al_vector_size(&path->segments));
276 
277    if (i < 0)
278       i = _al_vector_size(&path->segments) + i;
279 
280    ASSERT(i >= 0);
281 
282    al_ustr_free(get_segment(path, i));
283    _al_vector_delete_at(&path->segments, i);
284 }
285 
286 
287 /* Function: al_insert_path_component
288  */
al_insert_path_component(ALLEGRO_PATH * path,int i,const char * s)289 void al_insert_path_component(ALLEGRO_PATH *path, int i, const char *s)
290 {
291    ALLEGRO_USTR **slot;
292    ASSERT(path);
293    ASSERT(i <= (int)_al_vector_size(&path->segments));
294 
295    if (i < 0)
296       i = _al_vector_size(&path->segments) + i;
297 
298    ASSERT(i >= 0);
299 
300    slot = _al_vector_alloc_mid(&path->segments, i);
301    (*slot) = al_ustr_new(s);
302 }
303 
304 
305 /* Function: al_get_path_tail
306  */
al_get_path_tail(const ALLEGRO_PATH * path)307 const char *al_get_path_tail(const ALLEGRO_PATH *path)
308 {
309    ASSERT(path);
310 
311    if (al_get_path_num_components(path) == 0)
312       return NULL;
313 
314    return al_get_path_component(path, -1);
315 }
316 
317 
318 /* Function: al_drop_path_tail
319  */
al_drop_path_tail(ALLEGRO_PATH * path)320 void al_drop_path_tail(ALLEGRO_PATH *path)
321 {
322    if (al_get_path_num_components(path) > 0) {
323       al_remove_path_component(path, -1);
324    }
325 }
326 
327 
328 /* Function: al_append_path_component
329  */
al_append_path_component(ALLEGRO_PATH * path,const char * s)330 void al_append_path_component(ALLEGRO_PATH *path, const char *s)
331 {
332    ALLEGRO_USTR **slot = _al_vector_alloc_back(&path->segments);
333    (*slot) = al_ustr_new(s);
334 }
335 
336 
path_is_absolute(const ALLEGRO_PATH * path)337 static bool path_is_absolute(const ALLEGRO_PATH *path)
338 {
339    /* If the first segment is empty, we have an absolute path. */
340    return (_al_vector_size(&path->segments) > 0)
341       && (al_ustr_size(get_segment(path, 0)) == 0);
342 }
343 
344 
345 /* Function: al_join_paths
346  */
al_join_paths(ALLEGRO_PATH * path,const ALLEGRO_PATH * tail)347 bool al_join_paths(ALLEGRO_PATH *path, const ALLEGRO_PATH *tail)
348 {
349    unsigned i;
350    ASSERT(path);
351    ASSERT(tail);
352 
353    /* Don't bother concating if the tail is an absolute path. */
354    if (path_is_absolute(tail)) {
355       return false;
356    }
357 
358    /* We ignore tail->drive.  The other option is to do nothing if tail
359     * contains a drive letter.
360     */
361 
362    al_ustr_assign(path->filename, tail->filename);
363 
364    for (i = 0; i < _al_vector_size(&tail->segments); i++) {
365       al_append_path_component(path, get_segment_cstr(tail, i));
366    }
367 
368    return true;
369 }
370 
371 
372 /* Function: al_rebase_path
373  */
al_rebase_path(const ALLEGRO_PATH * head,ALLEGRO_PATH * tail)374 bool al_rebase_path(const ALLEGRO_PATH *head, ALLEGRO_PATH *tail)
375 {
376    unsigned i;
377    ASSERT(head);
378    ASSERT(tail);
379 
380    /* Don't bother concating if the tail is an absolute path. */
381    if (path_is_absolute(tail)) {
382       return false;
383    }
384 
385    al_set_path_drive(tail, al_get_path_drive(head));
386 
387    for (i = 0; i < _al_vector_size(&head->segments); i++) {
388       al_insert_path_component(tail, i, get_segment_cstr(head, i));
389    }
390 
391    return true;
392 }
393 
394 
path_to_ustr(const ALLEGRO_PATH * path,int32_t delim,ALLEGRO_USTR * str)395 static void path_to_ustr(const ALLEGRO_PATH *path, int32_t delim,
396    ALLEGRO_USTR *str)
397 {
398    unsigned i;
399 
400    al_ustr_assign(str, path->drive);
401 
402    for (i = 0; i < _al_vector_size(&path->segments); i++) {
403       al_ustr_append(str, get_segment(path, i));
404       al_ustr_append_chr(str, delim);
405    }
406 
407    al_ustr_append(str, path->filename);
408 }
409 
410 
411 /* Function: al_path_cstr
412  */
al_path_cstr(const ALLEGRO_PATH * path,char delim)413 const char *al_path_cstr(const ALLEGRO_PATH *path, char delim)
414 {
415    return al_cstr(al_path_ustr(path, delim));
416 }
417 
418 /* Function: al_path_ustr
419  */
al_path_ustr(const ALLEGRO_PATH * path,char delim)420 const ALLEGRO_USTR *al_path_ustr(const ALLEGRO_PATH *path, char delim)
421 {
422    path_to_ustr(path, delim, path->full_string);
423    return path->full_string;
424 }
425 
426 
427 /* Function: al_destroy_path
428  */
al_destroy_path(ALLEGRO_PATH * path)429 void al_destroy_path(ALLEGRO_PATH *path)
430 {
431    unsigned i;
432 
433    if (!path) {
434       return;
435    }
436 
437    if (path->drive) {
438       al_ustr_free(path->drive);
439       path->drive = NULL;
440    }
441 
442    if (path->filename) {
443       al_ustr_free(path->filename);
444       path->filename = NULL;
445    }
446 
447    for (i = 0; i < _al_vector_size(&path->segments); i++) {
448       al_ustr_free(get_segment(path, i));
449    }
450    _al_vector_free(&path->segments);
451 
452    if (path->basename) {
453       al_ustr_free(path->basename);
454       path->basename = NULL;
455    }
456 
457    if (path->full_string) {
458       al_ustr_free(path->full_string);
459       path->full_string = NULL;
460    }
461 
462    al_free(path);
463 }
464 
465 
466 /* Function: al_set_path_drive
467  */
al_set_path_drive(ALLEGRO_PATH * path,const char * drive)468 void al_set_path_drive(ALLEGRO_PATH *path, const char *drive)
469 {
470    ASSERT(path);
471 
472    if (drive)
473       al_ustr_assign_cstr(path->drive, drive);
474    else
475       al_ustr_truncate(path->drive, 0);
476 }
477 
478 
479 /* Function: al_get_path_drive
480  */
al_get_path_drive(const ALLEGRO_PATH * path)481 const char *al_get_path_drive(const ALLEGRO_PATH *path)
482 {
483    ASSERT(path);
484 
485    return al_cstr(path->drive);
486 }
487 
488 
489 /* Function: al_set_path_filename
490  */
al_set_path_filename(ALLEGRO_PATH * path,const char * filename)491 void al_set_path_filename(ALLEGRO_PATH *path, const char *filename)
492 {
493    ASSERT(path);
494 
495    if (filename)
496       al_ustr_assign_cstr(path->filename, filename);
497    else
498       al_ustr_truncate(path->filename, 0);
499 }
500 
501 
502 /* Function: al_get_path_filename
503  */
al_get_path_filename(const ALLEGRO_PATH * path)504 const char *al_get_path_filename(const ALLEGRO_PATH *path)
505 {
506    ASSERT(path);
507 
508    return al_cstr(path->filename);
509 }
510 
511 
512 /* Function: al_get_path_extension
513  */
al_get_path_extension(const ALLEGRO_PATH * path)514 const char *al_get_path_extension(const ALLEGRO_PATH *path)
515 {
516    int pos;
517    ASSERT(path);
518 
519    pos = al_ustr_rfind_chr(path->filename, al_ustr_size(path->filename), '.');
520    if (pos == -1)
521       pos = al_ustr_size(path->filename);
522 
523    return al_cstr(path->filename) + pos;  /* include dot */
524 }
525 
526 
527 /* Function: al_set_path_extension
528  */
al_set_path_extension(ALLEGRO_PATH * path,char const * extension)529 bool al_set_path_extension(ALLEGRO_PATH *path, char const *extension)
530 {
531    int dot;
532    ASSERT(path);
533 
534    if (al_ustr_size(path->filename) == 0) {
535       return false;
536    }
537 
538    dot = al_ustr_rfind_chr(path->filename, al_ustr_size(path->filename), '.');
539    if (dot >= 0) {
540       al_ustr_truncate(path->filename, dot);
541    }
542    al_ustr_append_cstr(path->filename, extension);
543    return true;
544 }
545 
546 
547 /* Function: al_get_path_basename
548  */
al_get_path_basename(const ALLEGRO_PATH * path)549 const char *al_get_path_basename(const ALLEGRO_PATH *path)
550 {
551    int dot;
552    ASSERT(path);
553 
554    dot = al_ustr_rfind_chr(path->filename, al_ustr_size(path->filename), '.');
555    if (dot >= 0) {
556       al_ustr_assign_substr(path->basename, path->filename, 0, dot);
557       return al_cstr(path->basename);
558    }
559 
560    return al_cstr(path->filename);
561 }
562 
563 
564 /* Function: al_make_path_canonical
565  */
al_make_path_canonical(ALLEGRO_PATH * path)566 bool al_make_path_canonical(ALLEGRO_PATH *path)
567 {
568    unsigned i;
569    ASSERT(path);
570 
571    for (i = 0; i < _al_vector_size(&path->segments); ) {
572       if (strcmp(get_segment_cstr(path, i), ".") == 0)
573          al_remove_path_component(path, i);
574       else
575          i++;
576    }
577 
578    /* Remove leading '..'s on absolute paths. */
579    if (_al_vector_size(&path->segments) >= 1 &&
580       al_ustr_size(get_segment(path, 0)) == 0)
581    {
582       while (_al_vector_size(&path->segments) >= 2 &&
583          strcmp(get_segment_cstr(path, 1), "..") == 0)
584       {
585          al_remove_path_component(path, 1);
586       }
587    }
588 
589    return true;
590 }
591 
592 /* vim: set sts=3 sw=3 et: */
593