diff --git a/Basis/Exception.py b/Basis/Exception.py index aa841e5..3b2c703 100644 --- a/Basis/Exception.py +++ b/Basis/Exception.py @@ -24,3 +24,8 @@ class managerException(Exception): # for manager error class checkException(Exception): # for check error def __init__(self, reason): self.reason = reason + + +class decodeException(Exception): # for url decode + def __init__(self, reason): + self.reason = reason diff --git a/Basis/Functions.py b/Basis/Functions.py index 7297183..c841cc0 100644 --- a/Basis/Functions.py +++ b/Basis/Functions.py @@ -8,6 +8,7 @@ import psutil import random import hashlib from IPy import IP +import urllib.parse from Basis.Logger import logging @@ -111,6 +112,14 @@ def toBool(raw) -> bool: raise RuntimeError('Unable convert to bool') +def urlEncode(content: str) -> str: + return urllib.parse.urlencode(content) + + +def urlDecode(content: str) -> str: + return urllib.parse.unquote(content) + + def getAvailablePort(rangeStart: int = 1024, rangeEnd: int = 65535, waitTime: int = 10) -> int: # get available port if rangeStart > rangeEnd or rangeStart < 1 or rangeEnd > 65535: raise RuntimeError('Invalid port range') diff --git a/Decoder/Shadowsocks.py b/Decoder/Shadowsocks.py new file mode 100644 index 0000000..28f9d5b --- /dev/null +++ b/Decoder/Shadowsocks.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# SIP002: https://shadowsocks.org/guide/sip002.html +# Plain / Common: https://shadowsocks.org/guide/configs.html#uri-and-qr-code + +from Basis.Logger import logging +from Basis.Functions import urlDecode +from Basis.Exception import decodeException + + +def ssBasicConfig() -> dict: # load shadowsocks basic config + return { + 'type': 'ss', + 'info': {} + } + + +def ssPlain(url: str, spaceRemark: bool = True) -> dict: + """ + FORMAT: ss://method:password@hostname:port#TAG + """ + config = ssBasicConfig() + logging.debug('Shadowsocks plain decode -> %s' % url) + if not url.startswith('ss://'): + logging.debug('Shadowsocks url should start with `ss://`') + raise decodeException('Shadowsocks prefix error') + url = url[5:] # remove `ss://` + if '#' in url: + url, remark = url.rsplit('#', 1) # split remark + if spaceRemark: # use `+` instead of space + remark = remark.replace('+', ' ') + config['name'] = urlDecode(remark) + logging.debug('Shadowsocks url remark -> %s' % config['name']) + userinfo, url = url.rsplit('@', 1) + config['info']['server'], config['info']['port'] = url.rsplit(':', 1) + config['info']['method'], config['info']['passwd'] = userinfo.split(':', 1) + logging.debug('Shadowsocks plain release -> %s', config) + return config + diff --git a/test.py b/test.py new file mode 100755 index 0000000..7bae81a --- /dev/null +++ b/test.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from Basis import Constant +Constant.LogLevel = 'DEBUG' +from Basis.Logger import logging + +from pprint import pprint +from Filter import Filter +from Decoder import Shadowsocks + +ret = Shadowsocks.ssPlain('ss://aes-128-ctr:password@8.210.148.24:34326') +ret = Filter(ret['type'], ret['info']) +pprint(ret, sort_dicts = False)