/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*/
package appauth
import std.convert.*
class URLEncoder {
static let DONT_NEED_ENCODING: BitSet = URLEncoder.getBitSet()
static let CASE_DIFF: UInt32 = 32 // ('a' - 'A') = 32
static func getBitSet(): BitSet {
let bs: BitSet = BitSet(256)
// 'a'-'z': 97-122
for (i in CHAR_a..=CHAR_z) {
bs.set(i)
}
// 'A'-'Z': 65-90
for (i in CHAR_A..=CHAR_Z) {
bs.set(i)
}
// '0'-'9': 48-57
for (i in CHAR_0..=CHAR_9) {
bs.set(i)
}
// 在encodes()方法中将空格编码为'+'。'+' = 43
bs.set(CHAR_SPACE) // ' ':32
bs.set(CHAR_B1) // '-':45
bs.set(CHAR_B2) // '_':95
bs.set(CHAR_B3) // '.':46
bs.set(CHAR_B4) // '*':42
return bs
}
public static func encode(s: String, enc: String): String {
if (enc.isEmpty()) {
throw Exception("charsetName can not be empty!")
}
if (!((enc == "UTF-8") || (enc == "utf-8") || (enc == "UTF8") || (enc == "utf8"))) {
throw Exception("only UTF-8 Character Set is supported!")
}
try {
return encode(s)
} catch (e: Exception) {
throw Exception("encode fail: " + e.message)
}
}
/**
* 使用特定字符集将字符串转换为application/x-wwwww-form-urlencoded格式。
* 此方法使用提供的字符集获取不安全字符的字节。
* 仅支持UTF-8字符集。
*
* @param 要转化的字符串.
*
* @return 返回类型为String,返回编码后的字符串。
* @since 0.37.2
*/
public static func encode(s: String): String {
var needToChange: Bool = false
let out: StringBuilder = StringBuilder()
let cas: CharArrayStream = CharArrayStream(512)
let charBuf: Array<Rune> = s.toRuneArray()
let len: Int64 = charBuf.size
var i: Int64 = 0
while (i < len) {
var ch: Rune = charBuf[i]
var c: Int64 = Int64(UInt32(ch))
if (DONT_NEED_ENCODING.get(c)) {
if (ch == r' ') {
ch = r'+'
needToChange = true
}
out.append(ch)
i++
} else {
i = convertToExternalEncoding(cas, ch, c, len, i, charBuf)
handleCharArrayStream(out, cas)
needToChange = true
}
}
if (needToChange) {
return out.toString()
}
return s
}
// 在十六进制转换之前转换为外部编码。
private static func convertToExternalEncoding(cas: CharArrayStream, char: Rune, numCh: Int64,
len: Int64, i: Int64, charBuf: Array<Rune>): Int64 {
var index: Int64 = i
var ch: Rune = char
var numChar: Int64 = numCh
do {
cas.write(ch)
if (numChar >= 0xD800 && numChar <= 0xDBFF && (index + 1) < len) {
var dChar: Rune = charBuf[index + 1]
let dNum: Int64 = Int64(UInt32(dChar))
if (dNum >= 0xDC00 && dNum <= 0xDFFF) {
cas.write(dChar)
index++
}
}
index++
if (index < len) {
ch = charBuf[index]
numChar = Int64(UInt32(ch))
} else {
break
}
} while (!DONT_NEED_ENCODING.get(numChar))
return index
}
private static func handleCharArrayStream(out: StringBuilder, cas: CharArrayStream): Unit {
let str: String = String(cas.toCharArray())
let ba: Array<UInt8> = str.toArray()
for (j in 0 .. ba.size) {
out.append('%')
var baChar: Rune = CharacterUtil.forDigit(Int64((ba[j] >> 4) & 0xF), 16)
// 如果ch是字母,则转换为使用大写字母作为十六进制值的一部分。
if (isLetter(baChar)) {
baChar = Rune(UInt32(baChar) - CASE_DIFF)
}
out.append(baChar)
baChar = CharacterUtil.forDigit(Int64(ba[j] & 0xF), 16)
if (isLetter(baChar)) {
baChar = Rune(UInt32(baChar) - CASE_DIFF)
}
out.append(baChar)
}
cas.reset()
}
private static func isLetter(ch: Rune): Bool {
return (ch >= r'a' && ch <= r'z') || (ch >= r'A' && ch <= r'Z')
}
/**
* 使用特定编码方案解码application/x-www-form-urlencoded字符串。
* 仅支持UTF-8字符集。
*
* @param 要转化的字符串.
* @param 指定字符集
*
* @return 返回类型为String,返回解码后的字符串。
*/
public static func decode(s: String, enc: String): String {
if (enc.isEmpty()) {
throw Exception("empty string enc parameter.");
}
if (!((enc == "UTF-8") || (enc == "utf-8") || (enc == "UTF8") || (enc == "utf8"))) {
throw Exception("only UTF-8 Character Set is supported!")
}
try {
return decode(s)
} catch (e: Exception) {
throw Exception(e.message)
}
}
/**
* 使用特定字符集解码application/x-www-form-urlencoded字符串。
* 提供的字符集用于确定由"%xy"形式的连续序列表示的字符。
* 仅支持UTF-8字符集。
*
* @param 要转化的字符串.
*
* @return 返回类型为String,返回解码后的字符串。
*/
public static func decode(s: String): String {
var needToChange: Bool = false
let numChars: Int64 = s.size
let sb: StringBuilder = StringBuilder()
var i: Int64 = 0
var c: Rune = ' '
var bytes: Array<UInt8> = Array<UInt8>()
let charBuf: Array<Rune> = s.toRuneArray()
while (i < numChars) {
c = charBuf[i]
match (c) {
case '+' =>
sb.append(' ')
i++
needToChange = true
case '%' =>
try {
// (numChars-i)/3是剩余字节的上限。
if (bytes.size == 0) {
bytes = Array<UInt8>((numChars - i) / 3, repeat: 0)
}
i = handlePercentageSign(s, i, numChars, c, bytes, charBuf, sb)
} catch (e: IllegalArgumentException) {
throw e
} catch (e: Exception) {
let err: String = "Illegal hex characters in escape (%) pattern - " + e.message
throw IllegalArgumentException(err)
}
needToChange = true
case _ =>
sb.append(c)
i++
}
}
if (needToChange) {
return sb.toString()
}
return s
}
private static func handlePercentageSign(s: String, index: Int64, numChars: Int64, char: Rune,
bytes: Array<UInt8>, charBuf: Array<Rune>, sb: StringBuilder): Int64 {
var pos: Int64 = 0
var i: Int64 = index
var c: Rune = char
while ( ((i + 2) < numChars) && (c == r'%')) {
let strNum: String = "0X" + s[(i+1)..(i+3)]
let v: Int64 = Int64.parse(strNum)
if (v < 0) {
let err: String = "Illegal hex characters in escape " + "(%) pattern - negative value."
throw IllegalArgumentException(err)
}
bytes[pos] = UInt8(v)
pos++
i += 3
if (i < numChars) {
c = charBuf[i]
}
}
// 结尾不完整的字节编码(如"%x")将导致抛出异常。
if (i < numChars && c == r'%') {
throw IllegalArgumentException("Incomplete trailing escape (%) pattern.")
}
sb.append(String.fromUtf8(bytes[0..pos]))
return i
}
}