Schlagwort: C#

ZIP-Archive mit C# (ent-)packen 👍 👎

In einem früheren Beitrag zur (De-)Kompression mit C# hatte ich bereits einen Beitrag zu ZIP-Archiven in Aussicht gestellt. Diese sind praktisch, um Dateien und Verzeichnisstrukturen komprimiert und zusammenhängend speichern und austauschen zu können und werden außerdem auf praktisch jeder Plattform unterstützt. Daher möchte ich mit diesem Beitrag zeigen, wie sich mit C# entsprechende Archive automatisch erstellen lassen.

Das .NET-Framework liefert im Namensraum System.IO.Compression grundlegende Funktionalität zur Arbeit mit entsprechenden Archiven, auf die wir in diesem Beitrag setzen werden. Darüber hinaus gibt es jedoch eine Vielzahl weiterer Implementierungen (z. B. DotNetZip oder SharpZipLib), welche beispielsweise die Erstellung von passwortgeschützten Archiven ermöglichen und weitere Konfigurationsmöglichkeiten bieten.

Zuerst möchte ich ein paar Möglichkeiten aufzeigen, um ZIP-Archive zu erstellen:
Archiv aus bestehendem Verzeichnis erstellen
01
ZipFile.CreateFromDirectory("data", "data.zip");
Archiv aus bestehenden Dateien erstellen
010203040506
using(FileStream fileStream = File.OpenWrite("data.zip")) {    using(ZipArchive zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Create)) {        zipArchive.CreateEntryFromFile("data/a.txt", "a.txt");        zipArchive.CreateEntryFromFile("data/b.txt", "b.txt");    }}
Archiv manuell zusammenstellen
010203040506070809
using(FileStream fileStream = File.OpenWrite("data.zip")) {    using(ZipArchive zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Create)) {        ZipArchiveEntry zipArchiveEntry = zipArchive.CreateEntry("README");
using(StreamWriter streamWriter = new StreamWriter(zipArchiveEntry.Open())) { streamWriter.Write("Hallo Welt!"); } }}
Abschließend möchten wir auf bestehende Archive zugreifen:
Archiv vollständig entpacken
01
ZipFile.ExtractToDirectory("data.zip", "data");
Archiv einlesen und verarbeiten
01020304050607080910111213141516
using(FileStream fileStream = File.OpenRead("data.zip")) {    using(ZipArchive zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read)) {        foreach(ZipArchiveEntry zipArchiveEntry in zipArchive.Entries) {              // "README" separat berücksichtigen            if(zipArchiveEntry.Name == "README") {                using(StreamReader streamReader = new StreamReader(zipArchiveEntry.Open())) {                      // Inhalt einlesen und auf Konsole ausgeben                    Console.WriteLine(streamReader.ReadToEnd());                }            } else {                  // Eintrag als Datei speichern                zipArchiveEntry.ExtractToFile(zipArchiveEntry.Name);            }        }    }}
Sehr viel mehr Möglichkeiten stehen leider tatsächlich nicht zur Verfügung; für weitergehende Einstellungen gibt es jedoch beispielsweise die bereits erwähnten externen Bibliotheken. Durch die Verwendung von Datenströmen besteht aber grundsätzlich die Möglichkeit, nahezu beliebige Quellen (und Ziele) für Archiv-Einträge heranzuziehen.

Kontakt für Domain-Missbrauch mit C# von abuse.net ermitteln 👍 👎

In meinem vorherigen Beitrag habe ich bereits ein paar allgemeine Gedanken und Lösungsansätze zum – leidigen – Thema Spam niedergeschrieben. In diesem Beitrag soll es, wie bereits angekündigt, darum gehen, den bereits kurz beschriebenen Dienst von abuse.net automatisiert (mit C#) anzusprechen.

Der Anbieter stellt alle wesentlichen Informationen zur Umsetzung zur Verfügung. Dort wird der Zugriff per DNS oder Whois beschrieben. Das Herunterladen der (Roh-)Daten ist jedoch nicht möglich. Beachtet bitte außerdem die Nutzungsbedingungen, so ist der kommerzielle Einsatz beispielsweise nicht ohne Weiteres gestattet.

Es werden bei den folgenden beispielhaften Implementierungen lediglich die ermittelten eMail-Adressen an sich zurückgeliefert und keine – ebenfalls verfügbaren – Kommentare, welche insbesondere Angaben zur Quelle enthalten. Des Weiteren findet keine Reduzierung von Duplikaten statt. Beides kann bei Bedarf aber selbstverständlich noch angepasst bzw. ergänzt werden. Die Methoden sind darüber hinaus variadisch und können daher mit mehreren Argumenten (oder einem Feld) aufgerufen werden.

Zugriff per Whois

Eine – auch mit .NET-Bordmitteln – sehr einfache Möglichkeit der Abfrage des für Missbrauch zuständigen Kontakts zu einer Domain ist das Whois-Protokoll. Die Abfrage erfolgt per TCP bei whois.abuse.net:43.

Zugriff per Whois
01020304050607080910111213141516171819202122
public static IEnumerable<string> GetAbuseContactList(params string[] domainList) {    using(TcpClient client = new TcpClient("whois.abuse.net", 43)) {        using(NetworkStream stream = client.GetStream()) {            byte[] request = Encoding.UTF8.GetBytes(String.Join(" ", domainList) + "\r\n");            stream.Write(request, 0, request.Length);
using(StreamReader streamReader = new StreamReader(stream)) { while(!streamReader.EndOfStream) { string response = streamReader.ReadLine();
if(response.Contains("(")) { response = response.Substring(0, response.IndexOf('(')); }
if(!String.IsNullOrWhiteSpace(response)) { yield return response.Trim(); } } } } }}

Es sei jedoch darauf hingewiesen, dass diese Methode vom Anbieter nicht (mehr) empfohlen und möglicherweise eingestellt wird. Darüber hinaus ist das Abfragen im Vergleich zu DNS um den Faktor 2-3 langsamer.

Zugriff per DNS

Eine sehr effiziente Möglichkeit der Abfrage stellt das DNS durch Auflösung des Bezeichners {domain}.contacts.abuse.net dar; die Rückgabe der Adressen erfolgt in Form von TXT-Records.

Leider stellt das .NET-Framework jedoch nur einfache Aufgaben der Adress- und Namensauflösung per DNS-Klasse zur Verfügung. Da die Details der Kommunikation – zumindest in diesem Beitrag – nicht weiter vertieft werden sollen, greifen wir auf eine externe Laufzeitbibliothek zu, die wir mit NuGet bequem per Visual Studio beziehen können. Für das folgende Beispiel verwende ich ARSoft.Tools.Net, welche sich bereits in einigen meiner Entwicklungen im Produktiveinsatz bewährt hat und zudem auch relativ einfach zu verwenden ist.

Zugriff per DNS
010203040506070809101112
public static IEnumerable<string> GetAbuseContactList(params string[] domainList) {    foreach(string domain in domainList) {        DnsMessage response = DnsClient.Default.Resolve(            DomainName.Parse(domain + ".contacts.abuse.net"),            RecordType.Txt        );
foreach(TxtRecord record in response.AnswerRecords) { yield return record.TextData; } }}
Unabhängig davon, welche der beiden Methoden ihr verwendet, gestaltet sich die Verwendung sehr einfach:
Verwendung der Methode(n)
0102030405060708
  // ["abuse@coders-online.net"]IEnumerable<string> abuseContactList = GetAbuseContactList("coders-online.net");
// ["abuse@google.com", "abuse@microsoft.de"]abuseContactList = GetAbuseContactList("google.com", "microsoft.de");
// ["abuse@facebook.com", "spamreport@spamreport.facebook.com"]abuseContactList = GetAbuseContactList("facebook.com");
Auf eine angemessene Fehlerbehandlung habe ich wie üblich zur besseren Verdeutlichung des eigentlichen Anliegens verzichtet, sollte von euch bei der Verwendung aber nicht vernachlässigt werden.

DNS-Cache unter Windows und mit C# leeren 👍 👎

Um den DNS-Zwischenspeicher unter Windows zu leeren, gibt es den bekannten Befehl ipconfig /flushdns.

Prinzipiell wäre es zwar möglich, das Programm ipconfig mit dem Argument /flushdns über die Process-Klasse per C# aufzurufen, jedoch ist dies nicht ganz unproblematisch, da die Ausführung längere Zeit dauern kann.

Alternativ hilft uns hier – wieder einmal – die Windows-API in Kombination mit dem DllImport-Attribut:
(Hilfs-)Methode deklarieren und verwenden
01020304050607
public static class DnsUtility {    [DllImport("dnsapi", EntryPoint = "DnsFlushResolverCache")]    public static extern void FlushCache();}
// DNS-Cache leerenDnsUtility.FlushCache();
Dies ist insbesondere in Verbindung mit der Dns-Klasse nützlich, um aktuelle Daten zu erhalten. Natürlich gibt es einen solchen Zwischenspeicher aber nicht grundlos, d. h. es ist nur äußerst selten ratsam, diesen immer wieder vor einer Namensauflösung zu leeren – Performanzdifferenzen im Rahmen einer Größenordnung sind hier üblich. Im Rahmen des Debuggings oder für besonders kritische Prozesse kann das Vorgehen jedoch durchaus sinnvoll sein.

Bezeichner eines Members mit C# ermitteln 👍 👎

Bei C# kommt man beispielsweise bei der Fehlerbehandlung (z. B. per ArgumentException und abgeleiteten Klassen), Logging (im Sinne von "Methode MethodName betreten/verlassen") oder auch beim Routing im Rahmen des ASP.NET MVC-Frameworks in die Notwendigkeit, Programmbezeichner als Zeichenketten anzugeben.

Etwas ärgerlich ist hier jedoch immer, was man bei grundsätzlich statisch typisierten Sprachen wie C# für gewöhnlich vermeiden möchte: Derartige Zeichenketten sind literal hinterlegt und dementsprechend einer Refaktorierung nicht ohne Weiteres zugänglich. Dieses Problem löst nun das relativ neue Konstrukt nameof, indem es die (einfachen) Bezeichner von Variablen, Typen und Membern als Zeichenkette liefert:
Beispiel-Klasse definieren und Member-Bezeichner ermitteln
01020304050607080910111213141516171819202122
public class Person {    public string FirstName {        get;        set;    }
public string LastName { get; set; }

public void Marry(Person person) { /* … */ }}

Debug.WriteLine(nameof(Person)); // "Person"Debug.WriteLine(nameof(Person.FirstName)); // "FirstName"Debug.WriteLine(nameof(Person.LastName)); // "LastName"Debug.WriteLine(nameof(Person.Marry)); // "Marry"
Praktisch ist nun, dass bei der Umbenennung einer Eigenschaft oder Methode, sich dieser Vorgang auch auf die entsprechende Verwendung innerhalb von nameof auswirkt und somit für Konsistenz sorgt.

Ein weiterer theoretischer Anwendungsfall ergibt sich im Rahmen einer INotifyPropertyChanged-Implementierung, jedoch lässt sich dieses Problem auch ganz ohne manuelle Angabe per CallerMemberName-Attribut lösen, wie ich in einem früheren Beitrag bereits beschrieben habe.

Anwendungssymbol mit C# extrahieren 👍 👎

Desktop-Anwendungen (*.exe) enthalten häufig ein Programmsymbol, welches insbesondere im Windows Explorer und auf der Taskleiste angezeigt wird. Das Ermitteln dieses Anwendungssymbols, beispielsweise für eigene Auflistungen, ist sehr einfach über Icon.ExtractAssociatedIcon() realisierbar:
Anwendungssymbol extrahieren
0102030405060708091011
using(Icon icon = Icon.ExtractAssociatedIcon("devenv.exe")) {      // Programmsymbol als *.ico-Datei speichern    using(FileStream fileStream = File.OpenWrite("devenv.ico")) {        icon.Save(fileStream);    }
// Programmsymbol als *.png-Datei speichern using(Bitmap bitmap = icon.ToBitmap()) { bitmap.Save("devenv.png", ImageFormat.Png); }}
Bitte beachtet wie üblich in solchen Fällen, dass die Verwendung der Grafik rechtlich eingeschränkt sein kann.

Projektverweise

Kategorien / Archiv  |  Übersicht RSS-Feed

Schlagworte

Suche