تعذر استئناف تنزيل النسخة الاحتياطية بعد الانقطاع لأن رمز البريد الإلكتروني تم استهلاكه بالفعل

تذكرت مشكلة مشابهة يبدو أنها تم إصلاحها في إصدار 2026.01.0-latest، لذا أعتقد أن هذه مشكلة حالية منفصلة تتعلق بالاسترداد من تنزيل نسخة احتياطية انقطع، وليس فشل التنزيل الأولي القديم.

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

لقد تمكنت الآن من إعادة إنتاج هذا السلوك بالضبط على إصدار Discourse الحالي.

إعادة الإنتاج

كنت أقوم بتنزيل نسخة احتياطية محلية لـ Discourse بحجم:

4,231,639,143 بايت

باستخدام Safari على iOS.

كان التنزيل يتم بشكل طبيعي. لدي تسجيل شاشة يُظهر زيادته من حوالي:

https://youtube.com/shorts/_Pjc-kxJ1OE?feature=shared

313.3 ميغابايت إلى 317.8 ميغابايت

ثم انتقلت من Safari لمدة حوالي 15 ثانية فقط.

عندما عدت إلى Safari، كان التنزيل قد توقف عند حوالي:

319.8 ميغابايت

وعرض Safari زر المحاولة مرة أخرى.


يُظهر سجل وصول Caddy للطلب الأصلي:

GET /admin/backups/...

HTTP/3

Range: none

status: 200

Content-Length: 4231639143

size: 319864024

ثم استخدمت زر المحاولة الخاص بـ Safari نفسه.

أصدر Safari طلب نطاق بايت HTTP حقيقي:

GET /admin/backups/...

HTTP/3

Range: bytes=319799904-

status: 422

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

رفض Discourse طلب النطاق (Range) ذلك برمز 422.

كود Discourse الحالي

بالنظر إلى Admin::BackupsController#show الحالي، تكون التسلسل الفعلي:

EmailBackupToken.compare(current_user.id, token)

        ↓

find backup

        ↓

EmailBackupToken.del(current_user.id)

        ↓

send_file

لذا، فإن الطلب المصادق عليه الأولي يستهلك رمز البريد الإلكتروني قبل أن يكتمل نقل البيانات الضخم (عدة جيجابايت) فعلياً.

إذا انقطع ذلك النقل لاحقاً، يقوم Safari بإصدار طلب آخر مثل:

Range: bytes=319799904-

لكن هذا الطلب يمر عبر /admin/backups/… مرة أخرى، وقد تم حذف رمز البريد الإلكتروني بالفعل.

يبدو أن هذا يفسر رمز الخطأ 422 الذي تم التقاطه.

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

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

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

لماذا أصبح من الأسهل إعادة إنتاج هذا على iOS

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

هناك على الأقل ثلاثة تقارير مستضافة على مجتمع Apple:

تقارير أقدم

«فشل التنزيلات في الخلفية في Safari بعد تحديث IOS 26» — أكتوبر 2025

Safari Background Downloads Failing After… - Apple Community

يُبلغ المستخدم عن فشل تنزيلات Safari الكبيرة عند تشغيلها في الخلفية بعد الترقية إلى iOS 26. كما قام بترقية iPhone 15 Pro إلى iOS 26 وأبلغ عن نفس السلوك.

«مشكلة تنزيل الخلفية في Safari iOS - الإلغاء عند التصغير» — ديسمبر 2025

Safari iOS Background Download Issue - Ca… - Apple Community

تم الإبلاغ عنها على iPhone 15 يعمل بنظام iOS 26.1. تبدأ التنزيلات بشكل طبيعي لكنها تُلغى بعد التحويل إلى تطبيق آخر أو تصغير Safari.

«لا يمكن لـ Safari إكمال تنزيل كبير» — أبريل 2026

تم الإبلاغ عنها على iPhone 17 Pro Max يعمل بنظام iOS 26.4. يُزعم أن التنزيلات الكبيرة تتوقف مؤقتاً بعد قفل الجهاز بفترة وجيزة، أو عند التبديل بين الشبكة الخلوية و Wi-Fi. يذكر المؤلف تحديداً وجود إشارة جيدة ويقول إن نفس التنزيلات تكتمل على أجهزة الكمبيوتر، Mac، وأندرويد.

