page-render.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. const fplay = require("@lawlers/4play");
  2. const http = require("http");
  3. // 4play settings
  4. var port = 3030;
  5. var timeout = 30000;
  6. var password = "cnc";
  7. // ==================================================
  8. // webserver
  9. var global_ws = null;
  10. const webserver_port = 3000;
  11. var callback_q = new Map();
  12. const server = http.createServer(async function(req, res){
  13. const parsed = new URL(req.url, "http://localhost");
  14. const url = parsed.searchParams.get("url");
  15. const pw = parsed.searchParams.get("pw");
  16. var proxy = parsed.searchParams.get("proxy");
  17. const url_match = parsed.searchParams.get("url_match");
  18. const content_match = parsed.searchParams.get("content_match");
  19. const fail_url_match = parsed.searchParams.get("fail_url_match");
  20. const fail_content_match = parsed.searchParams.get("fail_content_match");
  21. var container = parsed.searchParams.get("container");
  22. if(fplay.browser_connected === false){
  23. res.writeHead(
  24. 400,
  25. {
  26. "Content-Type": "text/plain"
  27. }
  28. );
  29. res.end("No browser available to render the motherfucking page");
  30. return;
  31. }
  32. var errors = [];
  33. if(
  34. pw === null ||
  35. pw != password
  36. ){
  37. errors.push("pw");
  38. }
  39. if(url === null){ errors.push("url"); }
  40. if(proxy === null){ errors.push("proxy"); }
  41. if(errors.length !== 0){
  42. res.writeHead(
  43. 400,
  44. {
  45. "Content-Type": "text/plain"
  46. }
  47. );
  48. res.end("The following parameters are missing or invalid: " + errors.join(", "));
  49. return;
  50. }
  51. console.log("Rendering " + url);
  52. if(
  53. container === null ||
  54. await fplay.container_exists(global_ws, container) === false
  55. ){
  56. container = await fplay.container_create(global_ws);
  57. proxy = get_proxy(proxy);
  58. // assign proxy
  59. if(proxy.config !== false){
  60. await fplay.container_attach_proxy(
  61. global_ws,
  62. container,
  63. proxy.config
  64. );
  65. }
  66. }else{
  67. proxy = {raw: proxy};
  68. }
  69. // open tab
  70. const newtab = await fplay.tab_open(global_ws, url, false, container);
  71. if(newtab === false){
  72. // failed to open tab
  73. res.writeHead(
  74. 400,
  75. {
  76. "Content-Type": "text/plain",
  77. "X-Proxy": proxy.raw,
  78. "X-Container-ID": typeof container.id == "undefined" ? container : container.id
  79. }
  80. );
  81. res.end("Failed to open tab");
  82. return;
  83. }
  84. // callback will be handled in "web_response"
  85. callback_q.set(
  86. newtab.id,
  87. {
  88. http_res: res,
  89. proxy: proxy,
  90. container: container,
  91. url_match: url_match,
  92. content_match: content_match,
  93. fail_url_match: fail_url_match,
  94. fail_content_match: fail_content_match
  95. }
  96. );
  97. // close tab in 30 seconds
  98. await fplay.wait_random(15000, 35000);
  99. await fplay.tab_close(global_ws, newtab);
  100. });
  101. server.listen(webserver_port, function(){
  102. console.log("http server running");
  103. });
  104. function get_proxy(parts){
  105. parts = parts.split(":");
  106. // format: type:address:port:username:password
  107. // PHP explode(":", $ip, 5) splits into max 5 parts
  108. var type = parts[0];
  109. const address = parts[1];
  110. const port = parts[2];
  111. const username = parts[3];
  112. const password = parts[4];
  113. var proxy_dns = true;
  114. switch(type){
  115. case "raw_ip":
  116. case "ip":
  117. // no proxy, use direct connection
  118. return {
  119. config: false,
  120. raw: "raw_ip::::"
  121. }
  122. break;
  123. case "socks5":
  124. type = "socks";
  125. proxy_dns = false;
  126. break;
  127. case "socks4a":
  128. type = "socks4";
  129. break;
  130. case "socks4":
  131. proxy_dns = false;
  132. break;
  133. case "socks5_hostname":
  134. case "socks5h":
  135. case "socks5a":
  136. type = "socks";
  137. break;
  138. case "http":
  139. case "https":
  140. // do nothing
  141. break;
  142. default:
  143. // invalid proxy provided
  144. return {
  145. config: false,
  146. raw: "raw_ip::::"
  147. }
  148. }
  149. return {
  150. config: {
  151. type: type,
  152. host: address,
  153. port: port,
  154. username: username,
  155. password: password,
  156. proxyDNS: proxy_dns
  157. },
  158. raw: proxy_line
  159. };
  160. }
  161. fplay.event.on("server_ready", function(){
  162. console.log("listening on port " + port + " (timeout=" + timeout + ")");
  163. });
  164. fplay.event.on("browser_connect", async function(ws){
  165. global_ws = ws;
  166. const ua = await fplay.get_ua(ws);
  167. console.log("New browser instance connected: " + ua);
  168. await fplay.close_all_tabs(ws);
  169. await fplay.delete_all_containers(ws);
  170. fplay.web_response_whitelist(ws, ["main_frame"]);
  171. // clean up
  172. const blanktab = await fplay.close_all_tabs(ws);
  173. await fplay.delete_all_containers(ws);
  174. });
  175. fplay.event.on("web_response", async function(page){
  176. var callback_data = callback_q.get(page.id);
  177. if(typeof callback_data == "undefined"){ return; }
  178. var body = page.body.toString();
  179. var url_match = page.url.match(new RegExp(callback_data.url_match, "i"));
  180. var content_match = body.match(new RegExp(callback_data.content_match, "i"));
  181. var fail_url_match = page.url.match(new RegExp(callback_data.fail_url_match, "i"));
  182. var fail_content_match = body.match(new RegExp(callback_data.fail_content_match, "i"));
  183. // handle positive response
  184. if(
  185. (
  186. callback_data.url_match === null &&
  187. callback_data.content_match === null
  188. ) ||
  189. (
  190. url_match &&
  191. content_match
  192. ) ||
  193. (
  194. url_match &&
  195. callback_data.content_match === null
  196. ) ||
  197. (
  198. callback_data.url_match === null &&
  199. content_match
  200. )
  201. ){
  202. callback_data.http_res.writeHead(
  203. 200,
  204. {
  205. "Content-Type": "text/plain;charset=UTF-8",
  206. "X-Proxy": callback_data.proxy.raw,
  207. "X-Container-ID": typeof callback_data.container.id == "undefined" ? callback_data.container : callback_data.container.id
  208. }
  209. );
  210. callback_data.http_res.end(body);
  211. callback_q.delete(page.id); // cleanup callback only
  212. return;
  213. }
  214. // handle negative response
  215. if(
  216. (
  217. callback_data.fail_url_match !== null &&
  218. callback_data.fail_content_match !== null
  219. ) &&
  220. (
  221. (
  222. fail_url_match &&
  223. fail_content_match
  224. ) ||
  225. (
  226. fail_url_match &&
  227. callback_data.fail_content_match === null
  228. ) ||
  229. (
  230. callback_data.fail_url_match === null &&
  231. fail_content_match
  232. )
  233. )
  234. ){
  235. callback_data.http_res.writeHead(
  236. 400,
  237. {
  238. "Content-Type": "text/plain",
  239. "X-Proxy": callback_data.proxy.raw,
  240. "X-Container-ID": callback_data.container.id
  241. }
  242. );
  243. callback_data.http_res.end("Reached fail state (matched fail_url_match or fail_content_match)");
  244. // cleanup browser + callback
  245. await fplay.tab_close(global_ws, page.id);
  246. await fplay.container_delete(global_ws, callback_data.container);
  247. callback_q.delete(page.id);
  248. }
  249. });
  250. fplay.event.on("dom_load_fail", async function(page){
  251. var callback_data = callback_q.get(page.id);
  252. if(typeof callback_data == "undefined"){ return; }
  253. callback_q.delete(page.id);
  254. callback_data.http_res.writeHead(
  255. 400,
  256. {
  257. "Content-Type": "text/plain",
  258. "X-Proxy": callback_data.proxy.raw,
  259. "X-Container-ID": typeof callback_data.container.id == "undefined" ? callback_data.container : callback_data.container.id
  260. }
  261. );
  262. callback_data.http_res.end("Network error (" + page.error + ")");
  263. // cleanup browser
  264. await fplay.tab_close(global_ws, page.id);
  265. await fplay.container_delete(global_ws, callback_data.container);
  266. });
  267. fplay.init(port, password, timeout);