الخلاصة

  • يضع الاختبار عتبة مقدارها بايت واحد، فيجبر العنصر غير الفارغ على التخزين في ملف مؤقت، ثم يقارن خرج FileUpload.getBytes() بالمدخل مقارنة تامة.
  • الاسم organizations.xlsx يحجب نصاً قصيراً، والاسم carta-ce.pdf يحجب ترويسة وتعليقاً؛ لا يفتح الاختبار حزمة OOXML ولا يحلل وثيقة PDF كاملة.
  • الأفضل إبقاء اختبار النقل السريع، ثم ربطه بشهادات مستقلة للبنية، وقواعد الانتخابات، والصلاحيات، وحدود الحجم والموارد.

ما الذي تقيسه التجربة فعلاً؟

في 11 سبتمبر 2026 ظهر في مستودع نظام انتخابات LACNIC العام اختبار اسمه wicketFileUploadGetBytesWorksForDiskBackedExcelAndPdf. يوحي الاسم بأن Excel وPDF اجتازا مسار رفع مخزناً على القرص. أما جسم الاختبار فيجيب عن سؤال أضيق: هل تعيد طبقة FileUpload في Wicket البايتات ذاتها عندما يحتفظ Commons FileUpload بالمحتوى في ملف مؤقت بدلاً من الذاكرة؟

يبني الاختبار DiskFileItemFactory ويحدد له مجلد JUnit المؤقت وحجماً للمخزن يساوي 1. لذلك ينتقل كل محتوى غير فارغ إلى مسار القرص. تكتب دالة مساعدة المصفوفة في العنصر، وتغلفه بكائن Wicket، وتستدعي getBytes()، ثم تقارن النتيجة بالأصل. وفي كتلة finally تحذف العنصر المؤقت. هذا ترتيب مقصود لاختبار حد فاصل قد يتأثر عند ترقية مكتبة الرفع أو إطار الويب.

توضح وثائق Commons FileUpload أن العناصر الصغيرة قد تبقى في الذاكرة، بينما تنتقل العناصر التي تبلغ العتبة إلى ملف مؤقت. من دون إجبار هذا الفرع، يمكن لاختبار صغير أن يظل أخضر رغم تعطل القراءة من القرص. لذلك تمنح العتبة ذات البايت الواحد الاختبار قيمة تشخيصية حقيقية.

لكن المادتين لا تمثلان الصيغتين المذكورتين. المصفوفة المسماة spreadsheet ليست سوى ORGID ثم سطر جديد ثم ALFA-001. ملف .xlsx الحقيقي حزمة OOXML مضغوطة تحتوي أجزاء وعلاقات وملفات XML. والمصفوفة المسماة pdf تحتوي %PDF-1.1 ثم %carta وتنتهي. لديها بداية مألوفة، لكن ليس لديها بناء PDF كامل. لا يستدعي الاختبار XSSFWorkbook ولا محلل PDF.

إذن النتيجة الدقيقة قوية ومحدودة: البايتات عبرت التخزين المؤقت وغلاف Wicket بلا تغيير. لا تقول النتيجة إن الملف يفتح، أو إن الأعمدة صحيحة، أو إن المرسل مخول، أو إن طلباً أحدث الأثر المقصود. قد تصل الحمولة سليمة ثم تُرفض بصورة صحيحة في الطبقة التالية.

الضوابط الأخرى موجودة في مسارات أخرى

لا يصح استنتاج أن المستودع يثق بأسماء الملفات دائماً. في فئة الاختبار نفسها، يمرر اختبار آخر نصاً باسم photo.jpg إلى مدقق صورة المرشح ويتوقع رفضه بسبب الصيغة. وينشئ اختبار ثالث صورة PNG حقيقية، ويحولها إلى JPEG، ثم يعيد فكها ليتأكد من أن العرض والارتفاع لا يتجاوزان 400 بكسل. هنا يجب أن يتصرف المحتوى كصورة، لا أن يحمل لاحقة فقط.

مسار جداول المؤسسات يتقدم أيضاً إلى ما وراء الاسم. يسمح المدقق الأولي بالاستمرار إذا انتهى الاسم بـ.xlsx أو إذا كان نوع MIME المعلن هو نوع جدول OOXML. كلا المؤشرين يقدمهما العميل، ولذلك فهما بوابة أولية. ملفات المديونين والحذف تذهب إلى تحقق بعيد، بينما تمر الإضافة والتحديث بتحقق تفصيلي عند إرسال النموذج.

تكتب ExcelUtils البايتات في ملف مؤقت، وتشترط MIME الخاص بـOOXML، وتفتح الملف باستخدام XSSFWorkbook من Apache POI. بعد ذلك تختار الورقة الأولى، وتبحث عن العناوين المطلوبة، وتجتاز الصفوف. تحتاج قائمة المديونين إلى ORGID؛ ويحتاج الاستيراد الشامل إلى مجموعة أوسع من الأعمدة. المعرفات الفارغة أو المكررة، والعناوين الغائبة، والقيم غير المقبولة تنتج أخطاء مرتبطة بالمهمة. وقبل وضع التغيير في قائمة العمل، يختبر اللوح ملاءمة العملية للانتخابات ويرفض طلباً ثانياً إذا كان عمل مماثل جارياً.

لهذا يستطيع النص ORGID\nALFA-001 النجاح في اختبار البايتات والفشل عند إنشاء XSSFWorkbook. لا يوجد تناقض. الأول يحرس تكامل Wicket مع Commons، والثاني يحرس بنية المصنف ومعناه.

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

