diff --git a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala index b7ad44d7..9fb9782d 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala @@ -20,13 +20,13 @@ package com.github.shadowsocks.database -import java.net.URLEncoder import java.util.Locale import android.content.SharedPreferences +import android.net.Uri import android.os.Binder -import android.text.TextUtils import android.util.Base64 +import com.github.shadowsocks.plugin.PluginConfiguration import com.github.shadowsocks.utils.Key import com.j256.ormlite.field.{DataType, DatabaseField} @@ -93,11 +93,19 @@ class Profile { def nameIsEmpty: Boolean = name == null || name.isEmpty def getName: String = if (nameIsEmpty) formattedAddress else name - override def toString: String = "ss://" + Base64.encodeToString("%s:%s@%s:%d".formatLocal(Locale.ENGLISH, - method, password, host, remotePort).getBytes, Base64.NO_PADDING | Base64.NO_WRAP) + - (if (TextUtils.isEmpty(name)) "" else '#' + URLEncoder.encode(name, "utf-8")) - - def isMethodUnsafe: Boolean = "table".equalsIgnoreCase(method) || "rc4".equalsIgnoreCase(method) + def toUri: Uri = { + val builder = new Uri.Builder() + .scheme("ss") + .authority("%s@%s:%d".formatLocal(Locale.ENGLISH, + Base64.encodeToString("%s:%s".formatLocal(Locale.ENGLISH, method, password).getBytes, + Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE), host, remotePort)) + val configuration = new PluginConfiguration(plugin) + if (configuration.selected.nonEmpty) + builder.appendQueryParameter(Key.plugin, configuration.selectedOptions.toString) + if (!nameIsEmpty) builder.fragment(name) + builder.build() + } + override def toString: String = toUri.toString def serialize(editor: SharedPreferences.Editor): SharedPreferences.Editor = editor .putString(Key.name, name) diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala index 8597da9a..a58efc49 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala @@ -20,31 +20,46 @@ package com.github.shadowsocks.utils -import java.net.URLDecoder - -import android.util.{Base64, Log} +import android.net.Uri +import android.util.Log import com.github.shadowsocks.database.Profile object Parser { val TAG = "ShadowParser" - private val pattern = "(?i)ss://([A-Za-z0-9+-/=_]+)(#(.+))?".r - private val decodedPattern = "(?i)^((.+?)(-auth)??:(.*)@(.+?):(\\d+?))$".r + private val pattern = "(?i)^ss://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]$".r + private val userInfoPattern = "^(.+?):(.*)$".r + private val legacyPattern = "^((.+?):(.*)@(.+?):(\\d+?))$".r def findAll(data: CharSequence): Iterator[Profile] = - pattern.findAllMatchIn(if (data == null) "" else data).map(m => try - decodedPattern.findFirstMatchIn(new String(Base64.decode(m.group(1), Base64.NO_PADDING), "UTF-8")) match { - case Some(ss) => + pattern.findAllMatchIn(if (data == null) "" else data).map(m => { + val uri = Uri.parse(m.matched) + uri.getUserInfo match { + case null => uri.getHost match { + case legacyPattern(_, method, password, host, port) => // legacy uri + val profile = new Profile + profile.method = method.toLowerCase + profile.password = password + profile.host = host + profile.remotePort = port.toInt + profile.plugin = uri.getQueryParameter(Key.plugin) + profile.name = uri.getFragment + profile + case _ => + Log.e(TAG, "Unrecognized URI: " + m.matched) + null + } + case userInfoPattern(method, password) => val profile = new Profile - profile.method = ss.group(2).toLowerCase - profile.password = ss.group(4) - profile.host = ss.group(5) - profile.remotePort = ss.group(6).toInt - if (m.group(2) != null) profile.name = URLDecoder.decode(m.group(3), "utf-8") + profile.method = method + profile.password = password + profile.host = uri.getHost + profile.remotePort = uri.getPort + profile.plugin = uri.getQueryParameter(Key.plugin) + profile.name = uri.getFragment profile - case _ => null - } catch { - case ex: Exception => - Log.e(TAG, "parser error: " + m.source, ex)// Ignore + case _ => + Log.e(TAG, "Unknown user info: " + m.matched) null - }).filter(_ != null) + } + }).filter(_ != null) }