Swift - codificar URL

295

Si codifico una cadena como esta:

var escapedString = originalString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)

no escapa a las barras /.

He buscado y encontrado este código de Objetivo C:

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

¿Hay alguna manera más fácil de codificar una URL y, si no, cómo escribo esto en Swift?

MegaCookie
fuente

Respuestas:

613

Swift 3

En Swift 3 hay addingPercentEncoding

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

Salida:

test% 2Ftest

Swift 1

En iOS 7 y superior hay stringByAddingPercentEncodingWithAllowedCharacters

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

Salida:

test% 2Ftest

Los siguientes son conjuntos de caracteres útiles (invertidos):

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

Si desea que se escape un conjunto diferente de caracteres, cree un conjunto:
Ejemplo con el carácter "=" agregado:

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

Salida:

test% 2Ftest% 3D42

Ejemplo para verificar caracteres ascii que no están en el 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
fuente
66
¿Nadie más está completamente asombrado de cuánto tiempo dura este código para hacer esto? Quiero decir que el nombre del método ya es muy largo, incluso sin elegir el conjunto de caracteres permitido.
thatidiotguy
38
No, estoy a favor de la comprensión sobre los nombres cortos. Autocompletar elimina el dolor. stringByAddingPercentEncodingWithAllowedCharacters()deja pocas dudas sobre lo que hace. Comentario interesante considerando cuánto tiempo dura la palabra: "estupefacto".
zaph
1
stringByAddingPercentEncodingWithAllowedCharacters (.URLHostAllowedCharacterSet ()) No codifica todos los caracteres correctamente La respuesta de Bryan Chen es una mejor solución.
Julio García
2
@zaph agregué &al conjunto de caracteres URLQueryAllowedCharacterSety obtuve cada carácter codificado. Comprobado con iOS 9, parece defectuoso, fui con la respuesta de @ bryanchen, ¡funciona bien!
Akash Kava
3
La respuesta a continuación que utiliza URLComponentsy URLQueryItemes mucho más limpia IMO.
Aaron Brager
65

Puede usar URLComponents para evitar tener que codificar manualmente el porcentaje de su cadena de 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
fuente
77
Esta respuesta necesita más atención, ya que hay problemas con todos los demás (aunque, para ser justos, pueden haber sido las mejores prácticas en ese momento).
Asa
44
Lamentablemente, URLQueryItemno siempre codifica correctamente. Por ejemplo, Formula+Onese codificaría a Formula+One, que se decodificaría a Formula One. Por lo tanto, tenga cuidado con el signo más.
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
fuente
Usé la primera solución pero quiero recuperar mi texto, como iOS 开发 工程师.
Akshay Phulare
2
Utilizando urlHostAllowedpara codificar parámetros de consulta incorrectos porque no codificará ?, =y +. Al codificar parámetros de consulta, debe codificar el nombre y el valor del parámetro por separado y correctamente. Esto no funcionará en un caso general.
Sulthan
@Sulthan .. ¿Ha encontrado ninguna solución / alternativaurlHostAllowed
Bharat
@Bharath Sí, debe crear un conjunto de caracteres usted mismo, por ejemplo stackoverflow.com/a/39767927/669586 o simplemente usarlo URLComponents.
Sulthan
Los componentes de URLC tampoco codifican el +carácter. Entonces, la única opción es hacerlo 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
fuente
2
Debe incluir `` (espacio) en la cadena de caracteres
AJP
1
También debe incluir ^
Mani
26

Swift 4

Para codificar un parámetro en URL, creo que usar el .alphanumericsjuego de caracteres es la opción más fácil:

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

El uso de cualquiera de los juegos de caracteres estándar para la codificación de URL (como URLQueryAllowedCharacterSeto URLHostAllowedCharacterSet) no funcionará, ya que no excluyen =ni& caracteres.

Nota que mediante el uso .alphanumericsque va a codificar algunos caracteres que no necesitan ser codificados (como -, ., _o ~- ver 2.3 Los caracteres no reservados. En el RFC 3986). Me parece .alphanumericsmás simple que construir un juego de caracteres personalizado y no me importa codificar algunos caracteres adicionales. Si eso te molesta, construye un juego de caracteres personalizado como se describe en Cómo codificar en porcentaje una Cadena URL , como por ejemplo:

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

Advertencia: el encodedparámetro es forzado sin envolver. Para una cadena unicode no válida, podría bloquearse. Consulte ¿Por qué el valor de retorno de String.addingPercentEncoding () es opcional? . En lugar de desenvolver la fuerza encoded!, puede usar encoded ?? ""o usar if let encoded = ....

Marián Černý
fuente
1
.aphanumerics hizo el truco, ¡gracias! Todos los otros conjuntos de caracteres no escaparon a los '' que causaron problemas al usar las cadenas como parámetros de obtención.
Dion
14

Todo es igual

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

// test%2Ftest
Bryan Chen
fuente
No hizo un .bridgeToOvjectiveC()segundo argumento y no obtuvo "¡No se puede convertir el tipo de expresión 'CFString!' escribir 'CFString!' "
Kreiri
@Kreiri ¿Por qué es necesario? Tanto el área de juegos como REPL están contentos con mi código.
Bryan Chen
Los míos no son: / (beta 2)
Kreiri
1
Esta es una mejor respuesta, ya que codifica el & correctamente.
Sam
13

