6 min czytaniaInżynieria

Kodowanie URL wyjaśnione: które znaki trzeba zakodować

Kodowanie URL zamienia znak na znak procentu i dwie cyfry szesnastkowe, żeby nie dało się go odczytać jako składni URL. Które znaki tego wymagają i gdzie to się psuje.

Marius Voß
DevRel · edge infra
Kodowanie URL pokazane jako query string, w którym spacja i ampersand zmieniają się w sekwencje procentowe wewnątrz jednej wartości parametru

Kodowanie URL zamienia znak na znak procentu i dwie cyfry szesnastkowe: spacja zmienia się w %20, ampersand w %26, znak zapytania w %3F. Cel jest taki, żeby znak nie został odczytany jako składnia URL, kiedy miał być danymi. Nic więcej.

Wydaje się to trudniejsze, niż jest, bo prawie każde pytanie o kodowanie URL jest w istocie pytaniem o zakres. Które znaki, w której części URL-a, zakodowane przez którą warstwę? Źle ustalony zakres daje jeden z dwóch klasycznych błędów: parametr śledzący, który po cichu się ucina, albo miejsce docelowe, które przychodzi jako https%3A%2F%2Fexample.com i zwraca 404. Ten artykuł opisuje dwa zbiory znaków, które decydują o odpowiedzi, miejsca, gdzie zasady się zmieniają, i jak sprawdzić, co link faktycznie niesie. Szerszy obraz tego, co robi z tym wszystkim przekierowanie, opisują typy przekierowań.

Dwa zbiory, które decydują o wszystkim

RFC 3986, sekcja 2.3 definiuje zbiór unreserved, który nigdy nie wymaga kodowania: litery, cyfry i dokładnie cztery znaki interpunkcyjne - myślnik, kropkę, podkreślenie, tyldę. Jeśli twoja wartość zawiera tylko takie znaki, nie musisz robić nic.

Wszystko inne wpada w jeden z dwóch koszyków. Znaki reserved niosą znaczenie strukturalne: : / ? # [ ] @ rozdzielają części URL-a, a ! $ & ' ( ) * + , ; = rozdzielają rzeczy wewnątrz tych części. Sekcja 2.2 je wymienia. Są legalne jako składnia i muszą być kodowane, kiedy występują jako dane. Reszta to wszystko poza ASCII, co jest kodowane bajt po bajcie po konwersji do UTF-8 - dlatego jedna litera z akcentem kosztuje zwykle sześć znaków, a nie trzy.

To daje jedyną zasadę, którą warto zapamiętać: koduj znak, kiedy jest danymi i inaczej zostałby odczytany jako składnia. Ampersand między dwoma parametrami to składnia. Ampersand wewnątrz nazwy kampanii to dane, i jeśli go nie zakodujesz, lista parametrów kończy się właśnie tam.

Query string, w którym wartość kampanii zawiera spację i ampersand, pokazany zakodowany prawidłowo wewnątrz wartości i nieprawidłowo w całym URL-u

Koduj wartość, nie URL

To błąd, który widzę najczęściej, i zawsze ma tę samą postać. Ktoś ma URL, wie, że trzeba go zakodować, więc wkleja całość do encodera i dostaje:

https%3A%2F%2Fexample.com%2Fspring%3Futm_campaign%3Dspring%20sale

Ten ciąg nie jest URL-em. To kawałek tekstu w kształcie URL-a, który może być tylko wartością wewnątrz innego URL-a - co jest właśnie tym miejscem, gdzie należy, kiedy przekazujesz miejsce docelowe przez redirector, i właśnie tym miejscem, gdzie nie należy, kiedy próbujesz go otworzyć.

Poprawne podejście koduje każdą wartość osobno:

https://example.com/spring?utm_campaign=spring%20sale&utm_source=flyer

Schemat, host, separatory ścieżki oraz ? i & zostają jako składnia. Zmieniła się tylko wartość. Każdy język ma dwie funkcje do tego rozróżnienia, i wybranie tej niewłaściwej to druga połowa problemu: strona MDN o encodeURIComponent mówi bez ogródek, że encodeURI celowo zostawia zarezerwowane znaki bez zmian, bo oczekuje całego URI, podczas gdy encodeURIComponent je koduje, bo oczekuje jego fragmentu. Wartości chcą encodeURIComponent. W Pythonie to urllib.parse.quote, w Go url.QueryEscape, w PHP rawurlencode.

Spacja to %20, poza miejscami, gdzie jest plusem

Obie formy są prawidłowe, w różnych miejscach, i to jest zdecydowanie najbardziej zagmatwana rzecz w tym temacie.

