Skip to content
العودة إلى المدوّنة
دروس تعليمية

ترميز وفك ترميز URL: دليل ترميز النسبة المئوية للمطورين

تعلّم ترميز URL: قواعد RFC 3986، متى تستخدم encodeURI أو encodeURIComponent، تعيين بايتات UTF-8، وأمثلة عملية بـ JS وPython وGo وJava.

12 دقيقة للقراءة

ترميز وفك ترميز URL: دليل ترميز النسبة المئوية للمطورين

تفتح سجلات الخادم وترى هذا في سلسلة الاستعلام: %E4%BD%A0%E5%A5%BD. بيانات تالفة؟ خطأ برمجي؟ لا — هذه الأحرف الصينية 你好، حُوِّل كل حرف منها إلى ثلاث بايتات UTF-8 ثم رُمِّز بالنسبة المئوية. كل مطور ويب يمر بهذا: شيء يبدو معطلاً، لكن العنوان يعمل تماماً كما صُمِّم.

ترميز URL — أو ترميز النسبة المئوية — يجعل الأحرف الخاصة آمنة داخل العناوين. هذا الدليل يغطي آلية العمل على مستوى البايت، والفرق بين encodeURI وencodeURIComponent، والترميز الصحيح في أربع لغات برمجة، والأخطاء التي تُربك حتى المطورين المخضرمين.

الصق أي عنوان في أداة ترميز وفك ترميز URL وتابع النتائج مباشرة.

ما هو ترميز URL (ترميز النسبة المئوية)؟

العنوان يقبل فقط مجموعة صغيرة من أحرف ASCII. الحروف والأرقام وبعض الرموز تمر دون مشاكل. أي شيء آخر — مسافات، علامة العطف، نصوص صينية، رموز تعبيرية — يجب تحويله لصيغة يستطيع العنوان حملها.

ترميز النسبة المئوية يستبدل كل بايت غير آمن بعلامة % يليها رقمان ست عشريان. المسافة تصبح %20، وعلامة العطف %26. التسمية من بادئة %.

القواعد محددة في RFC 3986 (نُشر 2005، لا يزال المعيار المعتمد). حلّ محل RFC 2396 وضبط تعريفات الأحرف الآمنة والمحجوزة والتعامل مع النصوص غير ASCII.

أمثلة سريعة:

المدخلالمرمَّزالسبب
hello worldhello%20worldالمسافات غير مسموحة في العناوين
price=10&tax=2price%3D10%26tax%3D2= و & لهما معنى هيكلي
%E4%B8%ADحرف غير ASCII ← بايتات UTF-8 ← ترميز نسبة مئوية
🚀%F0%9F%9A%80رمز تعبيري ← 4 بايتات UTF-8 ← ترميز نسبة مئوية

أي الأحرف تحتاج إلى ترميز؟

يقسم RFC 3986 الأحرف إلى ثلاث مجموعات. معرفة هذه المجموعات يوفر ساعات من تصحيح الأخطاء.

الأحرف غير المحجوزة (لا تُرمَّز أبداً)

هذه الأحرف الـ 66 تمر كما هي في أي جزء من العنوان:

A-Z  a-z  0-9  -  .  _  ~

فقط: أحرف أبجدية، أرقام، شرطة، نقطة، شرطة سفلية، مَدّة. أي حرف آخر يحتاج ترميزاً.

الأحرف المحجوزة (تعتمد على السياق)

هذه الأحرف تعمل كفواصل هيكلية في العناوين:

الحرفدوره في بنية العنوان
:يفصل المخطط عن السلطة (https:)
/يفصل أجزاء المسار
?يبدأ سلسلة الاستعلام
#يبدأ الجزء المرجعي
&يفصل معلمات الاستعلام
=يفصل مفتاح المعلمة عن قيمتها
@يفصل معلومات المستخدم عن المضيف
+ ! $ ' ( ) * , ; [ ]أدوار محجوزة متنوعة

القاعدة: عندما يؤدي الحرف المحجوز وظيفته الهيكلية، اتركه كما هو. عندما يظهر كبيانات (داخل قيمة معلمة مثلاً)، رمِّزه.

كل شيء آخر (يُرمَّز دائماً)

