مقاله فنی

لاک‌گیری (Redaction) واقعی PDF در دلفی: حذف و پاک‌سازی

لاک‌گیری (redaction) واقعی PDF در دلفی به معنای حذف بایت‌های زیرین است، نه رنگ‌آمیزی روی آن‌ها. کتابخانه PDF losLab این خط‌مشی را با دو API ترسیم می‌کند: RedactRegion جریان محتوای صفحه را ویرایش می‌کند تا متن، بردارها و تصاویر حذف‌شده به صورت فیزیکی از بین بروند، و SanitizeDocument متادیتا، اسکریپت‌ها و اکشن‌هایی را که اطلاعات لاک‌گیری‌شده نیز در آن‌ها پنهان می‌شوند، پاک می‌کند. اگر در هر کدام از این دو بخش اشتباه کنید، فایلی را تحویل خواهید داد که در ظاهر سانسور شده به نظر می‌رسد اما راز را به هر کسی که متن را کپی کند یا Document Properties را باز کند، برمی‌گرداند

این حالت شکست (failure mode) یک مسئله آشکار است. روزنامه‌ها، دادگاه‌ها و سازمان‌های دولتی همگی فایل‌های PDF را با یک مستطیل سیاه روی نام یا عدد منتشر کرده‌اند، اما خواننده توانسته است متن زیر آن را انتخاب کرده، کپی کند و رشته‌ای را که قرار بود لاک‌گیری شده باشد در معرض دید قرار دهد. آن مستطیل فقط یک ترسیم بود. متن هنوز آنجا بود، در جریان محتوا یک لایه پایین‌تر از رنگ نشسته بود، کاملاً دست‌نخورده و کاملاً قابل انتخاب. کتابخانه PDF losLab، موتور PDF دلفی و سی‌پلاس‌پلاس‌بیلدر که از طریق کلاس TPDFlib ارائه می‌شود، این تمایز را به عنوان هدف اصلی لاک‌گیری در نظر می‌گیرد تا یک ایده ثانویه

چرا کشیدن یک کادر سیاه روی متن PDF لاک‌گیری واقعی نیست؟

کتابخانه PDF losLab متد MaskRect را برای مواردی ارائه می‌دهد که پوشاندن واقعاً همان چیزی است که می‌خواهید: یک واترمارک، یک خاموشی بصری، یا یک مهر تایید. متد MaskRect(Page, Left, Top, Width, Height, Red, Green, Blue) یک مستطیل به جریان محتوای صفحه اضافه می‌کند و آن را با اپراتور ساخت مسیر re و سپس اپراتور پر کردن f رنگ‌آمیزی می‌نماید (استاندارد ISO 32000-1 §8.5). هر بایتی که از قبل روی صفحه بود — گلیف‌های نامی که پوشانده‌اید، خطوط برداری، تصویر تعبیه‌شده — دقیقاً در همان جایی که بود، یک دستور قبل از آن در همان جریان باقی می‌ماند. یک نمایشگر استاندارد، کادر سیاه را در آخر رسم می‌کند، بنابراین از نظر بصری اولویت پیدا می‌کند. این پوشش هیچ چیز زیرین را تغییر نمی‌دهد

هر کسی می‌تواند محتوای اصلی را به سه روش بازیابی کند: متن را مستقیماً از طریق کادر غیرشفاف انتخاب و کپی کند، یک ابزار استخراج متن را روی صفحه اجرا کند، یا مستطیل پوشاننده را از جریان محتوا حذف نماید. MaskRect در نام خود با شما صادق است — این متد ماسک می‌کند، اما حذف نمی‌کند. نتیجه کار مانند برچسبی روی یک کلمه است که همچنان می‌توانید با برداشتن برچسب، آن را بخوانید

چگونه RedactRegion محتوا را در سطح دستورالعمل حذف می‌کند