W ścieżce albo w ogólnym URI spacja to %20. W query string zbudowanym tak, jak buduje go formularz HTML, spacja to +, bo tak właśnie definiuje to serializacja application/x-www-form-urlencoded w standardzie URL WHATWG. Obie formy są odczytywane jako spacja przez każdy parser query po stronie serwera, na jaki prawdopodobnie natrafisz.

Pułapka jest w drugą stronę. Jeśli znak plus jest danymi - numer telefonu, wyszukiwana fraza, kampania nazwana spring+summer - trzeba go zapisać jako %2B. Zostawiony bez zmian w query string zmienia się w spację, i spędzisz popołudnie, zastanawiając się, czemu numer w twoim CRM zgubił kod kraju.

ZnakZakodowanyCzemu to ma znaczenie
spacja%20 lub ++ tylko wewnątrz query string, %20 wszędzie
&%26Niezakodowany, lista parametrów kończy się właśnie tam
?%3FNiezakodowany, wszystko po nim staje się query
#%23Niezakodowany, reszta nigdy nie trafia do serwera
+%2BNiezakodowany w query, przychodzi jako spacja
%%25Niezakodowany, następne dwa znaki zostają zjedzone

Wiersz # zasługuje na uwagę, bo to on generuje najbardziej zagmatwane zgłoszenia błędów. Fragment nigdy nie jest wysyłany do serwera. Umieść niezakodowany # w miejscu docelowym przekierowania, i serwer zobaczy ucięty URL, podczas gdy pole adresu w przeglądarce wciąż wygląda dobrze, więc osoba zgłaszająca problem przysięga, że link działa.

Jeśli budujesz URL-e kampanii ręcznie częściej niż od czasu do czasu, przestań: nasz UTM builder koduje każdą wartość na bieżąco, a konwencje nazewnictwa UTM opisują wybieranie wartości, które od razu nie wymagają kodowania. Skróć wynik na własnej domenie, i zakodowany bałagan przestaje być czymś, na co ktokolwiek musi patrzeć.

Podwójne kodowanie i jak je zauważyć

Podwójne kodowanie zdarza się, kiedy wartość przechodzi przez dwie warstwy, z których każda robi swoje. Sam znak procentu jest znakiem, który trzeba kodować, więc %20 zmienia się w %2520, a %2520 w %252520.

Symptomy da się rozpoznać, kiedy się je już raz widziało. Tytuł strony wyświetlający spring%20sale prawdziwemu odwiedzającemu. Parametr, który przychodzi do analityki z widocznymi sekwencjami ucieczki. Przekierowanie, które działa przy pierwszym skoku i zawodzi przy drugim. Przyczyną jest prawie zawsze wywołanie kodujące owinięte wokół wartości, która przyszła już zakodowana, często bo wyszła z bazy danych, która przechowywała zakodowaną formę.

Naprawa polega na tym, żeby zdecydować, która warstwa jest odpowiedzialna za kodowanie, i uczynić inne bezdotykowymi. Dekoduj raz, kiedy czytasz wartość, koduj raz, kiedy zapisujesz ją do URL-a, i nigdy nie rób obu w tej samej funkcji.

Wartość przechodząca przez dwie warstwy kodowania, tak że spacja zmienia się w %20, a potem w %2520, z widocznym symptomem w przeglądarce

Gdzie to najbardziej boli w praktyce

Trzy miejsca, w kolejności, w jakiej najprawdopodobniej je spotkasz.

Parametry śledzące. Wartość kampanii z niezakodowanym ampersandem ucina listę parametrów, więc sesja trafia do twojej analityki jako ruch bezpośredni, a kampania nie dostaje żadnego uznania. Nic się nie wywala. Parametry UTM nie pokazują się w GA4 opisuje diagnozę ze strony raportowania, a przeglądarki usuwają parametry UTM opisuje drugi powód, dla którego parametr może zniknąć między kliknięciem a stroną.

Przekierowania. Reguły serwera kodują ponownie niekonsekwentnie, a to, czy query string w ogóle przetrwa, zależy od użytej dyrektywy. Przekierowanie 301 w .htaccess ma pełną tabelę dla Apache; w skrócie: reguła, która zamienia query string, po cichu upuści twój.

Kody QR. Kodowanie zwiększa długość payloadu, a długość payloadu decyduje o tym, jak gęsty jest wydrukowany kod. Każda spacja kosztuje trzy znaki zamiast jednego, każda litera z akcentem sześć. URL śledzący z kilkoma zakodowanymi nazwami kampanii może przesunąć kod o wersję albo dwie wyżej, co jest realną różnicą w rozmiarze wizytówki - kod QR się nie skanuje właśnie z tego powodu umieszcza długość payloadu wśród czterech przyczyn. Zakodowanie krótkiego linku zamiast całego URL-a to najtańsza dostępna naprawa.

