مقدمه

یک همکار دو نسخهٔ اصلاحی از یک قرارداد می‌فرستد و از شما می‌خواهد تفاوت آن‌ها را بررسی کنید. هر دو را در سرویس مقایسهٔ خود می‌اندازید، نتیجه برمی‌گردد و همه چیز به نظر عادی می‌آید. چیزی که شما ندیده‌اید این است که یکی از این اسناد یک تصویر پیوندی دارد که به یک URL اشاره می‌کند و سرور شما به همان لحظه‌ای که فایل باز می‌شود به آن میزبان تماس می‌گیرد. هیچ‌چیزی در خروجی به شما نمی‌گوید این اتفاق افتاده است.

این یک نقص نیست – این همان چیزی است که «بارگذاری یک سند به‌صورت دقیق» به معنای آن است. یک فایل OOXML می‌تواند تصویری را که روی یک سرور وب قرار دارد به‌جای داخل بسته ارجاع دهد و هم Word و هم هر کتابخانه‌ای که سند را به‌درستی بارگذاری می‌کند، آن ارجاع را حل می‌کند. GroupDocs.Comparison برای .NET دو ویژگی در LoadOptions ارائه می‌دهد که به شما اجازه می‌دهد تصمیم بگیرید آیا این کار انجام شود یا نه: SkipExternalResources و WhitelistedResources.

این دو ویژگی سه پیکربندی مختلف را فراهم می‌کنند و این مقاله هر سه را مقایسه می‌کند – پیش‌فرض بازده، مسدود کردن همهٔ موارد، و مسدود کردن همه به‌جز ارجاعات نام‌گذاری‌شده. در پایان می‌دانید برای منبع سند خاص کدام گزینه را انتخاب کنید و دو اشتباهی که باعث می‌شوند این تنظیمات گویی کار نمی‌کنند را می‌شناسید.

💡 مثال کامل قابل اجرا: مثال کامل کارآمد برای مسدود کردن منابع خارجی هنگام بارگذاری سند در .NET – یک پروژهٔ کنسولی قابل اجرا که خود تصاویر ارجاع‌شده را سرو می‌کند و هر درخواست را لاگ می‌گیرد، تا بتوانید هر تنظیم را در عمل ببینید.

جایی که ارجاعات خارجی مخفی می‌شوند

قبل از انتخاب یک تنظیم، ارزش دارد بدانید دقیقاً چه چیزی را انتخاب می‌کنید. یک فایل .docx ارجاعات خارجی را در دو مکان متمایز نگه می‌دارد و به‌دلیل این‌که هیچ‌کدام در متن سند قابل مشاهده نیستند، به‌راحتی می‌توانند نادیده گرفته شوند.

اولین مورد یک رابطه (relationship) در word/_rels/document.xml.rels است که TargetMode="External" دارد و یک URL مطلق را شامل می‌شود. تصویر در بدنه به‌صورت یک drawing ظاهر می‌شود که با شناسهٔ رابطه به آن اشاره می‌کند، بنابراین URL خود هرگز در کنار محتوایی که تحت تأثیر آن است ظاهر نمی‌شود.

دومین مورد یک کد فیلد INCLUDEPICTURE در بدنهٔ سند است که URL خود را داخل یک دستور فیلد نگه می‌دارد. Word هنگام رندر صفحه آن را حل می‌کند؛ یک کتابخانهٔ مقایسهٔ اسناد نیز هنگام بارگذاری سند آن را حل می‌کند.

هر دو مکان به دو گزینهٔ بارگذاری که در ادامه بحث می‌کنیم احترام می‌گذارند، که مهم است چون یک سند می‌تواند از هر دو یا یکی از آن‌ها استفاده کند. ارجاعی که در فایل روابط مشاهده می‌کنید، اثباتی نیست که فیلد دیگری در کد فیلد وجود نداشته باشد.

روش ۱: پیش‌فرض – ارجاعات حل می‌شوند

SkipExternalResources به‌صورت پیش‌فرض false است، بنابراین سندی که بدون پیکربندی خاص بارگذاری می‌شود، ارجاعات ریموت خود را حل می‌کند:

LoadOptions loadOptions = new LoadOptions
{
    SkipExternalResources = false
};

using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
    comparer.Add(targetPath, loadOptions);
    comparer.Compare(outputPath);
}

این بالاترین دقت را فراهم می‌کند: اسناد مقایسه‌شده همهٔ چیزهایی را که ارجاع می‌دهند، دقیقاً همان‌طور که Word رندر می‌کند، شامل می‌شوند. برای اسنادی که برنامه یا قالب‌های خودتان تولید کرده‌اند، جایی که هر URL ارجاعی به زیرساختی که شما مدیریت می‌کنید اشاره دارد، این گزینه مناسب است – و یک تصویر پیوندی گمشده می‌تواند مقایسه را به‌طور فعال گمراه‌کننده کند.

