Controlla in modo sincrono se il file / directory esiste in Node.js


1209

Come posso verificare in modo sincrono, usando node.js , se esiste un file o una directory?


58
Le operazioni sincrone sono ottime per eseguire operazioni su file / directory una tantum prima di restituire un modulo. Ad esempio, il bootstrap di un file di configurazione.
jocull

1
@PaulDraper con una cache calda non è vero in tutti i casi.
mikemaccana,

12
Indipendentemente dalle prestazioni, a volte vuoi solo eseguirlo in modo sincronizzato per l'esperienza degli sviluppatori. Ad esempio, se si utilizza Nodo per uno script di elaborazione dei dati che dovrebbe essere bloccato per progettazione, in tal caso asincrono existsaggiunge solo richiami non necessari.
Kunok,

3
Sicuramente +1 alla dichiarazione di Kunok. Nel resto del mio codice lo rendo più complesso solo quando si tratta di un collo di bottiglia in cui la velocità conta davvero. Perché non dovrei applicare questo principio alla lettura dei file? In molte parti di molti programmi la semplicità / leggibilità del codice può essere più importante della velocità di esecuzione. Se si tratta di un collo di bottiglia, userò i metodi asincroni per evitare di interrompere l'ulteriore esecuzione del codice. Altrimenti ... la sincronizzazione è fantastica. Non odiare ciecamente la sincronizzazione.
BryanGrezeszak,

3
Per favore ... non "degno di nota" perché l'utente chiede esplicitamente come farlo in modo sincrono.
jClark

Risposte:


2241

La risposta a questa domanda è cambiata nel corso degli anni. La risposta attuale è qui in alto, seguita dalle varie risposte nel corso degli anni in ordine cronologico:

Risposta attuale

Puoi usare fs.existsSync():

const fs = require("fs"); // Or `import fs from "fs";` with ESM
if (fs.existsSync(path)) {
    // Do something
}

È stato deprecato per diversi anni, ma non lo è più. Dai documenti:

Si noti che fs.exists()è deprecato, ma fs.existsSync()non lo è. (Il parametro callback per fs.exists()accettare parametri incoerenti con altri callback Node.js. fs.existsSync()Non utilizza un callback.)

Hai richiesto specificamente un controllo sincrono , ma se invece puoi utilizzare un controllo asincrono (di solito meglio con I / O), usa fs.promises.accessse stai usando asyncfunzioni o fs.access(dato che existsè deprecato ) in caso contrario:

In una asyncfunzione:

try {
    await fs.promises.access("somefile");
    // The check succeeded
} catch (error) {
    // The check failed
}

O con un callback:

fs.access("somefile", error => {
    if (!error) {
        // The check succeeded
    } else {
        // The check failed
    }
});

Risposte storiche

