Swift - кодировать URL

295

Если я закодирую строку, как это:

var escapedString = originalString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)

это не избежать косой черты /.

Я искал и нашел этот код Objective C:

NSString *encodedString = (NSString *)CFURLCreateStringByAddingPercentEscapes(
                        NULL,
                        (CFStringRef)unencodedString,
                        NULL,
                        (CFStringRef)@"!*'();:@&=+$,/?%#[]",
                        kCFStringEncodingUTF8 );

Есть ли более простой способ закодировать URL, и если нет, то как мне написать это в Swift?

MegaCookie
источник

Ответы:

613

Свифт 3

В Swift 3 есть addingPercentEncoding

let originalString = "test/test"
let escapedString = originalString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
print(escapedString!)

Вывод:

Тест% 2Ftest

Свифт 1

В iOS 7 и выше есть stringByAddingPercentEncodingWithAllowedCharacters

var originalString = "test/test"
var escapedString = originalString.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())
println("escapedString: \(escapedString)")

Вывод:

Тест% 2Ftest

Ниже приведены полезные (инвертированные) наборы символов:

URLFragmentAllowedCharacterSet  "#%<>[\]^`{|}
URLHostAllowedCharacterSet      "#%/<>?@\^`{|}
URLPasswordAllowedCharacterSet  "#%/:<>?@[\]^`{|}
URLPathAllowedCharacterSet      "#%;<>?[\]^`{|}
URLQueryAllowedCharacterSet     "#%<>[\]^`{|}
URLUserAllowedCharacterSet      "#%/:<>?@[\]^`

Если вы хотите, чтобы другой набор символов был экранирован, создайте набор:
Пример с добавленным символом "=":

var originalString = "test/test=42"
var customAllowedSet =  NSCharacterSet(charactersInString:"=\"#%/<>?@\\^`{|}").invertedSet
var escapedString = originalString.stringByAddingPercentEncodingWithAllowedCharacters(customAllowedSet)
println("escapedString: \(escapedString)")

Вывод:

Тест% 2Ftest% 3D42

Пример проверки символов ascii, не входящих в набор:

func printCharactersInSet(set: NSCharacterSet) {
    var characters = ""
    let iSet = set.invertedSet
    for i: UInt32 in 32..<127 {
        let c = Character(UnicodeScalar(i))
        if iSet.longCharacterIsMember(i) {
            characters = characters + String(c)
        }
    }
    print("characters not in set: \'\(characters)\'")
}
zaph
источник
6
Никто больше не ошеломлен тем, как долго этот код делает это? Я имею в виду, что имя метода уже чертовски длинное, даже без выбора допустимого набора символов.
thatidiotguy
38
Нет, я предпочитаю понятность по сравнению с кратким наименованием. Автозаполнение снимает боль. stringByAddingPercentEncodingWithAllowedCharacters()оставляет мало сомнений в том, что он делает. Интересный комментарий, учитывая, как долго слово «ошеломленный».
Zaph
1
stringByAddingPercentEncodingWithAllowedCharacters (.URLHostAllowedCharacterSet ()) Не правильно кодирует все символы. Ответ Брайана Чена - лучшее решение.
Хулио Гарсия
2
@zaph Я добавил &в набор символов URLQueryAllowedCharacterSetи получил каждый кодированный символ. Проверено с iOS 9, похоже глючит, я пошел с ответом @ bryanchen, все работает хорошо !!
Акаш Кава
3
Ответ ниже, который использует URLComponentsи URLQueryItemнамного чище ИМО.
Аарон Брагер
65

Вы можете использовать URLComponents, чтобы избежать необходимости вручную кодировать строку запроса в процентах:

let scheme = "https"
let host = "www.google.com"
let path = "/search"
let queryItem = URLQueryItem(name: "q", value: "Formula One")


var urlComponents = URLComponents()
urlComponents.scheme = scheme
urlComponents.host = host
urlComponents.path = path
urlComponents.queryItems = [queryItem]

