Casino online











Mercato forex






B8. Input/Output su file


Le classi base per l'I/O su file sono derivate dalla classe astratta Stream, facente parte del namespace System.IO. In questo capitolo verranno analizzate StreamWriter e StreamReader per la modifica di file testuali, e BinaryWriter e BinaryReader per la modifica di file binari.


Lettura/scrittura di file testuali
I file testuali sono così denominati perchè contengono solo testo, ossia bytes codifcabili in una delle codifiche standard dei caratteri (ASCII o UTF-8). Alcuni particolari bytes vengono intepretati in modi diversi, come ad esempio la tabulazione, che viene rappresentata con uno spazio più lungo; altri vengono tralasciati nella visualizzazione e sembrano non esistere, ad esempio il NULL terminator, che rappresenta la fine di una stringa, oppure l'EOF (End Of File); altri ancora vengono presi a gruppi, come il carattere a capo, che in realtà è formato da una sequenza di due bytes (Carriage Return e Line Feed, rispettivamente 13 e 10). La differenza insita in questi tipi di file rispetto a quelli binari è il fatto di non poter leggere i singoli bytes perchè non ce n'è necessità: quello che importa è l'informazione che il testo porta al suo interno. La classe usata per la lettura è StreamReader, mentre quella per la scrittura StreamWriter: il costruttore di entrambi accetta un unico parametro, ossia il percorso del file in questione; esistono anche altri overloads dei costruttori, ma il più usato e quindi il più importante di tutti è quello appena citato. Ecco un piccolo esempio di come utilizzare tali classi in una semplice applicazione console:
Module Module1
    Sub Main()
        Dim File As String
        Dim Mode As Char

        Console.WriteLine("Premere R per leggere un file, W per scriverne uno.")
        'Console.ReadKey restituisce un oggetto ConsoleKeyInfo, al cui
        'interno ci sono tre proprietà: Key, enumeratore che definisce 
        'il codice del pulsante premuto; KeyChar, il carattere 
        'corrispondente a quel pulsante; Modifier, enumeratore che 
        'definisce i modificatori attivi, ossia Ctrl, Shift e Alt. 
        'Quello che serve ora è solo KeyChar
        Mode = Console.ReadKey.KeyChar
        'Dato che potrebbe essere attivo il Bloc Num, ci si assicura che
        'Mode contenga un carattere maiuscolo con la funzione statica ToUpper
        'del tipo base Char
        Mode = Char.ToUpper(Mode)
        'Pulisce lo schermo
        Console.Clear()

        Select Case Mode
            Case "R"
                Console.WriteLine("Inserire il percorso del file da leggere:")
                File = Console.ReadLine

                'Cosntrolla che il file esista
                If Not IO.File.Exists(File) Then
                    'Se non esiste, visualizza un messggio ed esce
                    Console.WriteLine("Il file specificato non esiste!")
                    Console.ReadKey()
                    Exit Sub
                End If

                Dim Reader As New IO.StreamReader(File)

                'Legge ogni singola riga del file, fintanto che non si è
                'raggiunta la fine
                Do While Not Reader.EndOfStream
                    'Come Console.Readline, la funzione d'istanza 
                    'ReadLine restituisce una linea di testo dal file
                    Console.WriteLine(Reader.ReadLine)
                Loop

                'Quindi chiude il file
                Reader.Close()
            Case "W"
                Console.WriteLine("Inserire il percorso del file da creare:")
                File = Console.ReadLine

                Dim Writer As New IO.StreamWriter(File)
                Dim Line As String

                Console.WriteLine("Immettere il testo del file, " & _
                    "premere due volte invio per terminare")
                'Fa immettere righe di testo fino a quando si termina
                Do
                    Line = Console.ReadLine
                    'Come Console.WriteLine, la funzione d'istanza 
                    'WriteLine scrive una linea di testo sul file
                    Writer.WriteLine(Line)
                Loop While Line <> ""

                'Chiude il file
                Writer.Close()
            Case Else
                Console.WriteLine("Comando non valido!")
        End Select

        Console.ReadKey()
    End Sub
