Dowód znajduje się w kodzie źródłowym PHP.
Przeprowadzę cię przez szybki proces samodzielnego odkrywania tego rodzaju rzeczy w przyszłości, kiedy tylko chcesz. Bądźcie ze mną, będzie dużo kodu źródłowego C, który można przejrzeć (wyjaśniam). Jeśli chcesz odświeżyć trochę C, dobrym miejscem do rozpoczęcia jest nasza wiki SO .
Pobierz źródło (lub użyj http://lxr.php.net/, aby przeglądać je online), grepuj wszystkie pliki dla nazwy funkcji, znajdziesz coś takiego:
PHP 5.3.6 (najnowsza w chwili pisania) opisuje dwie funkcje w ich natywnego kodu C w pliku url.c .
RawUrlEncode ()
PHP_FUNCTION(rawurlencode)
{
char *in_str, *out_str;
int in_str_len, out_str_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &in_str,
&in_str_len) == FAILURE) {
return;
}
out_str = php_raw_url_encode(in_str, in_str_len, &out_str_len);
RETURN_STRINGL(out_str, out_str_len, 0);
}
UrlEncode ()
PHP_FUNCTION(urlencode)
{
char *in_str, *out_str;
int in_str_len, out_str_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &in_str,
&in_str_len) == FAILURE) {
return;
}
out_str = php_url_encode(in_str, in_str_len, &out_str_len);
RETURN_STRINGL(out_str, out_str_len, 0);
}
Okej, więc co tu jest innego?
Oba w zasadzie wywołują odpowiednio dwie różne funkcje wewnętrzne: php_raw_url_encode i php_url_encode
Poszukaj tych funkcji!
Spójrzmy na php_raw_url_encode
PHPAPI char *php_raw_url_encode(char const *s, int len, int *new_length)
{
register int x, y;
unsigned char *str;
str = (unsigned char *) safe_emalloc(3, len, 1);
for (x = 0, y = 0; len--; x++, y++) {
str[y] = (unsigned char) s[x];
#ifndef CHARSET_EBCDIC
if ((str[y] < '0' && str[y] != '-' && str[y] != '.') ||
(str[y] < 'A' && str[y] > '9') ||
(str[y] > 'Z' && str[y] < 'a' && str[y] != '_') ||
(str[y] > 'z' && str[y] != '~')) {
str[y++] = '%';
str[y++] = hexchars[(unsigned char) s[x] >> 4];
str[y] = hexchars[(unsigned char) s[x] & 15];
#else /*CHARSET_EBCDIC*/
if (!isalnum(str[y]) && strchr("_-.~", str[y]) != NULL) {
str[y++] = '%';
str[y++] = hexchars[os_toascii[(unsigned char) s[x]] >> 4];
str[y] = hexchars[os_toascii[(unsigned char) s[x]] & 15];
#endif /*CHARSET_EBCDIC*/
}
}
str[y] = '\0';
if (new_length) {
*new_length = y;
}
return ((char *) str);
}
I oczywiście php_url_encode:
PHPAPI char *php_url_encode(char const *s, int len, int *new_length)
{
register unsigned char c;
unsigned char *to, *start;
unsigned char const *from, *end;
from = (unsigned char *)s;
end = (unsigned char *)s + len;
start = to = (unsigned char *) safe_emalloc(3, len, 1);
while (from < end) {
c = *from++;
if (c == ' ') {
*to++ = '+';
#ifndef CHARSET_EBCDIC
} else if ((c < '0' && c != '-' && c != '.') ||
(c < 'A' && c > '9') ||
(c > 'Z' && c < 'a' && c != '_') ||
(c > 'z')) {
to[0] = '%';
to[1] = hexchars[c >> 4];
to[2] = hexchars[c & 15];
to += 3;
#else /*CHARSET_EBCDIC*/
} else if (!isalnum(c) && strchr("_-.", c) == NULL) {
/* Allow only alphanumeric chars and '_', '-', '.'; escape the rest */
to[0] = '%';
to[1] = hexchars[os_toascii[c] >> 4];
to[2] = hexchars[os_toascii[c] & 15];
to += 3;
#endif /*CHARSET_EBCDIC*/
} else {
*to++ = c;
}
}
*to = 0;
if (new_length) {
*new_length = to - start;
}
return (char *) start;
}
Krótka wiedza zanim przejdę dalej, EBCDIC to kolejny zestaw znaków , podobny do ASCII, ale całkowity konkurent. PHP próbuje sobie z tym poradzić. Ale w zasadzie oznacza to, że bajt EBCDIC 0x4c nie jest bajtem L
w ASCII, to w rzeczywistości jest <
. Jestem pewien, że widzisz tutaj zamieszanie.
Obie te funkcje zarządzają EBCDIC, jeśli serwer go zdefiniował.
Obaj używają również tablicy znaków (typu łańcuchowego) hexchars
, aby uzyskać pewne wartości, tablica jest opisana jako taka:
/* rfc1738:
...The characters ";",
"/", "?", ":", "@", "=" and "&" are the characters which may be
reserved for special meaning within a scheme...
...Thus, only alphanumerics, the special characters "$-_.+!*'(),", and
reserved characters used for their reserved purposes may be used
unencoded within a URL...
For added safety, we only leave -_. unencoded.
*/
static unsigned char hexchars[] = "0123456789ABCDEF";
Poza tym funkcje są naprawdę różne i wyjaśnię je w ASCII i EBCDIC.
Różnice w ASCII:
URLENCODE:
- Oblicza długość początkową / końcową ciągu wejściowego, przydziela pamięć
- Przechodzi przez pętlę while, stopniowo, aż dojdziemy do końca łańcucha
- Łapie obecną postać
- Jeśli znak jest równy ASCII Char 0x20 (tj. „Spacja”), dodaj
+
znak do ciągu wyjściowego.
- Jeśli nie jest to spacja, a także nie jest alfanumeryczna (
isalnum(c)
), a także nie jest i _
, -
lub .
znak, wówczas wyprowadzamy %
znak do pozycji tablicy 0, sprawdzamy tablicę do hexchars
tablicy w celu wyszukania os_toascii
tablicy ( tablica z Apache, która tłumaczy char na kod szesnastkowy) dla klucza c
(obecnego znaku), następnie bitowo przesuwamy w prawo o 4, przypisujemy tę wartość do znaku 1, a do pozycji 2 przypisujemy to samo wyszukiwanie, z wyjątkiem tego, że wykonujemy preformę logiczne i sprawdzić, czy wartość wynosi 15 (0xF), i zwróć 1 w tym przypadku, lub 0 w przeciwnym razie. Na końcu skończysz z czymś zakodowanym.
- Jeśli skończy, to nie jest spacją, jest alfanumeryczny lub jeden z
_-.
znaków, wyświetla dokładnie to, czym jest.
KOD RAWURLEN:
- Przydziela pamięć dla ciągu
- Iteruje nad nim na podstawie długości podanej w wywołaniu funkcji (nie obliczonej w funkcji jak w przypadku URLENCODE).
Uwaga: Wielu programistów prawdopodobnie nigdy nie widziało powtarzania pętli for w ten sposób, jest to nieco hackerskie, a nie standardowa konwencja stosowana w większości pętli for, zwracaj uwagę, przypisuje x
i y
sprawdza wyjście po len
osiągnięciu 0 i zwiększa zarówno, jak x
i y
. Wiem, że nie jest to, czego można się spodziewać, ale to poprawny kod.
- Przypisuje obecną postać do pasującej pozycji postaci w
str
.
- Sprawdza, czy obecny znak jest alfanumeryczny, czy jest jednym z
_-.
znaków, a jeśli nie jest, wykonujemy prawie takie samo przypisanie, jak w przypadku URLENCODE, w którym wykonuje on wyszukiwanie, jednak zwiększamy go inaczej, używając y++
zamiast to[1]
tego, ponieważ ciągi są budowane na różne sposoby, ale i tak osiągają ten sam cel na końcu.
- Kiedy pętla jest skończona, a długość zniknęła, w rzeczywistości kończy ciąg znaków, przypisując
\0
bajt.
- Zwraca zakodowany ciąg.
Różnice:
- UrlEncode sprawdza miejsce, przypisuje znak +, RawURLEncode nie.
- UrlEncode nie przypisuje
\0
bajtu do ciągu, RawUrlEncode robi (może to być kwestia sporna)
- Powtarzają się one różnie, można mieć skłonność do przepełnienia zniekształconymi łańcuchami, sugeruję to tylko i tak naprawdę nie badałem.
Zasadniczo iterują inaczej, jeden przypisuje znak + w przypadku ASCII 20.
Różnice w EBCDIC:
URLENCODE:
- Taka sama konfiguracja iteracji jak w ASCII
- Nadal tłumaczy znak „spacja” na znak + . Uwaga - myślę, że należy to skompilować w EBCDIC, czy skończy się to błędem? Czy ktoś może to edytować i potwierdzić?
- Sprawdza, czy obecny jest znak znak wcześniej
0
, z wyjątkiem bycia .
lub -
, lub mniejsza niż A
, ale większa niż znak 9
, lub większej niż Z
mniej niż a a
, ale nie _
. LUB większy niż z
(tak, EBCDIC jest trochę zepsuty do pracy). Jeśli pasuje do któregokolwiek z nich, wykonaj podobne wyszukiwanie jak w wersji ASCII (po prostu nie wymaga wyszukiwania w os_toascii).
KOD RAWURLEN:
- Taka sama konfiguracja iteracji jak w ASCII
- Ta sama kontrola, jak opisano w wersji EBCDIC URL Encode, z tym wyjątkiem, że jeśli jest większa niż
z
, wyklucza ~
z kodowania URL.
- To samo przypisanie, co ASCII RawUrlEncode
- Nadal dołączam
\0
bajt do ciągu przed zwróceniem.
Wielkie podsumowanie
- Oba używają tej samej tabeli odnośników hexchars
- URIEncode nie kończy łańcucha z \ 0, raw robi.
- Jeśli pracujesz w EBCDIC, sugeruję użycie RawUrlEncode, ponieważ zarządza tym,
~
że UrlEncode nie ( jest to zgłaszany problem ). Warto zauważyć, że ASCII i EBCDIC 0x20 są spacjami.
- Iterują inaczej, jeden może być szybszy, może być podatny na exploity oparte na pamięci lub łańcuchach.
- URIEncode tworzy spację
+
, RawUrlEncode tworzy spację %20
poprzez wyszukiwanie tablic.
Oświadczenie: Nie dotykałem C od lat i nie patrzyłem na EBCDIC od naprawdę bardzo dawna. Jeśli gdzieś się mylę, daj mi znać.
Sugerowane wdrożenia
Opierając się na tym wszystkim, rawurlencode jest drogą do przejścia przez większość czasu. Jak widać w odpowiedzi Jonathana Finglanda, trzymaj się jej w większości przypadków. Zajmuje się nowoczesnym schematem komponentów URI, w którym jako urlencode robi się to w oldschoolowy sposób, gdzie + oznaczało „przestrzeń”.
Jeśli próbujesz przekonwertować stary format na nowy, upewnij się, że Twój kod nie wygłupia się i nie zamienia czegoś, co jest zdekodowanym znakiem + w spację przez przypadkowe podwójne kodowanie lub podobne scenariusze „ups” wokół tego problem spacja / 20% / +.
Jeśli pracujesz na starszym systemie ze starszym oprogramowaniem, które nie preferuje nowego formatu, trzymaj się kodu urlencode, jednak uważam, że% 20 faktycznie będzie kompatybilny wstecz, ponieważ w starym standardowym% 20 działało, po prostu preferowane. Daj mu szansę, jeśli masz ochotę się bawić, daj nam znać, jak Ci się udało.
Zasadniczo powinieneś trzymać się surowego, chyba że twój system EBCDIC naprawdę cię nienawidzi. Większość programistów nigdy nie natknie się na EBCDIC na żadnym systemie wyprodukowanym po 2000 roku, może nawet 1990 roku (to pcha, ale moim zdaniem nadal prawdopodobne).