المسافات، الأقواس الزاوية، الأقواس المعقوفة، الأنابيب، الشرطات المائلة العكسية، وكل حرف غير ASCII (صينية، عربية، رموز تعبيرية) — يجب ترميزها قبل وضعها في العنوان.

المسافة حالة خاصة: RFC 3986 يرمِّزها %20، لكن نماذج HTML تستخدم +. تفاصيل هذا التعارض لاحقاً.

كيف يعمل ترميز URL فعلياً: مسار UTF-8

لأحرف ASCII، الأمر بسيط: خذ قيمة البايت بالست عشري وضع % قبلها. المسافة (بايت 32، ست عشري 20) تصبح %20.

للنصوص غير ASCII، الترميز يمر بثلاث خطوات:

الخطوة 1 — الحرف إلى نقطة ترميز Unicode. الحرف é يُعيَّن لنقطة الترميز U+00E9. الرمز التعبيري 🚀 يُعيَّن لـ U+1F680.

الخطوة 2 — نقطة الترميز إلى بايتات UTF-8. يستخدم UTF-8 من 1 إلى 4 بايتات حسب نطاق نقطة الترميز. é (U+00E9) تصبح بايتين: 0xC3 0xA9. رمز الصاروخ (U+1F680) يصبح أربع بايتات: 0xF0 0x9F 0x9A 0x80.

الخطوة 3 — كل بايت إلى %XX. كل بايت من الخطوة 2 يحصل على ثلاثية ترميز النسبة المئوية الخاصة به.

إليك المسار الكامل لعدة أنواع من الأحرف:

الحرفنقطة الترميزبايتات UTF-8المرمَّزمضاعف الحجم
AU+004141A (لا يُرمَّز)
مسافةU+002020%20
éU+00E9C3 A9%C3%A9
U+4E2DE4 B8 AD%E4%B8%AD
🚀U+1F680F0 9F 9A 80%F0%9F%9A%8012×

تحقق بنفسك في JavaScript:

const char = '中';
const encoded = encodeURIComponent(char);
console.log(encoded); // '%E4%B8%AD'

// Trace the bytes
const bytes = new TextEncoder().encode(char);
console.log([...bytes].map(b => '%' + b.toString(16).toUpperCase()).join(''));
// '%E4%B8%AD' — matches

هذا التمدد يؤثر مباشرة على حدود طول العنوان. مثلاً: 20 حرفاً صينياً تضيف 180 حرفاً مرمَّزاً.

encodeURI مقابل encodeURIComponent — اختيار الدالة الصحيحة

الخلط بينهما هو أكثر خطأ شائع في ترميز العناوين بـ JavaScript. الاسمان متقاربان، لكن كل دالة ترمِّز مجموعة مختلفة تماماً.

encodeURI()encodeURIComponent()
الغرضترميز عنوان كاملترميز مكوّن واحد (مفتاح أو قيمة معلمة)
يحافظ على: / ? # & = @ + $ ,لا شيء من هذه
يرمِّزالمسافات، غير ASCII، بعض علامات الترقيمكل شيء عدا A-Z a-z 0-9 - _ . ~ ! ' ( ) *
استخدمها عندمالديك عنوان كامل فيه مسافات أو Unicode في المسارتبني معلمات استعلام من مدخلات المستخدم

خطأ يصل إلى الإنتاج أكثر مما تتوقع:

// ❌ BUG: encodeURI does NOT encode &
const search = 'Tom & Jerry';
const bad = `https://api.example.com/search?q=${encodeURI(search)}`;
// Result: https://api.example.com/search?q=Tom%20&%20Jerry
// The & splits the query string — server sees q=Tom%20 and a separate param %20Jerry

// ✅ FIX: encodeURIComponent encodes & as %26
const good = `https://api.example.com/search?q=${encodeURIComponent(search)}`;
// Result: https://api.example.com/search?q=Tom%20%26%20Jerry

القاعدة: عند الشك، استخدم encodeURIComponent(). هي الخيار الصحيح في 95% من الحالات الفعلية.

جرّب كلا الوضعين جنباً إلى جنب في أداة ترميز URL

ترميز URL في كل لغة برمجة

JavaScript (المتصفح و Node.js)

// Encode a parameter value
const value = encodeURIComponent('price >= 100 & currency = €');
// 'price%20%3E%3D%20100%20%26%20currency%20%3D%20%E2%82%AC'

