aboutsummaryrefslogtreecommitdiff
path: root/src/script/lua_api/l_http.cpp
blob: 8bd39b6ed158f41ce1aa2d70fd3fe4d38b1c5e39 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "lua_api/l_internal.h"
#include "common/c_converter.h"
#include "common/c_content.h"
#include "lua_api/l_http.h"
#include "httpfetch.h"
#include "settings.h"
#include "debug.h"
#include "log.h"

#include <algorithm>
#include <iomanip>
#include <cctype>

#define HTTP_API(name) \
	lua_pushstring(L, #name); \
	lua_pushcfunction(L, l_http_##name); \
	lua_settable(L, -3);

#if USE_CURL
void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req)
{
	luaL_checktype(L, 1, LUA_TTABLE);

	req.caller = httpfetch_caller_alloc_secure();
	getstringfield(L, 1, "url", req.url);
	lua_getfield(L, 1, "user_agent");
	if (lua_isstring(L, -1))
		req.useragent = getstringfield_default(L, 1, "user_agent", "");
	lua_pop(L, 1);
	req.multipart = getboolfield_default(L, 1, "multipart", false);
	req.timeout = getintfield_default(L, 1, "timeout", 3) * 1000;

	// post_data: if table, post form data, otherwise raw data
	lua_getfield(L, 1, "post_data");
	if (lua_istable(L, 2)) {
		lua_pushnil(L);
		while (lua_next(L, 2) != 0)
		{
			req.post_fields[luaL_checkstring(L, -2)] = luaL_checkstring(L, -1);
			lua_pop(L, 1);
		}
	} else if (lua_isstring(L, 2)) {
		req.post_data = lua_tostring(L, 2);
	}
	lua_pop(L, 1);

	lua_getfield(L, 1, "extra_headers");
	if (lua_istable(L, 2)) {
		lua_pushnil(L);
		while (lua_next(L, 2) != 0)
		{
			const char *header = luaL_checkstring(L, -1);
			req.extra_headers.push_back(header);
			lua_pop(L, 1);
		}
	}
	lua_pop(L, 1);
}

void ModApiHttp::push_http_fetch_result(lua_State *L, HTTPFetchResult &res, bool completed)
{
	lua_newtable(L);
	setboolfield(L, -1, "succeeded", res.succeeded);
	setboolfield(L, -1, "timeout", res.timeout);
	setboolfield(L, -1, "completed", completed);
	setintfield(L, -1, "code", res.response_code);
	setstringfield(L, -1, "data", res.data.c_str());
}

// http_api.fetch_async(HTTPRequest definition)
int ModApiHttp::l_http_fetch_async(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;

	HTTPFetchRequest req;
	read_http_fetch_request(L, req);

	actionstream << "Mod performs HTTP request with URL " << req.url << std::endl;
	httpfetch_async(req);

	// Convert handle to hex string since lua can't handle 64-bit integers
	std::stringstream handle_conversion_stream;
	handle_conversion_stream << std::hex << req.caller;
	std::string caller_handle(handle_conversion_stream.str());

	lua_pushstring(L, caller_handle.c_str());
	return 1;
}

// http_api.fetch_async_get(handle)
int ModApiHttp::l_http_fetch_async_get(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;

	std::string handle_str = luaL_checkstring(L, 1);

	// Convert hex string back to 64-bit handle
	u64 handle;
	std::stringstream handle_conversion_stream;
	handle_conversion_stream << std::hex << handle_str;
	handle_conversion_stream >> handle;

	HTTPFetchResult res;
	bool completed = httpfetch_async_get(handle, res);

	push_http_fetch_result(L, res, completed);

	return 1;
}

int ModApiHttp::l_request_http_api(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;

	// We have to make sure that this function is being called directly by
	// a mod, otherwise a malicious mod could override this function and
	// steal its return value.
	lua_Debug info;

	// Make sure there's only one item below this function on the stack...
	if (lua_getstack(L, 2, &info)) {
		return 0;
	}
	FATAL_ERROR_IF(!lua_getstack(L, 1, &info), "lua_getstack() failed");
	FATAL_ERROR_IF(!lua_getinfo(L, "S", &info), "lua_getinfo() failed");

	// ...and that that item is the main file scope.
	if (strcmp(info.what, "main") != 0) {
		return 0;
	}

	// Mod must be listed in secure.http_mods or secure.trusted_mods
	lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME);
	if (!lua_isstring(L, -1)) {
		return 0;
	}

	const char *mod_name = lua_tostring(L, -1);
	std::string http_mods = g_settings->get("secure.http_mods");
	http_mods.erase(std::remove(http_mods.begin(), http_mods.end(), ' '), http_mods.end());
	std::vector<std::string> mod_list_http = str_split(http_mods, ',');

	std::string trusted_mods = g_settings->get("secure.trusted_mods");
	trusted_mods.erase(std::remove(trusted_mods.begin(), trusted_mods.end(), ' '), trusted_mods.end());
	std::vector<std::string> mod_list_trusted = str_split(trusted_mods, ',');

	mod_list_http.insert(mod_list_http.end(), mod_list_trusted.begin(), mod_list_trusted.end());
	if (std::find(mod_list_http.begin(), mod_list_http.end(), mod_name) == mod_list_http.end()) {
		lua_pushnil(L);
		return 1;
	}

	lua_getglobal(L, "core");
	lua_getfield(L, -1, "http_add_fetch");

	lua_newtable(L);
	HTTP_API(fetch_async);
	HTTP_API(fetch_async_get);

	// Stack now looks like this:
	// <core.http_add_fetch> <table with fetch_async, fetch_async_get>
	// Now call core.http_add_fetch to append .fetch(request, callback) to table
	lua_call(L, 1, 1);

	return 1;
}
#endif

