class JsEngine(QObject): def __init__(self): QObject.__init__(self) self.engine = QJSEngine() t = self.engine.newQObject(self) self.engine.globalObject().setProperty("app", t) @pyqtSlot(str) def exec_js(self, js): val = self.engine.evaluate(js) if not val.isNull() and not val.isUndefined(): print("error:{0}".format(val.toString())) @pyqtSlot() def exec_js1(self): js = """ var c = 123; """ self.exec_js(js) @pyqtSlot(str, str) def log(self, p1, p2): print(p1, p2)
class PACResolver: """Evaluate PAC script files and resolve proxies.""" @staticmethod def _parse_proxy_host(host_str): host, _colon, port_str = host_str.partition(':') try: port = int(port_str) except ValueError: raise ParseProxyError("Invalid port number") return (host, port) @staticmethod def _parse_proxy_entry(proxy_str): """Parse one proxy string entry, as described in PAC specification.""" config = [c.strip() for c in proxy_str.split(' ') if c] if not config: raise ParseProxyError("Empty proxy entry") elif config[0] == "DIRECT": if len(config) != 1: raise ParseProxyError("Invalid number of parameters for " + "DIRECT") return QNetworkProxy(QNetworkProxy.NoProxy) elif config[0] == "PROXY": if len(config) != 2: raise ParseProxyError("Invalid number of parameters for PROXY") host, port = PACResolver._parse_proxy_host(config[1]) return QNetworkProxy(QNetworkProxy.HttpProxy, host, port) elif config[0] in ["SOCKS", "SOCKS5"]: if len(config) != 2: raise ParseProxyError("Invalid number of parameters for SOCKS") host, port = PACResolver._parse_proxy_host(config[1]) return QNetworkProxy(QNetworkProxy.Socks5Proxy, host, port) else: err = "Unknown proxy type: {}" raise ParseProxyError(err.format(config[0])) @staticmethod def _parse_proxy_string(proxy_str): proxies = proxy_str.split(';') return [PACResolver._parse_proxy_entry(x) for x in proxies] def _evaluate(self, js_code, js_file): ret = self._engine.evaluate(js_code, js_file) if ret.isError(): err = "JavaScript error while evaluating PAC file: {}" raise EvalProxyError(err.format(ret.toString())) def __init__(self, pac_str): """Create a PAC resolver. Args: pac_str: JavaScript code containing PAC resolver. """ self._engine = QJSEngine() self._ctx = _PACContext(self._engine) self._engine.globalObject().setProperty( "PAC", self._engine.newQObject(self._ctx)) self._evaluate(_PACContext.JS_DEFINITIONS, "pac_js_definitions") self._evaluate(utils.read_file("javascript/pac_utils.js"), "pac_utils") proxy_config = self._engine.newObject() proxy_config.setProperty("bindings", self._engine.newObject()) self._engine.globalObject().setProperty("ProxyConfig", proxy_config) self._evaluate(pac_str, "pac") global_js_object = self._engine.globalObject() self._resolver = global_js_object.property("FindProxyForURL") if not self._resolver.isCallable(): err = "Cannot resolve FindProxyForURL function, got '{}' instead" raise EvalProxyError(err.format(self._resolver.toString())) def resolve(self, query, from_file=False): """Resolve a proxy via PAC. Args: query: QNetworkProxyQuery. from_file: Whether the proxy info is coming from a file. Return: A list of QNetworkProxy objects in order of preference. """ if from_file: string_flags = QUrl.PrettyDecoded else: string_flags = QUrl.RemoveUserInfo if query.url().scheme() == 'https': string_flags |= QUrl.RemovePath | QUrl.RemoveQuery result = self._resolver.call( [query.url().toString(string_flags), query.peerHostName()]) result_str = result.toString() if not result.isString(): err = "Got strange value from FindProxyForURL: '{}'" raise EvalProxyError(err.format(result_str)) return self._parse_proxy_string(result_str)
class PACResolver: """Evaluate PAC script files and resolve proxies.""" @staticmethod def _parse_proxy_host(host_str): host, _colon, port_str = host_str.partition(':') try: port = int(port_str) except ValueError: raise ParseProxyError("Invalid port number") return (host, port) @staticmethod def _parse_proxy_entry(proxy_str): """Parse one proxy string entry, as described in PAC specification.""" config = [c.strip() for c in proxy_str.split(' ') if c] if not config: raise ParseProxyError("Empty proxy entry") elif config[0] == "DIRECT": if len(config) != 1: raise ParseProxyError("Invalid number of parameters for " + "DIRECT") return QNetworkProxy(QNetworkProxy.NoProxy) elif config[0] == "PROXY": if len(config) != 2: raise ParseProxyError("Invalid number of parameters for PROXY") host, port = PACResolver._parse_proxy_host(config[1]) return QNetworkProxy(QNetworkProxy.HttpProxy, host, port) elif config[0] in ["SOCKS", "SOCKS5"]: if len(config) != 2: raise ParseProxyError("Invalid number of parameters for SOCKS") host, port = PACResolver._parse_proxy_host(config[1]) return QNetworkProxy(QNetworkProxy.Socks5Proxy, host, port) else: err = "Unknown proxy type: {}" raise ParseProxyError(err.format(config[0])) @staticmethod def _parse_proxy_string(proxy_str): proxies = proxy_str.split(';') return [PACResolver._parse_proxy_entry(x) for x in proxies] def _evaluate(self, js_code, js_file): ret = self._engine.evaluate(js_code, js_file) if ret.isError(): err = "JavaScript error while evaluating PAC file: {}" raise EvalProxyError(err.format(ret.toString())) def __init__(self, pac_str): """Create a PAC resolver. Args: pac_str: JavaScript code containing PAC resolver. """ self._engine = QJSEngine() self._ctx = _PACContext(self._engine) self._engine.globalObject().setProperty( "PAC", self._engine.newQObject(self._ctx)) self._evaluate(_PACContext.JS_DEFINITIONS, "pac_js_definitions") self._evaluate(utils.read_file("javascript/pac_utils.js"), "pac_utils") proxy_config = self._engine.newObject() proxy_config.setProperty("bindings", self._engine.newObject()) self._engine.globalObject().setProperty("ProxyConfig", proxy_config) self._evaluate(pac_str, "pac") global_js_object = self._engine.globalObject() self._resolver = global_js_object.property("FindProxyForURL") if not self._resolver.isCallable(): err = "Cannot resolve FindProxyForURL function, got '{}' instead" raise EvalProxyError(err.format(self._resolver.toString())) def resolve(self, query, from_file=False): """Resolve a proxy via PAC. Args: query: QNetworkProxyQuery. from_file: Whether the proxy info is coming from a file. Return: A list of QNetworkProxy objects in order of preference. """ if from_file: string_flags = QUrl.PrettyDecoded else: string_flags = QUrl.RemoveUserInfo if query.url().scheme() == 'https': string_flags |= QUrl.RemovePath | QUrl.RemoveQuery result = self._resolver.call([query.url().toString(string_flags), query.peerHostName()]) result_str = result.toString() if not result.isString(): err = "Got strange value from FindProxyForURL: '{}'" raise EvalProxyError(err.format(result_str)) return self._parse_proxy_string(result_str)
class JsAlgorithm(QgsProcessingFeatureBasedAlgorithm): # pylint: disable=too-many-public-methods """ Javascript Algorithm """ def __init__(self, description_file, script=None): super().__init__() self.script = script self.js_script = '' self.codec = None self.engine = None self.process_js_function = None self.json_exporter = None self.fields = None self._name = '' self._display_name = '' self._group = '' self.description_file = os.path.realpath(description_file) if description_file else None self.error = None self.commands = list() self.is_user_script = False if description_file: self.is_user_script = not description_file.startswith(JsUtils.builtin_scripts_folder()) if self.script is not None: self.load_from_string() if self.description_file is not None: self.load_from_file() def createInstance(self): """ Returns a new instance of this algorithm """ if self.description_file is not None: return JsAlgorithm(self.description_file) return JsAlgorithm(description_file=None, script=self.script) def icon(self): """ Returns the algorithm's icon """ return GuiUtils.get_icon("providerJS.svg") def svgIconPath(self): """ Returns a path to the algorithm's icon as a SVG file """ return GuiUtils.get_icon_svg("providerJS.svg") def name(self): """ Internal unique id for algorithm """ return self._name def displayName(self): """ User friendly display name """ return self._display_name def shortDescription(self): """ Returns the path to the script file, for use in toolbox tooltips """ return self.description_file def group(self): """ Returns the algorithm's group """ return self._group def groupId(self): """ Returns the algorithm's group ID """ return self._group def load_from_string(self): """ Load the algorithm from a string """ lines = self.script.split('\n') self._name = 'unnamedalgorithm' self._display_name = self.tr('[Unnamed algorithm]') self.parse_script(iter(lines)) def load_from_file(self): """ Load the algorithm from a file """ filename = os.path.basename(self.description_file) self._display_name = self._name self._name = filename[:filename.rfind('.')] self._display_name = self._name.replace('_', ' ') with open(self.description_file, 'r') as f: lines = [line.strip() for line in f] self.parse_script(iter(lines)) def parse_script(self, lines): """ Parse the lines from an JS script, initializing parameters and outputs as encountered """ self.script = '' js_script_lines = list() self.error = None ender = 0 line = next(lines).strip('\n').strip('\r') while ender < 10: if line.startswith('//#'): try: self.process_metadata_line(line) except Exception: # pylint: disable=broad-except self.error = self.tr('This script has a syntax error.\n' 'Problem with line: {0}').format(line) else: if line == '': ender += 1 else: ender = 0 js_script_lines.append(line) self.script += line + '\n' try: line = next(lines).strip('\n').strip('\r') except StopIteration: break self.js_script = '\n'.join(js_script_lines) def process_metadata_line(self, line): """ Processes a "metadata" (##) line """ line = line.replace('//#', '') # special commands #if line.lower().strip().startswith('dontuserasterpackage'): # self.use_raster_package = False # return value, type_ = self.split_tokens(line) if type_.lower().strip() == 'group': self._group = value return if type_.lower().strip() == 'name': self._name = self._display_name = value self._name = JsUtils.strip_special_characters(self._name.lower()) return self.process_parameter_line(line) @staticmethod def split_tokens(line): """ Attempts to split a line into tokens """ tokens = line.split('=') return tokens[0], tokens[1] def process_parameter_line(self, line): """ Processes a single script line representing a parameter """ value, _ = self.split_tokens(line) description = JsUtils.create_descriptive_name(value) output = create_output_from_string(line) if output is not None: output.setName(value) output.setDescription(description) if issubclass(output.__class__, QgsProcessingOutputDefinition): self.addOutput(output) else: # destination type parameter self.addParameter(output) else: line = JsUtils.upgrade_parameter_line(line) param = getParameterFromString(line) if param is not None: self.addParameter(param) else: self.error = self.tr('This script has a syntax error.\n' 'Problem with line: {0}').format(line) def outputFields(self, fields): self.fields = fields return self.fields def outputCrs(self, inputCrs): self.input_crs = inputCrs return QgsCoordinateReferenceSystem('EPSG:4326') def prepareAlgorithm(self, parameters, context, feedback): """ Prepares the algorithm """ self.engine = QJSEngine() js_feedback = self.engine.newQObject(feedback) QQmlEngine.setObjectOwnership(feedback, QQmlEngine.CppOwnership) self.engine.globalObject().setProperty("feedback", js_feedback) js = """ function process(feature) { res = func(JSON.parse(feature)) if ( res && res.stack && res.message ) return res; return JSON.stringify(res); } """ for param in self.parameterDefinitions(): if param.isDestination(): continue if param.name() not in parameters or parameters[param.name()] is None: js += '{}=None;\n'.format(param.name()) continue if isinstance(param, (QgsProcessingParameterField, QgsProcessingParameterString, QgsProcessingParameterFile)): value = self.parameterAsString(parameters, param.name(), context) js += '{}="{}";'.format(param.name(), value) elif isinstance(param, QgsProcessingParameterNumber): value = self.parameterAsDouble(parameters, param.name(), context) js += '{}={};'.format(param.name(), value) js += self.js_script result = self.engine.evaluate(js) user_func = self.engine.globalObject().property("func") if not user_func: raise QgsProcessingException('No \'func\' function detected in script') if not user_func.isCallable(): raise QgsProcessingException('Object \'func\' is not a callable function') self.process_js_function = self.engine.globalObject().property("process") self.json_exporter = QgsJsonExporter() self.codec = QTextCodec.codecForName("System") return True def outputName(self): return 'Processed' def processFeature(self, feature, context, feedback): """ Executes the algorithm """ self.json_exporter.setSourceCrs(self.input_crs) geojson = self.json_exporter.exportFeature(feature) res = self.process_js_function.call([geojson]) if not res: return [] if res.isError(): error = "Uncaught exception at line {}:{}".format(res.property("lineNumber").toInt(), res.toString()) raise QgsProcessingException(error) return QgsJsonUtils.stringToFeatureList(res.toString(), self.fields, self.codec) def shortHelpString(self): """ Returns the algorithms helper string """ if self.description_file is None: return '' help_file = self.description_file + '.help' print(help_file) if os.path.exists(help_file): with open(help_file) as f: descriptions = json.load(f) return QgsProcessingUtils.formatHelpMapAsHtml(descriptions, self) return '' def tr(self, string, context=''): """ Translates a string """ if context == '': context = 'JsAlgorithmProvider' return QCoreApplication.translate(context, string)