您可以使用以下方式編碼路徑
<?php
$encoded = implode("/", array_map("rawurlencode", explode("/", $path)));
?>
(PHP 4, PHP 5, PHP 7, PHP 8)
rawurlencode — 根據 RFC 3986 進行 URL 編碼
string
要編碼的 URL。
傳回字串,其中所有非字母數字字元(除了 -_.~
)都已被百分比符號(%
)加上兩個十六進位數字取代。這是 » RFC 3986 中描述的編碼方式,用於保護字面字元不被解讀為特殊的 URL 分隔符號,並保護 URL 不受具有字元轉換的傳輸媒體(如某些電子郵件系統)損壞。
範例 1:在 FTP URL 中包含密碼
<?php
echo '<a href="ftp://user:', rawurlencode('foo @+%/'),
'@ftp.example.com/x.txt">';
?>
上面的範例會輸出
<a href="ftp://user:foo%20%40%2B%25%2F@ftp.example.com/x.txt">
或者,如果您在 URL 的 PATH_INFO 元件中傳遞資訊
範例 2:rawurlencode() 範例 2
<?php
echo '<a href="http://example.com/department_list_script/',
rawurlencode('sales and marketing/Miami'), '">';
?>
上面的範例會輸出
<a href="http://example.com/department_list_script/sales%20and%20marketing%2FMiami">
您可以使用以下方式編碼路徑
<?php
$encoded = implode("/", array_map("rawurlencode", explode("/", $path)));
?>
我寫了一個簡單的函數,將 UTF-8 字串轉換為 URL 編碼字串。所有給定的字元都會被轉換!
函數
<?php
function mb_rawurlencode($url){
$encoded='';
$length=mb_strlen($url);
for($i=0;$i<$length;$i++){
$encoded.='%'.wordwrap(bin2hex(mb_substr($url,$i,1)),2,'%',true);
}
return $encoded;
}
?>
範例
<?php
echo 'http://example.com/',
mb_rawurlencode('你好');
?>
上面的範例會輸出
http://example.com/%e4%bd%a0%e5%a5%bd
rawurlencode() 絕對不能用於未剖析的 URL。
rawurlencode() 不應使用於主機和網域名稱部分(可能包含使用「q--」前綴編碼的國際字元,後跟國際網域的特殊編碼,目前在測試中)。
rawurlencode() 可以單獨用於使用者名稱和密碼(這樣它就不會編碼「:」和「@」分隔符號)。
rawurlencode() 絕對不能使用於路徑(可能包含「/」分隔符號):已剖析 URL 的「path」元素必須先分解為個別的「目錄」名稱。包含空格的目錄或檔案名稱不得使用 urlencode() 編碼,而必須使用此 rawurlencode() 編碼,這樣它會顯示為「%20」十六進位序列(而不是「+」)。
rawurlencode() 絕對不能用於編碼已剖析 URL 的「query」元素。您必須改用 urlencode() 函數。
典型的查詢通常在每個參數之間使用「&」分隔符號。然而,此「&」分隔符號只是一種慣例,用於使用預設 GET 方法的 HTML 表單的 www-url-encoded 格式。但是,當 HTML 頁面中引用包含靜態查詢參數的 URL 時,這些「&」分隔符號應在 HTML 程式碼中編碼為「&」以符合 HTML 規範。這並非 URL 規範的一部分,而是 HTML 封裝的一部分!某些瀏覽器會忽略這一點,並以 HTTP GET 查詢傳送「&」。您可能希望在剖析和驗證 URL 時將「&」替換為「&」。這應該在對查詢部分呼叫 urlencode() 之前完成。
已剖析 URL 的「fragment」部分(在任何 URL 中找到的第一個「#」分隔符號之後)不得使用此 rawurlencode() 函數編碼,而應改用 urlencode()。
因此,驗證 HTTP 請求中傳送的 URL 比您想像的更複雜。這必須僅在已剖析的 URL 上完成(其中 URL 的基本元素已分割),然後您必須分解路徑元件,並檢查查詢或片段部分中是否存在「&」序列。
接下來要做的是檢查您想要支援的 URL 協定(例如,僅限「http」、「https」或「ftp」)。
您可能想要檢查「port」部分,以查看它是否真的是 1 到 65535 之間的十進位整數。
您可能希望移除您想要支援的 URL 協定所使用的預設連接埠號碼(例如「http」的連接埠「80」、「ftp」的連接埠「21」、「https」的連接埠「443」),並嚴格限制 1024 以下的所有連接埠號碼,或 140 以下的一些重要連接埠(包括 DNS 和 NetBios 連接埠)。
接著,您可能會希望嚴格控制 ['host'] 部分(實際上是一個完整的主機域名或 IP 位址),方法是禁止那些不包含至少一個點的主機名稱、禁止那些以點開頭的主機名稱、禁止那些包含兩個連續點的主機名稱、禁止那些以 '-' 連字號開頭或結尾的主機名稱、禁止那些包含 '.-' 或 '-.' 的主機名稱(這些在所有域名中都無效)、禁止那些域名部分在第二個和第三個字元以外的位置包含兩個連字號且後面沒有至少一個其他字元的名稱、禁止只有一個非數字字元的頂級域名,或超過 6 個字元的頂級域名(目前 ".museum" 是最長的可接受 TLD)、檢查那些純整數的虛擬 TLD 名稱是否確實介於 0 和 255 之間,在這種情況下,透過將其與 long2ip(ip2long($host)) 比較來檢查這是否為有效的 IPv4 位址,...
完成此操作後,您必須根據規範對所有部分(直到爆炸的路徑元素)使用 urlencode() 函式,並對查詢和片段部分使用 rawurlencode() 函式,以重新建立一個完整且經過驗證的 URL。
phpversion()>=5.3 將會符合 RFC 3986,而 phpversion()<=5.2.7RC1 則不符合 RFC 3986。
相關 RFC 的歷史
RFC 1738 第 2.2 節
僅能使用字母數字字符、特殊字符 "$-_.+!*'(),",以及
保留字符用於其保留目的時可以使用
在 URL 中未編碼。
RFC 2396 第 2.3 節
未保留 = 字母數字 | 標記
標記 = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
RFC 2732 第 3 節
(3) 將 "[" 和 "]" 新增到 '保留' 字元集中
RFC 3986 第 2.3 節
未保留 = ALPHA / DIGIT / "-" / "." / "_" / "~"
RFC 3987 第 2.2 節
未保留 = ALPHA / DIGIT / "-" / "." / "_" / "~"
用於清理完整 URL 的範例函式
<?php
private function sanitizeUrl($url){
$parts = parse_url($url);
// 可選,但我們只清理定義了 scheme 和 host 的 URL
if($parts === false || empty($parts["scheme"]) || empty($parts["host"])){
return $url;
}
$sanitizedPath = null;
if(!empty($parts["path"])){
$pathParts = explode("/", $parts["path"]);
foreach($pathParts as $pathPart){
if(empty($pathPart)) continue;
// Path 部分可能已經經過 url 編碼
$sanitizedPath .= "/" . rawurlencode(rawurldecode($pathPart));
}
}
// 建構 URL
$targetUrl = $parts["scheme"] . "://" .
((!empty($parts["user"]) && !empty($parts["pass"])) ? $parts["user"] . ":" . $parts["pass"] . "@" : "") .
$parts["host"] .
(!empty($parts["port"]) ? ":" . $parts["port"] : "") .
(!empty($sanitizedPath) ? $sanitizedPath : "") .
(!empty($parts["query"]) ? "?" . $parts["query"] : "") .
(!empty($parts["fragment"]) ? "#" . $parts["fragment"] : "");
return $targetUrl;
}
?>
--- 1) 關於 URL 中的「保留」字元
請注意,RFC 1738 規定字元 "{", "}", "|", "\", "^", "~", "[", "]", 和 "`" 皆被認為是不安全的,並且*應該*在*所有* URL 中使用 "%xx" 三元組進行 URL 編碼。
然而,某些 HTTP URL 看起來會使用 "~" 字元作為使用者帳號的前綴,例如
http://www.any.host.domain/~user/subpath/page.html?query#fragment
這種用法是可以接受的,但 RFC 規定路徑組件中應該使用 "%7E" 來代替 "~"。HTTP 伺服器應接受 "~" 等同於 "%7E",並且根據 RFC,"%7E" 形式應該是標準形式。
然而,某些 HTTP 伺服器並未完全遵守此 RFC,並且將 "%7E" 與 "~" 視為不同(也就是說,它們將其視為路徑組件名稱的一部分,並搜尋包含 "~" 字元的目錄名稱,而不是將 "~user" 路徑組件對應到使用者的目錄)。在這種情況下,這些不符合規範的 HTTP 伺服器將找不到與該 URL 相關的資源,並可能傳回 404 錯誤或其他錯誤,例如存取遭拒。
當在這種 HTTP URL 上使用 rawurlencode() 時,最好考慮這種舊式用法,方法是在結果上使用 str_replace() 將 "/%7E" 轉換回 "/~",以便 URL 可以正確對應到這些伺服器對 "~" 字元的舊式使用。在符合規範的 HTTP 伺服器上,它們會將 "~" 不安全字元與建議的 "%7E" 形式等效處理,因此它們會自動將 "~" 字元標準化為 "%7E"。
--- 2) URL 中主機名稱的編碼
最後,請注意,URL 中的主機域名部分*不得*使用 rawurlencode() 進行編碼,因為 "[" 和 "]" 是有效的定界符,*必須*用來引用 IPv6 位址或其他不符合主機名稱允許的受限字元集的主機名稱(如果主機名稱包含諸如 ":" 之類的字元,則*必須*使用 "[" 和 "]" 字元,這些字元通常用於指定備用的非預設埠號)。
主機名稱的編碼使用另一種編碼,該編碼需要對國際域名進行編碼,並使用 Unicode 字元的 base-64 編碼和 "bq--" 前綴。此編碼只能在個別子域名部分(以 "." 字元分隔)使用。此編碼不使用任何 "%xx" 三元組。
因此,除非此完整 URL 是查詢參數字串的一部分,否則切勿在未剖析的 URL 上使用 urlencode() 或 rawurlencode()!
--- 3) URL 中使用者名稱/密碼的編碼
沒有標準來指定 URL 中的密碼。事實上,有一種使用 ":" 字元分隔使用者名稱和密碼的舊式用法,但強烈建議不要這樣做。RFC 並未嘗試指定 URL 驗證部分的語意(在 "@" 字元和主機名稱部分之前)。
如果您需要對密碼進行編碼,請始終分別對使用者名稱和密碼使用 rawurlencode(),然後插入 ":" 字元以分隔這兩個組件。請勿使用 urlencode()(它可能會使用 "+" 來編碼空格,並且因為使用者名稱和密碼將 "+" 和空格視為不同而無法運作!)
請注意,RFC 1738 已被修訂
"[" 和 "]" 不再被認為是不安全的,而是現在被認為是「保留」的,這表示它們*可以*在 URL 中使用!
目前,此用法僅在主機名稱部分允許,但有一些提案允許在某些 URL 方案中使用這種用法。現在也發現了類似的擴充功能,它們使用 "{}" 字元作為具有特殊語意的「保留」字元,而不是必須進行 URL 編碼的「不安全」字元...
另請注意,某些字元目前是「保留」的,但應該被視為「不安全」:這包括括號 "()",當 URL 在 MIME 標頭中使用時,它們顯然是不安全的。
因此,如果有效的 URL 包含 "()" 字元,則應使用更上層的編碼,以使用在上層協定中定義的一對「不安全」字元封閉 URL(例如,MIME 標頭中的 "<>" 對,因為這些字元不能是有效 URL 的一部分)...。
關於 'rickyale at ig dot com dot br' 程式的注意事項
在 HTML 頁面中使用 charset=utf-8 是否無法解決整個問題?
我正在 HTML 表單和 PHP 程式之間傳遞一些資料 - 我的「特殊」字元與波蘭字母有關 - 並且 JavaScript 編碼看起來實際上... 有效。
當然,我可能只測試了有限的案例。
這只是一個想法。
關於 rickyale 和 djmaze 的評論...
您試圖實現的不是 utf8 和 URL 編碼的組合嗎?例如
<?
$str = "bl?f Charl?ne";
$enc = urlencode(utf8_encode($str));
$str2 = utf8_decode(urldecode($enc));
echo "$str -> $enc -> $str2";
?>
會輸出
bl?f Charl?ne -> bl%C3%B8f+Charl%C3%A8ne -> bl?f Charl?ne
至少對我來說有效,Jeroen Hofstee
Microsoft URLEncode 方法忽略了 RFC1738 中的文件,該文件指出
".... 特殊字元 "$-_.+!*'(),",以及用於其保留目的的保留字元可以在 URL 中不進行編碼地使用"
因此,例如,myaddress@mydomain.com 會變成 myaddress%40mydomain%2Ecom,而 php 和其他語言會將其編碼為 myaddress%40mydomain.com
當從 asp 移植或在不同平台上進行 URL 編碼的字串進行字串比較時,這可能會成為問題。
注意。php 會將 myaddress%40mydomain%2Ecom 正確解碼為 myaddress@mydomain.com,只有編碼不同
請注意,如果您以 HTTP 方式實作自己的伺服器請求引擎,例如
GET $request_uri
您應該先分割 $request_uri 路徑的所有部分,並對每個部分使用 rawurlencode(),然後再將這些部分重新串連在一起。此函式將正確轉換 URI
function translate_uri($uri) {
$parts = explode('/', $uri);
for ($i = 0; $i < count($parts); $i++) {
$parts[$i] = rawurlencode($parts[$i]);
}
return implode('/', $parts);
}
因為如果對整個 URI 使用 rawurlencode(),路徑分隔符號 '/' 也會被編碼,導致請求不正確。'/' 字元不應該被編碼,只有中間的部分才需要編碼。
希望這能幫助像我一樣的人...
URL/URI 編碼是非常複雜的問題。
例如
'http://example.org:port/path1/path2/data?key1=value1&argument#fragment' (1),或是
'scheme://user:password@example.com:port/path1/path2/data?key1=value1&key2=value2#fragment' (2)
例如,這個 (2) 應該被編碼為
'scheme://'.rawurlencode('user').':'.rawurlencode('password').'@example.com:port/'
.rawurlencode('path1').'/'.rawurlencode('path2').'/'.rawurlencode('data')
.'?'.htmlentities(urlencode('key1').'='.urlencode('value1').'&'.urlencode('key2').'='.urlencode('value2'))
.'#'.urlencode('fragment') 等等。
為了方便編碼,我寫了一個 'toURI' 函數,請見 https://gist.github.com/msegu/bf7160257037ec3e301e7e9c8b05b00a
URI 的結構是:[scheme:][//authority][path][?query][#fragment]
意思是:[scheme:][//[user[:password]@]host[:port]][/path][?query][#fragment]
或是:scheme:[user@host][?query] (mailto: 等等)
toURI() 簡短回顧
fragment ==> urlencode (例如將空格轉為 '+')
query,如 'key1=value1&key2' ==> 每個 key 和 value:urlencode (當 $type<0 時則使用 rawurlencode)
然後整個 query ==> htmlentities
path,如 dir/dir/file ==> 每個 dir 和 file:rawurlencode (例如將空格轉為 %20)
user:password ==> user 和 password 分開:rawurlencode
(請參考 2002-09-13 的匿名筆記!)
toURI() 使用範例
<?php
//簡單使用,query 參數/值中不含特殊字元
echo toURI('key1=value1&key2=value 2&argument1 argument2#fragment');
//'key1=value1&key2=value+2&argument1+argument2#fragment' - OK
echo toURI('?key1=value 1&argu+ments#frag');
//'?key1=value+1&argu%2Bments#frag' - OK
echo toURI('../path 1/path 2/file name');
//'../path%201/path%202/file%20name' - OK
echo toURI('example.com/path1/path2/data?key1=value1&key2=value2#fragment', 1);
//'example.com/path1/path2/data?key1=value1&key2=value2#fragment' - OK; 1 比自動偵測好
echo toURI('http://user:_pass word_@example.com:123/path 1/data?key1=value 1&key2=value2#fragment'); // 若有使用者名稱、密碼或未知的 query 參數,請使用 $spec_replace - 請見下方說明
echo toURI('path 1/path 2/da ta?key1=value 1&argu+ments#frag', 5);
//'path 1/path%202/da%20ta?key1=value+1&argu%2Bments#frag' - 錯誤,應該是 4:
echo toURI('path 1/path 2/da ta?key1=value 1&argu+ments#frag', 4);
//'path%201/path%202/da%20ta?key1=value+1&argu%2Bments#frag' - OK
echo toURI('example.com:port/path1/path2/data?key=value&path=dir 1/dir 2/file#fragment', 5);
//'example.com:port/path1/path2/data?key=value&path=dir+1/dir+2/file#fragment' - OK
echo toURI('path1/path2/data?key1=valueWith~!@?/#$%^&*()inside&arg#frag', 2);
//'path1/path2/data?key1=valueWith%7E%21@?/%23%24%25%5E&%2A%28%29inside&arg#frag' - 錯誤 (第一個 &),請使用 $spec_replace - 請參閱我在 github 上的完整範例 https://gist.github.com/msegu/bf7160257037ec3e301e7e9c8b05b00a
?>
這裡要小心。rawurlencode 會將 ä 轉為 %C3%83%C2%A4,但是 Firefox 內部會將它轉為 %c3%83%c2%a4。這可能會導致重寫迴圈的錯誤。
乾杯。
對於那些想要根據 RFC 3986 從 URL 中移除所有非保留字元的人來說,程式碼會看起來像這樣
<?php
$stripped = preg_replace('/[^[:alnum:]-._~]/', '', $source);
?>
要取得這個字串以便在 url 中正確使用,你可能仍然需要使用 rawurlencode,因為 [:alpha:] posix 括號運算式會捕捉帶有重音符號的字元 - 如果你只想包含 ascii 字元,請改用 [A-Za-z][0-9]。
所以一個基本的 "slug" 產生常式可能會看起來像這樣
<?php
function strtoslug($string) {
// 從 rfc:3986 中移除所有非保留字元
$stripped = preg_replace('/[^[:alnum:][:blank:]-._~]/', '', $string);
// 將壓縮的空白轉換為連字號
$slug = preg_replace('/[:blank:]+/', '-', $stripped);
return $slug;
}
?>
關於 URL 中 ";" 保留字元
rawurlencode() 會使用 "%2A" 三元組編碼它。當在 URL 的路徑部分使用時,這會破壞 URL RFC 中定義的用法,該用法允許為路徑的 *每個* 元素指定額外的參數(以 "/" 分隔)。
因此,如果路徑元素包含 ";" 字元(某些檔案系統允許,但不建議),作為目錄名稱的一部分,則必須對此字元進行編碼,使其不會與參數擴充混淆。
此映射允許在採用階層式結構的 URL(HTTP、HTTPS、FTP、FILE、...)上使用,以便每個以 "/" 為前綴的路徑元素都可以具有額外的導航參數,例如授權字串或語義參數。
路徑元素的通用格式可能包括以下路徑元素
"/." 或 "/.." 或 "/.specialname" 或 "/regularname"
每個部分後面都可以跟著 ";" 和其他以 ";" 分隔的參數。這些參數可以是有序或無序的。無序參數具有符號名稱,並使用等號與其值分隔。
不要將路徑元素參數與 query 字串混淆:這些參數直接附加到個別的路徑元素,當此路徑元素不是 URL 的最後一個元素時,這會造成差異。這些參數是資源名稱的一部分(與 query 字串不同),並且 "." 和 ".." 的語義適用於具有其參數的完整路徑元素,因此
"/subdir1/subdir2/page.html;charset=UTF-8/../index.html"
會解析為 "/subdir1/index.html"。
請注意
"/subdir1/subdir2/page.html;charset=UTF-8"
與
"/subdir1/subdir2/page.html"
指定一個不同的資源名稱。它不一定會涉及 query,因此預設情況下可以快取(與包含 query 字串的 URL 不同)。
當使用路徑元素參數時,它們的可選名稱和必要值必須先分別 rawurlencode(),然後再插入 ";" 和 "=" 參數並建立將在完整路徑中合併的路徑元素。
結果是你 *必須* 不要 urlencode() 或 rawurlencode 單獨的路徑元素,而不先解析它們
- 首先將路徑拆分為以 "/" 分隔的路徑元素
- 然後將每個路徑元素拆分為以 ";" 字元分隔的名稱和參數
- 然後將包含 "=" 符號的路徑元素參數分割為名稱/值組。
- 確保無序的路徑參數(已根據 "=" 分割為一組)在每個路徑元素中 *在* 有序參數(包括主要路徑元素名稱) *之後* 指定,並且沒有兩個無序參數具有相同的名稱(此限制不適用於僅提供值的無序、未命名的參數)。
- 最後,您可以解讀構成每個路徑元素的 rawurlencoded 名稱和值。
另請注意,某些不符合規範的 HTTP 伺服器認為已命名參數是有序的,並且不將語義添加到用於分隔路徑元素參數列表的 ";" 和 "="。在驗證 URL 時,用戶端代理最好不要嘗試解讀此列表,而應該僅通過隔離引入此列表的第一個 ";" 來分割路徑元素的主要部分和參數列表。但是,編碼的參數列表不能包含任何 "/" 參數。
注意事項:請注意,路徑元素參數(以 ";" 引入)可以在階層式 URL 的較高層級上使用,甚至在最終文件名稱及其 query 參數之前使用。建立 URL 列表時,不應盲目地使用 ";" 分隔符號分隔 URL,因為每個 URL 都可能在其路徑部分包含 ";" 字元(";" 字元無法在 query 字串中安全地出現)。在這種情況下,請使用諸如 "<>" 或引號之類的包圍對來封裝此類列表中的每個 URL。
如果您像我一樣,有時不幸被迫使用 PHP4,那麼這裡有一個 PHP 實作 http_build_query(),它產生與此函數大致相同的輸出,並接受相同的引數。
這裡唯一的差異是 RFC 選擇器引數的行為不完全正確。此實作通過 urlencode() 傳遞 RFC1738,並通過 rawurlencode() 傳遞 RFC3986,這不是 100% 正確的,有關更多資訊,請參閱這些函數的手冊頁面。
<?php
if (!function_exists('http_build_query')) {
if (!defined('PHP_QUERY_RFC1738')) define('PHP_QUERY_RFC1738', 1);
if (!defined('PHP_QUERY_RFC3986')) define('PHP_QUERY_RFC3986', 2);
function http_build_query ($query_data, $numeric_prefix = NULL, $arg_separator = NULL, $enc_type = PHP_QUERY_RFC1738, $base = NULL) {
$result = array();
$arg_separator = ($arg_separator != '') ? (string) $arg_separator : ini_get('arg_separator.output');
$enc_func = ($enc_type == PHP_QUERY_RFC3986) ? 'rawurlencode' : 'urlencode';
foreach ($query_data as $key => $item) $result[] = (is_array($item) || is_object($item)) ? http_build_query($item, NULL, $arg_separator, $enc_type, ($base !== NULL) ? "$base%5B".$enc_func($key).'%5D' : $enc_func($key)) : (($base !== NULL) ? "$base%5B".$enc_func($key).'%5D='.$enc_func($item) : ((is_int($key) && $numeric_prefix !== NULL) ? (string) $numeric_prefix : '').$enc_func($key).'='.$enc_func($item));
return implode($arg_separator, $result);
}
}
PHP 的函數 rawurlencode() 和 urlencode(),都會將整個參數字串編碼,導致結果無法作為有效的連結使用。
這裡列出的函數會將連結字串(例如 http://www.domain.com/long_path/to\file.php?query=param#fragm)編碼為有效的 <a href=""> 參數字串,同時保留原始的 URI 結構和給定的路徑。
function linkencode ($p_url) {
$ta = parse_url($p_url);
if (!empty($ta[scheme])) { $ta[scheme].='://'; }
if (!empty($ta[pass]) and !empty($ta[user])) {
$ta[user].=':';
$ta[pass]=rawurlencode($ta[pass]).'@';
} elseif (!empty($ta[user])) {
$ta[user].='@';
}
if (!empty($ta[port]) and !empty($ta[host])) {
$ta[host]=''.$ta[host].':';
} elseif (!empty($ta[host])) {
$ta[host]=$ta[host];
}
if (!empty($ta[path])) {
$tu='';
$tok=strtok($ta[path], "\\/");
while (strlen($tok)) {
$tu.=rawurlencode($tok).'/';
$tok=strtok("\\/");
}
$ta[path]='/'.trim($tu, '/');
}
if (!empty($ta[query])) { $ta[query]='?'.$ta[query]; }
if (!empty($ta[fragment])) { $ta[fragment]='#'.$ta[fragment]; }
return implode('', array($ta[scheme], $ta[user], $ta[pass], $ta[host], $ta[port], $ta[path], $ta[query], $ta[fragment]));
}
我嘗試將之前所有的評論,以及幾個錯誤修復,都整合到 dphantom 的 linkencode 函數中。我針對這些測試案例沒有發現任何錯誤:
http://example.com/path1;var1=val1/p2;v2
http://example.com/p1;v1/p2;v2
http://[ip:v6:440]:8080
http://example.com:8080
http://example.com/~joe
http://example.com/foobar/~joe
http://username:password@hostname/path 1//path 2/?arg 1=value 1&arg 2=value 2#fragment identifier
hostname/path 1//path 2/?arg 1=value 1&arg 2=value 2#fragment identifier
http://invalid_host..name/
function linkencode($p_url){
$uparts = @parse_url($p_url);
$scheme = array_key_exists('scheme',$uparts) ? $uparts['scheme'] : "";
$pass = array_key_exists('pass',$uparts) ? $uparts['pass'] : "";
$user = array_key_exists('user',$uparts) ? $uparts['user'] : "";
$port = array_key_exists('port',$uparts) ? $uparts['port'] : "";
$host = array_key_exists('host',$uparts) ? $uparts['host'] : "";
$path = array_key_exists('path',$uparts) ? $uparts['path'] : "";
$query = array_key_exists('query',$uparts) ? $uparts['query'] : "";
$fragment = array_key_exists('fragment',$uparts) ? $uparts['fragment'] : "";
if(!empty($scheme))
$scheme .= '://';
if(!empty($pass) && !empty($user)) {
$user = rawurlencode($user).':';
$pass = rawurlencode($pass).'@';
} elseif(!empty($user))
$user .= '@';
if(!empty($port) && !empty($host))
$host = ''.$host.':';
elseif(!empty($host))
$host=$host;
if(!empty($path)){
$arr = preg_split("/([\/;=])/", $path, -1, PREG_SPLIT_DELIM_CAPTURE); // needs php > 4.0.5.
$path = "";
foreach($arr as $var){
switch($var){
case "/"
case ";"
case "="
$path .= $var;
break;
default
$path .= rawurlencode($var);
}
}
// legacy patch for servers that need a literal /~username
$path = str_replace("/%7E","/~",$path);
}
if(!empty($query)){
$arr = preg_split("/([&=])/", $query, -1, PREG_SPLIT_DELIM_CAPTURE); // needs php > 4.0.5.
$query = "?";
foreach($arr as $var){
if( "&" == $var || "=" == $var )
$query .= $var;
else
$query .= urlencode($var);
}
}
if(!empty($fragment))
$fragment = '#'.urlencode($fragment);
return implode('', array($scheme, $user, $pass, $host, $port, $path, $query, $fragment));
}
我的 Apache 2 / Windows NT 機器在處理包含變音符號的本機 Windows 路徑時遇到了嚴重的問題。如果我只使用 rawurlencode,Apache 就找不到任何這些檔案。這裡沒有任何註記,但您只需先將路徑轉換為 utf8 即可修復此問題
rawurlencode(utf8_encode($str));
更容易的版本,用於 'rickyale at ig dot com dot br' 的範例
<?php
function encode($text)
{
$REQUEST_URI = str_replace('"', '%22', $text);
// 0 - 128
return preg_replace('#([\x3C\x3E])#e', '"%".bin2hex(\'\\1\')', $text);
}
?>
只需使用您需要編碼的所有字符填寫正規表達式即可。
注意:他陣列中的 142 和以上是特定語言的 ASCII 字符,因此轉換為它們的 unicode 等效項 ('%C5%BD') 可能對您有效或無效。
這需要一個更嚴肅和更大的系統來處理非美國的表格
除了我上次的發文之外,我想補充說明,此函數適用於「directories/somefile.ext」路徑
為了建立有效的 ftp url (包含加入的密碼)
請執行此操作
$valid_path = "ftp://" . rawurlencode($user) . ":" . rawurlencode($pass) . ftp_url_encode($your_server_path_to_file)
最後一個函數會編碼路徑 url,以便語言字符保持不變,並且在出現下載對話框後,您可以獲得相同的下載檔案名稱。
<?php
/*
:: 使用 rawurldecode() 時拉丁字符的問題 ::
------------------------------------------
如果需要使用 rawurldecode() 將 %C3%B1 轉換為 'ñ' 時會發生什麼? 嗯,它不會像我們期望的那樣工作。我們會得到 "ñ"。為了修正這個問題,我製作了以下函數:
*/
function urlRawDecode($raw_url_encoded)
{
# 十六進制轉換表
$hex_table = array(
0 => 0x00,
1 => 0x01,
2 => 0x02,
3 => 0x03,
4 => 0x04,
5 => 0x05,
6 => 0x06,
7 => 0x07,
8 => 0x08,
9 => 0x09,
"A"=> 0x0a,
"B"=> 0x0b,
"C"=> 0x0c,
"D"=> 0x0d,
"E"=> 0x0e,
"F"=> 0x0f
);
# 尋找具有像 %C3%[A-Z0-9]{2} 這種模式的拉丁字符,例如:-> %C3%B1 = 'ñ'
if(preg_match_all("/\%C3\%([A-Z0-9]{2})/i",$raw_url_encoded,$res))
{
$res = array_unique($res = $res[1]);
$arr_unicoded = array();
foreach($res as $key => $value){
$arr_unicoded[] = chr(
(0xc0 | ($hex_table[substr($value,0,1)]<<4)) | (0x03 & $hex_table[substr($value,1,1)])
);
$res[$key] = "%C3%" . $value;
}
$raw_url_encoded = str_replace($res,$arr_unicoded,$raw_url_encoded);
}
# 返回原始 URL 解碼後的結果
return rawurldecode($raw_url_encoded);
}
# 測試
print "解碼後的字符 -> " . urlRawDecode("%C3%B1");
// 輸出:
// 解碼後的字符 -> ñ
/*
:: 這個函數的作用的簡短說明 ::
-----------------------------------------------------
這個函數在 C3 和 B1 之間進行兩個二進制運算。為了獲得這種原始 URL 編碼字符的 ASCII 表示形式,我們必須在 0xC3 的高位 nibble (0xC) 和 0xB1 的高位 nibble (0xB) 之間進行邏輯 OR 運算 -> (0xC0 | 0xB0),然後在兩個低位 nibble (0x03 & 0x01) 之間進行邏輯 AND 運算,最後我們必須在這兩個結果之間進行邏輯 OR 運算 -> [hex] ((0xC0 | 0xB0) | (0x03 & 0x01)) = [binary] ((1100 0000 | 1011 0000) | (0000 0011 & 0000 0001)) = [hex] 0xF1 = [binary] 1111 0001 = "ñ" 字符。
希望對您有所幫助,如果您遇到類似問題,請嘗試使用此函數。
再見,
Javi =)
*/
?>
這似乎是編碼 ftp url 的正確方法,您可以將其提供給您的用戶
function ftp_url_encode($string) {
$hex="";
$retstr = "";
for ($i=0; $i < strlen($string) ;$i++) {
$char = $string[$i];
if(($char >= '0' && $char <= '9') || ($char >= 'A' && $char <= 'Z') || ($char >= 'a' && $char <= 'z') || $char == '.' || $char == '-' || $char == '/' || (ord($char) >=128) ) $retstr .= $char;
else
$retstr .= "%".strtoupper(dechex(ord($string[$i])));
}
return $retstr;
}
瀏覽器會損壞某些語言字符
我必須提一下關於 javier 的文章:您遇到的問題僅發生在您使用 ISO-8859-1(又名 ISO-LATIN-1)編碼時,它是 ASCII 的擴展,使用 128-255 的值來表示拉丁文特定字符(這些字符不是 ASCII 的一部分)。說像 0xF1 是 ASCII 中 "ñ" 的正確值是錯誤的:任何等於或高於 0x80 的值在 ASCII 中都是無效的;而且在 ASCII 中沒有 "ñ" 的「正確」值,因為 ASCII 字符集不包含該字符。
這些編碼/解碼函數旨在在 UTF-8 上工作,UTF-8 是一種與 ASCII 兼容的 Unicode 編碼,因此能夠表示整個 Unicode 字符範圍。
重點是:您得到的 "ñ" 是 0xC3 0xB1 序列,被解釋為兩個單字節 ISO-8859-1 字符;但是如果您將它們解釋為 UTF-8,它們實際上代表 "ñ"。如果您正在使用拉丁字符集和編碼,那麼您的方法是可以的(這本質上是一個 utf-8 => iso-latin-1 轉換器)。
對於任何正在使用 UTF-8 編碼的人,在使用像 javier 的方法之前,請檢查是否有任何問題:這些多字節值實際上是在 UTF-8 上表示任何非 ASCII 字符的正確方法。
有關 UTF-8 和 ISO-8859-1 編碼的更深入詳細資訊,請查看維基百科
http://en.wikipedia.org/wiki/UTF-8
http://en.wikipedia.org/wiki/ISO-8859-1
正如 peter@nospam 所說,微軟在發送數據時使用不同的表格來編碼字串...
經過一些測試,我建立了一個表格,其中包含針對特殊字符(例如 ? ? ? ? ?)的編碼。
這是給那些需要知道這個表格是什麼的人看的。
數組的索引是字符的 ord()。
使用 chr(index) 來知道字符.. 並用值替換.....
var $ENCODE_TABLE = ARRAY(33=>'%21', 35=>'%23', 36=>'%24', 37=>'%25', 38=>'%26', 40=>'%28', 41=>'%29', 43=>'%2B', 44=>'%2C', 47=>'%2F', 58=>'%3A', 59=>'%3B', 60=>'%3C', 61=>'%3D', 62=>'%3E', 63=>'%3F', 91=>'%5B', 92=>'%5C', 93=>'%5D', 123=>'%7B', 124=>'%7C', 125=>'%7D', 142=>'%C5%BD', 192=>'%C3%80', 193=>'%C3%81', 194=>'%C3%82', 195=>'%C3%83', 196=>'%C3%84', 197=>'%C3%85', 199=>'%C3%87', 200=>'%C3%88', 201=>'%C3%89', 202=>'%C3%8A', 203=>'%C3%8B', 204=>'%C3%8C', 205=>'%C3%8D', 206=>'%C3%8E', 207=>'%C3%8F', 210=>'%C3%92', 211=>'%C3%93', 212=>'%C3%94', 213=>'%C3%95', 214=>'%C3%96', 217=>'%C3%99', 218=>'%C3%9A', 219=>'%C3%9B', 220=>'%C3%9C', 221=>'%C3%9D', 224=>'%C3%A0', 225=>'%C3%A1', 226=>'%C3%A2', 227=>'%C3%A3', 228=>'%C3%A4', 229=>'%C3%A5', 231=>'%C3%A7', 232=>'%C3%A8', 233=>'%C3%A9', 234=>'%C3%AA', 235=>'%C3%AB', 236=>'%C3%AC', 237=>'%C3%AD', 238=>'%C3%AE', 239=>'%C3%AF', 242=>'%C3%B2', 243=>'%C3%B3', 244=>'%C3%B4', 245=>'%C3%B5', 246=>'%C3%B6', 249=>'%C3%B9', 250=>'%C3%BA', 251=>'%C3%BB', 252=>'%C3%BC', 253=>'%C3%BD', 255=>'%C3%BF');
例子:
function encode($text) {
while(list($ord, $enc) = each($ENCODE_TABLE)) {
$text = str_replace(chr($ord), $enc, $text);
}
return $text;
}
希望這對您有幫助...