void ModApiHttp::Initialize(lua_State *L, int top)
{
#if USE_CURL
	API_FCT(request_http_api);
#endif
}
class="hl slc">--per second, not divided by number of wagons advtrains.train_roll_force=0.5--per second, not divided by number of wagons, acceleration when rolling without brake advtrains.train_emerg_force=10--for emergency brakes(when going off track) advtrains.audit_interval=10 advtrains.save_and_audit_timer=advtrains.audit_interval minetest.register_globalstep(function(dtime_mt) --limit dtime: if trains move too far in one step, automation may cause stuck and wrongly braking trains local dtime=dtime_mt if dtime>0.2 then atprint("Limiting dtime to 0.2!") dtime=0.2 end advtrains.save_and_audit_timer=advtrains.save_and_audit_timer-dtime if advtrains.save_and_audit_timer<=0 then local t=os.clock() --save advtrains.save() advtrains.save_and_audit_timer=advtrains.audit_interval atprintbm("saving", t) end --regular train step -- do in two steps: -- a: predict path and add all nodes to the advtrains.detector.on_node table -- b: check for collisions based on these data -- (and more) local t=os.clock() advtrains.detector.on_node={} for k,v in pairs(advtrains.trains) do advtrains.train_step_a(k, v, dtime) end for k,v in pairs(advtrains.trains) do advtrains.train_step_b(k, v, dtime) end atprintbm("trainsteps", t) endstep() end) --[[ train step structure: - legacy stuff - preparing the initial path and creating index - setting node coverage old indices - handle velocity influences: - off-track - atc - player controls - environment collision - update index = move - create path - update node coverage - do less important stuff such as checking trainpartload or removing -- break -- - handle train collisions ]] function advtrains.train_step_a(id, train, dtime) --- 1. LEGACY STUFF --- if not train.drives_on or not train.max_speed then advtrains.update_trainpart_properties(id) end --TODO check for all vars to be present if not train.velocity then train.velocity=0 end if not train.movedir or (train.movedir~=1 and train.movedir~=-1) then train.movedir=1 end --- 2. prepare initial path and index if needed --- if not train.index then train.index=0 end if not train.path or #train.path<2 then if not train.last_pos then --no chance to recover atprint("train hasn't saved last-pos, removing train.") advtrains.trains[id]=nil return false end local node_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(train.last_pos), train.drives_on) if node_ok==nil then --block not loaded, do nothing atprint("last_pos not available") return nil elseif node_ok==false then atprint("no track here, (fail) removing train.") advtrains.trains[id]=nil return false end if not train.last_pos_prev then --no chance to recover atprint("train hasn't saved last-pos_prev, removing train.") advtrains.trains[id]=nil return false end local prevnode_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(train.last_pos_prev), train.drives_on) if prevnode_ok==nil then --block not loaded, do nothing atprint("prev not available") return nil elseif prevnode_ok==false then atprint("no track at prev, (fail) removing train.") advtrains.trains[id]=nil return false end train.index=(train.restore_add_index or 0)+(train.savedpos_off_track_index_offset or 0) --restore_add_index is set by save() to prevent trains hopping to next round index. should be between -0.5 and 0.5 --savedpos_off_track_index_offset is set if train went off track. see below. train.path={} train.path_dist={} train.path[0]=train.last_pos train.path[-1]=train.last_pos_prev train.path_dist[-1]=vector.distance(train.last_pos, train.last_pos_prev) train.path_extent_min=-1 train.path_extent_max=0 end --- 2a. set train.end_index which is required in different places, IF IT IS NOT SET YET by STMT afterwards. --- --- table entry to avoid triple recalculation --- if not train.end_index then train.end_index=advtrains.get_train_end_index(train) end --- 2b. set node coverage old indices --- train.detector_old_index = math.floor(train.index) train.detector_old_end_index = math.floor(train.end_index) --- 3. handle velocity influences --- local train_moves=(train.velocity~=0) if train.recently_collided_with_env then train.tarvelocity=0 if not train_moves then train.recently_collided_with_env=false--reset status when stopped end end if train.locomotives_in_train==0 then train.tarvelocity=0 end --apply off-track handling: --won't take any effect immediately after path reset because index_on_track not set, but that's not severe. local front_off_track=train.max_index_on_track and train.index>train.max_index_on_track local back_off_track=train.min_index_on_track and train.end_index<train.min_index_on_track if front_off_track and back_off_track then--allow movement in both directions if train.tarvelocity>1 then train.tarvelocity=1 end elseif front_off_track then--allow movement only backward if train.movedir==1 and train.tarvelocity>0 then train.tarvelocity=0 end if train.movedir==-1 and train.tarvelocity>1 then train.tarvelocity=1 end elseif back_off_track then--allow movement only forward if train.movedir==-1 and train.tarvelocity>0 then train.tarvelocity=0 end if train.movedir==1 and train.tarvelocity>1 then train.tarvelocity=1 end end --interpret ATC command if train.atc_brake_target and train.atc_brake_target>=train.velocity then train.atc_brake_target=nil end if train.atc_wait_finish then if not train.atc_brake_target and train.velocity==train.tarvelocity then train.atc_wait_finish=nil end end if train.atc_command then if train.atc_delay<=0 and not train.atc_wait_finish then advtrains.atc.execute_atc_command(id, train) else train.atc_delay=train.atc_delay-dtime end end --make brake adjust the tarvelocity if necessary if train.brake and (math.ceil(train.velocity)-1)<train.tarvelocity then train.tarvelocity=math.max((math.ceil(train.velocity)-1), 0) end --- 3a. actually calculate new velocity --- if train.velocity~=train.tarvelocity then local applydiff=0 local mass=#train.trainparts local diff=train.tarvelocity-train.velocity if diff>0 then--accelerating, force will be brought on only by locomotives. --atprint("accelerating with default force") applydiff=(math.min((advtrains.train_accel_force*train.locomotives_in_train*dtime)/mass, math.abs(diff))) else--decelerating if front_off_track or back_off_track or train.recently_collided_with_env then --every wagon has a brake, so not divided by mass. --atprint("braking with emergency force") applydiff= -(math.min((advtrains.train_emerg_force*dtime), math.abs(diff))) elseif train.brake or (train.atc_brake_target and train.atc_brake_target<train.velocity) then --atprint("braking with default force") --no math.min, because it can grow beyond tarvelocity, see up there --dont worry, it will never fall below zero. applydiff= -((advtrains.train_brake_force*dtime)) else --atprint("roll") applydiff= -(math.min((advtrains.train_roll_force*dtime), math.abs(diff))) end end train.last_accel=(applydiff*train.movedir) train.velocity=math.min(math.max( train.velocity+applydiff , 0), train.max_speed or 10) else train.last_accel=0 end --- 4. move train --- train.index=train.index and train.index+(((train.velocity*train.movedir)/(train.path_dist[math.floor(train.index)] or 1))*dtime) or 0 --- 4a. update train.end_index to the new position --- train.end_index=advtrains.get_train_end_index(train) --- 5. extend path as necessary --- local gen_front=math.max(train.index, train.detector_old_index) + 2 local gen_back=math.min(train.end_index, train.detector_old_end_index) - 2 local maxn=train.path_extent_max or 0 while maxn < gen_front do--pregenerate --atprint("maxn conway for ",maxn,minetest.pos_to_string(path[maxn]),maxn-1,minetest.pos_to_string(path[maxn-1])) local conway=advtrains.conway(train.path[maxn], train.path[maxn-1], train.drives_on) if conway then train.path[maxn+1]=conway train.max_index_on_track=maxn else --do as if nothing has happened and preceed with path --but do not update max_index_on_track atprint("over-generating path max to index "..(maxn+1).." (position "..minetest.pos_to_string(train.path[maxn]).." )") train.path[maxn+1]=vector.add(train.path[maxn], vector.subtract(train.path[maxn], train.path[maxn-1])) end train.path_dist[maxn]=vector.distance(train.path[maxn+1], train.path[maxn]) maxn=advtrains.maxN(train.path) end train.path_extent_max=maxn local minn=train.path_extent_min or -1 while minn > gen_back do --atprint("minn conway for ",minn,minetest.pos_to_string(path[minn]),minn+1,minetest.pos_to_string(path[minn+1])) local conway=advtrains.conway(train.path[minn], train.path[minn+1], train.drives_on) if conway then train.path[minn-1]=conway train.min_index_on_track=minn else --do as if nothing has happened and preceed with path --but do not update min_index_on_track atprint("over-generating path min to index "..(minn-1).." (position "..minetest.pos_to_string(train.path[minn]).." )") train.path[minn-1]=vector.add(train.path[minn], vector.subtract(train.path[minn], train.path[minn+1])) end train.path_dist[minn-1]=vector.distance(train.path[minn], train.path[minn-1]) minn=advtrains.minN(train.path) end train.path_extent_min=minn if not train.min_index_on_track then train.min_index_on_track=-1 end if not train.max_index_on_track then train.max_index_on_track=0 end --make pos/yaw available for possible recover calls if train.max_index_on_track<train.index then --whoops, train went too far. the saved position will be the last one that lies on a track, and savedpos_off_track_index_offset will hold how far to go from here train.savedpos_off_track_index_offset=train.index-train.max_index_on_track train.last_pos=train.path[train.max_index_on_track] train.last_pos_prev=train.path[train.max_index_on_track-1] atprint("train is off-track (front), last positions kept at "..minetest.pos_to_string(train.last_pos).." / "..minetest.pos_to_string(train.last_pos_prev)) elseif train.min_index_on_track+1>train.index then --whoops, train went even more far. same behavior train.savedpos_off_track_index_offset=train.index-train.min_index_on_track train.last_pos=train.path[train.min_index_on_track+1] train.last_pos_prev=train.path[train.min_index_on_track] atprint("train is off-track (back), last positions kept at "..minetest.pos_to_string(train.last_pos).." / "..minetest.pos_to_string(train.last_pos_prev)) else --regular case train.savedpos_off_track_index_offset=nil train.last_pos=train.path[math.floor(train.index+0.5)] train.last_pos_prev=train.path[math.floor(train.index-0.5)] end --- 6. update node coverage --- -- when paths get cleared, the old indices set above will be up-to-date and represent the state in which the last run of this code was made local ifo, ifn, ibo, ibn = train.detector_old_index, math.floor(train.index), train.detector_old_end_index, math.floor(train.end_index) local path=train.path for i=ibn, ifn do if path[i] then advtrains.detector.stay_node(path[i], id) end end if ifn>ifo then for i=ifo+1, ifn do if path[i] then advtrains.detector.enter_node(path[i], id) end end elseif ifn<ifo then for i=ifn, ifo-1 do if path[i] then advtrains.detector.leave_node(path[i], id) end end end if ibn<ibo then for i=ibn, ibo-1 do if path[i] then advtrains.detector.enter_node(path[i], id) end end elseif ibn>ibo then for i=ibo+1, ibn do if path[i] then advtrains.detector.leave_node(path[i], id) end end end --- 7. do less important stuff --- --check for any trainpart entities if they have been unloaded. do this only if train is near a player, to not spawn entities into unloaded areas train.check_trainpartload=(train.check_trainpartload or 0)-dtime local node_range=(math.max((minetest.setting_get("active_block_range") or 0),1)*16) if train.check_trainpartload<=0 then local ori_pos=advtrains.get_real_index_position(path, train.index) --not much to calculate --atprint("[train "..id.."] at "..minetest.pos_to_string(vector.round(ori_pos))) local should_check=false for _,p in ipairs(minetest.get_connected_players()) do should_check=should_check or ((vector.distance(ori_pos, p:getpos())<node_range)) end if should_check then advtrains.update_trainpart_properties(id) end train.check_trainpartload=2 end --remove? if #train.trainparts==0 then atprint("[train "..sid(id).."] has empty trainparts, removing.") advtrains.detector.leave_node(path[train.detector_old_index], id) advtrains.trains[id]=nil return end end function advtrains.train_step_b(id, train, dtime) --- 8. check for collisions with other trains --- local train_moves=(train.velocity~=0) if train_moves then --heh, new collision again. --this time, based on NODES and the advtrains.detector.on_node table. local collpos local coll_grace=1 if train.movedir==1 then collpos=advtrains.get_real_index_position(train.path, train.index-coll_grace) else collpos=advtrains.get_real_index_position(train.path, train.end_index+coll_grace) end if collpos then local rcollpos=advtrains.round_vector_floor_y(collpos) for x=-1,1 do for z=-1,1 do local testpos=vector.add(rcollpos, {x=x, y=0, z=z}) local testpts=minetest.hash_node_position(testpos) if advtrains.detector.on_node[testpts] and advtrains.detector.on_node[testpts]~=id then --collides advtrains.spawn_couple_on_collide(id, testpos, advtrains.detector.on_node[testpts], train.movedir==-1) train.recently_collided_with_env=true train.velocity=0.5*train.velocity train.movedir=train.movedir*-1 train.tarvelocity=0 end end end end end end --structure of train table: --[[ trains={ [train_id]={ trainparts={ [n]=wagon_id } path={path} velocity tarvelocity index trainlen path_inv_level last_pos | last_dir | for pathpredicting. } } --a wagon itself has the following properties: wagon={ unique_id train_id pos_in_train (is index difference, including train_span stuff) pos_in_trainparts (is index in trainparts tabel of trains) } inherited by metatable: wagon_proto={ wagon_span } ]] --returns new id function advtrains.create_new_train_at(pos, pos_prev) local newtrain_id=os.time()..os.clock() while advtrains.trains[newtrain_id] do newtrain_id=os.time()..os.clock() end--ensure uniqueness(will be unneccessary) advtrains.trains[newtrain_id]={} advtrains.trains[newtrain_id].last_pos=pos advtrains.trains[newtrain_id].last_pos_prev=pos_prev advtrains.trains[newtrain_id].tarvelocity=0 advtrains.trains[newtrain_id].velocity=0 advtrains.trains[newtrain_id].trainparts={} return newtrain_id end function advtrains.get_train_end_index(train)