Ecco le risposte storiche in ordine cronologico:

  • Risposta originale del 2010
    ( stat/ statSynco lstat/ lstatSync)
  • Aggiornamento settembre 2012
    ( exists/ existsSync)
  • Aggiornamento febbraio 2015
    (rilevando l'imminente deprezzamento di exists/ existsSync, quindi probabilmente torniamo a stat/ statSynco lstat/ lstatSync)
  • Aggiornamento dicembre 2015
    (c'è anche fs.access(path, fs.F_OK, function(){})/ fs.accessSync(path, fs.F_OK), ma nota che se il file / directory non esiste, è un errore; documenti da fs.statutilizzare fs.accessse è necessario verificare l'esistenza senza aprirla)
  • L'aggiornamento di dicembre 2016
    fs.exists() è ancora obsoleto ma fs.existsSync()non è più obsoleto. Quindi puoi usarlo tranquillamente ora.

Risposta originale del 2010:

Puoi usare statSynco lstatSync( link docs ), che ti dà un fs.Statsoggetto . In generale, se è disponibile una versione sincrona di una funzione, avrà lo stesso nome della versione asincrona con Syncalla fine. Così statSyncè la versione sincrona di stat; lstatSyncè la versione sincrona di lstat, ecc.

lstatSync ti dice se esiste qualcosa e, in tal caso, se si tratta di un file o di una directory (o in alcuni file system, un collegamento simbolico, un dispositivo a blocchi, un dispositivo a caratteri, ecc.), ad esempio se devi sapere se esiste e lo è una directory:

var fs = require('fs');
try {
    // Query the entry
    stats = fs.lstatSync('/the/path');

    // Is it a directory?
    if (stats.isDirectory()) {
        // Yes it is
    }
}
catch (e) {
    // ...
}

... e similmente, se è un file, c'è isFile; se è un dispositivo a blocchi, c'è isBlockDevice, ecc. ecc. Nota il try/catch; genera un errore se la voce non esiste affatto.

Se non vi interessa ciò che la voce è solo vuole sapere se esiste, è possibile utilizzare path.existsSync(o con l'ultima, fs.existsSync), come notato da user618408 :

var path = require('path');
if (path.existsSync("/the/path")) { // or fs.existsSync
    // ...
}

Non richiede un try/catchma non ti dà informazioni su cosa sia, solo che è lì. path.existsSyncè stato deprecato molto tempo fa.


Nota a margine: hai chiesto espressamente come verificare in modo sincrono , quindi ho usato le xyzSyncversioni delle funzioni sopra. Ma dove possibile, con l'I / O, è davvero meglio evitare chiamate sincrone. Le chiamate nel sottosistema I / O richiedono molto tempo dal punto di vista della CPU. Nota quanto è facile chiamare lstatpiuttosto che lstatSync:

// Is it a directory?
lstat('/the/path', function(err, stats) {
    if (!err && stats.isDirectory()) {
        // Yes it is
    }
});

Ma se hai bisogno della versione sincrona, è lì.

Aggiornamento settembre 2012

La risposta di seguito di un paio di anni fa è ora un po 'obsoleta. Il modo attuale è quello di usare fs.existsSyncun controllo sincrono per l'esistenza di file / directory (o ovviamente fs.existsper un controllo asincrono), piuttosto che le pathversioni sottostanti.

Esempio:

var fs = require('fs');

if (fs.existsSync(path)) {
    // Do something
}

// Or

fs.exists(path, function(exists) {
    if (exists) {
        // Do something
    }
});

Aggiornamento febbraio 2015

Ed eccoci nel 2015 e i documenti di Node ora dicono che fs.existsSync(e fs.exists) "saranno deprecati". (Perché la gente del Nodo pensa che sia stupido controllare se esiste qualcosa prima di aprirlo, ma non è l'unica ragione per verificare se esiste qualcosa!)

Quindi probabilmente torniamo ai vari statmetodi ... Fino a / a meno che questo non cambi ancora, naturalmente.

Aggiornamento dicembre 2015

Non so da quanto tempo è lì, ma c'è anche fs.access(path, fs.F_OK, ...)/fs.accessSync(path, fs.F_OK) . E almeno a partire da ottobre 2016, la fs.statdocumentazione consiglia di utilizzare fs.accessper eseguire controlli di esistenza ( "Per verificare se esiste un file senza manipolarlo in seguito, fs.access()si consiglia." ). Ma tieni presente che l'accesso non disponibile è considerato un errore , quindi questo sarebbe probabilmente il migliore se ti aspetti che il file sia accessibile:

var fs = require('fs');

try {
    fs.accessSync(path, fs.F_OK);
    // Do something
} catch (e) {
    // It isn't accessible
}

// Or

fs.access(path, fs.F_OK, function(err) {
    if (!err) {
        // Do something
    } else {
        // It isn't accessible
    }
});

Aggiornamento dicembre 2016

Puoi usare fs.existsSync():

if (fs.existsSync(path)) {
    // Do something
}

È stato deprecato per diversi anni, ma non lo è più. Dai documenti:

Si noti che fs.exists()è deprecato, ma fs.existsSync()non lo è. (Il parametro callback per fs.exists()accettare parametri incoerenti con altri callback Node.js. fs.existsSync()Non utilizza un callback.)


7
path.exists e path.existsSync sono stati entrambi deprecati a favore di fs.exists e fs.existsSync
Drew

15
"La gente del nodo pensa che sia stupido controllare se esiste qualcosa prima di aprirlo, che è;" Perché è stupido controllare se il file esiste?
Petr Hurtak,

32
@PetrHurtak: non è sempre (perché ci sono molti motivi per controllare l'esistenza), ma se hai intenzione di aprire il file, è meglio semplicemente emettere la openchiamata e gestire l'eccezione o qualunque altra cosa se il file non lo fosse trovato. Dopotutto, il mondo reale è caotico: se controlli prima ed è lì, ciò non significa che sarà ancora lì quando provi ad aprirlo; se controlli prima e non è lì, ciò non significa che non ci sarà un momento dopo. Le cose del genere sembrano casi limite, ma arrivano sempre . Quindi, se hai intenzione di aprire, non ha senso controllare prima.
TJ Crowder,

13
E qui ho pensato che fosse un anti-schema usare errori per il flusso di controllo: link
argyle

4
@jeromeyers: Potresti, ma Ionică lo ha già fatto per te (vedi commento sopra ). :-)
TJ Crowder,

124

Guardando la fonte, c'è una versione sincrona di path.exists- path.existsSync. Sembra che ci sia mancato nei documenti.

Aggiornare:

path.existse path.existsSyncora sono deprecati . Si prega di utilizzare fs.existsefs.existsSync .

Aggiornamento 2016:

fs.exists e fs.existsSyncsono stati anche deprecati . Utilizzare invece fs.stat () o fs.access () .

Aggiornamento 2019:

usare fs.existsSync. Non è deprecato. https://nodejs.org/api/fs.html#fs_fs_existssync_path


1
path.existsSync (p) è nel documento 0.4.10 nodejs.org/docs/v0.4.10/api/path.html
Paul Beusterien,

21
In realtà, una risposta più recente: path.existsSync è obsoleta. Ora è chiamato fs.existsSync.
Olivier Lalonde

9
Ora i documenti dicono che fs.exists sarà deprecato. nodejs.org/api/fs.html#fs_fs_existssync_path
Greg Hornby,

Ho scritto una piccola biblioteca per sostituire la vecchia existsfunzione:is-there
Ionică Bizău,

6
currenct docs (versione ~ 9) etichettato solo fs.existscome deprecato mentre fs.existsSyncnon lo è!
Kunok,

57

Utilizzando le API attualmente consigliate (a partire dal 2015) (secondo i documenti Node), questo è quello che faccio:

var fs = require('fs');

function fileExists(filePath)
{
    try
    {
        return fs.statSync(filePath).isFile();
    }
    catch (err)
    {
        return false;
    }
}

In risposta alla questione EPERM sollevata da @broadband nei commenti, ciò evidenzia un buon punto. fileExists () probabilmente non è un buon modo di pensarci in molti casi, perché fileExists () non può davvero promettere un ritorno booleano. Potresti essere in grado di determinare definitivamente che il file esiste o non esiste, ma potresti anche ricevere un errore di autorizzazione. L'errore di autorizzazione non implica necessariamente che il file esista, perché potresti non avere l'autorizzazione per la directory che contiene il file su cui stai controllando. E ovviamente c'è la possibilità che potresti riscontrare qualche altro errore nel controllo dell'esistenza del file.

Quindi il mio codice sopra è davvero doesFileExistAndDoIHaveAccessToIt (), ma la tua domanda potrebbe essere doFileNotExistAndCouldICreateIt (), che sarebbe una logica completamente diversa (che dovrebbe tenere conto di un errore EPERM, tra le altre cose).

Mentre la risposta fs.existsSync affronta direttamente la domanda posta qui, spesso non sarà quello che vuoi (non vuoi solo sapere se "qualcosa" esiste in un percorso, probabilmente ti importa se la "cosa" che esiste è un file o una directory).

La linea di fondo è che se stai verificando se esiste un file, probabilmente lo stai facendo perché intendi intraprendere alcune azioni in base al risultato e che la logica (il controllo e / o l'azione successiva) dovrebbe adattarsi all'idea che una cosa trovata in quel percorso potrebbe essere un file o una directory e che potresti riscontrare EPERM o altri errori nel processo di verifica.


4
Bello, ho aggiunto || isDirectory () per renderlo un controllo file / cartella. var stats = fs.statSync (filePath); return stats.isFile () || stats.isDirectory ();
bob

4
Se il programma non dispone dei diritti per accedere al file, restituisce comunque false anche se esiste un file, ad esempio rimuove tutti i rigts dal file chmod ugo-rwx file.txt o dal tasto destro di Windows ... Messaggio di eccezione: Exception fs.statSync (./ f.txt): errore: EPERM: operazione non consentita, stat 'X: \ f.txt'. Quindi questo caso non è coperto dal codice superiore.
banda larga

2
Wow, JS è ritardato a volte. Quindi, certo, il 97% delle volte utilizzerai il file, ma non hai un semplice programma di file.exists()utilità per il 3% e invece ci costringi a concludere questo in un tentativo di cattura? Diventa reale ... Puttana del giorno.
espulso il

20

Un altro aggiornamento

Ho bisogno di una risposta a questa domanda io stesso ho cercato i documenti del nodo, sembra che non dovresti usare fs.exists, invece usa fs.open e usa l'errore emesso per rilevare se un file non esiste:

dai documenti:

fs.exists () è un anacronismo ed esiste solo per ragioni storiche. Non dovrebbe esserci quasi mai un motivo per usarlo nel tuo codice.

In particolare, controllare se esiste un file prima di aprirlo è un anti-pattern che ti rende vulnerabile alle condizioni di gara: un altro processo può rimuovere il file tra le chiamate a fs.exists () e fs.open (). Basta aprire il file e gestire l'errore quando non è presente.

http://nodejs.org/api/fs.html#fs_fs_exists_path_callback


1
c'è un modo per farlo con openSync, piuttosto che aperto
Greg Hornby,

1
@GregHornby Immagino che dovrebbe funzionare allo stesso modo con openSync
Melbourne2991,

2
Per quelli che hanno ancora bisogno exists e existsSyncho creato is-there.
Ionică Bizău,

6
Questa deprecazione mi dà fastidio. Aprire un file solo per vedere se viene generato un errore o meno sembra uno spreco di risorse quando tutto ciò che serve è la conoscenza dell'esistenza del file.
Josh Hansen,

11

Uso la funzione di seguito per verificare se il file esiste. Cattura anche altre eccezioni. Quindi, nel caso in cui ci siano problemi di diritti, ad esempio chmod ugo-rwx filenameo nella Right Click -> Properties -> Security -> Advanced -> Permission entries: empty list ..funzione di Windows , si ottiene l'eccezione come dovrebbe. Il file esiste ma non abbiamo i diritti per accedervi. Sarebbe sbagliato ignorare questo tipo di eccezioni.

function fileExists(path) {

  try  {
    return fs.statSync(path).isFile();
  }
  catch (e) {

    if (e.code == 'ENOENT') { // no such file or directory. File really does not exist
      console.log("File does not exist.");
      return false;
    }

    console.log("Exception fs.statSync (" + path + "): " + e);
    throw e; // something else went wrong, we don't have rights, ...
  }
}

Output delle eccezioni, documentazione degli errori nodejs nel caso in cui il file non esista:

{
  [Error: ENOENT: no such file or directory, stat 'X:\\delsdfsdf.txt']
  errno: -4058,
  code: 'ENOENT',
  syscall: 'stat',
  path: 'X:\\delsdfsdf.txt'
}

Eccezione nel caso in cui non abbiamo diritti sul file, ma esiste:

{
  [Error: EPERM: operation not permitted, stat 'X:\file.txt']
  errno: -4048,
  code: 'EPERM',
  syscall: 'stat',
  path: 'X:\\file.txt'
}

2
Davvero così, è una delle poche risposte aggiornate da quando il nodo ha deprecato gli ultimi 37 modi per farlo
1mike12

Bah, mi hai battuto. Avrei potuto risparmiare un po 'di tempo se avessi letto questo.
jgmjgm,

5

fs.exists () è deprecato, non utilizzarlo https://nodejs.org/api/fs.html#fs_fs_exists_path_callback

È possibile implementare il modo nodejs core utilizzato in questo modo: https://github.com/nodejs/node-v0.x-archive/blob/master/lib/module.js#L86

function statPath(path) {
  try {
    return fs.statSync(path);
  } catch (ex) {}
  return false;
}

questo restituirà l'oggetto stats quindi una volta ottenuto l'oggetto stats potresti provare

var exist = statPath('/path/to/your/file.js');
if(exist && exist.isFile()) {
  // do something
}

4

Alcune risposte qui lo dicono fs.existse fs.existsSyncsono entrambe deprecate. Secondo i documenti questo non è più vero. Solo fs.existsora è deprecato:

Nota che fs.exists () è deprecato, ma fs.existsSync () non lo è. (Il parametro callback su fs.exists () accetta parametri incompatibili con altri callback di Node.js. Fs.existsSync () non utilizza un callback.)

Quindi puoi tranquillamente usare fs.existsSync () per verificare in modo sincrono se esiste un file.


3

Il pathmodulo non fornisce una versione sincrona di path.existsquindi devi cercare con il fsmodulo.

La cosa più veloce che posso immaginare è usare fs.realpathSyncche genererà un errore che devi catturare, quindi devi far funzionare il tuo wrapper con un tentativo / cattura.


1

L'uso dei test di fileSystem (fs) attiverà oggetti di errore, che sarà quindi necessario racchiudere in un'istruzione try / catch. Risparmia un po 'di sforzo e usa una funzionalità introdotta nel ramo 0.4.x.

var path = require('path');

var dirs = ['one', 'two', 'three'];

dirs.map(function(dir) {
  path.exists(dir, function(exists) {
    var message = (exists) ? dir + ': is a directory' : dir + ': is not a directory';
    console.log(message);
  });
});

2
Il path.exists è ora sotto fs, quindi è fs.exists (path, callback)
Todd Moses,

0

I documenti su fs.stat()dicono di usare fs.access()se non hai intenzione di manipolare il file. Non ha fornito una giustificazione, potrebbe essere più veloce o meno un uso memoriale?

Uso il nodo per l'automazione lineare, quindi ho pensato di condividere la funzione che utilizzo per verificare l'esistenza del file.

var fs = require("fs");

function exists(path){
    //Remember file access time will slow your program.
    try{
        fs.accessSync(path);
    } catch (err){
        return false;
    }
    return true;
}

0

asnwer aggiornato per quelle persone 'correttamente' sottolineando che non risponde direttamente alla domanda, più porta un'opzione alternativa.

Soluzione di sincronizzazione:

fs.existsSync('filePath')vedi anche i documenti qui .

Restituisce vero se esiste il percorso, falso altrimenti.

Soluzione promettente asincrona

In un contesto asincrono, puoi semplicemente scrivere la versione asincrona con il metodo di sincronizzazione usando la awaitparola chiave. Puoi semplicemente trasformare il metodo di callback asincrono in una promessa come questa:

function fileExists(path){
  return new Promise((resolve, fail) => fs.access(path, fs.constants.F_OK, 
    (err, result) => err ? fail(err) : resolve(result))
  //F_OK checks if file is visible, is default does no need to be specified.

}

async function doSomething() {
  var exists = await fileExists('filePath');
  if(exists){ 
    console.log('file exists');
  }
}

i documenti su access ().


1
l'OP vuole una soluzione sincrona
vdegenne,

dovresti aggiornare il tuo codice afunction asyncFileExists(path) { //F_OK checks if file is visible, is default does no need to be specified. return new Promise(function (res, rej) { fs.access( path, fs.constants.F_OK, function (err) { err ? rej(err) : res(true); }, ); }); }
pery mimon il

0

È probabile che, se si desidera sapere se esiste un file, si prevede di richiederlo in caso affermativo.

function getFile(path){
    try{
        return require(path);
    }catch(e){
        return false;
    }
}

-1

Ecco una semplice soluzione wrapper per questo:

var fs = require('fs')
function getFileRealPath(s){
    try {return fs.realpathSync(s);} catch(e){return false;}
}

Uso:

  • Funziona con directory e file
  • Se l'elemento esiste, restituisce il percorso al file o alla directory
  • Se l'articolo non esiste, restituisce false

Esempio:

var realPath,pathToCheck='<your_dir_or_file>'
if( (realPath=getFileRealPath(pathToCheck)) === false){
    console.log('file/dir not found: '+pathToCheck);
} else {
    console.log('file/dir exists: '+realPath);
}

Assicurarsi di utilizzare l'operatore === per verificare se return è uguale a false. Non vi è alcun motivo logico che fs.realpathSync () restituisca false in condizioni di lavoro adeguate, quindi penso che dovrebbe funzionare al 100%.

Preferirei vedere una soluzione che non genera un errore e il conseguente risultato delle prestazioni. Dal punto di vista dell'API, fs.exists () sembra la soluzione più elegante.


1
@Dan, grazie. Ho rimosso il testo troncato. Non ricordo quale fosse la nota. Se mi viene, aggiungerò delle note.
Timothy C. Quinn,

1
NP. Sto cancellando il mio commento.
Dan Dascalescu,

-2

Dalle risposte sembra che non ci sia supporto API ufficiale per questo (come in un controllo diretto ed esplicito). Molte delle risposte dicono di usare stat, tuttavia non sono rigide. Non possiamo supporre, ad esempio, che qualsiasi errore generato da stat significhi che qualcosa non esiste.

Diciamo che lo proviamo con qualcosa che non esiste:

$ node -e 'require("fs").stat("god",err=>console.log(err))'
{ Error: ENOENT: no such file or directory, stat 'god' errno: -2, code: 'ENOENT', syscall: 'stat', path: 'god' }

Proviamo con qualcosa che esiste ma che non abbiamo accesso a:

$ mkdir -p fsm/appendage && sudo chmod 0 fsm
$ node -e 'require("fs").stat("fsm/appendage",err=>console.log(err))'
{ Error: EACCES: permission denied, stat 'access/access' errno: -13, code: 'EACCES', syscall: 'stat', path: 'fsm/appendage' }

Almeno vorrai:

let dir_exists = async path => {
    let stat;
    try {
       stat = await (new Promise(
           (resolve, reject) => require('fs').stat(path,
               (err, result) => err ? reject(err) : resolve(result))
       ));
    }
    catch(e) {
        if(e.code === 'ENOENT') return false;
        throw e;
    }

    if(!stat.isDirectory())
        throw new Error('Not a directory.');

    return true;
};

La domanda non è chiara se si desidera che sia sincrona o se si desidera solo che sia scritta come se fosse sincrona. In questo esempio viene usato waitit / async in modo che sia scritto solo in modo sincrono ma venga eseguito in modo asincrono.

Ciò significa che devi chiamarlo come tale al livello superiore:

(async () => {
    try {
        console.log(await dir_exists('god'));
        console.log(await dir_exists('fsm/appendage'));
    }
    catch(e) {
        console.log(e);
    }
})();

Un'alternativa sta usando .then e .catch sulla promessa restituita dalla chiamata asincrona se ne hai bisogno più in basso.

Se vuoi verificare se esiste qualcosa, allora è una buona pratica assicurarsi anche che sia il giusto tipo di cose come una directory o un file. Questo è incluso nell'esempio. Se non è consentito essere un collegamento simbolico, è necessario utilizzare lstat anziché stat poiché stat attraverserà automaticamente i collegamenti.

Puoi sostituire tutto l'asincrono per sincronizzare il codice qui e utilizzare invece statSync. Tuttavia, aspettati che una volta che asincrono e attendi diventino universalmente supportati, le chiamate di sincronizzazione diventeranno ridondanti alla fine per essere ammortizzate (altrimenti dovresti definirle ovunque e in cima alla catena proprio come con asincrono che lo rende davvero inutile).


1
La domanda originale non lo specifica. Sto anche dimostrando come fare le cose senza ambiguità. Molte risposte potrebbero indurre bug a causa della mancanza di chiarezza. Le persone spesso vogliono programmare le cose in modo che appaiano sincrone ma non necessariamente vogliono un'esecuzione sincrona. statSync non è lo stesso del codice che ho dimostrato. Entrambi i resoconti di ciò che è effettivamente desiderato sono ambigui, quindi stai solo imponendo le tue interpretazioni personali. Se trovi una risposta che non capisci, potrebbe essere meglio chiedere semplicemente nei commenti o nel PM di capire quali modifiche sono necessarie.
jgmjgm,

1
Se vuoi puoi anche rubare il mio esempio di codice, nominarlo in modo appropriato, metterlo su github, aggiungerlo a npm e quindi la risposta sarà solo una riga / collegamento: D.
jgmjgm,

Il codice è abbreviato, per esempio, ma sei invitato a inviare un suggerimento di modifica per includere &&! IsFile o un controllo per i collegamenti simbolici, ecc. (Anche se la domanda non afferma mai esplicitamente che è quello che vogliono). Come ho già sottolineato, la mia risposta soddisfa un'interpretazione della domanda e non fa la stessa cosa che fa la tua proposta a una riga.
jgmjgm,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.