Swift - encoder l'URL

295

Si j'encode une chaîne comme celle-ci:

var escapedString = originalString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)

il n'échappe pas aux barres obliques /.

J'ai recherché et trouvé ce code Objective C:

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

Existe-t-il un moyen plus simple de coder une URL et sinon, comment l'écrire dans Swift?

MegaCookie
la source

Réponses:

613

Swift 3

Dans Swift 3, il y a addingPercentEncoding

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

Production:

test% 2Ftest

Swift 1

Dans iOS 7 et supérieur, il y a stringByAddingPercentEncodingWithAllowedCharacters

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

Production:

test% 2Ftest

Les jeux de caractères suivants sont utiles (inversés):

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

Si vous souhaitez échapper un autre jeu de caractères, créez un jeu:
Exemple avec un caractère "=" ajouté:

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

Production:

test% 2Ftest% 3D42

Exemple de vérification des caractères ascii absents de l'ensemble:

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
la source
6
Personne d'autre n'est complètement sidéré quant à la durée de ce code pour le faire? Je veux dire que le nom de la méthode est déjà très long, même sans choisir le jeu de caractères autorisé.
thatidiotguy
38
Non, je préfère la compréhensibilité à la courte dénomination concise. La saisie semi-automatique élimine la douleur. stringByAddingPercentEncodingWithAllowedCharacters()laisse peu de doute sur ce qu'il fait. Commentaire intéressant compte tenu de la longueur du mot: "sidéré".
zaph
1
stringByAddingPercentEncodingWithAllowedCharacters (.URLHostAllowedCharacterSet ()) N'encode pas correctement tous les caractères La réponse de Bryan Chen est une meilleure solution.
Julio Garcia
2
@zaph j'ai ajouté &au jeu de caractères de URLQueryAllowedCharacterSetet j'ai obtenu chaque caractère encodé. Vérifié avec iOS 9, ressemble à un buggy, je suis allé avec la réponse de @ bryanchen, cela fonctionne bien !!
Akash Kava
3
La réponse ci-dessous utilise URLComponentset URLQueryItemest beaucoup plus propre OMI.
Aaron Brager
65

Vous pouvez utiliser URLComponents pour éviter d'avoir à encoder manuellement en pourcentage votre chaîne de requête:

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
la source
7
Cette réponse nécessite plus d'attention, car il y a des problèmes avec toutes les autres (bien que pour être juste, elles pouvaient être les meilleures pratiques à l'époque).
Asa
4
Malheureusement, URLQueryItemne code pas toujours correctement. Par exemple, Formula+Oneserait codé en Formula+One, qui serait décodé en Formula One. Soyez donc prudent avec le signe plus.
Sulthan
37

Swift 3:

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

1. encodingQuery:

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

résultat:

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

résultat:

"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
la source
J'ai utilisé la première solution mais je veux récupérer mon texte, comme iOS 开发 工程师.
Akshay Phulare
2
Utilisation urlHostAllowedpour le codage des paramètres de requête incorrecte car elle ne sera pas codée ?, =et +. Lors du codage de paramètres de requête, vous devez coder le nom et la valeur du paramètre séparément et correctement. Cela ne fonctionnera pas dans un cas général.
Sulthan
@Sulthan .. Avez-vous trouvé une solution / alternative poururlHostAllowed
Bharath
@Bharath Oui, vous devez créer un jeu de caractères par vous-même, par exemple stackoverflow.com/a/39767927/669586 ou simplement utiliser URLComponents.
Sulthan
URLComponents n'encode pas le caractère +aussi. La seule option est donc de le faire manuellement: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
la source
2
Vous devez inclure `` (espace) dans la chaîne de caractères
AJP
1
Vous devez également inclure ^
Mani
26

Swift 4

Pour encoder un paramètre dans l'URL, je trouve en utilisant le .alphanumericsjeu de caractères l'option la plus simple:

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

L'utilisation de l'un des jeux de caractères standard pour le codage d'URL (comme URLQueryAllowedCharacterSetou URLHostAllowedCharacterSet) ne fonctionnera pas, car ils n'excluent pas =ou les &caractères.

Notez qu'en utilisant .alphanumericsce codent des caractères qui ne ont pas besoin d'être codées (comme -, ., _ou ~- voir 2.3 caractères non réservés. Dans la RFC 3986). Je trouve l'utilisation .alphanumericsplus simple que la construction d'un jeu de caractères personnalisé et cela ne me dérange pas que certains caractères supplémentaires soient encodés. Si cela vous dérange, créez un jeu de caractères personnalisé comme décrit dans Comment coder en pourcentage une chaîne d'URL , comme par exemple:

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

Avertissement: le encodedparamètre est déplié de force. Pour une chaîne Unicode non valide, elle peut se bloquer. Voir Pourquoi la valeur de retour de String.addingPercentEncoding () est-elle facultative? . Au lieu de déballer de force, encoded!vous pouvez utiliser encoded ?? ""ou utiliser if let encoded = ....

Marián Černý
la source
1
.aphanumerics a fait l'affaire, merci! Tous les autres jeux de caractères n'ont pas échappé au &, ce qui a causé des problèmes lors de l'utilisation des chaînes comme paramètres get.
Dion
14

Tout est pareil

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

// test%2Ftest
Bryan Chen
la source
Vous n'avez pas .bridgeToOvjectiveC()secondé l'argument et vous n'avez pas obtenu "Impossible de convertir le type d'expression" CFString! " taper 'CFString!' "?
Kreiri
@Kreiri Pourquoi est-ce nécessaire? Le terrain de jeu et REPL sont satisfaits de mon code.
Bryan Chen
Les miens ne le sont pas: / (beta 2)
Kreiri
1
C'est une meilleure réponse car elle code correctement le &.
Sam
13

