You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

90 lines
3.3 KiB

#!/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
import copy
from Utils.Logger import logger
from Basis.Exception import decodeException
from Utils.Common import urlDecode, base64Decode
ssBasicConfig = {
'type': 'ss',
'info': {}
}
def checkPrefix(url: str) -> str: # check url prefix and remove it
if not url.startswith('ss://'):
logger.debug('Shadowsocks url should start with `ss://`')
raise decodeException('Shadowsocks prefix error')
return url[5:]
def splitTag(url: str, fromLeft: bool = True, spaceRemark: bool = True) -> tuple[str, str]: # split tag after `#`
if '#' not in url: # without tag
return url, ''
if fromLeft:
url, remark = url.split('#', 1) # from left search
else:
url, remark = url.rsplit('#', 1) # from right search
if spaceRemark: # deal with space remark for space
remark = remark.replace('+', ' ')
return url, urlDecode(remark)
def ssPlain(url: str) -> dict:
"""
FORMAT: ss://method:password@hostname:port[#TAG]
"""
config = copy.deepcopy(ssBasicConfig)
logger.debug('Shadowsocks plain decode -> %s' % url)
url, config['name'] = splitTag(checkPrefix(url), False)
userinfo, url = url.rsplit('@', 1)
config['info']['server'], config['info']['port'] = url.rsplit(':', 1)
config['info']['method'], config['info']['passwd'] = userinfo.split(':', 1)
logger.debug('Shadowsocks plain decode release -> %s', config)
return config
def ssCommon(url: str) -> dict:
"""
FORMAT: ss://BASE64-ENCODED-STRING-WITHOUT-PADDING[#TAG]
base64('method:password@hostname:port')
"""
config = copy.deepcopy(ssBasicConfig)
logger.debug('Shadowsocks common decode -> %s' % url)
url, config['name'] = splitTag(checkPrefix(url))
userinfo, url = base64Decode(url).rsplit('@', 1)
config['info']['server'], config['info']['port'] = url.rsplit(':', 1)
config['info']['method'], config['info']['passwd'] = userinfo.split(':', 1)
logger.debug('Shadowsocks common decode release -> %s', config)
return config
def sip002(url: str) -> dict:
"""
FORMAT: ss://userinfo@hostname:port [ "/" ] [ "?" plugin ] [ #tag ]
userinfo => method:password or websafe-base64-encode-utf8(method:password)
"""
config = copy.deepcopy(ssBasicConfig)
logger.debug('Shadowsocks sip002 decode -> %s' % url)
url, config['name'] = splitTag(checkPrefix(url))
userinfo, url = url.rsplit('@', 1)
try:
userinfo = base64Decode(userinfo) # userinfo encode base64 is optional
except:
userinfo = urlDecode(userinfo) # not base64 decode -> url encode format
config['info']['method'], config['info']['passwd'] = userinfo.split(':', 1)
url = url.replace('/?plugin=', '?plugin=') # remove `/` character
if '?plugin=' in url:
url, plugin = url.split('?plugin=', 1)
plugin = urlDecode(plugin).split(';', 1)
config['info']['plugin'] = {
'type': plugin[0],
'param': '' if len(plugin) == 1 else plugin[1] # default as empty string
}
config['info']['server'], config['info']['port'] = url.rsplit(':', 1)
logger.debug('Shadowsocks sip002 decode release -> %s', config)
return config