Articolo tecnico

Protezione del foglio XLSX in Delphi: 15 opzioni consentite

Consegni una cartella di lavoro finita a un collega e gli chiedi di filtrarla, non di riscriverla. Quindi proteggi il foglio. Nelle versioni più vecchie di HotXLS quel gesto scriveva una sola cosa nel file: <sheetProtection sheet="1" objects="1" scenarios="1"/>, codificata in modo fisso, ogni volta. Il foglio si bloccava, l'hash della password veniva associato e l'utente non poteva fare nulla, nemmeno ordinare e filtrare come volevi lasciare aperto. La finestra di dialogo di Excel “Proteggi foglio” ha quindici caselle di controllo proprio per questo motivo, e il motore non poteva esprimerne nessuna. È quel divario che il modello di protezione v2.91.0 colma

HotXLS è un componente VCL nativo per fogli di calcolo per Delphi e C++Builder che legge e scrive XLS e XLSX senza Excel installato. Questo articolo riguarda il lato XLSX della protezione dei fogli di lavoro: il nuovo enum TXLSXSheetProtectionOption, la proprietà AllowOption che attiva o disattiva ogni autorizzazione, e la regola di codifica OOXML che manda in crisi chiunque scriva un elemento <sheetProtection> scritto a mano

Cosa protegge davvero la protezione del foglio

Prima il confine, perché da quello dipende quanto puoi fidarti di tutto questo. La protezione del foglio nel formato spreadsheet OOXML (ECMA-376) è una policy di interazione, non una crittografia. Indica a un'applicazione conforme quali modifiche rifiutare mentre il foglio è protetto. I valori delle celle sono ancora lì in xl/worksheets/sheetN.xml in testo normale; decomprimi il .xlsx e li trovi subito lì. La password opzionale è memorizzata come un breve hash legacy, non come una chiave che cifra qualcosa. Chiunque rinomini il file, apra la parte e rimuova la riga <sheetProtection> legge e modifica tutto

Quindi la protezione risponde a “impedire al mio collega di rovinare per errore una formula”, non a “tenere questi dati segreti da qualcuno determinato”. Sono problemi diversi con strumenti diversi. Se ti serve riservatezza, ti serve la crittografia a livello di cartella di lavoro descritta in Output XLSX protetto con AES, che cifra davvero il pacchetto. La protezione del foglio e la crittografia della cartella di lavoro si combinano senza attriti, ma solo la seconda è un lucchetto. Tieni ben distinti questi due piani e il resto di questa pagina è solo impianto

Diagramma che contrappone la protezione del foglio XLSX Delphi in HotXLS come politica di interazione con la cifratura del workbook AES come unico vero lucchetto
La protezione del foglio rifiuta le modifiche mentre i valori restano testo in chiaro; solo la crittografia della cartella di lavoro cifra il pacchetto

Le quindici opzioni e la proprietà AllowOption

Ogni foglio di lavoro ora porta un insieme di TXLSXSheetProtectionOption valori che descrivono cosa l'utente può ancora fare mentre il foglio è protetto. I membri corrispondono uno a uno agli attributi OOXML e alle caselle di controllo della finestra di dialogo di Excel:

  • xlsxSpoEditObjects, xlsxSpoEditScenarios - modifica oggetti grafici e scenari what-if
  • xlsxSpoFormatCells, xlsxSpoFormatColumns, xlsxSpoFormatRows - riformatta celle, colonne e righe
  • xlsxSpoInsertColumns, xlsxSpoInsertRows, xlsxSpoInsertHyperlinks - inserisce colonne, righe e collegamenti
  • xlsxSpoDeleteColumns, xlsxSpoDeleteRows - elimina colonne e righe
  • xlsxSpoSelectLockedCells, xlsxSpoSelectUnlockedCells - sposta la selezione su celle bloccate o sbloccate
  • xlsxSpoSort, xlsxSpoAutoFilter, xlsxSpoPivotTables - ordina intervalli, usa i menu a discesa di AutoFilter, lavora con le tabelle pivot

Leggi e scrivi i singoli bit tramite la proprietà indicizzata AllowOption su TXLSXWorksheet.AllowOption[Opt] = True significa che l'azione è consentita; impostarlo a False la vieta. L'intero insieme è accessibile anche tutto in una volta tramite SheetProtectionOptions, una TXLSXSheetProtectionOptions (un semplice Pascal set of), così puoi salvarlo, ripristinarlo o sostituirlo in blocco

