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