متد RedactRegion(Page, Left, Top, Width, Height, Options) یک لایه پایین‌تر از رنگ‌آمیزی کار می‌کند. کتابخانه PDF losLab هر لایه محتوای صفحه را در یک TPDFContentProgram تجزیه می‌کند، لیست دستورات را با یک آنالیزور ردیابی CTM (متد FindInstructionsInRect در مدل محتوا) پیمایش می‌کند و هر اپراتور نمایش متن، رنگ‌آمیزی مسیر و تصویر Do را که ناحیه رنگ‌شده آن با مستطیل شما تلاقی دارد، علامت‌گذاری می‌نماید. این دستورالعمل‌ها با TPDFContentProgram.Delete حذف می‌شوند و لایه ویرایش‌شده با WritePageContentLayer دوباره نوشته می‌شود. بایت‌ها به پشت یک پوشش منتقل نمی‌شوند — آنها جریان را ترک می‌کنند. یک ابزار استخراج متن که پس از آن اجرا می‌شود چیزی پیدا نمی‌کند، زیرا چیزی برای پیدا کردن وجود ندارد

دو جزئیات پیاده‌سازی ارزش این را دارند که در منطق کاری خود به آنها توجه کنید. متد RedactRegion یک مستطیل بالا-چپ را در مبدأ و واحدهای فعلی دریافت کرده و آن را در داخل به فضای کاربر پایین-چپ که جریان محتوا استفاده می‌کند تبدیل می‌نماید، بنابراین شما ناحیه را همان‌طور که روی صفحه می‌بینید مشخص می‌کنید. وقتی یک مستطیل چندین دستور را پوشش می‌دهد، RedactRegion آنها را به ترتیب نزولی ایندکس حذف می‌کند، زیرا حذف دستور ۴ قبل از دستور ۷، تمام ایندکس‌های بعدی را تغییر داده و محدوده را خراب می‌کند. اگر Options = 0 را ارسال کنید، کتابخانه PDF losLab یک کادر سیاه توپر نیز روی ناحیه پاک‌شده به عنوان یک نشانگر بصری قرار می‌دهد — اما آن نشانگر فقط جنبه ظاهری دارد و پس از اینکه محتوای واقعی از بین رفت، اعمال می‌شود

در عمل شما به ندرت مختصات مستطیل را به صورت هاردکد می‌نویسید. شما صفحه را برای یافتن رشته حساس جستجو می‌کنید و کادر محدودکننده (bounding box) آن را می‌خوانید — همان جستجوی صفحه و شمارش عناصر که در بخش مکان‌یابی متن و عناصر صفحه در دلفی پوشش داده شده است — سپس آن مستطیل را به RedactRegion تحویل می‌دهید:

var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('contract.pdf', '') = 1 then
    begin
      // Delete everything painted inside a 220 x 14 pt box on page 1.
      // Coordinates are top-left, in the current origin and units.
      PDF.RedactRegion(1, 90, 705, 220, 14, 0);   // Options=0 stamps a black marker
      PDF.SaveToFile('contract-redacted.pdf');
    end;
  finally
    PDF.Free;
  end;
end;

اطلاعات لاک‌گیری‌شده هنوز در کجای یک فایل PDF پنهان می‌شوند؟

لاک‌گیری که فقط در لایه مرئی متوقف شود ردپایی به جا می‌گذارد، زیرا یک فایل PDF اطلاعات را در مکان‌هایی ذخیره می‌کند که خواننده هرگز به آن‌ها نگاه نمی‌کند. کتابخانه PDF losLab این نشت‌ها را به کانال‌های قابل توجهی تقسیم می‌کند. دیکشنری اطلاعات سند (Document Information Dictionary) و جریان متادیتای XMP به طور معمول حاوی رشته‌های نویسنده (Author)، تولیدکننده (Producer) یا کلمات کلیدی (Keywords) هستند که نام همان شخص یا موردی را دارند که شما از بدنه حذف کرده‌اید. جاوا اسکریپت در سطح سند و OpenAction کاتالوگ می‌توانند در هنگام باز شدن اجرا شوند و به داده‌هایی که فکر می‌کردید از بین رفته‌اند دسترسی پیدا کنند. هر یادداشت توضیحی (annotation) حاوی یک اکشن اختیاری /A است و خود یادداشت — یک یادداشت چسبان، یک فیلد فرم قدیمی، یا یک لینک — می‌تواند مستقیماً روی نقطه لاک‌گیری‌شده قرار گیرد و کپی خود را از متن داشته باشد. هیچ‌کدام از این موارد تحت تاثیر رمزگذاری قرار نمی‌گیرند؛ یک فایل رمزگذاری‌شده با یک بسته متنی ساده XMP همچنان عنوان خود را به هر ایندکس‌کننده‌ای درز می‌دهد، شکافی که به خودی خود ارزش حسابرسی رمزگذاری و مجوزها را دارد. پاک‌سازی قابل دفاع باید به هر یک از این پناهگاه‌ها سر بزند، نه فقط صفحه‌ای که به آن نگاه می‌کردید

