Swift - koduje adres URL

295

Jeśli koduję taki ciąg:

var escapedString = originalString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)

nie ucieka przed ukośnikami /.

Szukałem i znalazłem ten kod celu C:

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

Czy istnieje łatwiejszy sposób na zakodowanie adresu URL, a jeśli nie, jak napisać to w Swift?

MegaCookie
źródło

Odpowiedzi:

613

Szybki 3

W Swift 3 jest addingPercentEncoding

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

Wynik:

test% 2Ftest

Szybki 1

W iOS 7 i nowszych jest stringByAddingPercentEncodingWithAllowedCharacters

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

Wynik:

test% 2Ftest

Przydatne (odwrócone) zestawy znaków to:

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

Jeśli chcesz, aby inny zestaw znaków był wybierany, utwórz zestaw:
Przykład z dodanym znakiem „=”:

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

Wynik:

test% 2Ftest% 3D42

Przykład weryfikacji znaków ascii spoza zestawu:

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
źródło
6
Czy nikt inny nie ma pojęcia, jak długo ten kod ma to robić? Mam na myśli, że nazwa metody jest już długa, nawet bez wybrania dozwolonego zestawu znaków.
thatidiotguy
38
Nie, wolę zrozumiałość niż krótkie nazewnictwo. Autouzupełnianie usuwa ból. stringByAddingPercentEncodingWithAllowedCharacters()nie pozostawia wątpliwości co do tego, co robi. Interesujący komentarz, biorąc pod uwagę, jak długie jest słowo „oszołomiony”.
zaph
1
stringByAddingPercentEncodingWithAllowedCharacters (.URLHostAllowedCharacterSet ()) Nie koduje poprawnie wszystkich znaków Odpowiedź Bryana Chena jest lepszym rozwiązaniem.
Julio Garcia,
2
@zaph Dodałem &do zestawu znaków URLQueryAllowedCharacterSeti każdy kod został zakodowany. Sprawdzone na iOS 9, wygląda na buggy, poszedłem z odpowiedzią @ bryanchen, działa dobrze !!
Akash Kava
3
Poniższa odpowiedź korzysta z IMO URLComponentsi URLQueryItemjest o wiele czystsza.
Aaron Brager,
65

Możesz użyć URLComponents, aby uniknąć konieczności ręcznego procentowego kodowania ciągu zapytania:

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
źródło
7
Ta odpowiedź wymaga większej uwagi, ponieważ występują problemy ze wszystkimi pozostałymi (choć być może były to w tym czasie najlepsze praktyki).
Asa
4
Niestety URLQueryItemnie zawsze koduje się poprawnie. Na przykład Formula+Onezostanie zakodowany do Formula+One, który zostałby zdekodowany Formula One. Dlatego bądź ostrożny ze znakiem plus.
Sulthan
37

Swift 3:

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

1. kodowanie Pytanie:

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

wynik:

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

2. kodowanieURL:

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

wynik:

"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
źródło
Użyłem pierwszego rozwiązania, ale chcę odesłać tekst, na przykład iOS 开发 工程师.
Akshay Phulare
2
Używanie urlHostAlloweddo kodowania parametrów zapytania jest niepoprawne, ponieważ nie koduje ?, =oraz +. Podczas kodowania parametrów zapytania musisz osobno i poprawnie zakodować nazwę i wartość parametru. To nie zadziała w ogólnym przypadku.
Sulthan
@Sulthan .. Czy znalazłeś jakieś rozwiązanie / alternatywę dlaurlHostAllowed
Bharath
@Bharath Tak, trzeba zbudować zestaw znaków przez siebie, np stackoverflow.com/a/39767927/669586 lub po prostu używać URLComponents.
Sułtan
URLComponents również nie koduje +znaku. Tak więc jedyną opcją jest zrobienie tego ręcznie: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
źródło
2
Musisz dołączyć `` (spację) do ciągu znaków
AJP
1
Musisz także dołączyć ^
Mani
26

Szybki 4

Aby zakodować parametr w adresie URL, znajduję użycie .alphanumericszestawu znaków najłatwiejszej opcji:

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