Swift 4:

Cela dépend des règles d'encodage suivies par votre serveur.

Apple propose cette méthode de classe, mais elle ne signale pas quel type de protocole RCF elle suit.

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

En suivant cet outil utile , vous devez garantir le codage de ces caractères pour vos paramètres:

  • $ (Signe dollar) devient% 24
  • & (Esperluette) devient% 26
  • + (Plus) devient% 2B
  • , (Virgule) devient% 2C
  • : (Deux points) devient% 3A
  • ; (Point-virgule) devient% 3B
  • = (Égale) devient% 3D
  • ? (Point d'interrogation) devient% 3F
  • @ (Commercial A / At) devient% 40

En d'autres termes, en parlant d'encodage d'URL, vous devez suivre le protocole RFC 1738 .

Et Swift ne couvre pas l'encodage du caractère + par exemple , mais cela fonctionne bien avec ces trois @:? caractères.

Donc, pour encoder correctement chacun de vos paramètres, l' .urlHostAllowedoption ne suffit pas, vous devez également ajouter les caractères spéciaux comme par exemple:

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

J'espère que cela aide quelqu'un qui devient fou à rechercher ces informations.

Alessandro Ornano
la source
Votre implémentation est complètement fausse. Comment un paramètre "věž" serait-il codé?
Marián Černý
13

Swift 4 (non testé - veuillez commenter si cela fonctionne ou non. Merci @sumizome pour la suggestion)

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 (Emprunt de Zaph et correction des valeurs de clé et de paramètre de requête d'URL)

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

Exemple:

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"

Ceci est une version courte de la réponse de Bryan Chen. Je suppose que cela urlQueryAllowedpermet aux caractères de contrôle de passer, à moins qu'ils ne fassent partie de la clé ou de la valeur de votre chaîne de requête à quel point ils doivent être échappés.

AJP
la source
2
J'aime la solution Swift 3, mais elle ne fonctionne pas pour moi dans Swift 4: "Impossible d'utiliser un membre en mutation sur une valeur immuable: 'urlQueryAllowed' est une propriété get-only".
Marián Černý
@ MariánČerný juste rendre le CharacterSet mutable (avec var) et ensuite l'appeler .removedans une deuxième étape.
sumizome
Je crois que cette solution et la plupart des autres ont des problèmes lors de l'application de la méthode deux fois, par exemple lors de l'inclusion d'une URL avec des paramètres codés dans les paramètres d'une autre URL.
FD_
@FD_ savez-vous ou avez-vous juste une intuition? Pouvez-vous l'expérimenter et le publier? Ce serait bien d'inclure cette information si c'est le cas. Je vous remercie.
AJP
@AJP Je viens de tester tous vos extraits. Swift 3 et 4 fonctionnent bien, mais celui de Swift 2.2 n'encode pas correctement% 20 en% 2520.
FD_
7

Swift 4.2

Une solution rapide en ligne. Remplacez originalStringpar la chaîne que vous souhaitez encoder.

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

Démo de Playground en ligne

ajzbc
la source
Cela a fonctionné grâce: vous pouvez vérifier et essayer de décoder et d'encoder les résultats. urldecoder.org
Rakshitha Muranga Rodrigo
4

J'en avais besoin moi-même, j'ai donc écrit une extension de chaîne qui permet à la fois les chaînes URLEncoding, ainsi que l'objectif final le plus courant, la conversion d'un dictionnaire de paramètres en paramètres d'URL de style "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
    }
}

Prendre plaisir!

BadPirate
la source
2
Cela n'encode pas le signe «&». L'utilisation d'un '&' dans un paramètre va f *** up la chaîne de requête
Sam
C'est faux, ça n'encode pas &ou =en paramètres. Vérifiez plutôt ma solution.
Marián Černý
2

Celui-ci fonctionne pour moi.

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
}

J'ai trouvé la fonction ci-dessus à partir de ce lien: http://useyourloaf.com/blog/how-to-percent-encode-a-url-string/ .

Gaurav Singla
la source
1

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

jaskiratjd
la source
0

SWIFT 4.2

Parfois, cela s'est produit simplement parce qu'il y a de l'espace dans la limace OU l'absence de codage d'URL pour les paramètres passant par l'URL de l'API.

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

REMARQUE: N'oubliez pas d'explorer bitmapRepresentation.

Vrushal Raut
la source
0

Cela fonctionne pour moi dans Swift 5 . Le cas d'utilisation prend une URL du presse-papiers ou similaire qui peut déjà avoir des caractères d'échappement mais qui contient également des caractères Unicode qui pourraient provoquer URLComponentsouURL(string:) échouer.

Tout d'abord, créez un jeu de caractères qui comprend tous les caractères autorisés par l'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
    }
}

Ensuite, étendez Stringavec une méthode pour encoder les 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
    }

}

Qui peut être testé comme:

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)

Valeur de urlà la fin: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

Notez que les deux %20et l' +espacement sont préservés, les caractères Unicode sont codés, l' %20original urlTextn'est pas codé en double et l'ancre (fragment ou #) reste.

Edit: vérifie maintenant la validité de chaque composant.

CartoonChess
la source
0

Pour Swift 5 à la chaîne de code de fin

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

Comment utiliser ?

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

Aucune de ces réponses n'a fonctionné pour moi. Notre application se bloquait lorsqu'une URL contenait des caractères non anglais.

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

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

En fonction des paramètres de ce que vous essayez de faire, vous pouvez simplement créer votre propre jeu de caractères. Ce qui précède permet les caractères anglais, et-._~/?%$!:

Jenel Ejercito Myers
la source