#include "path.h"
#include "repository.h"
#include "fs_path.h"
typedef struct {
git_repository *repo;
uint16_t file_mode;
unsigned int flags;
} repository_path_validate_data;
static int32_t next_hfs_char(const char **in, size_t *len)
{
while (*len) {
uint32_t codepoint;
int cp_len = git_utf8_iterate(&codepoint, *in, *len);
if (cp_len < 0)
return -1;
(*in) += cp_len;
(*len) -= cp_len;
switch (codepoint) {
case 0x200c:
case 0x200d:
case 0x200e:
case 0x200f:
case 0x202a:
case 0x202b:
case 0x202c:
case 0x202d:
case 0x202e:
case 0x206a:
case 0x206b:
case 0x206c:
case 0x206d:
case 0x206e:
case 0x206f:
case 0xfeff:
continue;
}
return git__tolower((int)codepoint);
}
return 0;
}
static bool validate_dotgit_hfs_generic(
const char *path,
size_t len,
const char *needle,
size_t needle_len)
{
size_t i;
char c;
if (next_hfs_char(&path, &len) != '.')
return true;
for (i = 0; i < needle_len; i++) {
c = next_hfs_char(&path, &len);
if (c != needle[i])
return true;
}
if (next_hfs_char(&path, &len) != '\0')
return true;
return false;
}
static bool validate_dotgit_hfs(const char *path, size_t len)
{
return validate_dotgit_hfs_generic(path, len, "git", CONST_STRLEN("git"));
}
GIT_INLINE(bool) validate_dotgit_ntfs(
git_repository *repo,
const char *path,
size_t len)
{
git_str *reserved = git_repository__reserved_names_win32;
size_t reserved_len = git_repository__reserved_names_win32_len;
size_t start = 0, i;
if (repo)
git_repository__reserved_names(&reserved, &reserved_len, repo, true);
for (i = 0; i < reserved_len; i++) {
git_str *r = &reserved[i];
if (len >= r->size &&
strncasecmp(path, r->ptr, r->size) == 0) {
start = r->size;
break;
}
}
if (!start)
return true;
if (path[start] == '\\' || path[start] == ':')
return false;
for (i = start; i < len; i++) {
if (path[i] != ' ' && path[i] != '.')
return true;
}
return false;
}
GIT_INLINE(bool) ntfs_end_of_filename(const char *path)
{
const char *c = path;
for (;; c++) {
if (*c == '\0' || *c == ':')
return true;
if (*c != ' ' && *c != '.')
return false;
}
return true;
}
GIT_INLINE(bool) validate_dotgit_ntfs_generic(
const char *name,
size_t len,
const char *dotgit_name,
size_t dotgit_len,
const char *shortname_pfix)
{
int i, saw_tilde;
if (name[0] == '.' && len >= dotgit_len &&
!strncasecmp(name + 1, dotgit_name, dotgit_len)) {
return !ntfs_end_of_filename(name + dotgit_len + 1);
}
if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' &&
name[7] >= '1' && name[7] <= '4')
return !ntfs_end_of_filename(name + 8);
for (i = 0, saw_tilde = 0; i < 8; i++) {
if (name[i] == '\0') {
return true;
} else if (saw_tilde) {
if (name[i] < '0' || name[i] > '9')
return true;
} else if (name[i] == '~') {
if (name[i+1] < '1' || name[i+1] > '9')
return true;
saw_tilde = 1;
} else if (i >= 6) {
return true;
} else if ((unsigned char)name[i] > 127) {
return true;
} else if (git__tolower(name[i]) != shortname_pfix[i]) {
return true;
}
}
return !ntfs_end_of_filename(name + i);
}
GIT_INLINE(size_t) common_prefix_icase(const char *str, size_t len, const char *prefix)
{
size_t count = 0;
while (len > 0 && tolower(*str) == tolower(*prefix)) {
count++;
str++;
prefix++;
len--;
}
return count;
}
static bool validate_repo_component(
const char *component,
size_t len,
void *payload)
{
repository_path_validate_data *data = (repository_path_validate_data *)payload;
if (data->flags & GIT_PATH_REJECT_DOT_GIT_HFS) {
if (!validate_dotgit_hfs(component, len))
return false;
if (S_ISLNK(data->file_mode) &&
git_path_is_gitfile(component, len, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_HFS))
return false;
}
if (data->flags & GIT_PATH_REJECT_DOT_GIT_NTFS) {
if (!validate_dotgit_ntfs(data->repo, component, len))
return false;
if (S_ISLNK(data->file_mode) &&
git_path_is_gitfile(component, len, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_NTFS))
return false;
}
if ((data->flags & GIT_PATH_REJECT_DOT_GIT_HFS) == 0 &&
(data->flags & GIT_PATH_REJECT_DOT_GIT_NTFS) == 0 &&
(data->flags & GIT_PATH_REJECT_DOT_GIT_LITERAL)) {
if (len >= 4 &&
component[0] == '.' &&
(component[1] == 'g' || component[1] == 'G') &&
(component[2] == 'i' || component[2] == 'I') &&
(component[3] == 't' || component[3] == 'T')) {
if (len == 4)
return false;
if (S_ISLNK(data->file_mode) &&
common_prefix_icase(component, len, ".gitmodules") == len)
return false;
}
}
return true;
}
GIT_INLINE(unsigned int) dotgit_flags(
git_repository *repo,
unsigned int flags)
{
int protectHFS = 0, protectNTFS = 1;
int error = 0;
flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL;
#ifdef __APPLE__
protectHFS = 1;
#endif
if (repo && !protectHFS)
error = git_repository__configmap_lookup(&protectHFS, repo, GIT_CONFIGMAP_PROTECTHFS);
if (!error && protectHFS)
flags |= GIT_PATH_REJECT_DOT_GIT_HFS;
if (repo)
error = git_repository__configmap_lookup(&protectNTFS, repo, GIT_CONFIGMAP_PROTECTNTFS);
if (!error && protectNTFS)
flags |= GIT_PATH_REJECT_DOT_GIT_NTFS;
return flags;
}
GIT_INLINE(unsigned int) length_flags(
git_repository *repo,
unsigned int flags)
{
#ifdef GIT_WIN32
int allow = 0;
if (repo &&
git_repository__configmap_lookup(&allow, repo, GIT_CONFIGMAP_LONGPATHS) < 0)
allow = 0;
if (allow)
flags &= ~GIT_FS_PATH_REJECT_LONG_PATHS;
#else
GIT_UNUSED(repo);
flags &= ~GIT_FS_PATH_REJECT_LONG_PATHS;
#endif
return flags;
}
bool git_path_str_is_valid(
git_repository *repo,
const git_str *path,
uint16_t file_mode,
unsigned int flags)
{
repository_path_validate_data data = {0};
if ((flags & GIT_PATH_REJECT_DOT_GIT))
flags = dotgit_flags(repo, flags);
if ((flags & GIT_FS_PATH_REJECT_LONG_PATHS))
flags = length_flags(repo, flags);
data.repo = repo;
data.file_mode = file_mode;
data.flags = flags;
return git_fs_path_str_is_valid_ext(path, flags, NULL, validate_repo_component, NULL, &data);
}
static const struct {
const char *file;
const char *hash;
size_t filelen;
} gitfiles[] = {
{ "gitignore", "gi250a", CONST_STRLEN("gitignore") },
{ "gitmodules", "gi7eba", CONST_STRLEN("gitmodules") },
{ "gitattributes", "gi7d29", CONST_STRLEN("gitattributes") }
};
extern int git_path_is_gitfile(
const char *path,
size_t pathlen,
git_path_gitfile gitfile,
git_path_fs fs)
{
const char *file, *hash;
size_t filelen;
if (!(gitfile >= GIT_PATH_GITFILE_GITIGNORE && gitfile < ARRAY_SIZE(gitfiles))) {
git_error_set(GIT_ERROR_OS, "invalid gitfile for path validation");
return -1;
}
file = gitfiles[gitfile].file;
filelen = gitfiles[gitfile].filelen;
hash = gitfiles[gitfile].hash;
switch (fs) {
case GIT_PATH_FS_GENERIC:
return !validate_dotgit_ntfs_generic(path, pathlen, file, filelen, hash) ||
!validate_dotgit_hfs_generic(path, pathlen, file, filelen);
case GIT_PATH_FS_NTFS:
return !validate_dotgit_ntfs_generic(path, pathlen, file, filelen, hash);
case GIT_PATH_FS_HFS:
return !validate_dotgit_hfs_generic(path, pathlen, file, filelen);
default:
git_error_set(GIT_ERROR_OS, "invalid filesystem for path validation");
return -1;
}
}