هزینهٔ این کار این است که هر ارجاعی تماس می‌گیرد، هر کسی که آن را قرار داده است. همچنین هزینهٔ زمانی وجود دارد که ربطی به اعتماد ندارد: یک URL ارجاعی که دیگر حل نمی‌شود، باعث می‌شود بارگذاری تا پایان تلاش اتصال صبر کند، در هر مقایسهٔ تک.

روش ۲: مسدود کردن همهٔ منابع خارجی

یک ویژگی حل ارجاع ریموت را برای آن سند غیرفعال می‌کند:

LoadOptions loadOptions = new LoadOptions
{
    SkipExternalResources = true
};

using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
    comparer.Add(targetPath, loadOptions);
    comparer.Compare(outputPath);
}

هیچ درخواستی ارسال نمی‌شود. تصاویر ارجاع‌شده از نتیجه حذف می‌شوند و – این همان بخشی است که باید واضح باشد – چیز دیگری تغییر نمی‌کند. این تنظیم تعیین می‌کند چه چیزی بارگذاری می‌شود، نه چطور اختلاف‌ها پیدا می‌شوند، بنابراین تغییرات متنی و ساختاری بین دو سند دقیقاً همان‌طور که قبل تشخیص داده می‌شوند. تنها چیزی که از دست می‌دهید این است که امکان تشخیص تغییر درون یک تصویر ارجاع‌شده که هرگز بارگذاری نشده است، وجود ندارد.

این پیکربندی را می‌توانید به‌عنوان پایهٔ خود برای اسنادی که خودتان ایجاد نکرده‌اید در نظر بگیرید: بارگذاری‌های کاربر در یک برنامهٔ وب، فایل‌های دریافت‌شده از ایمیل، هر چیزی که در یک عامل ساخت (build agent) مقایسه می‌شود و درخواست خروجی به‌ندرت مورد نظر است. این یک حالت «همه یا هیچ» است – اگر تصویری پیوندی که واقعاً می‌خواستید داشته باشید، همراه با بقیه مسدود می‌شود و نتیجه صرفاً بدون آن تصویر می‌ماند بدون اینکه این موضوع اعلام شود.

روش ۳: مسدود کردن همه به‌جز ارجاعات نام‌گذاری‌شده

پیکربندی سوم همان است که با دقت مطالعهٔ دقیق پاداش می‌دهد. WhitelistedResources یک List<string> می‌گیرد و فقط زمانی مورد بررسی قرار می‌گیرد که SkipExternalResources برابر true باشد:

LoadOptions loadOptions = new LoadOptions
{
    SkipExternalResources = true,
    WhitelistedResources = new List<string> { "includepicture-field.png" }
};

using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
    comparer.Add(targetPath, loadOptions);
    comparer.Compare(outputPath);
}

ورودی‌ها قطعات URL هستند، نه نام فایل. هر کدام با URL ارجاع مقایسه می‌شوند و تطبیق در هر جایی از آن، منبع را اجازه می‌دهد. این همان چیزی است که فهرست سفید (whitelist) را قابل حمل می‌کند: "includepicture-field.png" تصویری را می‌پذیرد که چه طرح، چه میزبان و چه مسیری پیش از آن باشد، بنابراین همان فهرست در محیط توسعه و تولید بدون نیاز به بازنویسی کار می‌کند.

ویژگی مشابه می‌تواند به‌عکس عمل کند. یک قطعهٔ کوتاه یا عمومی – logo.png یا بدتر، .png – می‌تواند ارجاعاتی را که هرگز قصد اجازهٔ آن را نداشتید، بپذیرد. یک قطعهٔ کافی خاص انتخاب کنید تا تنها منبعی که منظور دارید را شناسایی کند.

در نمونهٔ ارجاع، این پیکربندی تصویر موجود در فهرست سفید را می‌گیرد و تصویر دوم ارجاع‌شده که هیچ ورودی‌ای برای آن وجود ندارد را مسدود می‌کند. لاگ درخواست‌ها نشان می‌دهد سه درخواست انجام شده در حالی که پیش‌فرض بازده پنج درخواست می‌ساخت و فقط فایل موجود در فهرست سفید را نام می‌برد.

کدام پیکربندی را باید استفاده کنید؟

تنظیم را با منبع سند مطابقت دهید. اسنادی که برنامه یا قالب‌های خودتان تولید کرده‌اند می‌توانند پیش‌فرض را حفظ کنند، زیرا هر URL ارجاعی به زیرساختی که قبلاً اداره می‌کنید اشاره دارد. هر چیزی که از بیرون می‌آید – بارگذاری‌های کاربر، پیوست‌های ایمیل، فایل‌های شخص ثالث – مستلزم SkipExternalResources = true است. فقط زمانی که یک ارجاع مورد اعتماد واقعاً نیاز به حل داشته باشد، یک قطعهٔ محدود WhitelistedResources اضافه کنید.

مقایسهٔ سه پیکربندی