if let url = urlComponents.url {
    print(url)   // "https://www.google.com/search?q=Formula%20One"
}

extension URLComponents {
    init(scheme: String = "https",
         host: String = "www.google.com",
         path: String = "/search",
         queryItems: [URLQueryItem]) {
        self.init()
        self.scheme = scheme
        self.host = host
        self.path = path
        self.queryItems = queryItems
    }
}

let query = "Formula One"
if let url = URLComponents(queryItems: [URLQueryItem(name: "q", value: query)]).url {
    print(url)  // https://www.google.com/search?q=Formula%20One
}
Лео Дабус
источник
7
Этот ответ требует большего внимания, так как есть проблемы со всеми остальными (хотя, чтобы быть справедливым, они могли быть лучшей практикой в ​​то время).
Аса
4
К сожалению, URLQueryItemне всегда правильно кодирует. Например, Formula+Oneбудет закодирован в Formula+One, который будет декодирован в Formula One. Поэтому будьте осторожны со знаком плюс.
Sulthan
37

Свифт 3:

let originalString = "http://www.ihtc.cc?name=htc&title=iOS开发工程师"

1. encodingQuery:

let escapedString = originalString.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)

результат:

"http://www.ihtc.cc?name=htc&title=iOS%E5%BC%80%E5%8F%91%E5%B7%A5%E7%A8%8B%E5%B8%88" 

2. encodingURL:

let escapedString = originalString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)

результат:

"http:%2F%2Fwww.ihtc.cc%3Fname=htc&title=iOS%E5%BC%80%E5%8F%91%E5%B7%A5%E7%A8%8B%E5%B8%88"
iHTCboy
источник
Я использовал первое решение, но я хочу, чтобы мой текст обратно, как iOS 开发 工程师.
Акшай Фулар
2
Использование urlHostAllowedдля кодирования параметров запроса неверно, потому что он не будет кодировать ?, =и +. При кодировании параметров запроса вы должны кодировать имя и значение параметра отдельно и правильно. Это не будет работать в общем случае.
Султан
@Sulthan .. Нашли ли вы какое-либо решение / альтернативу дляurlHostAllowed
Бхарат
@Bharath Да, вы должны создать набор символов самостоятельно, например stackoverflow.com/a/39767927/669586 или просто использовать URLComponents.
Sulthan
URLComponents тоже не кодируют +символ. Поэтому единственный вариант - сделать это вручную:CharacterSet.urlQueryAllowed.subtracting(CharacterSet(charactersIn: "+"))
SoftDesigner,
36

Свифт 3:

let allowedCharacterSet = (CharacterSet(charactersIn: "!*'();:@&=+$,/?%#[] ").inverted)

if let escapedString = originalString.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) {
//do something with escaped string
}
Хун Вэй
источник
2
Вам нужно включить `` (пробел) в строку символов
AJP
1
Вы также должны включить ^
Мани
26

Swift 4

Для кодирования параметра в URL я нахожу использование .alphanumericsнабора символов самым простым вариантом:

let encoded = parameter.addingPercentEncoding(withAllowedCharacters: .alphanumerics)
let url = "http://www.example.com/?name=\(encoded!)"

Использование любого из стандартных наборов символов для кодирования URL (например, URLQueryAllowedCharacterSetилиURLHostAllowedCharacterSet ) не будет работать, потому что они не исключают =или& символы символы.

Обратите внимание , что при использовании .alphanumericsего кодируют некоторые символы , которые не должны быть закодированы (например -, ., _или ~- см 2.3 незарезервированной символы. В RFC 3986). Я считаю, что использовать .alphanumericsпроще, чем создавать собственный набор символов, и не против некоторых дополнительных символов для кодирования. Если это вас беспокоит, создайте пользовательский набор символов, как описано в разделе Как кодировать строку URL в процентах , например:

var allowed = CharacterSet.alphanumerics
allowed.insert(charactersIn: "-._~") // as per RFC 3986
let encoded = parameter.addingPercentEncoding(withAllowedCharacters: allowed)
let url = "http://www.example.com/?name=\(encoded!)"