End Module 
Ovviamente esistono anche i metodi Read e Write, che scrivono del testo senza mandare a capo: inoltre, Write e WriteLine hanno degli overloads che accettano anche stringhe di formato come quelle viste nel capitolo 31.
Come si è visto, le classi analizzate (e quelle che andremo a vedere tra breve) hanno metodi molti simili a quelli di Console: questo perchè anche la console è uno stream, capace di input e output allo stesso tempo. Per coloro che provengono dal C non sarà difficile richiamare questo concetto, ma per i neofiti della programmazione, fornisco una breve definizione (presa da Wikipedia), più generale, di stream.

Stream
In programmazione, il termine stream, talvolta tradotto con flusso, si riferisce a una rappresentazione astratta di un flusso di input/output nell'API di un linguaggio di programmazione

Con l'acronimo API ci si riferisce all'Application Programming Interface di un linguaggio di programmazione, ossia l'insieme di tutti i metodi resi disponibili al programmatore. Globalmente parlando, quindi, si può associare uno stream al flusso di dati proveniente da un qualsiasi dispositivo virtuale o fisico o da qualunque entità astratta all'interno della macchina: ad esempio è possibile avere uno stream associato a una stampante, a uno scanner, allo schermo, ad un file, alla memoria temporanea, a qualsiasi altra cosa. Questo concetto verrà espresso meglio nel capitolo 84, parlando della comunicazione di due computer in rete, gestita attraverso uno stream condiviso. Tuttavia, il .Net Framework non mette a disposizione tutta questa potenza alla classe Stream, ma piuttosto la suddivide in più classi differenti, così abbiamo FileStream, Console, NetworkStream, MemoryStream e via dicendo.


Lettura/scrittura di file binari
Come già accennato nel paragrafo precedente, la distinzione tra file binari e testuali avviene tramite l'interpretazione dei singoli bytes. Con questo tipo di file, c'è una corrispondenza biunivoca tra i bytes del file e i dati letti dal programma: infatti, non a caso, l'I/O viene gestito attraverso un array di byte (e qui mi riferisco proprio alle variabili di tipo Byte), denominato Buffer. Il Buffer, di solito, ha la stessa grandezza del file, o una dimensione prefissata in caso di file estremamente grandi (che vengono letti poco per volta). BinaryWriter e BinaryReader espongono procedure d'istanza simili a quelle viste prima: Write e Read. Entrambe accettano tre parametri: il buffer di lettura/scrittura, l'indice da cui iniziare a leggere/scrivere, il numero di bytes da leggere/scrivere. Nel caso di Read il primo argomento è passato per indirizzo e quindi, dopo la chiamata, contiene i bytes letti. Ecco un esempio di un semplice Hex Editor, con la sola funzionalità di lettura:
Module Module1
    Sub Main()
        Dim Reader As IO.BinaryReader
        Dim Buffer() As Byte
        Dim File As String

        Console.WriteLine("Inserire il nome del file da leggere:")
        File = Console.ReadLine

        If Not IO.File.Exists(File) Then
            Console.WriteLine("File inesistente!")
            Console.ReadKey()
            Exit Sub
        End If

        'Inizializza lo stream: il costruttore di BinaryReader e 
        'BinaryWriter è particolare: accetta un parametro di tipo 
        'Stream. Dato che Stream è una classe astratta, bisogna prima 
        'creare un nuovo oggetto FileStream con modalità di apertura 
        'e poi passarlo al costruttore
        Dim Temp As New IO.FileStream(File, IO.FileMode.Open)
        Reader = New IO.BinaryReader(Temp)
        'Ridimensiona Buffer sulla base delle dimensioni del file
        ReDim Buffer(Reader.BaseStream.Length)
        'Legge tutto il contenuto
        Reader.Read(Buffer, 0, Reader.BaseStream.Length)
        Reader.Close()

        'Itera su ogni bytes, a gruppetti di 16, e li scrive a schermo
        For I As Int32 = 0 To Buffer.Length - 1
            'La funzione statica String.Format accetta una stringa di
            'formato e restituisce una stringa costruita sulla sua base,
            'un pò come avviene in Console.WriteLine. In questo caso
            'il primo argomento (0) deve essere convertito in base
            'esadeciamle (X) ed essere sempre composto da due cifre (2).
            'Se l'argomento è minore di 10 (ossia 16 in decimale)
            'verranno aggiunto zeri sulla sinistra
            Console.Write(String.Format("{0:X2} ", Buffer(I)))
            If (I + 1) Mod 16 = 0 Then
                Console.WriteLine()
            End If
        Next

        Console.ReadKey()
    End Sub