تمیز کردن سند با SanitizeDocument

کتابخانه PDF losLab به این نشت‌های پراکنده با SanitizeDocument(Flags) پاسخ می‌دهد، یک فراخوانی هماهنگ‌کننده که مجموعه‌ای از حذف‌های مستقل انتخاب‌شده توسط یک ماسک بیتی (bitmask) را اجرا کرده و تعداد عملیات اعمال‌شده را برمی‌گرداند. این پرچم‌ها قابل ترکیب هستند. پرچم SANITIZE_JAVASCRIPT تمام بسته‌های جاوا اسکریپت در سطح سند و هر اسکریپت OpenAction را حذف می‌کند. پرچم SANITIZE_ACTIONS دیکشنری اکشن /A را از هر یادداشت توضیحی از طریق متد جدید TPDFAnnots.RemoveAnnotAction حذف کرده و OpenAction کاتالوگ را پاک می‌کند. پرچم SANITIZE_METADATA فیلدهای نویسنده، موضوع، کلمات کلیدی، خالق و تولیدکننده را در دیکشنری اطلاعات سند (استاندارد ISO 32000-1 §14.3.3) و جریان متادیتای XMP (§14.3.2) خالی می‌کند. پرچم SANITIZE_ANNOTATIONS تمام یادداشت‌های توضیحی را در کل سند پاک می‌کند و SANITIZE_OPEN_ACTION ورودی /OpenAction کاتالوگ را حذف می‌نماید. SANITIZE_ALL اجتماع بیتی (bitwise union) هر پنج مورد است

قراردادهای ایندکس‌گذاری در داخل این بخش‌ها دقیقاً از همان مواردی هستند که به طور بی‌صدا یک حلقه نوشته‌شده به صورت دستی را خراب می‌کنند، و این موضوع دلیل اصلی وجود SanitizeDocument به عنوان یک فراخوانی واحد است. ویژگی GlobalJavaScriptPackageName مبتنی بر ۰ است و RemoveGlobalJavaScript لیست را در حین حرکت کوچک می‌کند، بنابراین فرآیند حذف جاوا اسکریپت همیشه ایندکس بسته ۰ را می‌خواند و تا زمانی که تعداد به صفر برسد حذف می‌کند. در مقابل، DeleteAnnotation مبتنی بر ۱ است. AnnotationCount بر اساس صفحه انتخاب‌شده فعلی گزارش می‌دهد، بنابراین بخش‌های یادداشت توضیحی قبل از شمارش، هر صفحه را به نوبت انتخاب می‌کنند. کتابخانه PDF losLab این مرزها را در داخل به درستی رعایت می‌کند، بنابراین شما به جای بازتولید پنج قانون تکرار متفاوت، یک مجموعه پرچم را ارسال می‌کنید و یک تعداد برگشتی را می‌خوانید

var
  Applied: Integer;
begin
  // Strip metadata, scripts, and actions but keep the annotations:
  Applied := PDF.SanitizeDocument(SANITIZE_METADATA or SANITIZE_JAVASCRIPT or
    SANITIZE_ACTIONS or SANITIZE_OPEN_ACTION);

  // Or clear every hidden channel, annotations included:
  Applied := PDF.SanitizeDocument(SANITIZE_ALL);
end;

جریان کاری کامل لاک‌گیری و پاک‌سازی

این دو را کنار هم بگذارید تا روند کامل کار کوتاه شود. کتابخانه PDF losLab محتوای مرئی را با RedactRegion صفحه به صفحه حذف می‌کند، سپس هر کانال پنهانی را با SanitizeDocument(SANITIZE_ALL) قبل از نوشتن فایل پاک می‌سازد. از آنجا که لاک‌گیری تقریباً همیشه روی اسنادی انجام می‌شود که خارج از کنترل شما ارسال شده‌اند، ارزش دارد آنها را از طریق یک مسیر مقاوم‌شده بارگذاری کنید — همان وضعیت دفاعی توصیف‌شده در بخش تجزیه ایمن PDFهای نامعتبر — تا یک ورودی خراب به جای اینکه در اواسط مرحله لاک‌گیری متوقف شود، به طور تمیز شکست بخورد