Korzystanie ze standardowych zestawów znaków do kodowania adresów URL (takich jak URLQueryAllowedCharacterSetlub URLHostAllowedCharacterSet) nie będzie działać, ponieważ nie wykluczają =ani &znaków.

Uwaga , że używając .alphanumericsgo zakoduje pewne znaki, które nie muszą być zakodowane (jak -, ., _lub ~- patrz 2.3 znaki bezwarunkowe. W dokumencie RFC 3986). Uważam, że używanie jest .alphanumericsprostsze niż tworzenie niestandardowego zestawu znaków i nie przeszkadzają mi dodatkowe znaki do zakodowania. Jeśli Ci to przeszkadza, stwórz własny zestaw znaków, jak opisano w Jak procent kodować ciąg adresu URL , na przykład:

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

Ostrzeżenie:encoded parametr jest siła rozpakowany. Niepoprawny ciąg znaków Unicode może ulec awarii. Zobacz Dlaczego wartość zwracana przez String.addingPercentEncoding () jest opcjonalna? . Zamiast wymuszać rozpakowywanie encoded!, możesz użyć encoded ?? ""lub użyć if let encoded = ....

Marián Černý
źródło
1
.aphanumerics załatwiło sprawę, dziękuję! Wszystkie pozostałe zestawy znaków nie unikały znaków &, co powodowało problemy podczas używania ciągów jako parametrów pobierania.
Dion
14

Wszystko jest takie samo

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

// test%2Ftest
Bryan Chen
źródło
Nie .bridgeToOvjectiveC()podałeś drugiego argumentu i nie dostałeś „Nie można przekonwertować typu wyrażenia„ CFString! ” wpisać „CFString!” ”?
Kreiri,
@Kreiri Dlaczego jest potrzebny? Zarówno boisko, jak i REPL są zadowolone z mojego kodu.
Bryan Chen
Moje nie są: / (beta 2)
Kreiri
1
To lepsza odpowiedź, ponieważ koduje & poprawnie.
Sam
13

Swift 4:

Zależy to od reguł kodowania stosowanych przez serwer.

Apple oferuje tę klasę metody, ale nie zgłasza, jakiego rodzaju protokołu RCF używa.

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

Postępując zgodnie z tym przydatnym narzędziem , powinieneś zagwarantować kodowanie tych znaków dla swoich parametrów:

  • $ (Znak dolara) staje się% 24
  • & (Ampersand) staje się% 26
  • + (Plus) staje się% 2B
  • , (Przecinek) staje się% 2C
  • : (Dwukropek) staje się% 3A
  • ; (Średnik dwukropek) staje się% 3B
  • = (Równa się) staje się% 3D
  • ? (Znak zapytania) staje się% 3F
  • @ (Komercyjne A / At) staje się% 40

Innymi słowy, mówiąc o kodowaniu adresów URL, powinieneś postępować zgodnie z protokołem RFC 1738 .

Swift nie obejmuje na przykład kodowania znaku + , ale działa dobrze z tymi trzema @:? znaki

Tak więc, aby poprawnie zakodować każdy parametr, .urlHostAllowedopcja nie jest wystarczająca, należy również dodać znaki specjalne, jak na przykład:

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

Mam nadzieję, że to pomoże komuś, kto oszalał, przeszukać te informacje.

Alessandro Ornano
źródło
Twoje wdrożenie jest całkowicie błędne. W jaki sposób kodowany byłby parametr „věž”?
Marián Černý
13

Swift 4 (nie testowany - skomentuj, czy działa, czy nie. Dziękujemy @ sumizome za sugestię)

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

Szybki 3

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

Swift 2.2 (Pożyczanie od Zapha i poprawianie klucza kwerendy adresu URL i wartości parametrów)

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

Przykład:

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"

To jest krótsza wersja odpowiedzi Bryana Chena. Wydaje mi się, że urlQueryAllowedpozwala to na kontrolowanie znaków, o ile nie stanowią one części klucza lub wartości w ciągu zapytania, w którym to momencie trzeba ich uciec.

