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