Il valore predefinito conta ed è intenzionale: un foglio di lavoro appena creato parte con ogni opzione consentita. Il costruttore inizializza SheetProtectionOptions con l'intera gamma, [Low(TXLSXSheetProtectionOption)..High(TXLSXSheetProtectionOption)]. Da lì restringi il set escludendo le azioni che vuoi vietare, invece di costruire da zero un insieme di autorizzazioni. È questa scelta che fa combaciare la regola di codifica del writer, più sotto, con il comportamento di Excel

Proteggere un foglio lasciando aperti ordinamento e filtro

Ecco il caso tipico, dall'inizio alla fine: proteggi un report finito in modo che il layout non possa essere rimaneggiato, ma lascia al lettore la possibilità di ordinarlo e filtrarlo. Nota che Protect e le opzioni sono indipendenti. Protect porta il foglio nello stato protetto e salva l'hash opzionale della password; non tocca l'insieme delle opzioni. Tu modifichi AllowOption separatamente, e le impostazioni hanno effetto una volta che il foglio è protetto e salvato

var
  wb: TXLSXWorkbook;
  sh: TXLSXWorksheet;
begin
  wb := TXLSXWorkbook.Create;
  try
    sh := wb.Sheets.Add('Protected');
    sh.Cells[1, 1].Value := 'Region'; sh.Cells[1, 2].Value := 'Units';
    sh.Cells[2, 1].Value := 'North';  sh.Cells[2, 2].Value := 120;
    sh.Cells[3, 1].Value := 'South';  sh.Cells[3, 2].Value := 98;

    // Proteggi con una password. Questo imposta solo lo stato protetto + hash;
    // l'insieme delle opzioni resta al suo default tutto-consentito.
    sh.Protect('HotXLS-2026');

    // Ristretto: mantieni sort + AutoFilter, vieta rimodellare e riformattare.
    sh.AllowOption[xlsxSpoSort]          := True;
    sh.AllowOption[xlsxSpoAutoFilter]    := True;
    sh.AllowOption[xlsxSpoFormatCells]   := False;
    sh.AllowOption[xlsxSpoFormatColumns] := False;
    sh.AllowOption[xlsxSpoFormatRows]    := False;
    sh.AllowOption[xlsxSpoInsertRows]    := False;
    sh.AllowOption[xlsxSpoDeleteRows]    := False;

    if wb.SaveAs('protection.xlsx') <> 1 then
      Writeln('SaveAs failed');
  finally
    wb.Free;
  end;
end;

Due cose da trarre da questo frammento. Le righe Sort e AutoFilter sono scritte esplicitamente anche se entrambe predefinite a True; è documentazione per il prossimo manutentore, non un requisito funzionale. E poiché i valori predefiniti sono permissivi, le uniche righe che modificano il file di output sono quelle che impostano un'opzione a False. Non è un accidente di questa API, è il formato OOXML a farsi vedere, ed è la sezione successiva

La regola di codifica: omettere vuol dire consentire, attr=0 vuol dire vietare

Questo è l'unico fatto controintuitivo dell'intera funzione, ed è qui che un <sheetProtection> scritto a mano di solito sbaglia. In OOXML, ogni attributo per azione è un divieto flag, e la sua assenza equivale a consentire. Un attributo assente significa che l'azione è consentita. Un attributo scritto come "0" significa che l'azione è vietata mentre il foglio è protetto. Non esiste un formatCells="1" in un file ben formato per significare “la formattazione è consentita”; semplicemente lasci l'attributo fuori. (Il valore predefinito di un attributo assente è il booleano OOXML true, e questi attributi sono nominati in modo che “true” significhi che la modifica corrispondente è consentita.)

Il writer di HotXLS rispecchia esattamente questo. Emmette sheet="1" per attivare la protezione, poi percorre l'insieme delle opzioni e scrive attr="0" solo per le opzioni impostate a False. Le azioni consentite non contribuiscono all'output. Quindi la cartella di lavoro della sezione precedente si serializza in qualcosa di simile a questo, portando con sé solo le azioni vietate più l'hash della password:

// Output concettuale per il frammento sopra (attributi omessi per brevità):
// <sheetProtection sheet="1"
//   formatCells="0" formatColumns="0" formatRows="0"
//   insertRows="0" deleteRows="0"
//   password="...4-hex..."/>
// Nota ciò che NON c'è: niente sort, niente autoFilter, niente selectLockedCells.
// La loro assenza è esattamente ciò che dice a Excel che tali azioni restano consentite.

Se arrivavi dalla vecchia stringa hard-coded e ti aspettavi di vedere ogni attributo scritto per intero, questo sembra scarno, quasi sbagliato. È corretto. Un file che elencasse sort="1" e autoFilter="1" significherebbe la stessa cosa a un lettore conforme, ma Excel stesso scrive la forma minima solo-divieti, e allinearsi ad essa mantiene piccoli i diff e noiosi i round-trip. Gli attributi objects e scenarios seguono la stessa regola: sono consentiti per default, quindi compaiono come "0" quando li vieti, che è l'inverso del vecchio objects="1" scenarios="1" emesso in modo incondizionato

