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