var
  PDF: TPDFlib;
  Applied: Integer;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('incoming.pdf', '') = 1 then
    begin
      PDF.RedactRegion(1, 90, 705, 220, 14, 0);       // delete the sensitive line
      Applied := PDF.SanitizeDocument(SANITIZE_ALL);   // clear metadata, JS, actions, annots
      PDF.SaveToFile('published.pdf');
    end;
  finally
    PDF.Free;
  end;
end;

جایی که لاک‌گیری جریان محتوا متوقف می‌شود

متد RedactRegion روی جریان محتوا عمل می‌کند، که دقیقاً همان جایی است که متن‌های دیجیتالی اصلی در آن زندگی می‌کنند و دقیقاً جایی است که متن‌های اسکن‌شده در آن وجود ندارند. وقتی یک صفحه یک تصویر اسکن‌شده است، کلمات پیکسل‌های درون یک XObject تصویری هستند، نه عملگرهای متنی، و کتابخانه PDF losLab نمی‌تواند نامی را از وسط یک فایل شطرنجی جدا کند، همان‌طور که یک گلیف را حذف می‌کند. مدیریت تصویر آن عمداً محافظه‌کارانه است: هر تصویری که محل قرارگیری آن با مستطیل تلاقی داشته باشد به طور کامل حذف می‌شود، بنابراین یک کادر لاک‌گیری روی یک صفحه اسکن‌شده، به جای بخشی از آن، کل اسکن را با خود می‌برد. لاک‌گیری یک ناحیه در داخل یک تصویر به معنای شطرنجی‌سازی، رنگ‌آمیزی و تعبیه مجدد در سطح پیکسل است، که عملیاتی متفاوت از حذف جریان محتوا است. همین احتیاط برای لایه متنی OCR روی یک صفحه اسکن‌شده نیز اعمال می‌شود: RedactRegion عملگرهای متنی را که می‌تواند ببیند حذف می‌کند، اما تصویر زیرین دست‌نخورده باقی می‌ماند مگر اینکه XObject تصویری آن نیز با کادر تلاقی داشته باشد

دو نکته ظریف‌تر نتایج را دقیق نگه می‌دارد. تست تلاقی، عرض هر اجرای متنی را بر اساس تعداد کاراکتر و اندازه فونت آن تخمین می‌زند، به جای اینکه معیارهای دقیق گلیف را اندازه‌گیری کند، بنابراین یک کلمه بسیار کوتاه با فونت متناسب می‌تواند از یک مستطیل تنگ عبور کند — به کادر حاشیه کوچکی بدهید. حذف همچنین در سطح دستورالعمل است: RedactRegion کل عملگرهای نمایش متن را حذف می‌کند، بنابراین مستطیلی که فقط بخشی از یک متن را قطع می‌کند، می‌تواند کاراکترهای مجاورت را که همان عملگر را به اشتراک گذاشته‌اند با خود ببرد. هیچ‌کدام از این محدودیت‌ها دلیلی برای بی‌اعتمادی به این مکانیسم نیستند، بلکه فقط دلیلی برای محدود کردن دقیق مستطیل و بررسی نتیجه هستند

نظمی که هر جریان کار لاک‌گیری را به پایان می‌رساند، تایید صحت است، نه اعتماد. ناحیه را لاک‌گیری کنید، سند را پاک‌سازی نمایید، در یک فایل جدید ذخیره کنید، آن را دوباره باز کنید و سعی کنید متنی را که حذف کرده‌اید و متادیتاهایی را که پاک نموده‌اید بازیابی کنید؛ وقتی هر دو خالی برگردند، لاک‌گیری با موفقیت انجام شده است. APIهای RedactRegion و SanitizeDocument که در اینجا نشان داده شده‌اند همراه با losLab PDF Library برای دلفی و سی‌پلاس‌پلاس‌بیلدر، در کنار مرجع کامل مدل محتوا و پاک‌سازی ارائه می‌شوند