نگرانی پیش‌فرض مسدود کردن همه مسدود + فهرست سفید
تعداد ویژگی‌های تنظیمی ۰ ۱ ۲
درخواست‌های خروجی تمام ارجاعات هیچ‌کدام فقط موارد فهرست سفید
کنترل به‌ازای هر ارجاع خیر خیر بله
هزینهٔ URLهای مرده بر زمان بارگذاری بله خیر فقط موارد فهرست سفید
مناسب برای اسنادی که خودتان تولید کرده‌اید اسنادی که از هر منبع دیگری می‌آیند قالب‌های مورد اعتماد در میان محتوای غیرمطمئن

تصمیم بر پایهٔ منبع سند اتخاذ می‌شود نه بر پایهٔ عملکرد. اسنادی که سیستم‌های شما تولید می‌کند می‌توانند پیش‌فرض را حفظ کنند. اسنادی که از بیرون می‌آیند باید مسدود شوند. فهرست سفید را فقط در نقطه‌ای به‌کار ببرید که یک ارجاع خاص واقعاً نیاز به حل داشته باشد – مثلاً یک قالب شرکتی که تصویر سرصفحه‌اش را از یک URL داخلی می‌گیرد، در میان گزارش‌هایی که نویسندگانشان تصاویر را از هر جایی می‌چسبانند.

دو اشتباه

هر دو این اشتباهات همان علامت را تولید می‌کنند: تنظیم را اعمال می‌کنید و به‌نظر می‌رسد کاری انجام نمی‌دهد.

فهرست سفید بدون سوئیچ. WhitelistedResources فقط زمانی مورد بررسی قرار می‌گیرد که SkipExternalResources برابر true باشد. اگر به‌تنهایی تنظیم شود، اصلاً کاری انجام نمی‌دهد – چون هیچ مسدودی وجود ندارد که استثنا بدهد. اگر فهرست سفید به‌نظر می‌رسد نادیده گرفته شده، ابتدا این مورد را بررسی کنید.

گزینه‌ها فقط روی منبع. این مورد کمی پیچیده‌تر است. گزینه‌های بارگذاری توصیف می‌کنند که یک سند چگونه بارگذاری شود. سازندهٔ Comparer گزینه‌ها را برای منبع می‌گیرد؛ هر فراخوانی Add() گزینه‌ها را برای هدف مربوطه می‌گیرد:

using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
    comparer.Add(targetPath, loadOptions);
    comparer.Compare(outputPath);
}

اگر آن‌ها را فقط به سازنده بدهید و فراخوانی Add() را فراموش کنید، منبع محافظت می‌شود در حالی که هر هدف هنوز ارجاعات خود را می‌گیرد. مقایسه موفق می‌شود، نتیجه معقول به‌نظر می‌رسد و نیمی از اسناد شما هنوز به شبکه متصل می‌شوند. وقتی منبع و هدف نیاز به رفتار متفاوت دارند، نمونه‌های جداگانهٔ LoadOptions را پاس کنید – این دقیقاً دلیل این است که API این گزینه‌ها را برای هر سند جداگانه می‌گیرد.

تأیید اینکه واقعاً کار کرده است

یک منبع مسدود شده تقریباً هیچ ردپایی باقی نمی‌گذارد. سند خروجی یک تصویر را از دست می‌دهد که شبیه سندی است که هرگز تصویر نداشته است. بنابراین خواندن فایل خروجی راه ضعیفی برای تأیید اعمال تنظیم است.

به جای آن سمت سرویس‌دهنده را نظارت کنید. نمونهٔ ارجاع به‌طور عمدی این رویکرد را اتخاذ می‌کند: یک Listener HTTP کوچک روی یک پورت لوپ‌بک آزاد راه‌اندازی می‌کند، اسناد نمایشی خود را به آن پورت اشاره می‌دهد و هر درخواست را لاگ می‌کند، تعداد درخواست‌ها را برای هر مقایسه چاپ می‌کند. پنج درخواست، سپس صفر، سپس سه. یک ردیابی شبکه نسبت به منابع واقعی سند شما همان اطمینان را می‌دهد.

نتیجه‌گیری

سه پیکربندی، یک قاعده تصمیم‌گیری: برای اسنادی که خودتان تولید کرده‌اید پیش‌فرض را بگذارید، برای همهٔ موارد دیگر SkipExternalResources = true تنظیم کنید و فقط در جایی که یک ارجاع خاص مورد اعتماد نیاز به حل دارد، یک قطعهٔ URL محدود را در فهرست سفید بگذارید.

سپس دو نکته‌ای که به‌صورت ساکن کار شما را خنثی می‌کنند – فهرست سفید بدون SkipExternalResources = true و گزینه‌هایی که فقط به سازندهٔ Comparer پاس داده شده‌اند اما به هر فراخوانی Add() داده نشده‌اند – بررسی کنید و تأیید را از سمت سرویس‌دهنده انجام دهید نه از فایل خروجی.

منابع تکمیلی