qse/qse/lib/cmn/path-canon.c

320 lines
7.0 KiB
C
Raw Normal View History

2011-10-14 00:49:54 +00:00
/*
* $Id
*
Copyright 2006-2011 Chung, Hyung-Hwan.
This file is part of QSE.
QSE is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
QSE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with QSE. If not, see <http://www.gnu.org/licenses/>.
*/
2011-10-14 22:57:41 +00:00
#include <qse/cmn/path.h>
2011-10-14 00:49:54 +00:00
#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
# define ISSEP(c) ((c) == QSE_T('/') || (c) == QSE_T('\\'))
#else
# define ISSEP(c) ((c) == QSE_T('/'))
#endif
#define ISSEPNIL(c) (ISSEP(c) || ((c) == QSE_T('\0')))
2011-10-18 22:21:36 +00:00
#define ISDRIVE(s) \
(((s[0] >= QSE_T('A') && s[0] <= QSE_T('Z')) || \
2011-10-25 01:48:07 +00:00
(s[0] >= QSE_T('a') && s[0] <= QSE_T('z'))) && \
2011-10-18 22:21:36 +00:00
s[1] == QSE_T(':'))
int qse_isabspath (const qse_char_t* path)
{
if (ISSEP(path[0])) return 1;
#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
/* a drive like c:tmp is absolute in positioning the drive.
* but the path within the drive is kind of relative */
if (ISDRIVE(path)) return 1;
#endif
return 0;
}
2011-10-25 01:48:07 +00:00
int qse_isdrivepath (const qse_char_t* path)
{
#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
if (ISDRIVE(path)) return 1;
#endif
return 0;
}
int qse_isdrivecurpath (const qse_char_t* path)
{
#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
if (ISDRIVE(path) && path[2] == QSE_T('\0')) return 1;
#endif
return 0;
}
2011-10-14 15:51:20 +00:00
qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon)
2011-10-14 00:49:54 +00:00
{
const qse_char_t* ptr;
qse_char_t* dst;
2011-10-14 15:51:20 +00:00
qse_char_t* non_root_start;
int has_root = 0;
2011-10-25 17:20:25 +00:00
#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
int is_drive = 0;
#endif
2011-10-25 01:48:07 +00:00
qse_size_t canon_len;
2011-10-14 00:49:54 +00:00
2011-10-25 17:20:25 +00:00
if (path[0] == QSE_T('\0'))
{
/* if the source is empty, no translation is needed */
canon[0] = QSE_T('\0');
return 0;
}
2011-10-14 00:49:54 +00:00
ptr = path;
2011-10-14 15:51:20 +00:00
dst = canon;
#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
2011-10-18 22:21:36 +00:00
if (ISDRIVE(ptr))
2011-10-14 15:51:20 +00:00
{
/* handle drive letter */
*dst++ = *ptr++; /* drive letter */
*dst++ = *ptr++; /* colon */
2011-10-14 00:49:54 +00:00
2011-10-25 17:20:25 +00:00
is_drive = 1;
2011-10-14 15:51:20 +00:00
if (ISSEP(*ptr))
{
*dst++ = *ptr++; /* root directory */
has_root = 1;
}
}
else if (ISSEP(*ptr))
2011-10-14 00:49:54 +00:00
{
2011-10-14 15:51:20 +00:00
*dst++ = *ptr++; /* root directory */
has_root = 1;
2011-10-14 00:49:54 +00:00
2011-10-14 15:51:20 +00:00
#if defined(_WIN32)
2011-10-14 22:57:41 +00:00
/* handle UNC path for Windows */
2011-10-14 00:49:54 +00:00
if (ISSEP(*ptr))
{
*dst++ = *ptr++;
2011-10-14 22:57:41 +00:00
if (ISSEPNIL(*ptr))
{
/* if there is another separator after \\,
* it's not an UNC path. */
dst--;
}
else
{
/* if it starts with \\, process host name */
do { *dst++ = *ptr++; } while (!ISSEPNIL(*ptr));
if (ISSEP(*ptr)) *dst++ = *ptr++;
}
2011-10-14 00:49:54 +00:00
}
2011-10-14 15:51:20 +00:00
#endif
}
#else
if (ISSEP(*ptr))
{
*dst++ = *ptr++; /* root directory */
has_root = 1;
}
2011-10-14 00:49:54 +00:00
#endif
2011-10-14 15:51:20 +00:00
/* non_root_start points to the beginning of the canonicalized
* path excluding the root directory part. */
non_root_start = dst;
2011-10-14 00:49:54 +00:00
do
{
const qse_char_t* seg;
2011-10-14 15:51:20 +00:00
qse_size_t seglen;
2011-10-14 00:49:54 +00:00
/* skip duplicate separators */
while (ISSEP(*ptr)) ptr++;
/* end of path reached */
if (*ptr == QSE_T('\0')) break;
2011-10-14 15:51:20 +00:00
/* find the next segment */
2011-10-14 00:49:54 +00:00
seg = ptr;
while (!ISSEPNIL(*ptr)) ptr++;
2011-10-14 15:51:20 +00:00
seglen = ptr - seg;
2011-10-14 00:49:54 +00:00
2011-10-14 15:51:20 +00:00
/* handle the segment */
if (seglen == 1 && seg[0] == QSE_T('.'))
2011-10-14 00:49:54 +00:00
{
/* eat up . */
}
2011-10-14 15:51:20 +00:00
else if (seglen == 2 && seg[0] == QSE_T('.') && seg[1] == QSE_T('.'))
2011-10-14 00:49:54 +00:00
{
/* eat up the previous segment */
2011-10-14 15:51:20 +00:00
qse_char_t* tmp;
2011-10-14 00:49:54 +00:00
tmp = dst;
2011-10-14 15:51:20 +00:00
if (tmp > non_root_start)
2011-10-14 00:49:54 +00:00
{
2011-10-14 15:51:20 +00:00
/* there is a previous segment. */
tmp--; /* skip the separator just before .. */
/* find the beginning of the previous segment */
while (tmp > non_root_start)
{
tmp--;
if (ISSEP(*tmp))
{
tmp++; /* position it next to the separator */
break;
}
}
2011-10-14 00:49:54 +00:00
}
2011-10-14 15:51:20 +00:00
if (has_root)
2011-10-14 00:49:54 +00:00
{
2011-10-14 15:51:20 +00:00
/*
* Eat up the previous segment if it exists.
*
* If it doesn't exist, tmp == dst so dst = tmp keeps dst
* unchanged. If it exists, tmp != dst. so dst = tmp
* changes dst.
*
* path /abc/def/..
* ^ ^
* seg ptr
*
* canon /abc/def/
* ^ ^
* tmp dst
*/
2011-10-14 00:49:54 +00:00
dst = tmp;
}
else
{
2011-10-14 15:51:20 +00:00
qse_size_t prevlen;
prevlen = dst - tmp;
2011-10-14 00:49:54 +00:00
2011-10-14 15:51:20 +00:00
if (/*tmp == non_root_start &&*/ prevlen == 0)
2011-10-14 00:49:54 +00:00
{
2011-10-14 15:51:20 +00:00
/* there is no previous segment */
goto normal;
2011-10-14 00:49:54 +00:00
}
2011-10-14 15:51:20 +00:00
if (prevlen == 3 && tmp[0] == QSE_T('.') && tmp[1] == QSE_T('.'))
2011-10-14 00:49:54 +00:00
{
2011-10-14 15:51:20 +00:00
/* nothing to eat away because the previous segment is ../
*
* path ../../
* ^ ^
* seg ptr
*
* canon ../
* ^ ^
* tmp dst
*/
2011-10-14 00:49:54 +00:00
goto normal;
}
2011-10-14 15:51:20 +00:00
dst = tmp;
2011-10-14 00:49:54 +00:00
}
}
else
{
normal:
2011-10-14 22:57:41 +00:00
while (seg < ptr) *dst++ = *seg++;
if (ISSEP(*ptr))
{
/* this segment ended with a separator */
*dst++ = *seg++; /* copy the separator */
ptr++; /* move forward the pointer */
}
2011-10-14 00:49:54 +00:00
}
}
while (1);
2011-10-14 15:51:20 +00:00
if (dst > non_root_start && ISSEP(dst[-1]) && !ISSEP(ptr[-1]))
{
/* if the canoncal path composed so far ends with a separator
* and the original path didn't end with the separator, delete
* the ending separator.
*
* dst > non_root_start:
* there is at least 1 character after the root directory part.
* ISSEP(dst[-1]):
* the canonical path ends with a separator.
* ISSEP(ptr[-1]):
* the origial path ends with a separator
*/
dst[-1] = QSE_T('\0');
2011-10-25 01:48:07 +00:00
canon_len = dst - canon - 1;
2011-10-14 15:51:20 +00:00
}
else
{
/* just null-terminate the canonical path normally */
dst[0] = QSE_T('\0');
2011-10-25 01:48:07 +00:00
canon_len = dst - canon;
2011-10-14 15:51:20 +00:00
}
2011-10-25 01:48:07 +00:00
2011-10-25 17:20:25 +00:00
if (canon_len <= 0)
2011-10-25 01:48:07 +00:00
{
/* when resolving to a single dot, a trailing separator is not
2011-10-25 17:20:25 +00:00
* retained though the orignal path name contains it. */
canon[0] = QSE_T('.');
canon[1] = QSE_T('\0');
2011-10-25 01:48:07 +00:00
canon_len = 1;
}
2011-10-25 17:20:25 +00:00
else
{
/* drop a traling separator if the last segment is
* double slashes */
int adj_base_len = 3;
#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
if (is_drive && !has_root)
{
/* A path like A:..\\\ need some adjustment for
* finalization below. */
adj_base_len += 2;
}
#endif
if (canon_len == adj_base_len)
{
/* i don't have to retains a trailing separator
* if the last segment is double slashes because
* the double slahses indicate a directory obviously */
if (canon[canon_len-3] == QSE_T('.') &&
canon[canon_len-2] == QSE_T('.') &&
ISSEP(canon[canon_len-1]))
{
canon[--canon_len] = QSE_T('\0');
}
}
else if (canon_len > adj_base_len)
{
if (ISSEP(canon[canon_len-4]) &&
canon[canon_len-3] == QSE_T('.') &&
canon[canon_len-2] == QSE_T('.') &&
ISSEP(canon[canon_len-1]))
{
canon[--canon_len] = QSE_T('\0');
}
}
}
2011-10-25 01:48:07 +00:00
return canon_len;
2011-10-14 15:51:20 +00:00
}