Dwie komendy rozstrzygają prawie każdy spór. Pierwsza pokazuje, co dostaje serwer po przekierowaniu:

curl -sI 'https://example.com/spring?utm_campaign=spring%20sale' | grep -i '^location'

Druga buduje kodowanie za ciebie, zamiast liczyć na twoje palce, co jest przydatne, kiedy wartość zawiera kilku winowajców naraz:

curl -G --data-urlencode 'utm_campaign=spring & summer sale' \
  --data-urlencode 'utm_source=flyer' \
  -o /dev/null -w '%{url_effective}\n' https://example.com/spring

Czytaj wynik jako dane, nie jako dekorację. Jeśli widzisz %2520, masz problem z podwójnym kodowaniem, jeśli widzisz wartość kończącą się przedwcześnie, masz niezakodowany separator, a jeśli widzisz %3A%2F%2F na początku, zakodowałeś cały URL. Nasz link checker robi część z przekierowaniem w przeglądarce, jeśli nie chcesz otwierać terminala.

Zwyczaj, który warto sobie wyrobić, to spojrzeć na finalny URL raz, na własne oczy, zanim kampania wystartuje. Błędy kodowania są niewidoczne w przeglądarce i oczywiste w terminalu, i kosztują cię atrybucję, nie uptime, dlatego przetrwają tak długo.

Przeczytaj serię filarową

Ten artykuł należy do klastra inżynieryjnego. Po stronę przekierowań, typy przekierowań opisuje każdy kod statusu i metodę po stronie klienta, a jak działają skracacze URL opisuje, co dzieje się między kliknięciem a stroną.

Powiązane na blogu

Najczęściej zadawane pytania

Co to jest kodowanie URL?

Zamiana znaku na znak procentu, po którym następuje jego wartość bajtowa w systemie szesnastkowym, tak żeby znak nie mógł zostać pomylony ze składnią URL. Spacja zmienia się w %20, ampersand w %26, znak zapytania w %3F. Mechanizm definiuje RFC 3986, nazywa się też percent-encoding.

Które znaki trzeba kodować w URL?

Wszystko poza zbiorem unreserved, który RFC 3986 definiuje jako litery, cyfry i cztery znaki: myślnik, kropkę, podkreślenie i tyldę. Wszystko inne to albo zarezerwowana interpunkcja niosąca znaczenie strukturalne, albo bajt spoza ASCII, i oba przypadki muszą być zakodowane procentowo, kiedy występują wewnątrz wartości, a nie jako składnia.

Czy powinienem kodować cały URL, czy tylko jego części?

Tylko części. Przepuszczenie całego URL-a przez encoder zmienia https://example.com w https%3A%2F%2Fexample.com, co w ogóle nie jest już URL-em. Koduj każdą wartość parametru query i każdy segment ścieżki osobno, a schemat, host i separatory zostaw bez zmian.

Spacja to %20 czy znak plus?

Jedno i drugie, w różnych miejscach. W ścieżce i w ogólnym URI spacja to %20. W query string zbudowanym tak, jak buduje go formularz HTML, spacja to znak plus, bo tak właśnie definiuje to serializacja application/x-www-form-urlencoded. Dosłowny plus wewnątrz wartości query trzeba więc zapisać jako %2B, bo inaczej zostanie odczytany jako spacja.

Co to jest podwójne kodowanie?

Zakodowanie czegoś, co było już zakodowane, przez co %20 zmienia się w %2520, bo sam znak procentu zostaje zamieniony na %25. Symptomem jest strona wyświetlająca dosłowne %20 w tekście albo parametr, który przychodzi z widocznymi sekwencjami ucieczki. To prawie zawsze wartość przechodząca przez dwie warstwy, z których każda usłużnie ją zakodowała.

Czemu zakodowane znaki utrudniają skanowanie kodu QR?

Bo każdy z nich kosztuje trzy znaki zamiast jednego. Spacja to jeden znak intencji i trzy znaki payloadu, więc kilka z nich może przesunąć kod o wersję albo dwie wyżej, co znaczy więcej modułów na tej samej powierzchni druku. Zakodowanie długiego URL-a śledzącego w kod QR to jeden z najszybszych sposobów na zrobienie kodu, który skanuje się tylko z bliskiej odległości.

Wypróbuj Elido

Wklej URL, otrzymaj krótki link

Bez rejestracji. Link działa 30 dni. Zarejestruj się, aby zachować go na zawsze.

Za darmo, bez rejestracji · 2 dziennie

Wypróbuj Elido

Skracarka URL hostowana w UE: własne domeny, głęboka analityka i otwarte API. Darmowy plan - bez karty kredytowej.

Tagi
url encoding
percent encoding
encodeuricomponent
query string
utm parameters
url shortener

Czytaj dalej