Node.js interpreter integration
This commit is contained in:
parent
ac5f306991
commit
a770df7334
3 changed files with 138 additions and 50 deletions
|
@ -28,7 +28,8 @@ from .aterrors import ServerError
|
||||||
from .aterrors import ServerStartError
|
from .aterrors import ServerStartError
|
||||||
from .aterrors import FileError
|
from .aterrors import FileError
|
||||||
from .aterrors import AternosPermissionError
|
from .aterrors import AternosPermissionError
|
||||||
from .atjsparse import exec_js
|
from .atjsparse import Js2PyInterpreter
|
||||||
|
from .atjsparse import NodeInterpreter
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
||||||
|
@ -42,7 +43,8 @@ __all__ = [
|
||||||
'FileManager', 'AternosFile', 'AternosError',
|
'FileManager', 'AternosFile', 'AternosError',
|
||||||
'CloudflareError', 'CredentialsError', 'TokenError',
|
'CloudflareError', 'CredentialsError', 'TokenError',
|
||||||
'ServerError', 'ServerStartError', 'FileError',
|
'ServerError', 'ServerStartError', 'FileError',
|
||||||
'AternosPermissionError', 'exec_js',
|
'AternosPermissionError',
|
||||||
|
'Js2PyInterpreter', 'NodeInterpreter',
|
||||||
|
|
||||||
'Edition', 'Status', 'Lists',
|
'Edition', 'Status', 'Lists',
|
||||||
'ServerOpts', 'WorldOpts', 'WorldRules',
|
'ServerOpts', 'WorldOpts', 'WorldRules',
|
||||||
|
|
|
@ -1,36 +1,134 @@
|
||||||
"""Parsing and executing JavaScript code"""
|
"""Parsing and executing JavaScript code"""
|
||||||
|
|
||||||
|
import abc
|
||||||
import base64
|
import base64
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, Union, Any
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
import regex
|
import regex
|
||||||
import js2py
|
import js2py
|
||||||
|
|
||||||
# Thanks to http://regex.inginf.units.it/
|
js: Optional['Interpreter'] = None
|
||||||
arrowexp = regex.compile(r'\w[^\}]*+')
|
|
||||||
|
|
||||||
|
|
||||||
def to_ecma5_function(f: str) -> str:
|
class Interpreter(abc.ABC):
|
||||||
"""Converts a ECMA6 function
|
"""Base JS interpreter class"""
|
||||||
to ECMA5 format (without arrow expressions)
|
|
||||||
|
|
||||||
Args:
|
def __init__(self) -> None:
|
||||||
f (str): ECMA6 function
|
pass
|
||||||
|
|
||||||
Returns:
|
def __getitem__(self, name: str) -> Any:
|
||||||
ECMA5 function
|
return self.get_var(name)
|
||||||
"""
|
|
||||||
|
|
||||||
f = regex.sub(r'/\*.+?\*/', '', f)
|
@abc.abstractmethod
|
||||||
match = arrowexp.search(f)
|
def exec_js(self, func: str) -> None:
|
||||||
conv = '(function(){' + match.group(0) + '})()'
|
"""Executes JavaScript code
|
||||||
return regex.sub(
|
|
||||||
r'(?:s|\(s\)) => s.split\([\'"]{2}\).reverse\(\).join\([\'"]{2}\)',
|
Args:
|
||||||
'function(s){return s.split(\'\').reverse().join(\'\')}',
|
func (str): JS function
|
||||||
conv
|
"""
|
||||||
)
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_var(self, name: str) -> Any:
|
||||||
|
"""Returns JS variable value
|
||||||
|
from the interpreter
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Variable name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Variable value
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NodeInterpreter(Interpreter):
|
||||||
|
|
||||||
|
def __init__(self, node: Union[str, Path]) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.proc = subprocess.Popen(
|
||||||
|
node,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
|
||||||
|
def exec_js(self, func: str) -> None:
|
||||||
|
self.proc.communicate(func.encode('utf-8'))
|
||||||
|
|
||||||
|
def get_var(self, name: str) -> Any:
|
||||||
|
assert self.proc.stdout is not None
|
||||||
|
self.proc.stdout.read()
|
||||||
|
self.proc.communicate(name.encode('utf-8'))
|
||||||
|
return self.proc.stdout.read().decode('utf-8')
|
||||||
|
|
||||||
|
def __del__(self) -> None:
|
||||||
|
self.proc.terminate()
|
||||||
|
|
||||||
|
|
||||||
|
class Js2PyInterpreter(Interpreter):
|
||||||
|
|
||||||
|
# Thanks to http://regex.inginf.units.it
|
||||||
|
arrowexp = regex.compile(r'\w[^\}]*+')
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
ctx = js2py.EvalJs({'atob': atob})
|
||||||
|
ctx.execute('window.document = { };')
|
||||||
|
ctx.execute('window.Map = function(_i){ };')
|
||||||
|
ctx.execute('window.setTimeout = function(_f,_t){ };')
|
||||||
|
ctx.execute('window.setInterval = function(_f,_t){ };')
|
||||||
|
ctx.execute('window.encodeURIComponent = function(_s){ };')
|
||||||
|
|
||||||
|
self.ctx = ctx
|
||||||
|
|
||||||
|
def exec_js(self, func: str) -> None:
|
||||||
|
self.ctx.execute(self.to_ecma5(func))
|
||||||
|
|
||||||
|
def get_var(self, name: str) -> Any:
|
||||||
|
return self.ctx[name]
|
||||||
|
|
||||||
|
def to_ecma5(self, func: str) -> str:
|
||||||
|
"""Converts from ECMA6 format to ECMA5
|
||||||
|
(replacing arrow expressions)
|
||||||
|
and removes comment blocks
|
||||||
|
|
||||||
|
Args:
|
||||||
|
func (str): ECMA6 function
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ECMA5 function
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Delete anything between /* and */
|
||||||
|
func = regex.sub(r'/\*.+?\*/', '', func)
|
||||||
|
|
||||||
|
# Search for arrow expressions
|
||||||
|
match = self.arrowexp.search(func)
|
||||||
|
if match is None:
|
||||||
|
return func
|
||||||
|
|
||||||
|
# Convert the function
|
||||||
|
conv = '(function(){' + match[0] + '})()'
|
||||||
|
|
||||||
|
# Convert 1 more expression.
|
||||||
|
# It doesn't change,
|
||||||
|
# so it was hardcoded
|
||||||
|
# as a regexp
|
||||||
|
return regex.sub(
|
||||||
|
r'(?:s|\(s\)) => s.split\([\'"]{2}\).reverse\(\).join\([\'"]{2}\)',
|
||||||
|
'function(s){return s.split(\'\').reverse().join(\'\')}',
|
||||||
|
conv
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def atob(s: str) -> str:
|
def atob(s: str) -> str:
|
||||||
"""Decodes base64 string
|
"""Wrapper for the built-in library function.
|
||||||
|
Decodes base64 string
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
s (str): Encoded data
|
s (str): Encoded data
|
||||||
|
@ -42,21 +140,8 @@ def atob(s: str) -> str:
|
||||||
return base64.standard_b64decode(str(s)).decode('utf-8')
|
return base64.standard_b64decode(str(s)).decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
def exec_js(f: str) -> js2py.EvalJs:
|
def get_interpreter(create: Type[Interpreter] = Js2PyInterpreter) -> 'Interpreter':
|
||||||
"""Executes a JavaScript function
|
global js
|
||||||
|
if js is None:
|
||||||
Args:
|
js = create()
|
||||||
f (str): ECMA6 function
|
return js
|
||||||
|
|
||||||
Returns:
|
|
||||||
JavaScript interpreter context
|
|
||||||
"""
|
|
||||||
|
|
||||||
ctx = js2py.EvalJs({'atob': atob})
|
|
||||||
ctx.execute('window.document = { };')
|
|
||||||
ctx.execute('window.Map = function(_i){ };')
|
|
||||||
ctx.execute('window.setTimeout = function(_f,_t){ };')
|
|
||||||
ctx.execute('window.setInterval = function(_f,_t){ };')
|
|
||||||
ctx.execute('window.encodeURIComponent = function(_s){ };')
|
|
||||||
ctx.execute(to_ecma5_function(f))
|
|
||||||
return ctx
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ class TestJs2Py(unittest.TestCase):
|
||||||
|
|
||||||
self.tests = files.read_sample('token_input.txt')
|
self.tests = files.read_sample('token_input.txt')
|
||||||
self.results = files.read_sample('token_output.txt')
|
self.results = files.read_sample('token_output.txt')
|
||||||
|
self.js = atjsparse.Js2PyInterpreter()
|
||||||
|
|
||||||
def test_base64(self) -> None:
|
def test_base64(self) -> None:
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ class TestJs2Py(unittest.TestCase):
|
||||||
def test_conv(self) -> None:
|
def test_conv(self) -> None:
|
||||||
|
|
||||||
token = CONV_TOKEN_ARROW
|
token = CONV_TOKEN_ARROW
|
||||||
f = atjsparse.to_ecma5_function(token)
|
f = self.js.to_ecma5(token)
|
||||||
self.assertEqual(f, CONV_TOKEN_FUNC)
|
self.assertEqual(f, CONV_TOKEN_FUNC)
|
||||||
|
|
||||||
def test_ecma6parse(self) -> None:
|
def test_ecma6parse(self) -> None:
|
||||||
|
@ -38,21 +39,21 @@ class TestJs2Py(unittest.TestCase):
|
||||||
part2 = '''window.t2 = Boolean(!window[["p","Ma"].reverse().join('')]);'''
|
part2 = '''window.t2 = Boolean(!window[["p","Ma"].reverse().join('')]);'''
|
||||||
part3 = '''window.t3 = Boolean(!window[["ut","meo","i","etT","s"].reverse().join('')]);'''
|
part3 = '''window.t3 = Boolean(!window[["ut","meo","i","etT","s"].reverse().join('')]);'''
|
||||||
|
|
||||||
ctx0 = atjsparse.exec_js(code)
|
self.js.exec_js(code)
|
||||||
ctx1 = atjsparse.exec_js(part1)
|
self.js.exec_js(part1)
|
||||||
ctx2 = atjsparse.exec_js(part2)
|
self.js.exec_js(part2)
|
||||||
ctx3 = atjsparse.exec_js(part3)
|
self.js.exec_js(part3)
|
||||||
|
|
||||||
self.assertEqual(ctx0.window['t0'], False)
|
self.assertEqual(self.js['t0'], False)
|
||||||
self.assertEqual(ctx1.window['t1'], True)
|
self.assertEqual(self.js['t1'], True)
|
||||||
self.assertEqual(ctx2.window['t2'], False)
|
self.assertEqual(self.js['t2'], False)
|
||||||
self.assertEqual(ctx3.window['t3'], False)
|
self.assertEqual(self.js['t3'], False)
|
||||||
|
|
||||||
def test_exec(self) -> None:
|
def test_exec(self) -> None:
|
||||||
|
|
||||||
for i, f in enumerate(self.tests):
|
for i, f in enumerate(self.tests):
|
||||||
ctx = atjsparse.exec_js(f)
|
self.js.exec_js(f)
|
||||||
res = ctx.window['AJAX_TOKEN']
|
res = self.js['AJAX_TOKEN']
|
||||||
self.assertEqual(res, self.results[i])
|
self.assertEqual(res, self.results[i])
|
||||||
|
|
||||||
|
|
||||||
|
|
Reference in a new issue