ما هو ترميز النسبة المئوية (Percent Encoding)؟ شرح مبسط لمعيار RFC 3986
ترميز النسبة المئوية (Percent encoding) هو آلية مُعرّفة في معيار RFC 3986 لترميز المحارف الخاصة داخل معرّفات URI عبر استبدالها بعلامة نسبة مئوية (%) متبوعة برقمين ست عشريين. يضمن هذا الترميز أن تحتوي عناوين URL على محارف ASCII صالحة فقط يمكن نقلها بأمان عبر الإنترنت.
ما هو ترميز النسبة المئوية؟
ترميز النسبة المئوية، ويُسمى أيضًا ترميز URL، هو الطريقة القياسية لتمثيل المحارف داخل معرّف URI (المعرّف الموحّد للموارد) التي تكون غير مسموح بها أو تحمل معنى خاصًا. ويعمل عبر استبدال كل محرف يحتاج إلى ترميز بعلامة نسبة مئوية (%) متبوعة برقمين ست عشريين يمثّلان قيمة البايت للمحرف.
على سبيل المثال، يُرمّز محرف المسافة (قيمة البايت 0x20) على النحو %20. أما علامة @ (قيمة البايت 0x40) فتُرمّز على النحو %40. تضمن هذه الآلية إمكانية تضمين أي بيانات بأمان داخل معرّف URI دون أن تتعارض مع صياغة الـ URI.
يأتي مصطلح "ترميز النسبة المئوية" من استخدام محرف النسبة المئوية كبادئة هروب (escape prefix). أما المواصفة الرسمية فهي مُعرّفة في معيار RFC 3986، الذي نشرته فرقة عمل هندسة الإنترنت (IETF) في يناير 2005، وقام بتأليفه تيم بيرنرز-لي وروي فيلدنغ ولاري ماسينتر.
كيف يعمل ترميز النسبة المئوية
تتبع عملية الترميز الخطوات التالية: أولًا، يُحوَّل المحرف إلى تمثيله بالبايتات باستخدام ترميز محارف معيّن (وهو دائمًا تقريبًا UTF-8 في الويب الحديث). ثم يُمثَّل كل بايت بعلامة نسبة مئوية متبوعة برقمين ست عشريين (باستخدام الأحرف الكبيرة A-F عرفًا، وإن كان على المُفكِّكات قبول الأحرف الصغيرة).
// الترميز خطوة بخطوة لمحرف مسافة
// المحرف: ' ' (مسافة)
// قيمة البايت في ASCII/UTF-8: 0x20 (32 بالنظام العشري)
// بترميز النسبة المئوية: %20
// الترميز خطوة بخطوة لمحرف متعدد البايتات: e مع علامة حادّة
// المحرف: e مع علامة حادّة (U+00E9)
// بايتات UTF-8: 0xC3 0xA9
// بترميز النسبة المئوية: %C3%A9
// الترميز خطوة بخطوة لمحرف من 3 بايتات
// المحرف: محرف تصويري صيني-ياباني-كوري (U+4E2D)
// بايتات UTF-8: 0xE4 0xB8 0xAD
// بترميز النسبة المئوية: %E4%B8%AD
يجب أن تأتي الأرقام الست عشرية دائمًا في أزواج. فعلامة نسبة مئوية منفردة أو علامة نسبة مئوية متبوعة بمحارف غير ست عشرية تُعدّ غير صالحة وستؤدي إلى خطأ في فك الترميز. وهذا مصدر شائع لخطأ "URI malformed" في جافاسكربت.
ترميز النسبة المئوية قابل للعكس دائمًا. يقرأ المُفكِّك علامة النسبة المئوية، ويأخذ المحرفين التاليين كقيمة بايت ست عشرية، ثم يحوّلها مرة أخرى إلى المحرف الأصلي. أما بالنسبة لمحارف UTF-8 متعددة البايتات، فتُجمَّع البايتات المُرمَّزة المتتالية وتُفكَّك معًا.
أي المحارف يجب ترميزها؟
ليست كل المحارف بحاجة إلى ترميز نسبة مئوية. يُعرّف معيار RFC 3986 فئتين: المحارف غير المحجوزة (unreserved) التي لا تحتاج إلى ترميز أبدًا، والمحارف المحجوزة (reserved) التي يجب ترميزها عندما لا تُستخدم للغرض المحجوز لها. أما جميع المحارف الأخرى (بما فيها المسافات والمحارف غير ASCII ومحارف التحكم) فيجب ترميزها دائمًا.
المحارف التي لا تحتاج إلى ترميز (غير المحجوزة): الأحرف الكبيرة (A-Z)، والأحرف الصغيرة (a-z)، والأرقام (0-9)، والشرطة (-)، والنقطة (.)، والشرطة السفلية (_)، والمدّة (~). يمكن لهذه المحارف الـ 66 أن تظهر في أي مكان داخل الـ URI دون ترميز.
المحارف التي تحتاج أحيانًا إلى ترميز (المحجوزة): : / ? # [ ] @ ! $ & ' ( ) * + , ; =. تحمل هذه المحارف معنى صياغيًا خاصًا داخل معرّفات URI. وهي لا تحتاج إلى ترميز إلا عند استخدامها كبيانات بدلًا من استخدامها كفواصل.
المحارف التي تحتاج دائمًا إلى ترميز: المسافات، والمحارف غير ASCII (أي محرف فوق 127)، ومحارف التحكم (0-31 و127)، والمحارف غير الآمنة مثل < > { } | \ ^ ` ".
المحارف المحجوزة مقابل غير المحجوزة في RFC 3986
يُعدّ التمييز بين المحارف المحجوزة وغير المحجوزة أمرًا جوهريًا لفهم كيفية عمل معرّفات URI. فالمحارف المحجوزة تعمل كفواصل تُحدّد بنية الـ URI. على سبيل المثال، يفصل :// المخطط (scheme) عن السلطة (authority)، ويفصل / مقاطع المسار، ويبدأ ? سلسلة الاستعلام، ويبدأ # الجزء (fragment).
| الفئة | المحارف | متى تُرمَّز |
|---|---|---|
| غير المحجوزة | A-Z a-z 0-9 - . _ ~ |
أبدًا |
| الفواصل العامة | : / ? # [ ] @ |
عند استخدامها كبيانات لا كفواصل |
| الفواصل الفرعية | ! $ & ' ( ) * + , ; = |
عند استخدامها كبيانات في مكوّنات تُسند إليها معنى |
| جميع المحارف الأخرى | المسافات، غير ASCII، محارف التحكم، إلخ | دائمًا |
من المبادئ الجوهرية في معيار RFC 3986 أن معرّفات URI التي تختلف فقط في كون محرف محجوز مُرمّزًا بالنسبة المئوية أم ظاهرًا حرفيًا ليست متكافئة. فعلى سبيل المثال، /path/to و/path%2Fto هما معرّفا URI مختلفان، رغم أن %2F يُفكّ إلى /. فالأول يحتوي على مقطعي مسار؛ أما الثاني فيحتوي على مقطع مسار واحد يتضمّن شرطة مائلة حرفية.
ترميز النسبة المئوية مقابل ترميز URL: هل هما الشيء نفسه؟
كثيرًا ما يُستخدم "ترميز النسبة المئوية" و"ترميز URL" بالتبادل، وهما في معظم السياقات يعنيان الشيء نفسه. غير أن هناك تمييزًا تاريخيًا دقيقًا. فمصطلح "ترميز URL" قد يشير أحيانًا إلى الصيغة الأقدم application/x-www-form-urlencoded المستخدمة في نماذج HTML، والتي تُرمّز المسافات على النحو + بدلًا من %20.
عُرِّفت صيغة application/x-www-form-urlencoded في مواصفة HTML، وهي أسبق من معيار RFC 3986. ولها قواعد مختلفة قليلًا: فالمسافات تصبح +، ومجموعة المحارف التي لا تُرمَّز مختلفة قليلًا. أما الاستخدام الحديث لمصطلح "ترميز URL" فيشير دائمًا تقريبًا إلى ترميز النسبة المئوية وفق RFC 3986، حيث تصبح المسافات %20.
عمليًا، ينبغي أن تستخدم ترميز النسبة المئوية وفق RFC 3986 في جميع سياقات الـ URI (مقاطع المسار، ومعاملات الاستعلام في واجهات REST البرمجية، ومعرّفات الأجزاء). واستخدم صيغة application/x-www-form-urlencoded فقط عند التعامل مع إرسال نماذج HTML أو عندما تتطلبها واجهة برمجية معيّنة.
// ترميز النسبة المئوية وفق RFC 3986 (مُوصى به لمعرّفات URI)
encodeURIComponent('hello world') // "hello%20world"
// application/x-www-form-urlencoded (نماذج HTML)
new URLSearchParams({q: 'hello world'}).toString() // "q=hello+world"
// المكافئ في بايثون
from urllib.parse import quote, quote_plus
quote('hello world', safe='') # "hello%20world"
quote_plus('hello world') # "hello+world"