Swift - codificar URL

295

Se eu codificar uma string como esta:

var escapedString = originalString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)

não escapa das barras /.

Pesquisei e encontrei este código do Objective C:

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

Existe uma maneira mais fácil de codificar uma URL e, se não, como eu escrevo isso no Swift?

MegaCookie
fonte

Respostas:

613

Swift 3

No Swift 3 há addingPercentEncoding

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

Resultado:

teste% 2Ftest

Swift 1

No iOS 7 e acima, há stringByAddingPercentEncodingWithAllowedCharacters

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

Resultado:

teste% 2Ftest

Os seguintes são conjuntos de caracteres úteis (invertidos):

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

Se você deseja que um conjunto diferente de caracteres seja escapado, crie um conjunto:
Exemplo com o caractere "=" adicionado:

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

Resultado:

teste% 2Ftest% 3D42

Exemplo para verificar caracteres ascii que não estão no conjunto:

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
fonte
6
Ninguém mais fica perplexo com o tempo que esse código faz? Quero dizer que o nome do método já é demorado, mesmo sem escolher o conjunto de caracteres permitido.
thatidiotguy
38
Não, sou a favor da compreensibilidade em relação à nomeação concisa curta. O preenchimento automático elimina a dor. stringByAddingPercentEncodingWithAllowedCharacters()deixa pouca dúvida sobre o que faz. Comentário interessante considerando quanto tempo a palavra: "perplexo" é.
Zaph 30/09/14
1
stringByAddingPercentEncodingWithAllowedCharacters (.URLHostAllowedCharacterSet ()) Não codifica todos os caracteres corretamente A resposta de Bryan Chen é uma solução melhor.
Julio Garcia
2
@ zaph eu adicionei &ao conjunto de caracteres URLQueryAllowedCharacterSete obtive cada caractere codificado. Verificado com iOS 9, parece buggy, fui com a resposta de @ bryanchen, funciona bem !!
Akash Kava
3
A resposta abaixo que usa URLComponentse URLQueryItemé muito mais limpa IMO.
Aaron Brager
65

Você pode usar URLComponents para evitar a codificação manual da porcentagem da sua consulta:

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
}
Leo Dabus
fonte
7
Essa resposta precisa de mais atenção, pois há problemas com todos os outros (embora, para ser justo, possam ter sido as melhores práticas na época).
Asa
4
Infelizmente, URLQueryItemnem sempre codifica corretamente. Por exemplo, Formula+Oneseria codificado para Formula+One, que seria decodificado para Formula One. Portanto, tenha cuidado com o sinal de mais.
Sulthan
37

Swift 3:

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

1. encodingQuery:

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

resultado:

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

resultado:

"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
fonte
Usei a primeira solução, mas quero o meu texto de volta, como o iOS 开发 工程师.
Akshay Phulare
2
Usando urlHostAllowedpara codificar parâmetros de consulta em incorreta porque não vai codificar ?, =e +. Ao codificar parâmetros de consulta, você deve codificar o nome e o valor do parâmetro separadamente e corretamente. Isso não funcionará em um caso geral.
Sulthan
@Sulthan .. Você encontrou alguma solução / alternativa paraurlHostAllowed
Bharath
@Bharath Sim, você precisa criar um conjunto de caracteres sozinho, por exemplo, stackoverflow.com/a/39767927/669586 ou apenas usar URLComponents.
Sulthan
Os URLComponents também não codificam o +caracter. Portanto, a única opção é fazê-lo manualmente:CharacterSet.urlQueryAllowed.subtracting(CharacterSet(charactersIn: "+"))
SoftDesigner
36

Swift 3:

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

if let escapedString = originalString.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) {
//do something with escaped string
}
Hong Wei
fonte
2
Você precisa incluir `` (espaço) na cadeia de caracteres
AJP
1
Você também precisa incluir ^ #
Mani
26

Swift 4

Para codificar um parâmetro no URL, acho que usar o .alphanumericsconjunto de caracteres é a opção mais fácil:

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

O uso de qualquer um dos conjuntos de caracteres padrão para codificação de URL (como URLQueryAllowedCharacterSetou URLHostAllowedCharacterSet) não funcionará, porque eles não excluem =ou &caracteres.

Nota que, usando .alphanumericsele vai codificar alguns personagens que não precisam de ser codificados (como -, ., _ou ~- ver 2.3 Caracteres não-reservados. Em RFC 3986). Acho que usar .alphanumericsmais simples do que construir um conjunto de caracteres personalizado e não me importo com a adição de alguns caracteres adicionais a serem codificados. Se isso lhe incomoda, construa um conjunto de caracteres personalizado, conforme descrito em Como codificar por cento uma String de URL , como por exemplo:

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

Aviso: O encodedparâmetro é força desembrulhada. Para uma string unicode inválida, pode falhar. Consulte Por que o valor de retorno de String.addingPercentEncoding () é opcional? . Em vez de forçar o desembrulhamento, encoded!você pode usar encoded ?? ""ou usar if let encoded = ....

Marián Černý
fonte
1
.aphanumerics fez o truque, obrigado! Todos os outros conjuntos de caracteres não escaparam dos ', o que causou problemas ao usar as strings como parâmetros de obtenção.
Dion
14

Tudo é igual

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

// test%2Ftest
Bryan Chen
fonte
Você não .bridgeToOvjectiveC()argumentou e não recebeu "Não é possível converter o tipo da expressão 'CFString!' digitar 'CFString!' "?
Kreiri
@Kreiri Por que é necessário? Tanto o playground quanto o REPL estão felizes com meu código.
Bryan Chen
Os meus não são: / (beta 2)
Kreiri
1
Esta é uma resposta melhor, pois codifica o & corretamente.
26415 Sam
13

