The Battle for Wesnoth  1.17.0-dev
lua_fileops.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2021
3  by Chris Beck <render787@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
17 
18 #include "filesystem.hpp"
19 #include "game_config.hpp" //for game_config::debug_lua
20 #include "game_errors.hpp"
21 #include "log.hpp"
22 #include "scripting/lua_common.hpp" // for chat_message, luaW_pcall
23 #include "scripting/push_check.hpp"
24 #include "picture.hpp"
25 #include "sdl/surface.hpp"
26 
27 #include <algorithm>
28 #include <exception>
29 #include <string>
30 
31 #include "lua/lauxlib.h"
32 #include "lua/lua.h"
33 #include "lua/luaconf.h" // for LUAL_BUFFERSIZE
34 
35 static lg::log_domain log_scripting_lua("scripting/lua");
36 #define DBG_LUA LOG_STREAM(debug, log_scripting_lua)
37 #define LOG_LUA LOG_STREAM(info, log_scripting_lua)
38 #define WRN_LUA LOG_STREAM(warn, log_scripting_lua)
39 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
40 
41 /**
42 * Gets the dimension of an image.
43 * - Arg 1: string.
44 * - Ret 1: width.
45 * - Ret 2: height.
46 */
48 {
49  char const *m = luaL_checkstring(L, 1);
50  image::locator img(m);
51  if(!img.file_exists()) return 0;
52  surface s = get_image(img);
53  lua_pushinteger(L, s->w);
54  lua_pushinteger(L, s->h);
55  return 2;
56 }
57 
58 /**
59  * Returns true if an asset with the given path can be found in the binary paths.
60  * - Arg 1: asset type (generally one of images, sounds, music, maps)
61  * - Arg 2: relative path
62  */
63 static int intf_have_asset(lua_State* L)
64 {
65  std::string type = luaL_checkstring(L, 1), name = luaL_checkstring(L, 2);
67  return 1;
68 }
69 
70 /**
71  * Given an asset path relative to binary paths, resolves to an absolute
72  * asset path starting from data/
73  * - Arg 1: asset type
74  * - Arg 2: relative path
75  */
77 {
78  std::string type = luaL_checkstring(L, 1), name = luaL_checkstring(L, 2);
80  return 1;
81 }
82 
83 namespace lua_fileops {
84 static std::string get_calling_file(lua_State* L)
85 {
86  std::string currentdir;
87  lua_Debug ar;
88  if(lua_getstack(L, 1, &ar)) {
89  lua_getinfo(L, "S", &ar);
90  if(ar.source[0] == '@') {
91  std::string calling_file(ar.source + 1);
92  for(int stack_pos = 2; calling_file == "lua/package.lua"; stack_pos++) {
93  if(!lua_getstack(L, stack_pos, &ar)) {
94  return currentdir;
95  }
96  lua_getinfo(L, "S", &ar);
97  if(ar.source[0] == '@') {
98  calling_file.assign(ar.source + 1);
99  }
100  }
101  currentdir = filesystem::directory_name(calling_file);
102  }
103  }
104  return currentdir;
105 }
106 /**
107  * resolves @a filename to an absolute path
108  * @returns true if the filename was successfully resolved.
109  */
110 static bool canonical_path(std::string& filename, const std::string& currentdir)
111 {
112  if(filename.size() < 2) {
113  return false;
114  }
115  if(filename[0] == '.' && filename[1] == '/') {
116  filename = currentdir + filename.substr(1);
117  }
118  if(std::find(filename.begin(), filename.end(), '\\') != filename.end()) {
119  return false;
120  }
121  //resolve /./
122  while(true) {
123  std::size_t pos = filename.find("/./");
124  if(pos == std::string::npos) {
125  break;
126  }
127  filename = filename.replace(pos, 2, "");
128  }
129  //resolve //
130  while(true) {
131  std::size_t pos = filename.find("//");
132  if(pos == std::string::npos) {
133  break;
134  }
135  filename = filename.replace(pos, 1, "");
136  }
137  //resolve /../
138  while(true) {
139  std::size_t pos = filename.find("/..");
140  if(pos == std::string::npos) {
141  break;
142  }
143  std::size_t pos2 = filename.find_last_of('/', pos - 1);
144  if(pos2 == std::string::npos || pos2 >= pos) {
145  return false;
146  }
147  filename = filename.replace(pos2, pos- pos2 + 3, "");
148  }
149  if(filename.find("..") != std::string::npos) {
150  return false;
151  }
152  return true;
153 }
154 
155 /**
156  * resolves @a filename to an absolute path
157  * @returns true if the filename was successfully resolved.
158  */
159 static bool resolve_filename(std::string& filename, const std::string& currentdir, std::string* rel = nullptr)
160 {
161  if(!canonical_path(filename, currentdir)) {
162  return false;
163  }
164  std::string p = filesystem::get_wml_location(filename);
165  if(p.empty()) {
166  return false;
167  }
168  if(rel) {
169  *rel = filename;
170  }
171  filename = p;
172  return true;
173 }
174 
176 {
177  std::string m = luaL_checkstring(L, 1);
178  if(canonical_path(m, get_calling_file(L))) {
179  lua_push(L, m);
180  return 1;
181  } else {
182  return luaL_argerror(L, 1, "invalid path");
183  }
184 }
185 /**
186  * Checks if a file exists (not necessarily a Lua script).
187  * - Arg 1: string containing the file name.
188  * - Arg 2: if true, the file must be a real file and not a directory
189  * - Ret 1: boolean
190  */
192 {
193  std::string m = luaL_checkstring(L, 1);
194  if(!resolve_filename(m, get_calling_file(L))) {
195  lua_pushboolean(L, false);
196  } else if(luaW_toboolean(L, 2)) {
198  } else {
199  lua_pushboolean(L, true);
200  }
201  return 1;
202 }
203 
204 /**
205  * Reads a file into a string, or a directory into a list of files therein.
206  * - Arg 1: string containing the file name.
207  * - Ret 1: string
208  */
210 {
211  std::string p = luaL_checkstring(L, 1);
212 
213  if(!resolve_filename(p, get_calling_file(L))) {
214  return luaL_argerror(L, -1, "file not found");
215  }
216 
217  if(filesystem::is_directory(p)) {
218  std::vector<std::string> files, dirs;
219  filesystem::get_files_in_dir(p, &files, &dirs);
221  std::size_t ndirs = dirs.size();
222  std::copy(files.begin(), files.end(), std::back_inserter(dirs));
223  lua_push(L, dirs);
224  lua_pushnumber(L, ndirs);
225  lua_setfield(L, -2, "ndirs");
226  return 1;
227  }
228  const std::unique_ptr<std::istream> fs(filesystem::istream_file(p));
229  fs->exceptions(std::ios_base::goodbit);
230  std::size_t size = 0;
231  fs->seekg(0, std::ios::end);
232  if(!fs->good()) {
233  return luaL_error(L, "Error when reading file");
234  }
235  size = fs->tellg();
236  fs->seekg(0, std::ios::beg);
237  if(!fs->good()) {
238  return luaL_error(L, "Error when reading file");
239  }
240  luaL_Buffer b;
241  luaL_buffinit(L, &b);
242  //throws an exception if malloc failed.
243  char* out = luaL_prepbuffsize(&b, size);
244  fs->read(out, size);
245  if(fs->good()) {
246  luaL_addsize(&b, size);
247  }
248  luaL_pushresult(&b);
249  return 1;
250 }
251 
253 {
254 public:
255  lua_filestream(const std::string& fname)
256  : buff_()
258  {
259 
260  }
261 
262  static const char * lua_read_data(lua_State * /*L*/, void *data, std::size_t *size)
263  {
264  lua_filestream* lfs = static_cast<lua_filestream*>(data);
265 
266  //int startpos = lfs->pistream_->tellg();
267  lfs->pistream_->read(lfs->buff_, LUAL_BUFFERSIZE);
268  //int newpos = lfs->pistream_->tellg();
269  *size = lfs->pistream_->gcount();
270 #if 0
271  ERR_LUA << "read bytes from " << startpos << " to " << newpos << " in total " *size << " from steam\n";
272  ERR_LUA << "streamstate being "
273  << " goodbit:" << lfs->pistream_->good()
274  << " endoffile:" << lfs->pistream_->eof()
275  << " badbit:" << lfs->pistream_->bad()
276  << " failbit:" << lfs->pistream_->fail() << "\n";
277 #endif
278  return lfs->buff_;
279  }
280 
281  static int lua_loadfile(lua_State *L, const std::string& fname, const std::string& relativename)
282  {
283  lua_filestream lfs(fname);
284  //lua uses '@' to know that this is a file (as opposed to something loaded via loadstring )
285  std::string chunkname = '@' + relativename;
286  LOG_LUA << "starting to read from " << fname << "\n";
287  return lua_load(L, &lua_filestream::lua_read_data, &lfs, chunkname.c_str(), "t");
288  }
289 private:
291  const std::unique_ptr<std::istream> pistream_;
292 };
293 
294 /**
295  * Loads a Lua file and pushes the contents on the stack.
296  * - Arg 1: string containing the file name.
297  * - Ret 1: the loaded contents of the file
298  */
300 {
301  std::string p = luaL_checkstring(L, -1);
302  std::string rel;
303 
304  if(!resolve_filename(p, get_calling_file(L), &rel)) {
305  return luaL_argerror(L, -1, "file not found");
306  }
307 
308  try
309  {
310  if(lua_filestream::lua_loadfile(L, p, rel)) {
311  return lua_error(L);
312  }
313  }
314  catch(const std::exception & ex)
315  {
316  luaL_argerror(L, -1, ex.what());
317  }
318  lua_remove(L, -2); //remove the filename from the stack
319 
320  return 1;
321 }
322 
324 {
325  static luaL_Reg const callbacks[] {
326  { "have_file", &lua_fileops::intf_have_file },
327  { "read_file", &lua_fileops::intf_read_file },
328  { "canonical_path", &lua_fileops::intf_canonical_path },
329  { "image_size", &intf_get_image_size },
330  { "have_asset", &intf_have_asset },
331  { "resolve_asset", &intf_resolve_asset },
332  { nullptr, nullptr }
333  };
334  lua_newtable(L);
335  luaL_setfuncs(L, callbacks, 0);
336  return 1;
337 }
338 
339 } // end namespace lua_fileops
#define luaL_addsize(B, s)
Definition: lauxlib.h:186
Definition: lua.h:469
surface get_image(const image::locator &i_locator, TYPE type)
Caches and returns an image.
Definition: picture.cpp:816
int intf_read_file(lua_State *L)
Reads a file into a string, or a directory into a list of files therein.
int intf_have_file(lua_State *L)
Checks if a file exists (not necessarily a Lua script).
LUALIB_API char * luaL_prepbuffsize(luaL_Buffer *B, size_t sz)
Definition: lauxlib.cpp:565
int intf_canonical_path(lua_State *L)
lua_filestream(const std::string &fname)
LUA_API void lua_pushboolean(lua_State *L, int b)
Definition: lapi.cpp:581
const std::unique_ptr< std::istream > pistream_
LUALIB_API void luaL_pushresult(luaL_Buffer *B)
Definition: lauxlib.cpp:584
int luaW_open(lua_State *L)
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
#define lua_remove(L, idx)
Definition: lua.h:391
const char * source
Definition: lua.h:474
#define LOG_LUA
Definition: lua_fileops.cpp:37
std::string get_binary_file_location(const std::string &type, const std::string &filename)
Returns a complete path to the actual file of a given type or an empty string if the file isn&#39;t prese...
LUA_API int lua_getstack(lua_State *L, int level, lua_Debug *ar)
Definition: ldebug.cpp:164
void lua_push(lua_State *L, const T &val)
Definition: push_check.hpp:385
static const char * lua_read_data(lua_State *, void *data, std::size_t *size)
void remove_blacklisted_files_and_dirs(std::vector< std::string > &files, std::vector< std::string > &directories) const
std::string get_independent_binary_file_path(const std::string &type, const std::string &filename)
Returns an asset path to filename for binary path-independent use in saved games. ...
#define ERR_LUA
Definition: lua_fileops.cpp:39
static bool canonical_path(std::string &filename, const std::string &currentdir)
resolves filename to an absolute path
#define b
static int intf_get_image_size(lua_State *L)
Gets the dimension of an image.
Definition: lua_fileops.cpp:47
#define LUAL_BUFFERSIZE
Definition: luaconf.h:734
LUALIB_API int luaL_argerror(lua_State *L, int arg, const char *extramsg)
Definition: lauxlib.cpp:175
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
bool luaW_toboolean(lua_State *L, int n)
Definition: lua_common.cpp:972
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs, name_mode mode, filter_mode filter, reorder_mode reorder, file_tree_checksum *checksum)
Get a list of all files and/or directories in a given directory.
Definition: filesystem.cpp:349
LUALIB_API void luaL_buffinit(lua_State *L, luaL_Buffer *B)
Definition: lauxlib.cpp:620
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
#define lua_newtable(L)
Definition: lua.h:366
LUA_API void lua_pushnumber(lua_State *L, lua_Number n)
Definition: lapi.cpp:481
static int intf_resolve_asset(lua_State *L)
Given an asset path relative to binary paths, resolves to an absolute asset path starting from data/...
Definition: lua_fileops.cpp:76
Generic locator abstracting the location of an image.
Definition: picture.hpp:60
static int lua_loadfile(lua_State *L, const std::string &fname, const std::string &relativename)
std::string get_wml_location(const std::string &filename, const std::string &current_dir)
Returns a complete path to the actual WML file or directory or an empty string if the file isn&#39;t pres...
LUA_API int lua_getinfo(lua_State *L, const char *what, lua_Debug *ar)
Definition: ldebug.cpp:386
mock_party p
static map_location::DIRECTION s
LUA_API int lua_load(lua_State *L, lua_Reader reader, void *data, const char *chunkname, const char *mode)
Definition: lapi.cpp:1054
Declarations for File-IO.
int load_file(lua_State *L)
Loads a Lua file and pushes the contents on the stack.
static lg::log_domain log_scripting_lua("scripting/lua")
LUALIB_API int luaL_error(lua_State *L, const char *fmt,...)
Definition: lauxlib.cpp:234
static std::string get_calling_file(lua_State *L)
Definition: lua_fileops.cpp:84
LUA_API int lua_error(lua_State *L)
Definition: lapi.cpp:1205
static int intf_have_asset(lua_State *L)
Returns true if an asset with the given path can be found in the binary paths.
Definition: lua_fileops.cpp:63
Standard logging facilities (interface).
LUALIB_API void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup)
Definition: lauxlib.cpp:904
static bool resolve_filename(std::string &filename, const std::string &currentdir, std::string *rel=nullptr)
resolves filename to an absolute path
LUA_API void lua_pushinteger(lua_State *L, lua_Integer n)
Definition: lapi.cpp:489
bool file_exists() const
Tests whether the file the locator points at exists.
Definition: picture.cpp:659
LUA_API void lua_setfield(lua_State *L, int idx, const char *k)
Definition: lapi.cpp:837
std::string directory_name(const std::string &file)
Returns the directory name of a file, with filename stripped.
char buff_[LUAL_BUFFERSIZE]
static const blacklist_pattern_list default_blacklist
Definition: filesystem.hpp:91
#define luaL_checkstring(L, n)
Definition: lauxlib.h:138