The Battle for Wesnoth  1.15.11+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 
24 #include <algorithm>
25 #include <exception>
26 #include <string>
27 
28 #include "lua/lauxlib.h"
29 #include "lua/lua.h"
30 #include "lua/luaconf.h" // for LUAL_BUFFERSIZE
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 namespace lua_fileops {
39 static std::string get_calling_file(lua_State* L)
40 {
41  std::string currentdir;
42  lua_Debug ar;
43  if(lua_getstack(L, 1, &ar)) {
44  lua_getinfo(L, "S", &ar);
45  if(ar.source[0] == '@') {
46  std::string calling_file(ar.source + 1);
47  for(int stack_pos = 2; calling_file == "lua/package.lua"; stack_pos++) {
48  if(!lua_getstack(L, stack_pos, &ar)) {
49  return currentdir;
50  }
51  lua_getinfo(L, "S", &ar);
52  if(ar.source[0] == '@') {
53  calling_file.assign(ar.source + 1);
54  }
55  }
56  currentdir = filesystem::directory_name(calling_file);
57  }
58  }
59  return currentdir;
60 }
61 /**
62  * resolves @a filename to an absolute path
63  * @returns true if the filename was successfully resolved.
64  */
65 static bool canonical_path(std::string& filename, const std::string& currentdir)
66 {
67  if(filename.size() < 2) {
68  return false;
69  }
70  if(filename[0] == '.' && filename[1] == '/') {
71  filename = currentdir + filename.substr(1);
72  }
73  if(std::find(filename.begin(), filename.end(), '\\') != filename.end()) {
74  return false;
75  }
76  //resolve /./
77  while(true) {
78  std::size_t pos = filename.find("/./");
79  if(pos == std::string::npos) {
80  break;
81  }
82  filename = filename.replace(pos, 2, "");
83  }
84  //resolve //
85  while(true) {
86  std::size_t pos = filename.find("//");
87  if(pos == std::string::npos) {
88  break;
89  }
90  filename = filename.replace(pos, 1, "");
91  }
92  //resolve /../
93  while(true) {
94  std::size_t pos = filename.find("/..");
95  if(pos == std::string::npos) {
96  break;
97  }
98  std::size_t pos2 = filename.find_last_of('/', pos - 1);
99  if(pos2 == std::string::npos || pos2 >= pos) {
100  return false;
101  }
102  filename = filename.replace(pos2, pos- pos2 + 3, "");
103  }
104  if(filename.find("..") != std::string::npos) {
105  return false;
106  }
107  return true;
108 }
109 
110 /**
111  * resolves @a filename to an absolute path
112  * @returns true if the filename was successfully resolved.
113  */
114 static bool resolve_filename(std::string& filename, const std::string& currentdir, std::string* rel = nullptr)
115 {
116  if(!canonical_path(filename, currentdir)) {
117  return false;
118  }
119  std::string p = filesystem::get_wml_location(filename);
120  if(p.empty()) {
121  return false;
122  }
123  if(rel) {
124  *rel = filename;
125  }
126  filename = p;
127  return true;
128 }
129 
131 {
132  std::string m = luaL_checkstring(L, 1);
133  if(canonical_path(m, get_calling_file(L))) {
134  lua_push(L, m);
135  return 1;
136  } else {
137  return luaL_argerror(L, 1, "invalid path");
138  }
139 }
140 /**
141  * Checks if a file exists (not necessarily a Lua script).
142  * - Arg 1: string containing the file name.
143  * - Arg 2: if true, the file must be a real file and not a directory
144  * - Ret 1: boolean
145  */
147 {
148  std::string m = luaL_checkstring(L, 1);
149  if(!resolve_filename(m, get_calling_file(L))) {
150  lua_pushboolean(L, false);
151  } else if(luaW_toboolean(L, 2)) {
153  } else {
154  lua_pushboolean(L, true);
155  }
156  return 1;
157 }
158 
159 /**
160  * Reads a file into a string, or a directory into a list of files therein.
161  * - Arg 1: string containing the file name.
162  * - Ret 1: string
163  */
165 {
166  std::string p = luaL_checkstring(L, 1);
167 
168  if(!resolve_filename(p, get_calling_file(L))) {
169  return luaL_argerror(L, -1, "file not found");
170  }
171 
172  if(filesystem::is_directory(p)) {
173  std::vector<std::string> files, dirs;
174  filesystem::get_files_in_dir(p, &files, &dirs);
176  std::size_t ndirs = dirs.size();
177  std::copy(files.begin(), files.end(), std::back_inserter(dirs));
178  lua_push(L, dirs);
179  lua_pushnumber(L, ndirs);
180  lua_setfield(L, -2, "ndirs");
181  return 1;
182  }
183  const std::unique_ptr<std::istream> fs(filesystem::istream_file(p));
184  fs->exceptions(std::ios_base::goodbit);
185  std::size_t size = 0;
186  fs->seekg(0, std::ios::end);
187  if(!fs->good()) {
188  return luaL_error(L, "Error when reading file");
189  }
190  size = fs->tellg();
191  fs->seekg(0, std::ios::beg);
192  if(!fs->good()) {
193  return luaL_error(L, "Error when reading file");
194  }
195  luaL_Buffer b;
196  luaL_buffinit(L, &b);
197  //throws an exception if malloc failed.
198  char* out = luaL_prepbuffsize(&b, size);
199  fs->read(out, size);
200  if(fs->good()) {
201  luaL_addsize(&b, size);
202  }
203  luaL_pushresult(&b);
204  return 1;
205 }
206 
208 {
209 public:
210  lua_filestream(const std::string& fname)
211  : buff_()
213  {
214 
215  }
216 
217  static const char * lua_read_data(lua_State * /*L*/, void *data, std::size_t *size)
218  {
219  lua_filestream* lfs = static_cast<lua_filestream*>(data);
220 
221  //int startpos = lfs->pistream_->tellg();
222  lfs->pistream_->read(lfs->buff_, LUAL_BUFFERSIZE);
223  //int newpos = lfs->pistream_->tellg();
224  *size = lfs->pistream_->gcount();
225 #if 0
226  ERR_LUA << "read bytes from " << startpos << " to " << newpos << " in total " *size << " from steam\n";
227  ERR_LUA << "streamstate being "
228  << " goodbit:" << lfs->pistream_->good()
229  << " endoffile:" << lfs->pistream_->eof()
230  << " badbit:" << lfs->pistream_->bad()
231  << " failbit:" << lfs->pistream_->fail() << "\n";
232 #endif
233  return lfs->buff_;
234  }
235 
236  static int lua_loadfile(lua_State *L, const std::string& fname, const std::string& relativename)
237  {
238  lua_filestream lfs(fname);
239  //lua uses '@' to know that this is a file (as opposed to something loaded via loadstring )
240  std::string chunkname = '@' + relativename;
241  LOG_LUA << "starting to read from " << fname << "\n";
242  return lua_load(L, &lua_filestream::lua_read_data, &lfs, chunkname.c_str(), "t");
243  }
244 private:
246  const std::unique_ptr<std::istream> pistream_;
247 };
248 
249 /**
250  * Loads a Lua file and pushes the contents on the stack.
251  * - Arg 1: string containing the file name.
252  * - Ret 1: the loaded contents of the file
253  */
255 {
256  std::string p = luaL_checkstring(L, -1);
257  std::string rel;
258 
259  if(!resolve_filename(p, get_calling_file(L), &rel)) {
260  return luaL_argerror(L, -1, "file not found");
261  }
262 
263  try
264  {
265  if(lua_filestream::lua_loadfile(L, p, rel)) {
266  return lua_error(L);
267  }
268  }
269  catch(const std::exception & ex)
270  {
271  luaL_argerror(L, -1, ex.what());
272  }
273  lua_remove(L, -2); //remove the filename from the stack
274 
275  return 1;
276 }
277 
278 } // end namespace lua_fileops
#define luaL_addsize(B, s)
Definition: lauxlib.h:186
Definition: lua.h:469
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
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:34
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
#define ERR_LUA
Definition: lua_fileops.cpp:36
static bool canonical_path(std::string &filename, const std::string &currentdir)
resolves filename to an absolute path
Definition: lua_fileops.cpp:65
#define b
#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.
LUA_API void lua_pushnumber(lua_State *L, lua_Number n)
Definition: lapi.cpp:481
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
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:39
LUA_API int lua_error(lua_State *L)
Definition: lapi.cpp:1205
Standard logging facilities (interface).
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_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:88
#define luaL_checkstring(L, n)
Definition: lauxlib.h:138