End Module 
Provando, ad esempio, con un file contenente questo testo:
Questo file sarà analizzato da un programma in grado di
evidenziare i singoli bytes in codifica esadecimale. 
Si otterrà questo output:
51 75 65 73 74 6F 20 66 69 6C 65 20 73 61 72 E0 
20 61 6E 61 6C 69 7A 7A 61 74 6F 20 64 61 20 75 
6E 20 70 72 6F 67 72 61 6D 6D 61 20 69 6E 20 67 
72 61 64 6F 20 64 69 0D 0A 65 76 69 64 65 6E 7A 
69 61 72 65 20 69 20 73 69 6E 67 6F 6C 69 20 62 
79 74 65 73 20 69 6E 20 63 6F 64 69 66 69 63 61 
20 65 73 61 64 65 63 69 6D 61 6C 65 2E 00  
Con questo programma si possono analizzare i singoli bytes. Ne ho evidenziato qualcuno come dimostrazione. I primi in grassetto sono 0D 0A, che, convertiti in base decimale, sono rispettivamente 13 e 10, ossia, come già detto, i caratteri che costituiscono l'a capo, invisibili nel testo; il secondo è 00 alla fine, ossia il carattere terminatore di stringa che in questo caso termina anche il file. In altri tipi di file 00 può anche trovarsi all'interno, ma non vi è problema poichè, nella modalità binaria, la scasione continua comunque.


Esempio: steganografia su immagini
La steganografia è l'arte di nascondere del testo all'interno di un qualsiasi tipo di file. Per i più curiosi, mi avventurerò nella scrittura di un semplicissimo programma di steganografia su immagini, nascondendo del testo al loro interno.
Per prima cosa, si costruisca l'interfaccia grafica, con questi controlli:
  • Una Label, Label1, Text = "Inserire il testo da nascondere:"
  • Una TextBox, txtText, ScrollBars = Both, MultiLine = True
  • Un Button, cmdHide, Text = "Avvia"
  • Un Button, cmdRead, Text = "Leggi da"


Steganografia su immagini