لا أعتقد أن تلك التقارير المجتمعية كافية للقول إن Apple أكدت وجود تراجع (Regression) في iOS.

ومع ذلك، فهي متسقة مع تسجيل الشاشة الخاص بي: كانت النسخة الاحتياطية قيد التنزيل بشكل طبيعي، انتقلت بعيداً عن Safari لفترة وجيزة، وعندما عدت أصبح النقل منقطعاً.

النقطة المهمة من جانب Discourse مستقلة عن سبب حدوث الانقطاع:

انقطاع نقل النسخة الاحتياطية الكبيرة

        ↓

يحاول المتصفح استئناف النطاق (Range) المعتمد على المعايير

        ↓

تم استهلاك رمز النسخة الاحتياطية بالفعل

        ↓

يتلقى الاستئناف رمز 422

ملاحظات من جانب الخادم

لا يبدو أن هذا مشكلة عامة في مسار nginx/sendfile الحالي.

تم إكمال نفس مسار النسخة الاحتياطية الضخم (عدة جيجابايت) بنجاح:

  • من Firefox/Linux عبر HTTP/3؛ و
  • من نفس iPhone عبر Wi-Fi باستخدام HTTP/3.

يعلن nginx الحالي أيضاً عن دعم نطاقات البايت (byte-range).

لذا، لا أقترح أن HTTP/3 أو Caddy أو nginx غير قادرة جوهرياً على تسليم النسخة الاحتياطية.

في إعادة الإنتاج هذه على وجه الخصوص، انقطع النقل الأصلي بعد حوالي 320 ميغابايت، ثم أصدر Safari طلب نطاق (Range) يبدو صالحاً، وتم رفض ذلك الطلب الثاني بواسطة Discourse.

هل من المعقول أن يكون لتنزيل نسخة احتياطية مصرح بها مسبقاً آلية قصيرة العمر تسمح لنفس المسؤول المصادق عليه باستئناف نفس ذلك الملف بعد انقطاع النقل؟

I had a look through the history and current test coverage to get a better idea of what a possible fix would need to preserve.

The one-use behaviour dates back to the March 2017 security-hardening change:

That commit introduced emailed backup-download tokens and also consumed the token immediately once the download request had been authorized.

The same ordering still exists in current Admin::BackupsController#show:

The underlying token implementation is also still very simple:

EmailBackupToken stores one Redis token per user with a one-day expiry. It is user-scoped rather than being tied to a particular backup.

Looking through that file’s history, the changes since 2017 appear to have been mechanical/style changes rather than changes to the authorization model.

That makes simply leaving the existing emailed token reusable seem undesirable: it would weaken the original one-use restriction, and the token itself is not scoped to the particular backup in the URL.

The current request tests are here:

They cover an initial valid download, invalid tokens, invalid/missing backup IDs and permissions, but I couldn’t find coverage for an interrupted transfer followed by a Range: request.

The API request spec likewise documents the ordinary filename + token download path:

A possible patch direction therefore seems to be to preserve the existing one-use email token for initiating the backup download, while providing a separate short-lived resume authorization for the already-authorized local backup.

I would expect such a resume authorization to be narrowly scoped to the authenticated user and specific backup, and to permit a subsequent byte-range request without making the original one-day user-wide email token generally reusable.

The behaviours I’d aim to preserve/add are:

  • initial GET with a valid emailed token remains allowed;

  • another ordinary full GET using the consumed email token remains rejected;

  • a byte-range retry for the same backup by the same authenticated admin can be resumed for a short bounded period;

  • a resume attempt against another backup remains rejected;

  • an expired resume authorization remains rejected.

I haven’t assumed that this is necessarily the implementation maintainers will prefer, but the code/test surface appears fairly contained.

I’m happy to put together a draft patch and regression tests for the Safari Range: reproduction if this general security model sounds reasonable.