Swift - кодувати URL


295

Якщо я кодую такий рядок:

var escapedString = originalString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)

це не уникне косоокості /.

Я шукав і знайшов цей об'єктивний код C:

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

Чи є простіший спосіб кодування URL-адреси, а якщо ні, то як це написати в Swift?

Відповіді:


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)\'")
}

6
Хіба ніхто більше не здивований, як довго цей код робити? Я маю на увазі, що назва методу вже давно, навіть не вибираючи дозволений набір символів.
thatidiotguy

38
Ні, я підтримую зрозумілість у зв'язку з короткими терміновими назвами. Автозавершення знімає біль. stringByAddingPercentEncodingWithAllowedCharacters()залишає мало сумнівів у тому, що це робить. Цікавий коментар, враховуючи, як довго триває слово: "заграбоване".
zaph

1
stringByAddingPercentEncodingWithAllowedCharacters (.URLHostAllowedCharacterSet ()) Не кодує всіх символів належним чином відповідь Брайана Чена є кращим рішенням.
Хуліо Гарсія

2
@zaph Я додав &до набору символів, URLQueryAllowedCharacterSetі я кодував кожен символ. Перевірив iOS 9, схоже на помилку, я пішов з відповіддю @ bryanchen, це працює добре !!
Акаш Кава

3
Відповідь нижче, що використовує URLComponentsта URLQueryItemнабагато чистіший IMO.
Аарон Брагер

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. Тому будьте обережні зі знаком плюс.
Султан

37

Швидкий 3:

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

1. Кодуваннязапит:

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. кодуванняURL:

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"

Я використовував перше рішення, але хочу повернути текст назад, як iOS 开发 工程师.
Акшай-Фуларе

2
Використання urlHostAllowedдля кодування параметрів запиту неправильно, оскільки він не буде кодувати ?, =і +. При кодуванні параметрів запиту необхідно кодувати ім'я та значення параметра окремо та правильно. У загальному випадку це не спрацює.
Султан

@Sulthan .. Чи ви яке - або рішення / альтернативаurlHostAllowed
Бхарат

@Bharath Так, ви повинні створити набір символів самостійно, наприклад, stackoverflow.com/a/39767927/669586 або просто використовувати URLComponents.
Султан

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
Вам також потрібно включити ^
Mani

26

Швидкий 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 задоволені моїм кодом.
Брайан Чен

Мої немає: / (beta 2)
Kreiri

1
Це краща відповідь, оскільки вона кодує & правильно.
Сем

13

Швидкий 4:

Це залежить від правил кодування, якими керується ваш сервер.

Apple пропонує цей метод класу, але він не повідомляє про такий протокол RCF, який він випливає.

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

Дотримуючись цього корисного інструменту, ви повинні гарантувати кодування цих символів для своїх параметрів:

  • $ (Знак долара) стає% 24
  • & (Ampersand) стає% 26
  • + (Плюс) стає% 2B
  • , (Кома) стає% 2C
  • : (Двокрапка) стає% 3A
  • ; (Напівколон) стає% 3B
  • = (Дорівнює) стає% 3D
  • ? (Знак запитання) стає% 3F
  • @ (Комерційна A / At) стає% 40

Іншими словами, говорячи про кодування URL-адрес, слід слідувати протоколу RFC 1738 .

І Swift, наприклад, не покриває кодування позначки + , але це добре працює з цими трьома @:? символів.

Отже, щоб правильно кодувати кожен ваш параметр, .urlHostAllowedопція недостатня, вам слід додати також спеціальні символи, наприклад:

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

Сподіваюся, це допоможе тому, хто з глузду шукає цю інформацію.


Ваша реалізація абсолютно неправильна. Як би кодувався параметр "věž"?
Marián Černý

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's та виправлення значень ключа та параметрів запиту 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це дозволяє контрольним символам, через які добре, якщо вони не є частиною ключа або значення у рядку запиту, в який момент їх потрібно уникнути.


2
Мені подобається рішення Swift 3, але воно не працює для мене в Swift 4: "Неможливо використовувати мутуючий член за незмінним значенням: 'urlQueryAllowed' - це властивість лише для отримання".
Marián Černý

@ MariánČerný просто зробіть CharacterSet змінним (з var), а потім зателефонуйте .removeна нього на другому кроці.
sumizome

Я вважаю, що це та більшість інших рішень мають проблеми при застосуванні методу двічі, наприклад, при включенні URL-адреси з закодованими парамами в параметри іншої URL-адреси.
FD_

@FD_ ти знаєш чи ти просто маєш уяву? Чи можете ви експериментувати з цим і відправляти назад? Було б добре включити цю інформацію, якщо так. Дякую.
AJP

@AJP Я лише перевірив усі ваші фрагменти. Swift 3 і 4 працюють добре, але той, який використовується для Swift 2.2, не кодує належним чином% 20 як% 2520.
FD_

7

Швидкий 4.2

Швидке рішення на одну лінію Замініть originalStringрядок, який потрібно кодувати.

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

Демонстрація онлайн-майданчика


Це спрацювало завдяки: ви можете перевірити та спробувати розшифрувати та кодувати результати. 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
    }
}

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


2
Це не кодує знак "&". Використання параметра "&" в параметрі призведе до введення рядка запиту
Sam

Це неправильно, воно не кодує &і не =параметри. Перевірте замість мого рішення.
Marián Černý

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) ?? "")


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-адреси з буфера обміну або подібного, який, можливо, вже уникнув символи, але який також містить символи Unicode, які можуть викликати 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кодується не подвійне, а якір (фрагмент, або# ) залишається.

Редагувати: Тепер перевіряємо достовірність кожного компонента.


0

Щоб Swift 5 закінчував рядок endcode

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)

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.