مُرمِّز / فاكّ ترميز العناوين

رمّز عناوين URL وفكّ ترميزها، واختر نطاق المكوّن أو العنوان الكامل، وفكّك العنوان إلى أجزائه.

لقيمة مفردة، كمعامل استعلام واحد. ويهرّب أيضًا / ? : @ & = + لأنها هناك بيانات لا بنية.

الإدخال
الإخراج

سيظهر الإخراج هنا

ما الغاية من الترميز النسبي

لا يُسمح للعنوان (URL) إلا بمجموعة صغيرة من المحارف: الحروف A–Z وa–z، والأرقام، وحفنة من العلامات مثل - . _ ~، ومجموعة من المحارف المحجوزة التي تمنح العنوان شكله — / ? # & = : @ وقليل غيرها. وكل ما عدا ذلك، من مسافةٍ إلى كلمة بالعبرية إلى رمز تعبيري، يجب تمثيله تمثيلًا غير مباشر. والترميز النسبي هو ذلك التمثيل: يُحوَّل المحرف إلى بايتاته بترميز UTF-8، ويُكتب كل بايت علامةَ نسبة مئوية يتبعها رقمان ست عشريان. فتصير المسافة %20، وتصير א هي %D7%90.

والمحارف المحجوزة هي الحالة المثيرة للاهتمام. فهي مشروعة داخل العنوان، لكن لها معنًى بنيويًا: فـ ? يفتح الاستعلام، و& يفصل بين المعاملات، و= يفصل المفتاح عن قيمته. وحين يكون أحد هذه المحارف جزءًا من بياناتك لا من البنية، وجب ترميزه — وإلا قرأه المحلّل في الطرف الآخر علامةَ ترقيم وشطر قيمتك في الموضع الخطأ.

مكوّن أم عنوان كامل — التمييز الذي يكسر الروابط

هذا أشيع مصدر لعلل العناوين، ولهذا تطلب منك هذه الأداة اختيار النطاق بدل أن تخمّنه.

  • نطاق المكوّن يهرّب الفواصل أيضًا: / ? : @ & = + $ , و#. استعمله لقطعة بيانات واحدة — كلمة بحث، أو وجهة إعادة توجيه، أو رمز — ستوضع داخل عنوان أكبر.
  • نطاق العنوان الكامل يترك تلك الفواصل كما هي ولا يهرّب إلا ما هو غير مشروع فعلًا، كالمسافات والنص غير اللاتيني. استعمله حين يكون بين يديك عنوان كامل مبنيّ بنية صحيحة ولا يحتاج إلا إلى تنظيف.

والخطأ في أي من الاتجاهين يكسر شيئًا. رمّز عنوانًا كاملًا في نطاق المكوّن فتتحوّل كل شرطة مائلة وكل علامة استفهام إلى %2F و%3F، وتخرج بسلسلة واحدة لا تصلح لشيء. ورمّز قيمة مفردة في نطاق العنوان الكامل فتنجو علامة & داخلها بوصفها فاصلًا، فيصير البحث عن «قطط & كلاب» معاملَين في صمت، ويختفي النصف الثاني من قيمتك.

// القيمة تحوي فاصلًا، فوجب تهريبها:
const q = 'cats & dogs'
`/search?q=${encodeURIComponent(q)}`  // /search?q=cats%20%26%20dogs
`/search?q=${encodeURI(q)}`           // /search?q=cats%20&%20dogs  ✗

// والأفضل: دع المنصّة تركّب العنوان
const url = new URL('https://example.com/search')
url.searchParams.set('q', 'cats & dogs')

ويجدر اتخاذ هذا النهج الأخير عادةً. فـ URL وURLSearchParams مدمجان في كل متصفح وفي Node، ويطبّقان الترميز الصحيح على كل جزء نيابةً عنك — وهو ما يُلغي القرار من أصله بدل أن يلزمك بإصابته يدويًا في كل مرة.

لماذا تكون المسافة أحيانًا + وأحيانًا %20

يعمل هنا معياران مختلفان، ويختلفان في محرف واحد بالضبط. فـ RFC 3986، الذي يحكم العناوين عمومًا، يرمّز المسافة %20. أما الصيغة الأقدم application/x-www-form-urlencoded — وهي ما ترسله نماذج HTML، ومن ثمّ ما تبدو عليه أغلب سلاسل الاستعلام — فترمّزها +.

والمكمن أن decodeURIComponent لا ينفّذ إلا القاعدة الأولى. أعطه hello+world فيعيد إليك hello+world بعلامة الزائد وكل شيء — بلا خطأ ولا تحذير، بل قيمة خاطئة على نحوٍ خفي. ولهذا تقدّم هذه الأداة خيار «عامِل + كمسافة» وتنبّهك إليه حين يحتوي مدخلك على علامة زائد والخيار مُطفأ.