Предупреждение:encoded параметр силы разворачивали. Для недопустимой строки Unicode может произойти сбой. См. Почему возвращаемое значение String.addingPercentEncoding () необязательно? , Вместо принудительного развертывания encoded!вы можете использовать encoded ?? ""или использовать if let encoded = ....

Мариан Черны
источник
1
.aphanumerics сделали свое дело, спасибо! Все остальные наборы символов не избежали символов &, которые вызывали проблемы при использовании строк в качестве параметров получения.
Дион
14

Все одинаково

var str = CFURLCreateStringByAddingPercentEscapes(
    nil,
    "test/test",
    nil,
    "!*'();:@&=+$,/?%#[]",
    CFStringBuiltInEncodings.UTF8.rawValue
)

// test%2Ftest
Брайан Чен
источник
Вы не .bridgeToOvjectiveC()указали второй аргумент и не получили "Невозможно преобразовать тип выражения 'CFString!' набрать 'CFString!' "?
Крейри
@ Kreiri Зачем это нужно? И детская площадка, и REPL довольны моим кодом.
Брайан Чен
Мои нет: / (бета 2)
Kreiri
1
Это лучший ответ, так как кодирует & правильно.
Сэм
13

Свифт 4:

Это зависит от правил кодирования вашего сервера.

Apple предлагает этот метод класса, но он не сообщает, какой протокол RCF следует.

var escapedString = originalString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!

Следуя этому полезному инструменту, вы должны гарантировать кодирование этих символов для ваших параметров:

  • $ (Знак доллара) становится% 24
  • & (Амперсанд) становится% 26
  • + (Плюс) становится% 2B
  • , (Запятая) становится% 2C
  • : (Двоеточие) становится% 3A
  • ; (Полуколон) становится% 3B
  • = (Равно) становится% 3D
  • ? (Знак вопроса) становится% 3F
  • @ (Commercial A / At) становится% 40

Другими словами, говоря о кодировании URL, вы должны следовать протоколу RFC 1738 .

И Swift не охватывает, например , кодировку + char , но она хорошо работает с этими тремя @:? символы.

Таким образом, для правильной кодировки каждого вашего параметра .urlHostAllowedопции недостаточно, вам следует добавить также специальные символы, например:

encodedParameter = parameter.replacingOccurrences(of: "+", with: "%2B")

Надеюсь, что это помогает кому-то, кто сходит с ума, чтобы искать эту информацию.

Алессандро Орнано
источник
Ваша реализация совершенно неверна. Как будет закодирован параметр "věž"?
Мариан Черны
13

Swift 4 (не тестировался - прокомментируйте, работает ли он или нет. Спасибо @sumizome за предложение)

var allowedQueryParamAndKey = NSCharacterSet.urlQueryAllowed
allowedQueryParamAndKey.remove(charactersIn: ";/?:@&=+$, ")
paramOrKey.addingPercentEncoding(withAllowedCharacters: allowedQueryParamAndKey)

Свифт 3

let allowedQueryParamAndKey =  NSCharacterSet.urlQueryAllowed.remove(charactersIn: ";/?:@&=+$, ")
paramOrKey.addingPercentEncoding(withAllowedCharacters: allowedQueryParamAndKey)

Swift 2.2 (заимствование у Zaph и исправление для ключа URL-запроса и значений параметров)

var allowedQueryParamAndKey =  NSCharacterSet(charactersInString: ";/?:@&=+$, ").invertedSet
paramOrKey.stringByAddingPercentEncodingWithAllowedCharacters(allowedQueryParamAndKey)

Пример:

let paramOrKey = "https://some.website.com/path/to/page.srf?a=1&b=2#top"
paramOrKey.addingPercentEncoding(withAllowedCharacters: allowedQueryParamAndKey)
// produces:
"https%3A%2F%2Fsome.website.com%2Fpath%2Fto%2Fpage.srf%3Fa%3D1%26b%3D2%23top"