Ed ecco il codice ampiamente commentato:
Class Form1
    Private Sub cmdHide_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles cmdHide.Click
        Dim Open As New OpenFileDialog
        Open.Filter = "Immagini JPEG|*.jpg"

        If Open.ShowDialog = Windows.Forms.DialogResult.OK Then
            Dim Path As String = Open.FileName
            'La funzione ReadAllBytes è molto più veloce:
            'legge e restituisce in un sol colpo tutti i bytes di un file
            Dim Buffer() As Byte = IO.File.ReadAllBytes(Path)

            'Crea un nuovo nome per il file steganografato, aggiungendo
            'una stringa "_st" alla fine del nome
            Path = Path.Insert(Path.LastIndexOf("."), "_st")

            'Un file JPEG finisce sempre con i bytes FF D9. Dopo si può
            'scrivere qualsiasi cosa, non influirà minimamente sulla
            'lettura dell'immagine da un qualsiasi editor.

            'Questo buffer contiene il testo convertito in bytes
            Dim TextBuffer() As Byte = _
                System.Text.ASCIIEncoding.ASCII.GetBytes(txtText.Text)
            'Ora bisogna ampliare il buffer precedente per farci stare 
            'anche il testo da nascondere
            Dim NewBuffer(Buffer.Length + TextBuffer.Length - 1) As Byte

            'Usando la funzione statica Copy, si copia il Buffer 
            'precedente all'inizio del nuovo buffer
            '* Copia Buffer.Length bytes da Buffer e li deposita in 
            'NewBuffer
            Array.Copy(Buffer, NewBuffer, Buffer.Length)

            'Usando un overload della stessa funzione, si copia 
            'TextBuffer alla fine del nuovo buffer
            '* Copia tutto TextBuffer.Length bytes da TextBuffer a 
            'partire dalla posizione 0 in NewBuffer, partendo dalla 
            'posizione Buffer.Length, ossia il primo byte disponibile
            Array.Copy(TextBuffer, 0, NewBuffer, Buffer.Length, _
                TextBuffer.Length)

            'Ora che si disponde di tutto il necessario, basta scrivere
            'i nuovi bytes su un altro file
            IO.File.WriteAllBytes(Path, NewBuffer)
            MessageBox.Show("Operazione completata!", "Steganografia", _
                MessageBoxButtons.OK)
        End If
    End Sub

    Private Sub cmdRead_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles cmdRead.Click
        Dim Open As New OpenFileDialog
        Open.Filter = "Immagini JPEG|*.jpg"

        If Open.ShowDialog = Windows.Forms.DialogResult.OK Then
            Dim Buffer() As Byte = IO.File.ReadAllBytes(Open.FileName)
            'Determina se si è raggiunta la fine del file
            Dim EOF As Boolean = False
            'Costruisce a poco a poco la stringa nascosta
            Dim HiddenBytes As New List(Of Byte)

            'Prima bisogna trovare la sequenza FF D9, corrispondente
            'al decimale 255 217
            For I As Int64 = 0 To Buffer.Length - 1
                'Questo byte fa parte del testo nasosto, quindi...
                If EOF Then
                    'Lo aggiunge alla lista
                    HiddenBytes.Add(Buffer(I))
                    'E salta il controllo successivo
                    Continue For
                End If
                
                'Questa istruzione viene eseguita solo se EOF = False
                'e quando I è uguale all'ultimo indice dell'array
                'Significa che non c'è nessun testo nascosto, perciò
                'esce ond'evitare un errore nell'espressione
                'Buffer(I+1) della riga successiva, poichè I+1
                'non esiste
                If I = Buffer.Length - 1 Then
                    Exit For
                End If

                If Buffer(I) = 255 And Buffer(I + 1) = 217 Then
                    'Fine del file JPEG: se c'è dell'altro testo, verrà 
                    'accodato mediante HiddenBytes nell'If precedente
                    EOF = True
                    'Avanza di un byte, poichè c'è ancora D9 che
                    'non è stato analizzato
                    I += 1
                End If
            Next

            'Copia la lista generic in un array di bytes, così da
            'poterlo convertire in stringa
            Dim Bytes(HiddenBytes.Count - 1) As Byte
            HiddenBytes.CopyTo(Bytes)

            'Converte i bytes in stringa e li visualizza
            Dim HiddenString = _
                System.Text.ASCIIEncoding.ASCII.GetString(Bytes)
            txtText.Text = HiddenString
        End If
    End Sub
End Class 
Il testo accodato può essere rilevato facilmente con un Hex Editor, per questo lo si dovrebbe criptare con una password: per ulteriori informazioni sulla criptazione in .Net, vedere capitolo 91. Per il resto funziona egregiamente: provare per credere! Potete scaricare questa immagine e verificare il testo steganografato all'interno con l'applicazione appena creata.




 

The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.