أما رسائل النتائج فلها مسار مختلف. نموذجها متعدد الأجزاء ومحدود بـ10 ميغابايت. عند الحفظ تعيد الصفحة تحميل الانتخابات، وتطبق التحكم في الوصول، وتتوقف إذا كانت الانتخابات مغلقة. تفحص الملفات الاختيارية بالإسبانية والإنجليزية والبرتغالية قبل حفظ البايتات المختارة مع سياق المسؤول وعنوان العميل.

غير أن ElectionResultLetterSupport.isPdf يقارن البايتات الأربعة الأولى فقط بـ%PDF. ولذلك ينجح المثال القصير. هذه إشارة أولية وليست برهاناً على وثيقة قابلة للعرض أو سليمة البنية. حد الحجم والصلاحية وحالة الانتخابات ضوابط حقيقية، لكنها لا تتحول إلى محلل وثائق.

خمس شهادات بدلاً من علامة واحدة

يمكن تنظيم ضمان الرفع في خمس شهادات يعرف كل منها السؤال الذي يجيب عنه.

شهادة النقل تختبر مساري الذاكرة والقرص، وتؤكد صراحة أن isInMemory() == false في الحالة المقصودة، وتقارن البايتات، وتغلق التدفقات، وتتحقق من التنظيف. الاختبار الجديد نواة جيدة. اسم يذكر حفظ البايتات عبر FileUpload.getBytes() سيمنع اللاحقة من استعارة سلطة المحلل.

شهادة الصيغة تستخدم وثائق حقيقية وحالات مضادة. يحتاج OOXML إلى مصنف صغير منشأ بـPOI أو أداة مكافئة، يحوي الورقة والعناوين المطلوبة، وإلى ZIP ليس OOXML، وحزمة مبتورة، وحالة مشفرة وفق السياسة. ويحتاج PDF إلى وثيقة دنيا كاملة يقبلها المحلل المختار، وإلى ترويسة وحدها، وملف مبتور، وعينات ملتبسة ذات صلة. اللاحقة وMIME والتوقيع قرائن متراكمة، لا بديلاً عن البنية.

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

شهادة الإجراء تختبر السلطة والأثر. يستطيع مسؤول مخول تعديل انتخابات مفتوحة؛ ولا يستطيع مستخدم غير مخول أو انتخابات مغلقة ذلك. ينشئ الملف الصالح مهمة واحدة فقط، ويحمل السجل سياق التدقيق المناسب، ويطابق الأثر الصفوف المقبولة. ويجب أن تواجه الطلبات المتزامنة حماية «المعالجة جارية». وفي الرسائل ينبغي أن يمنع فشل لغة واحدة تطبيق أي جزء من التغييرات الثلاثة.

شهادة الموارد تقيس حجم الطلب والذاكرة والمجلد المؤقت والحذف ووقت التحليل والتمدد بعد فك الضغط. توثق Commons FileUpload العتبة وحدود الطلب. وتحذر Apache POI من أن تحليل وثائق غير موثوقة يحتاج طبقات إضافية لأن المكتبة لا تستطيع منع كل أثر ضار. يقدم ZipSecureFile حدوداً لنسبة الضغط وحجم المدخل بعد الفك. وتوصي OWASP بدمج الصلاحية والامتداد وMIME والتوقيع وحد الحجم، بما في ذلك ما بعد فك الضغط.

هذه الإرشادات العامة ليست دليلاً على ثغرة في LACNIC. ما نراه لقطة من مستودع عام، لا بيئة تشغيل. يعلن POM المثبت Wicket 10.9.0، ويشير مكون WildFly إلى POI OOXML 5.0.0. هذا يصف الشفرة التي جرت قراءتها فقط. كما لا تثبت مجاورة ترقية واختبار في الزمن أن الترقية سببت عطلاً أو أن عطلاً وقع أصلاً.

قيمة الادعاء الضيق

الاختبار الصغير مفيد لأنه سريع ومعزول. إذا تعطلت قراءة الملف المؤقت بعد تحديث مكتبة، يشير الفشل إلى حد النقل قبل دخول محلل الجدول وقواعد الانتخابات. وإذا نجح، نعرف أن البايتات اجتازت ذلك الحد، ولا نحتاج إلى تحميل النتيجة معنى أكبر.

المشكلة تظهر عندما يتحول اسم الاختبار إلى ذاكرة مؤسسية. في مراجعة إصدار أو تدقيق، تُقرأ الأسماء أكثر مما تُقرأ الأجسام. قد تصبح عبارة «Excel وPDF يعملان من القرص» ضماناً متداولاً رغم أن التجربة قارنت مصفوفتين. تضييق الاسم وربطه ببقية الشهادات الآن أقل كلفة من تصحيح هذا الفهم بعد حادثة.

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

ومن المفيد حفظ سجل مستقل للحالات السلبية. يوضح السجل لماذا يُرفض كل نموذج: هل هو ZIP عادي لا يمثل OOXML، أم حزمة مبتورة، أم مصنف يفتقد الورقة المطلوبة، أم صف يكرر ORGID، أم محتوى يبدأ بـ%PDF ولا يكتمل. عندها يستطيع المراجع معرفة الفروع التي نُفذت، ولا يضطر إلى التعامل مع ملف ثنائي غامض باعتباره ممثلاً لكل الأخطاء. كما يسمح هذا السجل بربط كل قاعدة بالأثر الذي تحميه وبالرسالة التي يراها المسؤول، من دون افتراض إعداد غير ظاهر في بيئة التشغيل.

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

المصادر