// Decode
const original = decodeURIComponent(value);
// 'price >= 100 & currency = €'

// Modern approach: URLSearchParams handles encoding automatically
const params = new URLSearchParams({ q: 'hello world', lang: '中文' });
console.log(params.toString());
// 'q=hello+world&lang=%E4%B8%AD%E6%96%87'
// Note: URLSearchParams uses + for spaces (form encoding)

Python

from urllib.parse import quote, unquote, urlencode

# Encode a path segment
quote('hello world/file name.txt', safe='/')
# 'hello%20world/file%20name.txt'

# Encode query parameters
urlencode({'q': '你好', 'page': '1'})
# 'q=%E4%BD%A0%E5%A5%BD&page=1'

# quote_plus uses + for spaces (form encoding)
from urllib.parse import quote_plus
quote_plus('hello world')  # 'hello+world'
quote('hello world')       # 'hello%20world'

Go

import "net/url"

// Encode a query value (uses + for spaces)
url.QueryEscape("hello world & more")
// "hello+world+%26+more"

// Encode a path segment (uses %20 for spaces)
url.PathEscape("hello world & more")
// "hello%20world%20&%20more"

// Build a URL safely with url.Values
params := url.Values{}
params.Set("q", "你好世界")
params.Set("page", "1")
fmt.Println(params.Encode())
// "page=1&q=%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C"

Java

import java.net.URLEncoder;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

// Encode (uses + for spaces — Java follows form encoding)
String encoded = URLEncoder.encode("hello world & more", StandardCharsets.UTF_8);
// "hello+world+%26+more"

// For RFC 3986 compliance, replace + with %20
String rfc3986 = encoded.replace("+", "%20");
// "hello%20world%20%26%20more"

// Decode
String decoded = URLDecoder.decode(encoded, StandardCharsets.UTF_8);
// "hello world & more"

Go وJava يستخدمان ترميز النماذج افتراضياً (المسافات كـ +). لمخرجات RFC 3986، استبدل + بـ %20 بعد الترميز.

خمسة أخطاء في ترميز URL تُعطِّل الإنتاج

1. الترميز المزدوج (%2520 بدلاً من %20)

ترمِّز سلسلة، يمررها إطار العمل عبر ترميز ثانٍ، فتتحول % في %20 إلى %25. النتيجة: الخادم يرى نص %20 الحرفي بدلاً من مسافة.

العَرَض: العناوين تحتوي على %2520 أو %253D أو أنماط %25xx أخرى.

التشخيص: أي %25 في عنوان مشبوه — يعني أن حرف % تم ترميزه، مما يشير عادة إلى ترميز مزدوج.

الحل: فُكّ الترميز أولاً، ثم رمِّز مرة واحدة. لا ترمِّز سلسلة دون التأكد أنها غير مرمَّزة سابقاً.

// Detect double encoding
function isDoubleEncoded(str) {
  return /%25[0-9A-Fa-f]{2}/.test(str);
}

// Safe encode: decode first, then encode
function safeEncode(str) {
  try { str = decodeURIComponent(str); } catch (e) { /* not encoded, that's fine */ }
  return encodeURIComponent(str);
}

2. + في أجزاء المسار

مطور يرمِّز اسم ملف بمكتبة تُخرِج + للمسافات. الملف my report.pdf يصبح my+report.pdf. الخادم يعامل + كعلامة جمع حرفية ويعيد 404.

القاعدة: + تعني مسافة فقط في سلاسل الاستعلام (بعد ?). في أجزاء المسار، + هي مجرد +. استخدم دائماً %20 للمسافات في المسارات.

3. عناوين إعادة التوجيه المعطلة في OAuth

عنوان التخويل يبدو هكذا:

https://auth.provider.com/authorize?redirect_uri=https://myapp.com/callback?code=abc&state=xyz

خادم OAuth يقرأ redirect_uri=https://myapp.com/callback?code=abc ويعامل state=xyz كمعلمة مستقلة. المصادقة تفشل.

الحل: رمِّز قيمة عنوان إعادة التوجيه بالكامل:

const redirectUri = 'https://myapp.com/callback?code=abc&state=xyz';
const authUrl = `https://auth.provider.com/authorize?redirect_uri=${encodeURIComponent(redirectUri)}`;
// redirect_uri=https%3A%2F%2Fmyapp.com%2Fcallback%3Fcode%3Dabc%26state%3Dxyz

