2024-04-13 10:12:47 -07:00
/* Copyright (C) 2024 Wildfire Games.
2023-12-02 16:30:12 -08:00
* This file is part of 0 A . D .
2009-04-18 10:00:33 -07:00
*
2023-12-02 16:30:12 -08:00
* 0 A . D . is free software : you can redistribute it and / or modify
2009-04-18 10:00:33 -07:00
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 2 of the License , or
* ( at your option ) any later version .
*
2023-12-02 16:30:12 -08:00
* 0 A . D . is distributed in the hope that it will be useful ,
2009-04-18 10:00:33 -07:00
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
2023-12-02 16:30:12 -08:00
* along with 0 A . D . If not , see < http : //www.gnu.org/licenses/>.
2009-04-18 10:00:33 -07:00
*/
2006-04-23 16:14:18 -07:00
# include "precompiled.h"
2018-04-27 09:48:44 -07:00
# include "JSInterface_VFS.h"
2006-04-23 16:14:18 -07:00
2018-04-27 09:48:44 -07:00
# include "lib/file/vfs/vfs_util.h"
2014-01-04 02:14:53 -08:00
# include "ps/CLogger.h"
2006-04-23 16:14:18 -07:00
# include "ps/CStr.h"
2007-12-20 12:21:45 -08:00
# include "ps/Filesystem.h"
2021-03-02 12:01:14 -08:00
# include "scriptinterface/FunctionWrapper.h"
2021-05-14 03:18:03 -07:00
# include "scriptinterface/JSON.h"
2023-07-07 13:12:16 -07:00
# include "scriptinterface/Object.h"
2018-04-27 09:48:44 -07:00
2024-04-13 10:12:47 -07:00
# include <algorithm>
2018-04-27 09:48:44 -07:00
# include <sstream>
2006-04-23 16:14:18 -07:00
2021-03-02 12:01:14 -08:00
namespace JSI_VFS
{
2024-04-13 10:12:47 -07:00
using namespace std : : literals ;
2017-12-03 15:02:27 -08:00
// Only allow engine compartments to read files they may be concerned about.
2024-04-13 10:12:47 -07:00
namespace PathRestriction
{
constexpr std : : array < std : : wstring_view , 8 > GUI { L " gui/ " sv , L " simulation/ " sv , L " maps/ " sv , L " campaigns/ " sv ,
L " saves/campaigns/ " sv , L " config/matchsettings.json " sv , L " config/matchsettings.mp.json " sv ,
L " moddata " sv } ;
constexpr std : : array < std : : wstring_view , 1 > SIMULATION { L " simulation/ " sv } ;
constexpr std : : array < std : : wstring_view , 2 > MAPS { L " simulation/ " sv , L " maps/ " sv } ;
}
2017-12-03 15:02:27 -08:00
2006-04-23 16:14:18 -07:00
// shared error handling code
# define JS_CHECK_FILE_ERR(err)\
/* this is liable to happen often, so don't complain */ \
2010-11-16 15:00:52 -08:00
if ( err = = ERR : : VFS_FILE_NOT_FOUND ) \
2006-04-23 16:14:18 -07:00
{ \
2014-01-04 02:14:53 -08:00
return 0 ; \
2006-04-23 16:14:18 -07:00
} \
2014-01-04 02:14:53 -08:00
/* unknown failure. We output an error message. */ \
2010-11-16 15:00:52 -08:00
else if ( err < 0 ) \
2015-01-22 12:31:30 -08:00
LOGERROR ( " Unknown failure in VFS %i " , err ) ;
2006-04-23 16:14:18 -07:00
/* else: success */
2021-03-02 12:01:14 -08:00
// Tests whether the current script context is allowed to read from the given directory
2024-04-13 10:12:47 -07:00
template < auto & restriction >
bool PathRestrictionMet ( const ScriptRequest & rq , const std : : wstring & filePath )
2021-03-02 12:01:14 -08:00
{
2024-04-13 10:12:47 -07:00
if ( std : : any_of ( restriction . begin ( ) , restriction . end ( ) , [ & ] ( const std : : wstring_view allowedPath )
{
return filePath . find ( allowedPath ) = = 0 ;
} ) )
{
return true ;
}
2021-03-02 12:01:14 -08:00
2024-04-13 10:12:47 -07:00
std : : wstring allowedPaths ;
for ( std : : size_t i = 0 ; i < restriction . size ( ) ; + + i )
2021-03-02 12:01:14 -08:00
{
if ( i ! = 0 )
allowedPaths + = L " , " ;
2024-04-13 10:12:47 -07:00
allowedPaths + = L " \" " + static_cast < std : : wstring > ( restriction [ i ] ) + L " \" " ;
2021-03-02 12:01:14 -08:00
}
Restrict access for Read/WriteFile functions
For security reasons planing to restrict access for ReadFile,
ReadFileLines, WriteJSONFile, ReadJSONFile, ListDirectoryFiles,
FileExists to the following folders/files:
"gui, simulation, maps, campaigns, saves/campaigns,
config/matchsettings.json, config/matchsettings.mp.json"
adding "moddata" if some mods need to access and ship custom files that
don't fit into other locations mentioned above
Differential revision: D4617
Reviewed by: @phosit
Comments by: @Stan @vladislavbelov
Fixes: #5850
This was SVN commit r27202.
2022-11-05 01:33:27 -07:00
ScriptException : : Raise ( rq , " Restricted access to %s. This part of the engine may only read from %s! " , utf8_from_wstring ( filePath ) . c_str ( ) , utf8_from_wstring ( allowedPaths ) . c_str ( ) ) ;
2021-03-02 12:01:14 -08:00
return false ;
}
2006-09-08 11:41:30 -07:00
// state held across multiple BuildDirEntListCB calls; init by BuildDirEntList.
struct BuildDirEntListState
2006-04-23 16:14:18 -07:00
{
2021-03-02 12:01:14 -08:00
const ScriptRequest & rq ;
2015-01-24 06:46:52 -08:00
JS : : PersistentRootedObject filename_array ;
2006-04-23 16:14:18 -07:00
int cur_idx ;
2021-03-02 12:01:14 -08:00
BuildDirEntListState ( const ScriptRequest & rq )
: rq ( rq ) ,
filename_array ( rq . cx ) ,
2015-01-24 06:46:52 -08:00
cur_idx ( 0 )
2006-04-23 16:14:18 -07:00
{
2024-10-25 01:24:14 -07:00
filename_array = JS : : NewArrayObject ( rq . cx , 0 ) ;
2006-04-23 16:14:18 -07:00
}
} ;
// called for each matching directory entry; add its full pathname to array.
2013-09-10 07:17:04 -07:00
static Status BuildDirEntListCB ( const VfsPath & pathname , const CFileInfo & UNUSED ( fileINfo ) , uintptr_t cbData )
2006-04-23 16:14:18 -07:00
{
2007-09-25 02:39:20 -07:00
BuildDirEntListState * s = ( BuildDirEntListState * ) cbData ;
2006-04-23 16:14:18 -07:00
2021-03-02 12:01:14 -08:00
JS : : RootedObject filenameArrayObj ( s - > rq . cx , s - > filename_array ) ;
JS : : RootedValue val ( s - > rq . cx ) ;
2021-05-13 02:43:33 -07:00
Script : : ToJSVal ( s - > rq , & val , CStrW ( pathname . string ( ) ) ) ;
2021-03-02 12:01:14 -08:00
JS_SetElement ( s - > rq . cx , filenameArrayObj , s - > cur_idx + + , val ) ;
2011-07-18 02:21:56 -07:00
return INFO : : OK ;
2006-04-23 16:14:18 -07:00
}
// Return an array of pathname strings, one for each matching entry in the
// specified directory.
// filter_string: default "" matches everything; otherwise, see vfs_next_dirent.
// recurse: should subdirectories be included in the search? default false.
2024-04-13 10:12:47 -07:00
template < auto & restriction >
JS : : Value BuildDirEntList ( const ScriptRequest & rq , const std : : wstring & path , const std : : wstring & filterStr ,
bool recurse )
2006-04-23 16:14:18 -07:00
{
2024-04-13 10:12:47 -07:00
if ( ! PathRestrictionMet < restriction > ( rq , path ) )
2017-12-04 15:46:55 -08:00
return JS : : NullValue ( ) ;
2009-11-03 13:46:35 -08:00
// convert to const wchar_t*; if there's no filter, pass 0 for speed
2006-04-23 16:14:18 -07:00
// (interpreted as: "accept all files without comparing").
2009-11-03 13:46:35 -08:00
const wchar_t * filter = 0 ;
2014-01-04 02:14:53 -08:00
if ( ! filterStr . empty ( ) )
filter = filterStr . c_str ( ) ;
2016-11-23 05:02:58 -08:00
2014-01-04 02:14:53 -08:00
int flags = recurse ? vfs : : DIR_RECURSIVE : 0 ;
2006-04-23 16:14:18 -07:00
// build array in the callback function
2021-03-02 12:01:14 -08:00
BuildDirEntListState state ( rq ) ;
2011-05-25 03:39:13 -07:00
vfs : : ForEachFile ( g_VFS , path , BuildDirEntListCB , ( uintptr_t ) & state , filter , flags ) ;
2006-04-23 16:14:18 -07:00
2019-01-13 08:37:41 -08:00
return JS : : ObjectValue ( * state . filename_array ) ;
2006-04-23 16:14:18 -07:00
}
2013-11-07 12:07:24 -08:00
// Return true iff the file exits
2024-04-13 10:12:47 -07:00
template < auto & restriction >
bool FileExists ( const ScriptRequest & rq , const std : : wstring & filename )
2013-11-07 12:07:24 -08:00
{
2024-04-13 10:12:47 -07:00
return PathRestrictionMet < restriction > ( rq , filename ) & & g_VFS - > GetFileInfo ( filename , 0 ) = = INFO : : OK ;
2013-11-07 12:07:24 -08:00
}
2006-04-23 16:14:18 -07:00
// Return current size of file.
2021-03-02 12:01:14 -08:00
unsigned int GetFileSize ( const std : : wstring & filename )
2006-04-23 16:14:18 -07:00
{
2013-09-10 07:17:04 -07:00
CFileInfo fileInfo ;
2011-05-03 05:38:42 -07:00
Status err = g_VFS - > GetFileInfo ( filename , & fileInfo ) ;
2006-04-23 16:14:18 -07:00
JS_CHECK_FILE_ERR ( err ) ;
2014-01-04 02:14:53 -08:00
return ( unsigned int ) fileInfo . Size ( ) ;
2006-04-23 16:14:18 -07:00
}
2012-05-03 20:46:05 -07:00
// Return file contents in a string. Assume file is UTF-8 encoded text.
2024-04-13 10:12:47 -07:00
template < auto & restriction >
JS : : Value ReadFile ( const ScriptRequest & rq , const std : : wstring & filename )
2006-04-23 16:14:18 -07:00
{
2024-04-13 10:12:47 -07:00
if ( ! PathRestrictionMet < restriction > ( rq , filename ) )
Restrict access for Read/WriteFile functions
For security reasons planing to restrict access for ReadFile,
ReadFileLines, WriteJSONFile, ReadJSONFile, ListDirectoryFiles,
FileExists to the following folders/files:
"gui, simulation, maps, campaigns, saves/campaigns,
config/matchsettings.json, config/matchsettings.mp.json"
adding "moddata" if some mods need to access and ship custom files that
don't fit into other locations mentioned above
Differential revision: D4617
Reviewed by: @phosit
Comments by: @Stan @vladislavbelov
Fixes: #5850
This was SVN commit r27202.
2022-11-05 01:33:27 -07:00
return JS : : NullValue ( ) ;
2012-05-03 20:46:05 -07:00
CVFSFile file ;
if ( file . Load ( g_VFS , filename ) ! = PSRETURN_OK )
2014-07-14 12:52:35 -07:00
return JS : : NullValue ( ) ;
2006-04-23 16:14:18 -07:00
2012-05-03 20:46:05 -07:00
CStr contents = file . DecodeUTF8 ( ) ; // assume it's UTF-8
2006-04-23 16:14:18 -07:00
// Fix CRLF line endings. (This function will only ever be used on text files.)
contents . Replace ( " \r \n " , " \n " ) ;
2009-07-16 08:51:35 -07:00
// Decode as UTF-8
2020-11-13 05:18:22 -08:00
JS : : RootedValue ret ( rq . cx ) ;
2021-05-13 02:43:33 -07:00
Script : : ToJSVal ( rq , & ret , contents . FromUTF8 ( ) ) ;
2015-01-24 06:46:52 -08:00
return ret ;
2006-04-23 16:14:18 -07:00
}
2012-05-03 20:46:05 -07:00
// Return file contents as an array of lines. Assume file is UTF-8 encoded text.
2024-04-13 10:12:47 -07:00
template < auto & restriction >
JS : : Value ReadFileLines ( const ScriptRequest & rq , const std : : wstring & filename )
2006-04-23 16:14:18 -07:00
{
2024-04-13 10:12:47 -07:00
if ( ! PathRestrictionMet < restriction > ( rq , filename ) )
Restrict access for Read/WriteFile functions
For security reasons planing to restrict access for ReadFile,
ReadFileLines, WriteJSONFile, ReadJSONFile, ListDirectoryFiles,
FileExists to the following folders/files:
"gui, simulation, maps, campaigns, saves/campaigns,
config/matchsettings.json, config/matchsettings.mp.json"
adding "moddata" if some mods need to access and ship custom files that
don't fit into other locations mentioned above
Differential revision: D4617
Reviewed by: @phosit
Comments by: @Stan @vladislavbelov
Fixes: #5850
This was SVN commit r27202.
2022-11-05 01:33:27 -07:00
return JS : : NullValue ( ) ;
2012-05-03 20:46:05 -07:00
CVFSFile file ;
if ( file . Load ( g_VFS , filename ) ! = PSRETURN_OK )
2019-01-13 08:37:41 -08:00
return JS : : NullValue ( ) ;
2006-04-23 16:14:18 -07:00
2012-05-03 20:46:05 -07:00
CStr contents = file . DecodeUTF8 ( ) ; // assume it's UTF-8
2006-04-23 16:14:18 -07:00
// Fix CRLF line endings. (This function will only ever be used on text files.)
contents . Replace ( " \r \n " , " \n " ) ;
// split into array of strings (one per line)
2010-11-16 15:00:52 -08:00
std : : stringstream ss ( contents ) ;
2019-07-22 12:35:14 -07:00
2020-11-13 05:18:22 -08:00
JS : : RootedValue line_array ( rq . cx ) ;
2021-05-13 10:23:52 -07:00
Script : : CreateArray ( rq , & line_array ) ;
2019-07-22 12:35:14 -07:00
2006-04-23 16:14:18 -07:00
std : : string line ;
int cur_line = 0 ;
2015-01-24 06:46:52 -08:00
2010-11-16 15:00:52 -08:00
while ( std : : getline ( ss , line ) )
2006-04-23 16:14:18 -07:00
{
2009-07-16 08:51:35 -07:00
// Decode each line as UTF-8
2020-11-13 05:18:22 -08:00
JS : : RootedValue val ( rq . cx ) ;
2021-05-13 02:43:33 -07:00
Script : : ToJSVal ( rq , & val , CStr ( line ) . FromUTF8 ( ) ) ;
2021-05-13 10:23:52 -07:00
Script : : SetPropertyInt ( rq , line_array , cur_line + + , val ) ;
2006-04-23 16:14:18 -07:00
}
2019-07-22 12:35:14 -07:00
return line_array ;
2006-04-23 16:14:18 -07:00
}
2017-09-07 21:01:26 -07:00
2021-03-02 12:01:14 -08:00
// Return file contents parsed as a JS Object
2024-04-13 10:12:47 -07:00
template < auto & restriction >
JS : : Value ReadJSONFile ( const ScriptInterface & scriptInterface , const std : : wstring & filePath )
2017-09-11 15:21:34 -07:00
{
2021-03-02 12:01:14 -08:00
ScriptRequest rq ( scriptInterface ) ;
2024-04-13 10:12:47 -07:00
if ( ! PathRestrictionMet < restriction > ( rq , filePath ) )
2017-12-03 15:02:27 -08:00
return JS : : NullValue ( ) ;
2020-11-13 05:18:22 -08:00
JS : : RootedValue out ( rq . cx ) ;
2021-05-14 03:18:03 -07:00
Script : : ReadJSONFile ( rq , filePath , & out ) ;
2017-09-11 15:21:34 -07:00
return out ;
}
2021-03-02 12:01:14 -08:00
// Save given JS Object to a JSON file
2024-04-13 10:12:47 -07:00
template < auto & restriction >
void WriteJSONFile ( const ScriptInterface & scriptInterface , const std : : wstring & filePath ,
JS : : HandleValue val1 )
2017-09-11 15:21:34 -07:00
{
Improve JS Exception handling.
- Check for pending exceptions after function calls and script
executions.
- Call LOGERROR instead of JS_ReportError when there is a conversion
error in FromJSVal, since that can only be called from C++ (where JS
errors don't really make sense). Instead, C++ callers of FromJSVal
should handle the failure and, themselves, either report an error or
simply do something else.
- Wrap JS_ReportError since that makes updating it later easier.
This isn't a systematical fix since ToJSVal also ought return a boolean
for failures, and we probably should trigger errors instead of warnings
on 'implicit' conversions, rather a preparation diff.
Part of the SM52 migration, stage: SM45 compatible (actually SM52
incompatible, too).
Based on a patch by: Itms
Comments by: Vladislavbelov, Stan`
Refs #742, #4893
Differential Revision: https://code.wildfiregames.com/D3093
This was SVN commit r24187.
2020-11-15 10:29:17 -08:00
ScriptRequest rq ( scriptInterface ) ;
2024-04-13 10:12:47 -07:00
if ( ! PathRestrictionMet < restriction > ( rq , filePath ) )
Restrict access for Read/WriteFile functions
For security reasons planing to restrict access for ReadFile,
ReadFileLines, WriteJSONFile, ReadJSONFile, ListDirectoryFiles,
FileExists to the following folders/files:
"gui, simulation, maps, campaigns, saves/campaigns,
config/matchsettings.json, config/matchsettings.mp.json"
adding "moddata" if some mods need to access and ship custom files that
don't fit into other locations mentioned above
Differential revision: D4617
Reviewed by: @phosit
Comments by: @Stan @vladislavbelov
Fixes: #5850
This was SVN commit r27202.
2022-11-05 01:33:27 -07:00
return ;
2017-09-11 15:21:34 -07:00
// TODO: This is a workaround because we need to pass a MutableHandle to StringifyJSON.
2020-11-13 05:18:22 -08:00
JS : : RootedValue val ( rq . cx , val1 ) ;
2017-09-11 15:21:34 -07:00
2021-05-14 03:18:03 -07:00
std : : string str ( Script : : StringifyJSON ( rq , & val , false ) ) ;
2017-09-11 15:21:34 -07:00
VfsPath path ( filePath ) ;
WriteBuffer buf ;
buf . Append ( str . c_str ( ) , str . length ( ) ) ;
2022-03-05 09:28:00 -08:00
if ( g_VFS - > CreateFile ( path , buf . Data ( ) , buf . Size ( ) ) = = INFO : : OK )
2022-03-07 07:46:28 -08:00
{
OsPath realPath ;
g_VFS - > GetRealPath ( path , realPath , false ) ;
2022-03-05 10:30:38 -08:00
debug_printf ( " FILES| JSON data written to '%s' \n " , realPath . string8 ( ) . c_str ( ) ) ;
2022-03-07 07:46:28 -08:00
}
2022-03-05 09:28:00 -08:00
else
2022-03-07 07:46:28 -08:00
debug_printf ( " FILES| Failed to write JSON data to '%s' \n " , path . string8 ( ) . c_str ( ) ) ;
2017-09-11 15:21:34 -07:00
}
2021-03-02 12:01:14 -08:00
bool DeleteCampaignSave ( const CStrW & filePath )
2021-03-02 07:43:44 -08:00
{
OsPath realPath ;
if ( filePath . Left ( 16 ) ! = L " saves/campaigns/ " | | filePath . Right ( 12 ) ! = L " .0adcampaign " )
return false ;
return VfsFileExists ( filePath ) & &
g_VFS - > GetRealPath ( filePath , realPath ) = = INFO : : OK & &
g_VFS - > RemoveFile ( filePath ) = = INFO : : OK & &
wunlink ( realPath ) = = 0 ;
}
2023-11-19 11:19:32 -08:00
void RegisterScriptFunctions_ReadWriteAnywhere ( const ScriptRequest & rq ,
const u16 flags /*= JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT */ )
2017-09-07 21:01:26 -07:00
{
2024-04-13 10:12:47 -07:00
ScriptFunction : : Register < & BuildDirEntList < PathRestriction : : GUI > > ( rq , " ListDirectoryFiles " , flags ) ;
ScriptFunction : : Register < & FileExists < PathRestriction : : GUI > > ( rq , " FileExists " , flags ) ;
2023-11-19 11:19:32 -08:00
ScriptFunction : : Register < & GetFileSize > ( rq , " GetFileSize " , flags ) ;
2024-04-13 10:12:47 -07:00
ScriptFunction : : Register < & ReadFile < PathRestriction : : GUI > > ( rq , " ReadFile " , flags ) ;
ScriptFunction : : Register < & ReadFileLines < PathRestriction : : GUI > > ( rq , " ReadFileLines " , flags ) ;
ScriptFunction : : Register < & ReadJSONFile < PathRestriction : : GUI > > ( rq , " ReadJSONFile " , flags ) ;
ScriptFunction : : Register < & WriteJSONFile < PathRestriction : : GUI > > ( rq , " WriteJSONFile " , flags ) ;
2023-11-19 11:19:32 -08:00
ScriptFunction : : Register < & DeleteCampaignSave > ( rq , " DeleteCampaignSave " , flags ) ;
2017-11-24 09:19:16 -08:00
}
2023-11-19 11:19:32 -08:00
void RegisterScriptFunctions_ReadOnlySimulation ( const ScriptRequest & rq ,
const u16 flags /*= JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT */ )
2017-11-24 09:19:16 -08:00
{
2024-04-13 10:12:47 -07:00
ScriptFunction : : Register < & BuildDirEntList < PathRestriction : : SIMULATION > > ( rq , " ListDirectoryFiles " , flags ) ;
ScriptFunction : : Register < & FileExists < PathRestriction : : SIMULATION > > ( rq , " FileExists " , flags ) ;
ScriptFunction : : Register < & ReadJSONFile < PathRestriction : : SIMULATION > > ( rq , " ReadJSONFile " , flags ) ;
2017-12-03 15:02:27 -08:00
}
2023-11-19 11:19:32 -08:00
void RegisterScriptFunctions_ReadOnlySimulationMaps ( const ScriptRequest & rq ,
const u16 flags /*= JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT */ )
2017-12-03 15:02:27 -08:00
{
2024-04-13 10:12:47 -07:00
ScriptFunction : : Register < & BuildDirEntList < PathRestriction : : MAPS > > ( rq , " ListDirectoryFiles " , flags ) ;
ScriptFunction : : Register < & FileExists < PathRestriction : : MAPS > > ( rq , " FileExists " , flags ) ;
ScriptFunction : : Register < & ReadJSONFile < PathRestriction : : MAPS > > ( rq , " ReadJSONFile " , flags ) ;
2021-03-02 12:01:14 -08:00
}
2017-09-07 21:01:26 -07:00
}