Это более короткая версия ответа Брайана Чена. Я предполагаю, что urlQueryAllowedразрешается использовать управляющие символы, если они не являются частью ключа или значения в строке запроса, и в этот момент их необходимо экранировать.

AJP
источник
2
Мне нравится решение Swift 3, но оно не работает для меня в Swift 4: «Невозможно использовать мутирующий член для неизменяемого значения:« urlQueryAllowed »является свойством только для получения».
Мариан Черны
@ MariánČerný просто сделайте изменяемый набор символов (с var), а затем вызовите .removeего на втором этапе.
Sumizome
Я считаю, что это и большинство других решений имеют проблемы при двойном применении метода, например, при включении URL-адреса с закодированными параметрами в параметры другого URL-адреса.
FD_
@FD_ ты знаешь или у тебя есть предчувствие? Можете ли вы поэкспериментировать с этим и отправить обратно? Было бы хорошо, чтобы включить эту информацию, если так. Спасибо.
AJP
@AJP Я только что проверил все твои фрагменты. Swift 3 и 4 работают нормально, но для Swift 2.2 неправильно кодирует% 20 как% 2520.
FD_
7

Swift 4.2

Быстрое решение в одну строку. Замените originalStringна строку, которую вы хотите кодировать.

var encodedString = originalString.addingPercentEncoding(withAllowedCharacters: CharacterSet(charactersIn: "!*'();:@&=+$,/?%#[]{} ").inverted)

Демоверсия игровой площадки

ajzbc
источник
Это сработало, спасибо: вы можете проверить и попробовать декодировать и кодировать результаты. urldecoder.org
Ракшита Муранга Родриго
4

Я сам нуждался в этом, поэтому я написал расширение String, которое учитывает строки URLEncoding, а также более общую конечную цель - преобразование словаря параметров в URL-параметры стиля "GET":

extension String {
    func URLEncodedString() -> String? {
        var escapedString = self.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
        return escapedString
    }
    static func queryStringFromParameters(parameters: Dictionary<String,String>) -> String? {
        if (parameters.count == 0)
        {
            return nil
        }
        var queryString : String? = nil
        for (key, value) in parameters {
            if let encodedKey = key.URLEncodedString() {
                if let encodedValue = value.URLEncodedString() {
                    if queryString == nil
                    {
                        queryString = "?"
                    }
                    else
                    {
                        queryString! += "&"
                    }
                    queryString! += encodedKey + "=" + encodedValue
                }
            }
        }
        return queryString
    }
}

Наслаждайтесь!

BadPirate
источник
2
Это не кодирует знак «&». Использование '&' в параметре приведет к появлению строки запроса
Сэм
Это неправильно, это не кодирует &или =в параметрах. Проверьте мое решение вместо этого.
Мариан Черны
2

Этот работает на меня.

func stringByAddingPercentEncodingForFormData(plusForSpace: Bool=false) -> String? {

    let unreserved = "*-._"
    let allowed = NSMutableCharacterSet.alphanumericCharacterSet()
    allowed.addCharactersInString(unreserved)

    if plusForSpace {
        allowed.addCharactersInString(" ")
    }

    var encoded = stringByAddingPercentEncodingWithAllowedCharacters(allowed)

    if plusForSpace {
        encoded = encoded?.stringByReplacingOccurrencesOfString(" ", withString: "+")
    }
    return encoded
}

Я нашел выше функцию по этой ссылке: http://useyourloaf.com/blog/how-to-percent-encode-a-url-string/ .

Гаурав Сингла
источник
1