ولاحظ أن URLSearchParams، الذي يشغّل جدول التفكيك أسفل الأداة، يتبع قواعد النماذج ويحوّل + إلى مسافة فعلًا. فسلسلة الاستعلام الواحدة قد تُفَكّ بطريقتين مختلفتين تبعًا للواجهة التي تستعملها — عن قصد، وبصواب، في الحالتين.

ونتيجة واحدة تستحق التذكّر: إن كانت علامة الزائد جزءًا حقيقيًا من بياناتك — رقم هاتف، أو تعبير تصفية — وجب ترميزها %2B. فالعلامة + الحرفية في سلسلة استعلام ملتبسة في أحسن الأحوال، وستقرؤها أغلب المحلّلات مسافةً.

قراءة العنوان

الصق عنوانًا مطلقًا فتفكّكه الأداة إلى أجزائه. ويجدر أن تعرف اسم كل جزء، لأن رسائل الأخطاء والوثائق تفترض هذه المصطلحات:

  • المخطّط — https أو mailto أو ftp. كل ما قبل النقطتين، وهو ما يحدّد كيف يُفهم الباقي.
  • بيانات المستخدم — ‏user:password اختياري قبل المضيف. لا يزال جائزًا، ولا يزال فكرة سيئة: فهو يسافر مع كل طلب وينتهي في السجلات وتاريخ المتصفح وترويسات referrer.
  • المضيف — اسم النطاق أو عنوان IP. وكل ما بعده يعالجه ذلك الخادم لا الشبكة.
  • المنفذ — غائب عادةً، لأن 443 لـ https و80 لـ http مفهومان ضمنًا.
  • الأصل — المخطّط والمضيف والمنفذ مجتمعة. وهو الوحدة التي بُني عليها أمن المتصفح: فسياسة الأصل نفسه و‑CORS ونطاق الكعكات جميعها تقارن الأصول لا المسارات.
  • المسار — الجزء الذي يلي المضيف ويسبق أي ? أو #.
  • الاستعلام — أزواج المفتاح/القيمة بعد ?، مفصولة بـ &.
  • الجزء المرجعي — كل ما بعد #. وهو وحده لا يُرسَل إلى الخادم أبدًا؛ بل يعالجه المتصفح كاملًا.

وهذه التفصيلة عن الجزء المرجعي أهمّ مما تبدو. فلأنه لا يغادر المتصفح قط، يبقى كل ما تضعه بعد # غير مرئي لسجلات الخادم — ولهذا استعملته بعض تطبيقات الصفحة الواحدة تاريخيًا في التوجيه، ولهذا ليس هو الموضع الذي تبحث فيه حين تنقّح طلبًا لم يصل قط.

وتحذير أخير عمّا لا يفعله الترميز. الترميز النسبي يجعل النص آمن النقل داخل عنوان؛ وليس تعقيمًا ولا ضابطًا أمنيًا. فترميز قيمة لا يجعلها آمنة للإدراج في HTML أو SQL أو أمر صدفة، وفكّ ترميز مدخل غير موثوق قد يكشف محارف — فواصل مسارات، بايتات صفرية — كانت الصورة المرمَّزة تخفيها. تحقّق مما تكون عليه القيمة، بمعزل عن ترميز كيفية سفرها.

العنوان الذي كتبته ليس دائمًا العنوان الذي يُرسَل

قبل أن يذهب العنوان إلى أي مكان يُطبَّع، وإعادة الكتابة صامتة. وتعرض هذه الأداة الناتج كلما اختلف عن المدخل، لأن تلك السلسلة — لا التي كتبتها — هي التي تصل إلى الخادم وتظهر في السجل:

HTTPS://Example.COM:443\a\b     كُتب
https://example.com/a/b         أُرسل

أربع قواعد منفصلة عملت هناك. فالمخطّط والمضيف يصيران بأحرف صغيرة، إذ لا يميز أي منهما حالة الأحرف. ويُحذف المنفذ لأن 443 هو الافتراضي لـ https. وتصير الشرطات الخلفية مائلة أمامية، وهي قاعدة توافق تفاجئ من يكتب مسارات على طريقة Windows. وكان المسار الفارغ سيصير شرطة مائلة واحدة. ولا شيء من ذلك خطأ، لكن إن كنت تقارن عنوانين أو تطابق عنوانًا مع قائمة سماح، فعليك مقارنة الصور المطبَّعة وإلا حصلت على جواب خاطئ.

واسم المضيف الذي يحوي محارف خارج ASCII يُعاد كتابته أيضًا إلى صورة ASCII تبدأ بـ‏xn--، تسمى punycode. وتعرض الأداة الاتجاهين: الاسم المقروء والاسم الذي يسافر فعلًا. ويستحق ذلك النظر لا التخطي، لأن اسمين مختلفين بترميز يونيكود قد يبدوان متطابقين على الشاشة — فحرف a اللاتيني وحرف а السيريلي محرفان منفصلان — بينما ينتجان punycode مختلفًا تمامًا. ومقارنة صور ASCII هي الطريقة الموثوقة الوحيدة للتفريق بين زوج كهذا.