Swift 4:

Depende das regras de codificação seguidas pelo seu servidor.

A Apple oferece esse método de classe, mas não informa o tipo de protocolo RCF a seguir.

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

Seguindo esta ferramenta útil , você deve garantir a codificação desses caracteres para seus parâmetros:

  • $ (Cifrão) torna-se% 24
  • & (E comercial) se torna% 26
  • + (Mais) torna-se% 2B
  • , (Vírgula) se torna% 2C
  • : (Dois pontos) se torna% 3A
  • ; (Ponto e vírgula) torna-se% 3B
  • = (Igual) se torna% 3D
  • ? (Ponto de interrogação) torna-se% 3F
  • @ (Comercial A / At) torna-se% 40

Em outras palavras, falando sobre a codificação de URL, você deve seguir o protocolo RFC 1738 .

E Swift não cobre a codificação do + char por exemplo , mas funciona bem com esses três @:?caracteres.

Portanto, para codificar corretamente cada parâmetro, a .urlHostAllowedopção não é suficiente, você deve adicionar também os caracteres especiais, como por exemplo:

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

Espero que isso ajude alguém que fica louco a procurar essas informações.

Alessandro Ornano
fonte
Sua implementação está completamente errada. Como um parâmetro "věž" seria codificado?
Marián Černý
13

Swift 4 (não testado - por favor, comente se funciona ou não. Obrigado @ sumizome pela sugestão)

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

Swift 3

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

Swift 2.2 (Empréstimo de Zaph e correção de chave de consulta de URL e valores de parâmetro)

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

Exemplo:

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"

Esta é uma versão mais curta da resposta de Bryan Chen. Eu acho que isso urlQueryAllowedestá permitindo que os caracteres de controle sejam bons, a menos que eles façam parte da chave ou do valor na cadeia de caracteres da consulta, quando eles precisam ser escapados.

AJP
fonte
2
Eu gosto da solução Swift 3, mas ela não funciona para mim no Swift 4: "Não é possível usar o membro mutante em valor imutável: 'urlQueryAllowed' é uma propriedade get-only".
Marián Černý
@ MariánČerný apenas torna o CharacterSet mutável (com var) e depois o chama .removeem uma segunda etapa.
sumizome
Acredito que esta e muitas outras soluções tenham problemas ao aplicar o método duas vezes, por exemplo, ao incluir uma URL com parâmetros codificados nos parâmetros de outra URL.
FD_ 14/09/19
@FD_ você conhece ou você apenas tem um palpite? Você pode experimentar e postar de volta? Seria bom incluir essa informação se sim. Obrigado.
AJP
@AJP Acabei de testar todos os seus snippets. O Swift 3 e 4 funcionam bem, mas o do Swift 2.2 não codifica corretamente% 20 como% 2520.
FD_ 21/09/19
7

Swift 4.2

Uma solução rápida de uma linha. Substitua originalStringpela String que você deseja codificar.

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

Demonstração do Playground on-line

ajzbc
fonte
Isso funcionou graças: você pode verificar e tentar decodificar e codificar os resultados. urldecoder.org
Rakshitha Muranga Rodrigo 11/03
4

Eu também precisava disso, então escrevi uma extensão String que permite as seqüências de caracteres URLEncoding, bem como o objetivo final mais comum, converter um dicionário de parâmetros em URLs no estilo "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
    }
}

Aproveitar!

BadPirate
fonte
2
Isso não codifica o sinal '&'. Usando um '&' em um parâmetro vontade f *** a querystring
Sam
Isso está errado, ele não codifica &ou =nos parâmetros. Verifique minha solução.
Marián Černý
2

Este está trabalhando para mim.

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
}

Eu encontrei a função acima neste link: http://useyourloaf.com/blog/how-to-percent-encode-a-url-string/ .

Gaurav Singla
fonte
1

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

jaskiratjd
fonte
0

SWIFT 4.2

Às vezes, isso aconteceu apenas porque há espaço na lesma OU ausência de codificação de URL para os parâmetros que passam pela URL da API.

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

NOTA: Não esqueça de explorar bitmapRepresentation.

Vrushal Raut
fonte
0

Isso está funcionando para mim no Swift 5 . O caso de uso está usando um URL da área de transferência ou similar que já pode ter caracteres escapados, mas que também contém caracteres Unicode que podem causar URLComponentsouURL(string:) falhar.

Primeiro, crie um conjunto de caracteres que inclua todos os caracteres legais de 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
    }
}

Em seguida, estenda Stringcom um método para codificar URLs:

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
    }

}

Que pode ser testado como:

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)

Valor de urlno final: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

Observe que os espaços %20e o +espaçamento são preservados, os caracteres Unicode são codificados, %20o original urlTextnão é codificado duas vezes e a âncora (fragmento ou #) permanece.

Editar: agora verificando a validade de cada componente.

Desenho animado
fonte
0

Para que o Swift 5 finalize a sequência de códigos

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

Como usar ?

let strEncoded = self.escape(string: "http://www.edamam.com/ontologies/edamam.owl#recipe_e2a1b9bf2d996cbd9875b80612ed9aa4")
print("escapedString: \(strEncoded)")
Hardik Thakkar
fonte
0

Nenhuma dessas respostas funcionou para mim. Nosso aplicativo falhou quando um URL continha caracteres não ingleses.

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

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

Dependendo dos parâmetros do que você está tentando fazer, você pode apenas criar seu próprio conjunto de caracteres. O texto acima permite caracteres em inglês e-._~/?%$!:

Jenel Ejercito Myers
fonte