The Battle for Wesnoth  1.19.4+dev
lua_fileops.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2024
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 "log.hpp"
20 #include "scripting/lua_common.hpp" // for chat_message, luaW_pcall
21 #include "scripting/push_check.hpp"
22 #include "picture.hpp"
23 #include "sdl/point.hpp"
24 
25 #include <algorithm>
26 #include <exception>
27 #include <string>
28 
29 static lg::log_domain log_scripting_lua("scripting/lua");
30 #define DBG_LUA LOG_STREAM(debug, log_scripting_lua)
31 #define LOG_LUA LOG_STREAM(info, log_scripting_lua)
32 #define WRN_LUA LOG_STREAM(warn, log_scripting_lua)
33 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
34 
35 /**
36 * Gets the dimension of an image.
37 * - Arg 1: string.
38 * - Ret 1: width.
39 * - Ret 2: height.
40 */
41 static int intf_get_image_size(lua_State *L)
42 {
43  char const *m = luaL_checkstring(L, 1);
44  image::locator img(m);
45  if(!image::exists(img)) return 0;
46  const point s = get_size(img);
47  lua_pushinteger(L, s.x);
48  lua_pushinteger(L, s.y);
49  return 2;
50 }
51 
52 /**
53  * Returns true if an asset with the given path can be found in the binary paths.
54  * - Arg 1: asset type (generally one of images, sounds, music, maps)
55  * - Arg 2: relative path
56  */
57 static int intf_have_asset(lua_State* L)
58 {
59  std::string type = luaL_checkstring(L, 1), name = luaL_checkstring(L, 2);
60  lua_pushboolean(L, filesystem::get_binary_file_location(type, name).has_value());
61  return 1;
62 }
63 
64 /**
65  * Given an asset path relative to binary paths, resolves to an absolute
66  * asset path starting from data/
67  * - Arg 1: asset type
68  * - Arg 2: relative path
69  */
70 static int intf_resolve_asset(lua_State* L)
71 {
72  std::string type = luaL_checkstring(L, 1), name = luaL_checkstring(L, 2);
74  return 1;
75 }
76 
77 namespace lua_fileops {
78 static std::string get_calling_file(lua_State* L)
79 {
80  std::string currentdir;
81  lua_Debug ar;
82  if(lua_getstack(L, 1, &ar)) {
83  lua_getinfo(L, "S", &ar);
84  if(ar.source[0] == '@') {
85  std::string calling_file(ar.source + 1);
86  for(int stack_pos = 2; calling_file == "lua/package.lua"; stack_pos++) {
87  if(!lua_getstack(L, stack_pos, &ar)) {
88  return currentdir;
89  }
90  lua_getinfo(L, "S", &ar);
91  if(ar.source[0] == '@') {
92  calling_file.assign(ar.source + 1);
93  }
94  }
95  currentdir = filesystem::directory_name(calling_file);
96  }
97  }
98  return currentdir;
99 }
100 /**
101  * resolves @a filename to an absolute path
102  * @returns true if the filename was successfully resolved.
103  */
104 static bool canonical_path(std::string& filename, const std::string& currentdir)
105 {
106  if(filename.size() < 2) {
107  return false;
108  }
109  if(filename[0] == '.' && filename[1] == '/') {
110  filename = currentdir + filename.substr(1);
111  }
112  if(std::find(filename.begin(), filename.end(), '\\') != filename.end()) {
113  return false;
114  }
115  //resolve /./
116  while(true) {
117  std::size_t pos = filename.find("/./");
118  if(pos == std::string::npos) {
119  break;
120  }
121  filename = filename.replace(pos, 2, "");
122  }
123  //resolve //
124  while(true) {
125  std::size_t pos = filename.find("//");
126  if(pos == std::string::npos) {
127  break;
128  }
129  filename = filename.replace(pos, 1, "");
130  }
131  //resolve /../
132  while(true) {
133  std::size_t pos = filename.find("/..");
134  if(pos == std::string::npos) {
135  break;
136  }
137  std::size_t pos2 = filename.find_last_of('/', pos - 1);
138  if(pos2 == std::string::npos || pos2 >= pos) {
139  return false;
140  }
141  filename = filename.replace(pos2, pos- pos2 + 3, "");
142  }
143  if(filename.find("..") != std::string::npos) {
144  return false;
145  }
146  return true;
147 }
148 
149 /**
150  * resolves @a filename to an absolute path
151  * @returns true if the filename was successfully resolved.
152  */
153 static bool resolve_filename(std::string& filename, const std::string& currentdir, std::string* rel = nullptr)
154 {
155  if(!canonical_path(filename, currentdir)) {
156  return false;
157  }
159  if(!p) {
160  return false;
161  }
162  if(rel) {
163  *rel = filename;
164  }
165  filename = p.value();
166  return true;
167 }
168 
169 int intf_canonical_path(lua_State *L)
170 {
171  std::string m = luaL_checkstring(L, 1);
172  if(canonical_path(m, get_calling_file(L))) {
173  lua_push(L, m);
174  return 1;
175  } else {
176  return luaL_argerror(L, 1, "invalid path");
177  }
178 }
179 /**
180  * Checks if a file exists (not necessarily a Lua script).
181  * - Arg 1: string containing the file name.
182  * - Arg 2: if true, the file must be a real file and not a directory
183  * - Ret 1: boolean
184  */
185 int intf_have_file(lua_State *L)
186 {
187  std::string m = luaL_checkstring(L, 1);
188  if(!resolve_filename(m, get_calling_file(L))) {
189  lua_pushboolean(L, false);
190  } else if(luaW_toboolean(L, 2)) {
191  lua_pushboolean(L, !filesystem::is_directory(m));
192  } else {
193  lua_pushboolean(L, true);
194  }
195  return 1;
196 }
197 
198 /**
199  * Reads a file into a string, or a directory into a list of files therein.
200  * - Arg 1: string containing the file name.
201  * - Ret 1: string
202  */
203 int intf_read_file(lua_State *L)
204 {
205  std::string p = luaL_checkstring(L, 1);
206 
208  return luaL_argerror(L, -1, "file not found");
209  }
210 
212  std::vector<std::string> files, dirs;
213  filesystem::get_files_in_dir(p, &files, &dirs);
215  std::size_t ndirs = dirs.size();
216  std::copy(files.begin(), files.end(), std::back_inserter(dirs));
217  lua_push(L, dirs);
218  lua_pushnumber(L, ndirs);
219  lua_setfield(L, -2, "ndirs");
220  return 1;
221  }
222  const std::unique_ptr<std::istream> fs(filesystem::istream_file(p));
223  fs->exceptions(std::ios_base::goodbit);
224  std::size_t size = 0;
225  fs->seekg(0, std::ios::end);
226  if(!fs->good()) {
227  return luaL_error(L, "Error when reading file");
228  }
229  size = fs->tellg();
230  fs->seekg(0, std::ios::beg);
231  if(!fs->good()) {
232  return luaL_error(L, "Error when reading file");
233  }
234  luaL_Buffer b;
235  luaL_buffinit(L, &b);
236  //throws an exception if malloc failed.
237  char* out = luaL_prepbuffsize(&b, size);
238  fs->read(out, size);
239  if(fs->good()) {
240  luaL_addsize(&b, size);
241  }
242  luaL_pushresult(&b);
243  return 1;
244 }
245 
247 {
248 public:
249  lua_filestream(const std::string& fname)
250  : buff_()
252  {
253 
254  }
255 
256  static const char * lua_read_data(lua_State * /*L*/, void *data, std::size_t *size)
257  {
258  lua_filestream* lfs = static_cast<lua_filestream*>(data);
259 
260  //int startpos = lfs->pistream_->tellg();
261  lfs->pistream_->read(lfs->buff_, luaL_buffersize);
262  //int newpos = lfs->pistream_->tellg();
263  *size = lfs->pistream_->gcount();
264 #if 0
265  ERR_LUA << "read bytes from " << startpos << " to " << newpos << " in total " *size << " from steam";
266  ERR_LUA << "streamstate being "
267  << " goodbit:" << lfs->pistream_->good()
268  << " endoffile:" << lfs->pistream_->eof()
269  << " badbit:" << lfs->pistream_->bad()
270  << " failbit:" << lfs->pistream_->fail();
271 #endif
272  return lfs->buff_;
273  }
274 
275  static int lua_loadfile(lua_State *L, const std::string& fname, const std::string& relativename)
276  {
277  lua_filestream lfs(fname);
278  //lua uses '@' to know that this is a file (as opposed to something loaded via loadstring )
279  std::string chunkname = '@' + relativename;
280  LOG_LUA << "starting to read from " << fname;
281  return lua_load(L, &lua_filestream::lua_read_data, &lfs, chunkname.c_str(), "t");
282  }
283 private:
285  const std::unique_ptr<std::istream> pistream_;
286 };
287 
288 /**
289  * Loads a Lua file and pushes the contents on the stack.
290  * - Arg 1: string containing the file name.
291  * - Ret 1: the loaded contents of the file
292  */
293 int load_file(lua_State *L)
294 {
295  std::string p = luaL_checkstring(L, -1);
296  std::string rel;
297 
298  if(!resolve_filename(p, get_calling_file(L), &rel)) {
299  return luaL_argerror(L, -1, "file not found");
300  }
301 
302  try
303  {
304  if(lua_filestream::lua_loadfile(L, p, rel)) {
305  return lua_error(L);
306  }
307  }
308  catch(const std::exception & ex)
309  {
310  luaL_argerror(L, -1, ex.what());
311  }
312  lua_remove(L, -2); //remove the filename from the stack
313 
314  return 1;
315 }
316 
317 int luaW_open(lua_State* L)
318 {
319  static luaL_Reg const callbacks[] {
320  { "have_file", &lua_fileops::intf_have_file },
321  { "read_file", &lua_fileops::intf_read_file },
322  { "canonical_path", &lua_fileops::intf_canonical_path },
323  { "image_size", &intf_get_image_size },
324  { "have_asset", &intf_have_asset },
325  { "resolve_asset", &intf_resolve_asset },
326  { nullptr, nullptr }
327  };
328  lua_newtable(L);
329  luaL_setfuncs(L, callbacks, 0);
330  return 1;
331 }
332 
333 } // end namespace lua_fileops
void remove_blacklisted_files_and_dirs(std::vector< std::string > &files, std::vector< std::string > &directories) const
Generic locator abstracting the location of an image.
Definition: picture.hpp:59
static const char * lua_read_data(lua_State *, void *data, std::size_t *size)
lua_filestream(const std::string &fname)
static int lua_loadfile(lua_State *L, const std::string &fname, const std::string &relativename)
char buff_[luaL_buffersize]
const std::unique_ptr< std::istream > pistream_
Declarations for File-IO.
Standard logging facilities (interface).
bool luaW_toboolean(lua_State *L, int n)
Definition: lua_common.cpp:998
#define ERR_LUA
Definition: lua_fileops.cpp:33
static lg::log_domain log_scripting_lua("scripting/lua")
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:57
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:70
#define LOG_LUA
Definition: lua_fileops.cpp:31
static int intf_get_image_size(lua_State *L)
Gets the dimension of an image.
Definition: lua_fileops.cpp:41
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
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:444
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
utils::optional< std::string > get_wml_location(const std::string &path, const utils::optional< std::string > &current_dir)
Returns a translated path to the actual file or directory, if it exists.
utils::optional< 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, if it exists.
std::string directory_name(const std::string &file)
Returns the directory name of a file, with filename stripped.
utils::optional< 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.
const blacklist_pattern_list default_blacklist
Definition: filesystem.cpp:264
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:818
point get_size(const locator &i_locator, bool skip_cache)
Returns the width and height of an image.
Definition: picture.cpp:777
int luaW_open(lua_State *L)
static bool canonical_path(std::string &filename, const std::string &currentdir)
resolves filename to an absolute path
int load_file(lua_State *L)
Loads a Lua file and pushes the contents on the stack.
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).
static std::string get_calling_file(lua_State *L)
Definition: lua_fileops.cpp:78
int intf_canonical_path(lua_State *L)
static bool resolve_filename(std::string &filename, const std::string &currentdir, std::string *rel=nullptr)
resolves filename to an absolute path
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
std::string_view data
Definition: picture.cpp:178
void lua_push(lua_State *L, const T &val)
Definition: push_check.hpp:425
std::string filename
Filename.
Holds a 2D point.
Definition: point.hpp:25
mock_party p
static map_location::DIRECTION s
constexpr int luaL_buffersize
#define b