Diagramma dei quindici valori TXLSXSheetProtectionOption HotXLS raggruppati in sei famiglie e commutati attraverso la proprietà indicizzata AllowOption e l'insieme SheetProtectionOptions in Delphi
Le quindici opzioni si raggruppano in sei famiglie, ciascuna commutata bit per bit o scambiata in blocco come set Pascal

Leggere la protezione all'indietro: fedeltà round-trip

A permission model that you can write but not read is a one-way door, and the usual symptom is a load-edit-save cycle that silently widens permissions. HotXLS closes that. When ParseWorksheetXml hits a <sheetProtection> element it sets the sheet protected, captures the password hash if present, and then decodes each per-action attribute back into AllowOption usando la stessa convenzione al contrario: un attributo presente ed equivalente a "0" vieta l'azione; un attributo assente lascia l'opzione al suo valore consentito predefinito

var
  wb: TXLSXWorkbook;
  sh: TXLSXWorksheet;
begin
  wb := TXLSXWorkbook.Create;
  try
    wb.Open('protection.xlsx');
    sh := wb.Sheets[1];                  // i fogli XLSX sono con base 1
    if sh.IsProtected then
    begin
      Writeln('Protected; password hash present: ',
        sh.SheetProtectHash <> '');
      Writeln('Sort allowed:       ', sh.AllowOption[xlsxSpoSort]);
      Writeln('AutoFilter allowed: ', sh.AllowOption[xlsxSpoAutoFilter]);
      Writeln('FormatCells allowed:', sh.AllowOption[xlsxSpoFormatCells]);
    end;
  finally
    wb.Free;
  end;
end;

Carichi il file prodotto dal writer e ottieni Sort e AutoFilter di nuovo come True, FormatCells, False - l'insieme che hai salvato, intatto. Questa simmetria è proprio il punto: modifica una cella in un foglio protetto e con autorizzazioni parziali, salva di nuovo, e le quattordici autorizzazioni che non hai toccato sopravvivono invece di ricadere nel vecchio default tutto-o-niente

Diagramma della regola di codifica sheetProtection XLSX HotXLS in Delphi dove un attributo omesso consente un'azione e attr=0 la vieta, contrapponendo la vecchia riga hard-coded all'output del writer minimale solo-vieti
Un'azione permessa non contribuisce alcun attributo; solo le azioni vietate appaiono come attr=0

Note pratiche e limiti

Alcune cose da sapere prima di integrare tutto questo in una pipeline di report:

  • La password è debole per progetto. La protezione del foglio XLSX memorizza un hash legacy a 16 bit, lo stesso usato da Excel da decenni, conservato qui per interoperabilità. Serve a scoraggiare le modifiche accidentali; non resiste a un attaccante. Non trattarlo come un custode di segreti. Per una protezione reale, cifra la cartella di lavoro
  • Impostare le opzioni prima di proteggere va benissimo. AllowOption può essere assegnato sia che il foglio sia attualmente protetto sia che non lo sia; le opzioni descrivono semplicemente ciò che la protezione consentirà una volta che Protect è in vigore. UnProtect cancella lo stato protetto e l'hash, ma lascia l'insieme delle opzioni al suo posto per la prossima volta
  • Anche la semantica delle celle bloccate continua ad applicarsi. La protezione blocca solo le modifiche alle celle il cui Locked attributo è impostato, che è il valore predefinito della cartella di lavoro. Lasciare modificabile un'area di input è compito dello stile della cella, non di un'opzione di protezione; i due livelli si combinano come in Excel
  • Questo è il motore XLSX. Il modello delle opzioni rispecchia le più vecchie proprietà Allow* del motore XLS, ma i nomi dell'enum e della proprietà qui (xlsxSpo*, AllowOption appartengono a TXLSXWorksheet in lxHandleX. Se gestisci anche il layout di stampa sugli stessi fogli, la guida alla protezione e all'impostazione pagina spiega come queste impostazioni convivono con aree di stampa e intestazioni, e convalida dati, AutoFilter e tabelle si abbina naturalmente al lasciare xlsxSpoAutoFilter aperto su un report bloccato

Il modello di protezione granulare e il resto del motore di lettura/scrittura XLSX sono inclusi nel HotXLS Delphi Component per Delphi e C++Builder; la pagina prodotto include l'API completa del foglio di lavoro, compreso il riferimento completo alle opzioni di protezione