#include "mathlib.h"
#include "inout.h"
#include "vfs.h"
-#include "unzip.h"
+#include <unzip.h>
+#include <glib.h>
typedef struct
{
char* name;
- unz_s zipinfo;
unzFile zipfile;
+ unz_file_pos zippos;
guint32 size;
} VFS_PAKFILE;
for ( i = 0; i < gi.number_entry; i++ )
{
char filename_inzip[NAME_MAX];
+ char *filename_lower;
unz_file_info file_info;
VFS_PAKFILE* file;
if ( err != UNZ_OK ) {
break;
}
+ unz_file_pos pos;
+ err = unzGetFilePos( uf, &pos );
+ if ( err != UNZ_OK ) {
+ break;
+ }
file = (VFS_PAKFILE*)safe_malloc( sizeof( VFS_PAKFILE ) );
g_pakFiles = g_slist_append( g_pakFiles, file );
vfsFixDOSName( filename_inzip );
- g_strdown( filename_inzip );
+ //-1 null terminated string
+ filename_lower = g_ascii_strdown( filename_inzip, -1 );
- file->name = strdup( filename_inzip );
+ file->name = strdup( filename_lower );
file->size = file_info.uncompressed_size;
file->zipfile = uf;
- memcpy( &file->zipinfo, uf, sizeof( unz_s ) );
+ file->zippos = pos;
if ( ( i + 1 ) < gi.number_entry ) {
err = unzGoToNextFile( uf );
break;
}
}
+ g_free( filename_lower );
}
}
{
char *ext = strrchr( dirlist, '.' );
- if ( ext && ( !Q_stricmp( ext, ".pk3dir" ) || !Q_stricmp( ext, ".dpkdir" ) ) ) {
+ if ( ext != NULL && ( !Q_stricmp( ext, ".pk3dir" ) || !Q_stricmp( ext, ".dpkdir" ) ) ) {
if ( g_numDirs == VFS_MAXDIRS ) {
+ g_free( dirlist );
continue;
}
snprintf( g_strDirs[g_numDirs], PATH_MAX, "%s/%s", path, name );
- g_strDirs[g_numDirs][PATH_MAX] = '\0';
+ g_strDirs[g_numDirs][PATH_MAX-1] = '\0';
vfsFixDOSName( g_strDirs[g_numDirs] );
vfsAddSlash( g_strDirs[g_numDirs] );
++g_numDirs;
}
- if ( ( ext == NULL ) || ( Q_stricmp( ext, ".pk3" ) != 0 || !Q_stricmp( ext, ".dpk" ) != 0 ) ) {
+ if ( ext == NULL || ( Q_stricmp( ext, ".pk3" ) != 0 && Q_stricmp( ext, ".dpk" ) != 0 ) ) {
+ g_free( dirlist );
continue;
}
}
int vfsGetFileCount( const char *filename ){
int i, count = 0;
char fixed[NAME_MAX], tmp[NAME_MAX];
+ char *lower;
GSList *lst;
strcpy( fixed, filename );
vfsFixDOSName( fixed );
- g_strdown( fixed );
+ lower = g_ascii_strdown( fixed, -1 );
for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
{
VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
- if ( strcmp( file->name, fixed ) == 0 ) {
+ if ( strcmp( file->name, lower ) == 0 ) {
count++;
}
}
for ( i = 0; i < g_numDirs; i++ )
{
strcpy( tmp, g_strDirs[i] );
- strcat( tmp, fixed );
+ strcat( tmp, lower );
if ( access( tmp, R_OK ) == 0 ) {
count++;
}
}
-
+ g_free( lower );
return count;
}
+static qboolean isSymlink(const unz_file_info64 *fileInfo) {
+ // see https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/stat.h
+ // redefine so it works outside of Unices
+ const unsigned long Q3MAP_S_IFMT = 00170000;
+ const unsigned long Q3MAP_S_IFLNK = 0120000;
+ // see https://trac.edgewall.org/attachment/ticket/8919/ZipDownload.patch
+ const unsigned long PKZIP_EXTERNAL_ATTR_FILE_TYPE_SHIFT = 16;
+
+ unsigned long attr = fileInfo->external_fa >> PKZIP_EXTERNAL_ATTR_FILE_TYPE_SHIFT;
+ return (attr & Q3MAP_S_IFMT) == Q3MAP_S_IFLNK;
+}
+
+// The zip format has a maximum filename size of 64K
+static const int MAX_FILENAME_BUF = 65537;
+
+/* The symlink implementation is ported from Dæmon engine implementation by slipher which was a complete rewrite of one illwieckz did on Dæmon by taking inspiration from Darkplaces engine.
+
+See:
+
+- https://github.com/DaemonEngine/Daemon/blob/master/src/common/FileSystem.cpp
+- https://gitlab.com/xonotic/darkplaces/-/blob/div0-stable/fs.c
+
+Some words by slipher:
+
+> Symlinks are a bad feature which you should not use. Therefore, the implementation is as
+> slow as possible with a full iteration of the archive performed for each symlink.
+
+> The symlink path `relative` must be relative to the symlink's location.
+> Only supports paths consisting of "../" 0 or more times, followed by non-magical path components.
+*/
+void resolveSymlinkPath( const char* base, const char* relative, char* resolved ){
+
+ base = g_path_get_dirname( base );
+
+ while( g_str_has_prefix( relative, "../" ) )
+ {
+ if ( base[0] == '\0' )
+ {
+ Sys_FPrintf( SYS_WRN, "Error while reading symbolic link: \"%s\": no such directory\n", base );
+ resolved[0] = '\0';
+ return;
+ }
+
+ base = g_path_get_dirname( base );
+ relative += 3;
+ }
+
+ snprintf( resolved, MAX_FILENAME_BUF, "%s/%s", base, relative);
+}
+
// NOTE: when loading a file, you have to allocate one extra byte and set it to \0
int vfsLoadFile( const char *filename, void **bufferptr, int index ){
int i, count = 0;
char tmp[NAME_MAX], fixed[NAME_MAX];
+ char *lower;
GSList *lst;
// filename is a full path
}
*bufferptr = NULL;
- strcpy( fixed, filename );
+ strncpy( fixed, filename, sizeof( fixed ) );
vfsFixDOSName( fixed );
- g_strdown( fixed );
+ lower = g_ascii_strdown( fixed, -1 );
for ( i = 0; i < g_numDirs; i++ )
{
}
}
+ // Do not resolve more than 5 recursive symbolic links to
+ // prevent circular symbolic links.
+ int max_symlink_depth = 5;
+
+ openSymlinkTarget:
for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
{
VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
- if ( strcmp( file->name, fixed ) != 0 ) {
+ if ( strcmp( file->name, lower ) != 0 ) {
continue;
}
if ( count == index ) {
- memcpy( file->zipfile, &file->zipinfo, sizeof( unz_s ) );
+ if ( unzGoToFilePos( file->zipfile, &file->zippos ) != UNZ_OK ) {
+ return -1;
+ }
if ( unzOpenCurrentFile( file->zipfile ) != UNZ_OK ) {
return -1;
}
+ unz_file_info64 fileInfo;
+ if ( unzGetCurrentFileInfo64( file->zipfile, &fileInfo, filename, sizeof(filename), NULL, 0, NULL, 0 ) != UNZ_OK ) {
+ return -1;
+ }
+
*bufferptr = safe_malloc( file->size + 1 );
// we need to end the buffer with a 0
( (char*) ( *bufferptr ) )[file->size] = 0;
i = unzReadCurrentFile( file->zipfile, *bufferptr, file->size );
unzCloseCurrentFile( file->zipfile );
+
+ if ( isSymlink( &fileInfo ) ) {
+ Sys_FPrintf( SYS_VRB, "Found symbolic link: \"%s\"\n", filename );
+
+ if ( max_symlink_depth == 0 ) {
+ Sys_FPrintf( SYS_WRN, "Maximum symbolic link depth reached\n" );
+ g_free( lower );
+ return -1;
+ }
+
+ max_symlink_depth--;
+
+ const char* relative = (const char*) *bufferptr;
+ char resolved[MAX_FILENAME_BUF];
+
+ resolveSymlinkPath( file->name, relative, resolved );
+
+ Sys_FPrintf( SYS_VRB, "Resolved symbolic link: \"%s\"\n", resolved );
+
+ g_free( lower );
+ strncpy( fixed, resolved, sizeof( fixed ) );
+ vfsFixDOSName( fixed );
+ lower = g_ascii_strdown( fixed, -1 );
+
+ // slow as possible full iteration of the archive
+ goto openSymlinkTarget;
+ }
+
if ( i < 0 ) {
+ g_free( lower );
return -1;
}
else{
+ g_free( lower );
return file->size;
}
}
count++;
}
+ g_free( lower );
return -1;
}