وشيء واحد لا يُطبَّع أبدًا: معامل استعلام مكرر. فكتابة ‎?tag=a&tag=b جائزة تمامًا، والمعيار لا يقول ما معناها، فاختار كل نظام جوابه. إذ يبقي PHP القيمة الأخيرة، ويجمعها Express في مصفوفة، ويأخذ كثير من الأطر ومعها URLSearchParams.get الأولى. وتضع الأداة علامة على المفاتيح المكررة بدل سردها مرتين دون تعليق، لأن العلة الناتجة — قيمة تعمل في خدمة وتختفي في أخرى — يصعب حقًا التقاطها بالقراءة.

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

ما الفرق بين نطاق المكوّن ونطاق العنوان الكامل؟
نطاق المكوّن يهرّب الفواصل / ? : @ & = + أيضًا، وهو الصواب لقيمة مفردة توضع داخل عنوان. أما نطاق العنوان الكامل فيتركها كما هي لأنها هناك تفصل بين أجزاء العنوان. واستعمال نطاق المكوّن على عنوان كامل يحوّل كل شرطة مائلة إلى %2F ويعطي سلسلة لا تصلح لشيء.
لماذا ما زال نصي المفكوك يحوي +؟
لأن decodeURIComponent يتبع RFC 3986، حيث + مجرّد علامة زائد. أما إرسال النماذج وأغلب سلاسل الاستعلام فتستعمل ترميز النماذج حيث + تعني مسافة. فعّل «عامِل + كمسافة» حين يأتي النص من أيٍّ منهما.
كيف أرمّز علامة زائد هي فعلًا علامة زائد؟
اكتبها %2B. فالعلامة + الحرفية في سلسلة استعلام ستقرؤها أغلب المحلّلات مسافةً، ومن ثمّ فأي علامة زائد تكون جزءًا حقيقيًا من بياناتك — في رقم هاتف مثلًا — يجب تهريبها.
أأرمّز العنوان كله أم الأجزاء فقط؟
الأجزاء فقط، ويُفضَّل ألّا يكون ذلك يدويًا: ابنِ العنوان بـ URL وURLSearchParams اللذين يطبّقان الترميز الصحيح على كل مكوّن. فترميز عنوانٍ مركَّب بعد تركيبه هو منشأ أغلب علل الترميز المزدوج.
ما الترميز المزدوج؟
هو ترميز قيمة مرمَّزة أصلًا، فيصير %20 هو %2520 — إذ تُهرَّب علامة النسبة نفسها. ويظهر ذلك عادةً في صورة تسلسلات %20 حرفية بادية في الصفحة. فُكّ الترميز مرة واحدة وانظر: إن بقيت تسلسلات تهريب فقد رُمِّز مرتين.
هل يجعل الترميزُ القيمةَ آمنة؟
لا. الترميز النسبي شأنه النقل لا الأمان. فالقيمة المرمَّزة تبقى ما كانت عليه، وتحتاج التحقّق نفسه والتهريب الملائم للسياق نفسه قبل أن تبلغ HTML أو SQL أو صدفة.
لماذا لا يُرسَل الجزء المرجعي إلى الخادم؟
هكذا صُمّم: فكل ما بعد # يعالجه المتصفح وحده ولا يظهر في الطلب أبدًا. ولهذا لا يُرى في سجلات الخادم، ولهذا استُعمل تاريخيًا في التوجيه من جهة العميل.
هل يُرسَل ما ألصقه إلى أي جهة؟
لا. الترميز وفكّه وتفكيك العنوان تستعمل كلها الدوال المدمجة في المتصفح نفسه وتجري كلها على جهازك. ولا يغادره شيء مما تلصقه.
لماذا يعرض التفكيك عنوانًا مختلفًا عمّا ألصقته؟
لأن المعروض هو ما يُرسَل. فالعنوان يُطبَّع قبل الاستعمال: المخطّط والمضيف يصيران بأحرف صغيرة، ويُحذف منفذ افتراضي، وتصير الشرطات الخلفية مائلة، ويصير المسار الفارغ شرطة مائلة، ويصير المضيف خارج ASCII بترميز punycode. فإن كنت تقارن عناوين أو تطابقها مع قائمة سماح، فقارن هذه الصور المطبَّعة لا النص الخام.
لماذا لا يظهر تفكيك لـ example.com/path؟
لأنه ليس عنوانًا مطلقًا — فلا مخطّط له، ومن ثم لا مضيف يمكن تحديده. والأداة لا تخمّن واحدًا نيابةً عنك: فـ‎example.com:8080‎ عنوان مطلق صالح أصلًا مخططه example.com ومساره 8080، ولذا فإن إضافة ‎https://‎ في صمت قد تنتج تفكيكًا يبدو معقولًا وهو خاطئ. أضف المخطّط بنفسك فتظهر الأجزاء.