AJP
źródło
2
Podoba mi się rozwiązanie Swift 3, ale nie działa dla mnie w Swift 4: „Nie można użyć mutującego elementu o niezmiennej wartości:„ urlQueryAllowed ”jest właściwością tylko do pobrania”.
Marián Černý
@ MariánČerný wystarczy ustawić zmienną CharacterSet (z var), a następnie wywołać .removeją w drugim kroku.
sumizome
Wierzę, że to i większość innych rozwiązań ma problemy przy dwukrotnym zastosowaniu metody, np. Przy włączaniu adresu URL z zakodowanymi parametrami do parametrów innego adresu URL.
FD_
@FD_ czy wiesz czy masz przeczucie? Czy możesz z tym eksperymentować i wysyłać wiadomości z powrotem? Dobrze byłoby dołączyć te informacje, jeśli tak. Dziękuję Ci.
AJP,
@AJP Właśnie przetestowałem wszystkie twoje fragmenty. Swift 3 i 4 działają dobrze, ale ten dla Swift 2.2 nie koduje poprawnie% 20 jako% 2520.
FD_
7

Szybki 4.2

Szybkie rozwiązanie dla jednej linii. Zastąp originalStringciągiem, który chcesz zakodować.

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

Demo placu zabaw online

ajzbc
źródło
Działa to dzięki: możesz sprawdzić i spróbować dekodować i kodować wyniki. urldecoder.org
Rakshitha Muranga Rodrigo
4

Sam tego potrzebowałem, więc napisałem rozszerzenie String, które pozwala zarówno na łańcuchy URLEncoding, jak i na bardziej powszechny cel końcowy, konwersję słownika parametrów na adres URL w stylu „GET” Parametry:

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
    }
}

Cieszyć się!

BadPirate
źródło
2
To nie koduje znaku „&”. Użycie „&” w parametrze spowoduje zakłócenie kwerendy
Sam
To źle, nie koduje &ani =parametrów. Zamiast tego sprawdź moje rozwiązanie.
Marián Černý
2

Ten działa dla mnie.

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
}

Znalazłem powyższą funkcję z tego linku: http://useyourloaf.com/blog/how-to-percent-encode-a-url-string/ .

Gaurav Singla
źródło
1

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

jaskiratjd
źródło
0

SWIFT 4.2

Czasami dzieje się tak tylko dlatego, że jest miejsce w ślimaku LUB brak kodowania adresu URL dla parametrów przechodzących przez adres URL interfejsu API.

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

UWAGA: Nie zapomnij o zbadaniu bitmapRepresentation.

Vrushal Raut
źródło
0

To działa dla mnie w Swift 5 . Przypadek użycia polega na pobieraniu adresu URL ze schowka lub podobnego, który mógł już mieć znaki ucieczki, ale który zawiera także znaki Unicode, które mogą powodować URLComponentslub URL(string:)nie działać.

Najpierw utwórz zestaw znaków zawierający wszystkie znaki zgodne z adresem 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
    }
}

Następnie rozszerz Stringmetodę kodowania adresów 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
    }

}

Które można przetestować jak:

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)

Wartość urlna końcu: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

Zauważ, że zachowane są oba znaki %20i +odstępy, znaki Unicode są kodowane, %20oryginał urlTextnie jest podwójnie kodowany, a kotwica (fragment lub #) pozostaje.

Edycja: teraz sprawdzanie ważności każdego komponentu.

CartoonChess
źródło
0

Dla Swift 5 do końca łańcucha znaków

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

Jak używać ?

let strEncoded = self.escape(string: "http://www.edamam.com/ontologies/edamam.owl#recipe_e2a1b9bf2d996cbd9875b80612ed9aa4")
print("escapedString: \(strEncoded)")
Hardik Thakkar
źródło
0

Żadna z tych odpowiedzi nie działała dla mnie. Nasza aplikacja ulegała awarii, gdy adres URL zawierał znaki inne niż angielskie.

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

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

W zależności od parametrów tego, co próbujesz zrobić, możesz po prostu stworzyć własny zestaw znaków. Powyższe pozwala na angielskie znaki i-._~/?%$!:

Jenel Ejercito Myers
źródło