Swift 4:

Depende de las reglas de codificación seguidas por su servidor.

Apple ofrece este método de clase, pero no informa qué tipo de protocolo RCF sigue.

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

Siguiendo esta útil herramienta , debe garantizar la codificación de estos caracteres para sus parámetros:

  • $ (Signo de dólar) se convierte en% 24
  • & (Ampersand) se convierte en% 26
  • + (Más) se convierte en% 2B
  • , (Coma) se convierte en% 2C
  • : (Colon) se convierte en% 3A
  • ; (Semicolón) se convierte en% 3B
  • = (Igual) se convierte en% 3D
  • ? (Signo de interrogación) se convierte en% 3F
  • @ (Comercial A / At) se convierte en% 40

En otras palabras, hablando de codificación de URL, debe seguir el protocolo RFC 1738 .

Y Swift no cubre la codificación del + char por ejemplo , pero funciona bien con estos tres @:?caracteres

Entonces, para codificar correctamente cada uno de sus parámetros, la .urlHostAllowedopción no es suficiente, debe agregar también los caracteres especiales como, por ejemplo:

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

Espero que esto ayude a alguien que se vuelva loco a buscar esta información.

Alessandro Ornano
fuente
Su implementación es completamente incorrecta. ¿Cómo se codificaría un parámetro "věž"?
Marián Černý
13

Swift 4 (no probado - por favor comente si funciona o no. Gracias @sumizome por su sugerencia)

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 (Préstamo de Zaph's y corrección de valores de parámetros y clave de consulta de URL)

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

Ejemplo:

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 es una versión más corta de la respuesta de Bryan Chen. Supongo que eso urlQueryAllowedpermite que los caracteres de control estén bien, a menos que formen parte de la clave o el valor en la cadena de consulta, en cuyo punto deben escaparse.

AJP
fuente
2
Me gusta la solución Swift 3, pero no me funciona en Swift 4: "No se puede usar el miembro mutante en un valor inmutable: 'urlQueryAllowed' es una propiedad de solo obtención".
Marián Černý
@ MariánČerný solo hace que el CharacterSet sea mutable (con var) y luego invoque .removeen un segundo paso.
sumizome
Creo que esta y la mayoría de las otras soluciones tienen problemas al aplicar el método dos veces, por ejemplo, cuando se incluye una URL con parámetros codificados en los parámetros de otra URL.
FD_
@FD_, ¿sabes o solo tienes una corazonada? ¿Puedes experimentar con él y publicar de nuevo? Sería bueno incluir esta información si es así. Gracias.
AJP
@AJP Acabo de probar todos tus fragmentos. Swift 3 y 4 funcionan bien, pero el de Swift 2.2 no codifica correctamente% 20 como% 2520.
FD_
7

Swift 4.2

Una solución rápida de una línea. Reemplace originalStringcon la cadena que desea codificar.

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

Demostración de juegos en línea

ajzbc
fuente
Esto funcionó gracias: puede verificar e intentar decodificar y codificar resultados. urldecoder.org
Rakshitha Muranga Rodrigo
4

Necesitaba esto yo mismo, así que escribí una extensión de cadena que permite las cadenas URLEncoding, así como el objetivo final más común, convertir un diccionario de parámetros en parámetros de URL de 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
    }
}

¡Disfrutar!

BadPirate
fuente
2
Esto no codifica el signo '&'. El uso de un '&' en un parámetro mejorará la cadena de consulta
Sam
Esto está mal, no codifica &ni =en los parámetros. Verifique mi solución en su lugar.
Marián Černý
2

Este me está funcionando.

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
}

Encontré la función anterior desde este enlace: http://useyourloaf.com/blog/how-to-percent-encode-a-url-string/ .

Gaurav Singla
fuente
1

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

jaskiratjd
fuente
0

SWIFT 4.2

A veces esto sucedió solo porque hay espacio en slug O ausencia de codificación de URL para los parámetros que pasan por la URL de 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: No olvide explorar sobre bitmapRepresentation.

Vrushal Raut
fuente
0

Esto está funcionando para mí en Swift 5 . El caso de uso es tomar una URL del portapapeles o similar que ya puede tener caracteres de escape pero que también contiene caracteres Unicode que podrían causar URLComponentsoURL(string:) fallar.

Primero, cree un conjunto de caracteres que incluya todos los caracteres legales 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
    }
}

A continuación, extienda Stringcon un método para codificar 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
    }

}

Que se puede probar 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 urlal 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

Tenga en cuenta que ambos %20y el +espacio se conservan, los caracteres Unicode están codificados, %20el original urlTextno está doblemente codificado y el ancla (fragmento o# ) permanece.

Editar: ahora verifica la validez de cada componente.

Dibujos animados
fuente
0

Para Swift 5 para codificar la cadena

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

Cómo utilizar ?

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

Ninguna de estas respuestas funcionó para mí. Nuestra aplicación se bloqueaba cuando una url contenía caracteres que no estaban en inglés.

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

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

Dependiendo de los parámetros de lo que intente hacer, es posible que desee crear su propio conjunto de caracteres. Lo anterior permite caracteres en inglés y-._~/?%$!:

Jenel Ejercito Myers
fuente