let Url = URL(string: urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")

jaskiratjd
источник
0

SWIFT 4.2

Иногда это происходит только потому, что в слаге ИЛИ отсутствует кодировка URL для параметров, проходящих через API URL.

let myString = self.slugValue
                let csCopy = CharacterSet(bitmapRepresentation: CharacterSet.urlPathAllowed.bitmapRepresentation)
                let escapedString = myString!.addingPercentEncoding(withAllowedCharacters: csCopy)!
                //always "info:hello%20world"
                print(escapedString)

ПРИМЕЧАНИЕ: не забудьте изучить bitmapRepresentation.

Врушал Раут
источник
0

Это работает для меня в Swift 5 . В случае использования берется URL-адрес из буфера обмена или аналогичный, который, возможно, уже содержит экранированные символы, но также содержит символы Юникода, которые могут вызвать URLComponentsили вызвать URL(string:)сбой.

Сначала создайте набор символов, который включает все допустимые для URL символы:

extension CharacterSet {

    /// Characters valid in at least one part of a URL.
    ///
    /// These characters are not allowed in ALL parts of a URL; each part has different requirements. This set is useful for checking for Unicode characters that need to be percent encoded before performing a validity check on individual URL components.
    static var urlAllowedCharacters: CharacterSet {
        // Start by including hash, which isn't in any set
        var characters = CharacterSet(charactersIn: "#")
        // All URL-legal characters
        characters.formUnion(.urlUserAllowed)
        characters.formUnion(.urlPasswordAllowed)
        characters.formUnion(.urlHostAllowed)
        characters.formUnion(.urlPathAllowed)
        characters.formUnion(.urlQueryAllowed)
        characters.formUnion(.urlFragmentAllowed)

        return characters
    }
}

Затем добавьте Stringметод для кодирования URL:

extension String {

    /// Converts a string to a percent-encoded URL, including Unicode characters.
    ///
    /// - Returns: An encoded URL if all steps succeed, otherwise nil.
    func encodedUrl() -> URL? {        
        // Remove preexisting encoding,
        guard let decodedString = self.removingPercentEncoding,
            // encode any Unicode characters so URLComponents doesn't choke,
            let unicodeEncodedString = decodedString.addingPercentEncoding(withAllowedCharacters: .urlAllowedCharacters),
            // break into components to use proper encoding for each part,
            let components = URLComponents(string: unicodeEncodedString),
            // and reencode, to revert decoding while encoding missed characters.
            let percentEncodedUrl = components.url else {
            // Encoding failed
            return nil
        }

        return percentEncodedUrl
    }

}

Который может быть проверен как:

let urlText = "https://www.example.com/폴더/search?q=123&foo=bar&multi=eggs+and+ham&hangul=한글&spaced=lovely%20spam&illegal=<>#top"
let url = encodedUrl(from: urlText)

Значение urlв конце:https://www.example.com/%ED%8F%B4%EB%8D%94/search?q=123&foo=bar&multi=eggs+and+ham&hangul=%ED%95%9C%EA%B8%80&spaced=lovely%20spam&illegal=%3C%3E#top

Обратите внимание, что оба %20и +интервал сохраняются, символы Unicode кодируются, %20в оригинале urlTextне кодируется дважды, а привязка (фрагмент или #) остается.

Редактировать: теперь проверка на правильность каждого компонента.

CartoonChess
источник
0

Для Swift 5 в конец строки кода

func escape(string: String) -> String {
    let allowedCharacters = string.addingPercentEncoding(withAllowedCharacters: CharacterSet(charactersIn: ":=\"#%/<>?@\\^`{|}").inverted) ?? ""
    return allowedCharacters
}

Как пользоваться ?

let strEncoded = self.escape(string: "http://www.edamam.com/ontologies/edamam.owl#recipe_e2a1b9bf2d996cbd9875b80612ed9aa4")
print("escapedString: \(strEncoded)")
Хардик Таккар
источник
0

Ни один из этих ответов не работал для меня. Наше приложение зависало, когда URL содержал неанглийские символы.

 let unreserved = "-._~/?%$!:"
 let allowed = NSMutableCharacterSet.alphanumeric()
     allowed.addCharacters(in: unreserved)

 let escapedString = urlString.addingPercentEncoding(withAllowedCharacters: allowed as CharacterSet)

В зависимости от параметров того, что вы пытаетесь сделать, вы можете просто создать свой собственный набор символов. Выше учитывает английские символы, и-._~/?%$!:

Дженел Эджерсито Майерс
источник