4. نصوص غير مقروءة في السجلات

سجلات الخادم تعرض %E4%BD%A0%E5%A5%BD بدلاً من أحرف صينية. ليس خطأ — الترميز صحيح. المشكلة أن عارض السجلات لا يفك تسلسلات النسبة المئوية.

الحل: مرِّر السجلات عبر فاكّ ترميز، أو الصق العنوان في أداة فك ترميز URL لقراءة النص الأصلي.

5. فشل توقيع API

OAuth 1.0 وAWS Signature V4 يتطلبان ترميز RFC 3986 صارماً. لكن encodeURIComponent() في JavaScript لا ترمِّز ! و' و( و) و*. إن ظهرت في مدخلات التوقيع، لن يتطابق.

الحل: عالج المخرجات لاحقاً:

function rfc3986Encode(str) {
  return encodeURIComponent(str).replace(/[!'()*]/g, c =>
    '%' + c.charCodeAt(0).toString(16).toUpperCase()
  );
}

%20 مقابل + — معضلة ترميز المسافة

معياران، حرف واحد، ارتباك لا ينتهي.

المعيارالمسافة تصبحأين ينطبق
RFC 3986 (صياغة URI)%20في كل مكان في العنوان
application/x-www-form-urlencoded+سلاسل الاستعلام من إرسال نماذج HTML

اتفاقية + موروثة من بدايات الويب. عند إرسال نموذج <form> بـ method="GET"، المتصفح يرمِّز المسافات كـ + في سلسلة الاستعلام. سلوك مُدمج في مواصفات HTML ولن يختفي.

لكن + تعني “مسافة” في سلاسل الاستعلام فقط. في المسار، + حرفية. لذا https://example.com/my+file.pdf يقدم ملفاً اسمه my+file.pdf وليس my file.pdf.

إرشادات عملية:

  • استخدم %20 عند بناء العناوين يدوياً أو ترميز أجزاء المسار. يعمل في كل مكان.
  • اقبل + عند تحليل سلاسل الاستعلام من إرسال النماذج — إطار العمل الخاص بك يتعامل مع هذا على الأرجح.
  • لا تخلط بينهما. اختر اتفاقية واحدة لكل مكوّن والتزم بها.

ترميز URL والأمان

ترميز URL ليس تشفيراً

ترميز النسبة المئوية تحويل حتمي وقابل للعكس تماماً. لا مفتاح ولا سر. أي شخص يفك ترميز %48%65%6C%6C%6F إلى Hello في ثوانٍ.

لا تستخدم ترميز URL لإخفاء بيانات حساسة. استخدم HTTPS لتشفير الطلب كاملاً. العناوين تظهر في سجلات الخادم وسجل المتصفح وترويسات Referer — ضع البيانات الحساسة في أجسام الطلبات فقط.

هجمات إعادة التوجيه المفتوح

المهاجمون يصنعون عناوين مرمَّزة تتجاوز التحقق السطحي. معلمة إعادة توجيه تحتوي %2F%2Fevil.com تُفَك إلى //evil.com — يفسره المتصفح كعنوان نسبي للبروتوكول يشير لنطاق المهاجم.

الدفاع: تحقق دائماً من العنوان بعد فك الترميز، لا من الشكل المرمَّز. استخدم قوائم السماح لنطاقات إعادة التوجيه.

استغلال الترميز المزدوج

جدار حماية تطبيقات الويب (WAF) يفحص العناوين بحثاً عن وسوم <script>. المهاجم يرسل %253Cscript%253EWAF يرى نصاً مرمَّزاً فيمرره. التطبيق يفك الترميز مرة فيصبح %3Cscript%3E، ثم مرة ثانية فينتج <script> — تجاوز ناجح للفلتر.

الدفاع: وحِّد المدخلات (فُكّ الترميز بالكامل) قبل أي فحص أمني. فك ترميز واحد لا يكفي.

للتعمق أكثر، راجع دليل أساسيات أمان الويب.

حدود طول العنوان ومتى يصبح الترميز مكلفاً

مواصفات HTTP لا تحدد حداً أقصى لطول العنوان، لكن كل طبقة في المكدس تفرض حدوداً عملية.

الطبقةالحد
التوصية العامة2,000 حرف
Chrome، Firefoxحوالي 2 ميغابايت (لكن الخوادم ترفض قبل ذلك بكثير)
Apache (افتراضي)8,190 بايت
Nginx (افتراضي)8,192 بايت
IIS16,384 بايت (سلسلة الاستعلام)
شبكات CDN، البروكسياتمتغير — غالباً 4,096-8,192 بايت

الترميز يزيد طول العنوان بشكل ملحوظ. حرف صيني واحد يتمدد من حرف إلى 9 أحرف (%E4%B8%AD). رمز تعبيري يتمدد إلى 12. بـ 200 حرف صيني في سلسلة الاستعلام، تحصل على 1,800 حرف مرمَّز قبل حساب العنوان الأساسي.

عند بلوغ الحد: انقل البيانات من معلمات الاستعلام إلى جسم طلب POST. لواجهات البحث، استخدم نقطة نهاية POST تقبل جسم JSON بمعايير البحث.

الأسئلة الشائعة

ما هو ترميز URL ولماذا يحتاجه المطورون؟

ترميز URL (ترميز النسبة المئوية) يحوّل الأحرف غير المسموحة في العناوين إلى تسلسلات %XX ست عشرية. العناوين تدعم 66 حرفاً غير محجوز فقط من ASCII، فالمسافات وعلامات العطف ونصوص Unicode ومعظم علامات الترقيم تحتاج ترميزاً كي لا تكسر بنية العنوان.

ما الفرق بين encodeURI و encodeURIComponent؟

encodeURI() ترمِّز عنواناً كاملاً وتحافظ على الأحرف الهيكلية (://، /، ?، &). encodeURIComponent() ترمِّز كل شيء عدا A-Z a-z 0-9 - _ . ~ ! ' ( ) *. استخدم الثانية لقيم المعلمات، والأولى فقط عندما تريد إصلاح مسافات أو أحرف غير ASCII في عنوان كامل دون كسر بنيته.

لماذا يظهر %20 أحياناً كـ + في العناوين؟

كلاهما يمثل مسافة لكن من معيارين مختلفين. %20 من RFC 3986 ويعمل في كل أجزاء العنوان. + من مواصفات نماذج HTML ويعمل فقط في سلاسل الاستعلام — في المسار، + علامة جمع حرفية. %20 آمن دائماً؛ + اتفاقية قديمة باقية بسبب سلوك النماذج.

كيف أرمِّز نصاً في Python و JavaScript و Go و Java؟

JavaScript: encodeURIComponent('hello world')hello%20world. Python: urllib.parse.quote('hello world')hello%20world. Go: url.QueryEscape("hello world")hello+world. Java: URLEncoder.encode("hello world", UTF_8)hello+world. Go و Java يستخدمان افتراضياً ترميز النماذج (المسافة كـ +) — استبدل + بـ %20 لمخرجات RFC 3986.

هل يمكن استخدام ترميز URL للأمان أو التشفير؟

لا. الترميز قابل للعكس فوراً دون مفتاح — لا سرية فيه. احمِ البيانات الحساسة بـ HTTPS (تشفير TLS). العناوين مكشوفة في سجلات الخادم وسجل المتصفح وترويسات Referer، فضع البيانات الحساسة في أجسام الطلبات.

ما هو الترميز المزدوج وكيف أصلحه؟

يحدث عندما تُرمَّز سلسلة مرمَّزة سابقاً مرة أخرى. % في %20 تصبح %25، فتنتج %2520. الخادم يرى %20 كنص حرفي بدلاً من مسافة. الحل: فُكّ الترميز أولاً ثم رمِّز مرة واحدة. وجود %25 متبوعاً برقمين ست عشريين هو العلامة الكاشفة.

ما الحد الأقصى لطول العنوان؟

لا حد رسمي في مواصفات HTTP، لكن 2,000 حرف هو الحد الآمن عملياً. Apache يسمح بـ 8,190 بايت افتراضياً، وNginx بـ 8,192 بايت. الأحرف غير ASCII تتمدد 3-12 ضعفاً بالترميز، فالعناوين الدولية تبلغ الحد أسرع. للبيانات الكبيرة، انتقل إلى POST مع جسم طلب.

مقالات ذات صلة

عرض جميع المقالات