A legjobb módszer email címek validálására

RoliSoft
2010 május 31, hétfõ 19:00

Email címet validálni egy nagyon nehéz feladat. Ha azt hiszed, hogy egy szimpla [a-z0-9]+@[a-z0-9]+\.[a-z]+ reguláris kifejezés megteszi, akkor nagyon rossz hírem van neked. &*=?^+{}'~@árvíztűrő-tükörfúrógép.net bizony egy szintaktikailag valid email cím.

Ha meg szeretnéd tudni egész pontosan hogyan is kell validálni egy címet, akkor a következőket tanulmányozd:

  1. RFC0822 – Az eredeti specifikáció; az egyetlen problémája, hogy 1982-ben lett megírva, úgyhogy...
  2. RFC1123, 5.2.15 RFC-822 Syntax Change
  3. RFC2822, 3.4. Address Specification

Ha sikerült keresni vagy összedobni egy reguláris kifejezést, ami ezeknek a feltételeknek megfelelően validál, akkor jön a következő kérdés: a cím valid, de létezik-e az a cím? És igen, ezt könnyen le lehet ellenőrözni.

Ebben a cikkben bemutatom, hogy hogyan lehet egy email címet teljesen leellenőrizni három lépésből.

Első lépés – Az email cím szintaktikai ellenőrzése

Noha nem létezik olyan reguláris kifejezés ami tökéletesen validálna, léteznek olyanok amik közel vannak hozzá.

Ha egy egyszerű preg_match-et szeretnél használni egy cím validálásához, akkor a következő reguláris kifejezés a legjobb (a cikk írásakor):

/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i

Ha pedig inkább egy pontosabb függvényt szeretnél használni akkor meg töltsd le az EmailAddressValidation osztályt. A használata nagyon egyszerű:

include('EmailAddressValidator.php');

$validator = new EmailAddressValidator;

if ($validator->check_email_address('rolisoft@gmail.com')) { 
    // A cím szintaktikailag helyes, jöhet a következő lépés...
} else {
    die('A beírt email cím nem helyes!');
}

Második lépés – Tud-e a domain emaileket fogadni

Megnézzük hogy az email cím domainjének van-e MX vagy A rekordjai. Ha nincs, akkor nem tud emailt fogadni.

rolisoft@ncc1701e:~$ nslookup -type=MX gmail.com
Server:         208.113.192.18
Address:        208.113.192.18#53

Non-authoritative answer:
gmail.com       mail exchanger = 40 alt4.gmail-smtp-in.l.google.com.
gmail.com       mail exchanger = 5 gmail-smtp-in.l.google.com.
gmail.com       mail exchanger = 30 alt3.gmail-smtp-in.l.google.com.
gmail.com       mail exchanger = 10 alt1.gmail-smtp-in.l.google.com.
gmail.com       mail exchanger = 20 alt2.gmail-smtp-in.l.google.com.

rolisoft@ncc1701e:~$ nslookup -type=A gmail.com
Server:         208.113.192.18
Address:        208.113.192.18#53

Non-authoritative answer:
Name:   gmail.com
Address: 209.85.225.83
Name:   gmail.com
Address: 74.125.79.83
Name:   gmail.com
Address: 74.125.127.83

PHP-ből az MX és A rekordokat a Net_DNS segítségével lehet megnézni.

Harmadik lépés – Megpróbálunk emailt küldeni

Megpróbálunk csatlakozni az előzőleg kapott MX vagy A rekordokhoz és megpróbálunk elküldeni egy emailt. A cím beírása után a mail server figyelmeztet ha a cím nem létezik, ha pedig létezik akkor kijelentkezünk.

Egy létező email cím esetén így folyik le a beszélgetés:

rolisoft@ncc1701e:~$ telnet gmail-smtp-in.l.google.com 25
Trying 209.85.212.84...
Connected to mail-vw0-f84.google.com.
Escape character is '^]'.
220 mx.google.com ESMTP 16si12020910vwj.23
HELO rolisoft.net
250 mx.google.com at your service
MAIL FROM: <address-probe@rolisoft.net>
250 2.1.0 OK 16si12020910vwj.23
RCPT TO: <rolisoft@gmail.com>
250 2.1.5 OK 16si12020910vwj.23
QUIT
221 2.0.0 closing connection 16si12020910vwj.23
Connection closed by foreign host.

Ha pedig nem létezik az email cím, akkor:

rolisoft@ncc1701e:~$ telnet gmail-smtp-in.l.google.com 25
Trying 209.85.210.8...
Connected to mail-yx0-f8.google.com.
Escape character is '^]'.
220 mx.google.com ESMTP 8si21418269yxe.0
HELO rolisoft.net
250 mx.google.com at your service
MAIL FROM: <address-probe@rolisoft.net>
250 2.1.0 OK 8si21418269yxe.0
RCPT TO: <nem-regisztralt-cim@gmail.com>
550-5.1.1 The email account that you tried to reach does not exist. Please try
550-5.1.1 double-checking the recipient's email address for typos or
550-5.1.1 unnecessary spaces. Learn more at
550 5.1.1 http://mail.google.com/support/bin/answer.py?answer=6596 8si21418269yxe.0
QUIT
221 2.0.0 closing connection 8si21418269yxe.0
Connection closed by foreign host.

Ezek után teljesen biztos lehetsz, hogy egy email cím létezik, és nem kelletett megerősítő emailt sem küldened a felhasználónak. Meg kell említenem ugyan, hogy ez a módszer sem véd a disposable email szolgáltatások – mint például mailinator.com – ellen, ugyanis ott bármilyen cím el lesz fogadva.

Bemutatóként megvalósítottam PHP-ben a fenti három lépést. Az ékezetes domainek egyelőre nem mennek át a második lépésen, ugyanis a Net_DNS meg a PHP úgy nagyjából eléggé shit és nem támogatja még az ékezetes domaineket.

Próbáld ki:

Morse kódok Console.Beep()-pel

RoliSoft
2010 május 30, vasárnap 17:27

Érdekességként írtam ezt a kódrészletet, ami a Console.Beep()-pet hasznossá teszi. Megadsz neki egy üzenetet, azt meg átalakítja morse kóddá és lecsipogja. Hasznos lehet például tanulás céljából, de akármilyen célra fel lehet használni. Mondjuk puskázás céljából.

A függvény pedig a következő: Nem, nem működik szervereken, így ASP.Net-ből nem lehet a szervergépet csipogtatni a szerverteremben. Shit.

// A tábla forrása: http://hu.wikipedia.org/wiki/Morzekód
public static readonly Dictionary<char, string> Table = new Dictionary<char, string>
{
	// latin betűk
	{ 'A', ".-"     }, { 'B', "-..."    }, { 'C', "-.-."   }, { 'D', "-.."    }, { 'E', "."       },
	{ 'F', "..-."   }, { 'G', "--."     }, { 'H', "...."   }, { 'I', ".."     }, { 'J', ".---"    },
	{ 'K', "-.-"    }, { 'L', ".-.."    }, { 'M', "--"     }, { 'N', "-."     }, { 'O', "---"     },
	{ 'P', ".--."   }, { 'Q', "--.-"    }, { 'R', ".-."    }, { 'S', "..."    }, { 'T', "-"       },
	{ 'U', "..-"    }, { 'V', "...-"    }, { 'W', ".--"    }, { 'X', "-..-"   }, { 'Y', "-.--"    },
	{ 'Z', "--.."   },
	// számok
	{ '1', ".----"  }, { '2', "..---"   }, { '3', "...--"  }, { '4', "....-"  }, { '5', "....."   },
	{ '6', "-...."  }, { '7', "--..."   }, { '8', "---.."  }, { '9', "----."  }, { '0', "-----"   },
	// speciális karakterek
	{ '/', "-..-."  }, { '+', ".-.-."   }, { '=', "-...-"  }, { '.', ".-.-.-" }, { ',', "--..--"  },
	{ '?', "..--.." }, { '!', "--.--"   }, { '(', "-.--."  }, { ')', "-.--.-" }, { '-', "-....-"  },
	{ '_', "..--.-" }, { '\'', ".----." }, { ':', "---..." }, { ';', "-.-.-." }, { '$', "...-..-" },
	{ '@', ".--.-." }, { ' ', " "       },
	// magyar ékezetes karaterek
	{ 'Á', ".--.-"  }, { 'É', "..-.."   }, { 'Ö', "---."   }, { 'Ü', "..--"   }, { 'Í', ".."      },
	{ 'Ó', "---"    }, { 'Ú', "..-"     }, { 'Ő', "---."   }, { 'Ű', "..--"   }
};

public static void Morse(string message)
{
	Console.Write(String.Format("Üzenet: '{0}'\r\nMorse : ", message));

	foreach (var codes in from chr in message.ToUpper() where Table.ContainsKey(chr) select Table[chr])
	{
		foreach (var code in codes)
		{
			Console.Write(code);

			switch (code)
			{
				case '.':
					Console.Beep(392, 500); // ti esetén 392 Hz 500 milliszekundumig
					Thread.Sleep(100);
					break;
				case '-':
					Console.Beep(220, 1000); // tá esetén 220 Hz 1 másodpercig
					Thread.Sleep(100);
					break;
				case ' ':
					Thread.Sleep(1000); // szóköz esetén 1 másodpercig kussol
					break;
			}
		}

		Console.Write(" ");
		Thread.Sleep(1000);
	}
}

Egyszerű titkosítás C#-ban a String osztály kibővítésével

RoliSoft
2010 május 24, hétfõ 23:57

A titkosítás egy nehéz feladat tud lenni kezdőknek és haladóknak is egyaránt. Velem is többször előfordult, hogy C#-ban valamit titkosítani kelletett, éppen ezért írtam egy kis osztályt ami kihasználja az osztályok bővíthetőségét és beszúrja az Encrypt() meg Decrypt() függvényeket a System.String osztályba. Így bármilyen stringre meg lehet majd őket hívni.

A függvények a .Net Framework beépített AES 256 bites algoritmust használják CBC módban és PBKDF2-t jelszó generáláshoz.

public static class EncryptionExtensions
{
    public static string Encrypt(this string secret, string password)
    {
        var raw = Encoding.UTF8.GetBytes(secret);

        var pdb = new Rfc2898DeriveBytes(password,
                                         new byte[]
                                             {
                                                 0x20, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
                                                 0x72, 0x6f, 0x6c, 0x69, 0x73, 0x6f, 0x66, 0x74, 0x2e,
                                                 0x6e, 0x65, 0x74, 0x2f, 0x20, 0x3b, 0x29, 0x20, 0x20
                                             });

        var ms = new MemoryStream();

        var alg = Rijndael.Create();
        alg.Mode = CipherMode.CBC;
        alg.Key = pdb.GetBytes(32);
        alg.IV = pdb.GetBytes(16);

        var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write);
        cs.Write(raw, 0, raw.Length);
        cs.Close();

        return Convert.ToBase64String(ms.ToArray());
    }

    public static string Decrypt(this string secret, string password)
    {
        var enc = Convert.FromBase64String(secret);

        var pdb = new Rfc2898DeriveBytes(password,
                                         new byte[]
                                             {
                                                 0x20, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
                                                 0x72, 0x6f, 0x6c, 0x69, 0x73, 0x6f, 0x66, 0x74, 0x2e,
                                                 0x6e, 0x65, 0x74, 0x2f, 0x20, 0x3b, 0x29, 0x20, 0x20
                                             });

        var ms = new MemoryStream();

        var alg = Rijndael.Create();
        alg.Mode = CipherMode.CBC;
        alg.Key = pdb.GetBytes(32);
        alg.IV = pdb.GetBytes(16);

        var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write);
        cs.Write(enc, 0, enc.Length);
        cs.Close();

        return Encoding.UTF8.GetString(ms.ToArray());
    }
}

Használatuk egyszerű, a kód bemásolása után bármilyen stringre meghívhatod őket:

var secretMessage = "Árvíztűrő-tükörfúrógép";

var encryptedMesssage = secretMessage.Encrypt("fl00d-t0l3r4nt m1rr0r dr1ll");
// encryptedMessage = "5qB0JeE+gk+qcT505YwftIxQDnHzhZ/rROW/JGBLbxE="

var decryptedMessage = encryptedMessage.Decrypt("fl00d-t0l3r4nt m1rr0r dr1ll");
// decryptedMessage = "Árvíztűrő-tükörfúrógép"

Cross-Site Scripting in the wild

RoliSoft
2010 május 21, péntek 23:23

A Cross-Site Scripting ma már egy nagyon ismert módszer, és mindenki védekezik ellene. Viszont vannak helyek ahol azt gondolod, hogy nincs minek szűrni az adatot és kiírod úgy ahogy van. Ezzel viszont azonnal megszegted azt az aranyszabályt, hogy don't trust user input.

A rolisoft.net-en már régebb óta állítgatok saját Server headert, eleinte viszont csak rövidebb neveket, mint például "bunch of monkeys", ezúttal viszont kíváncsiságból és érdekességből a következőt állítottam be:

header("Server: <script>alert('hello from rolisoft.net!')</script>");

Teszteljük le a headert:

rolisoft@ncc1701e:~$ wget -S --spider http://rolisoft.net
--20:24:20--  http://rolisoft.net/
           => `index.html'
Resolving rolisoft.net... 69.163.231.16
Connecting to rolisoft.net|69.163.231.16|:80... connected.
HTTP request sent, awaiting response...
  HTTP/1.1 200 OK
  Date: Fri, 21 May 2010 20:24:20 GMT
  Content-Type: text/html; charset=UTF-8
  Connection: keep-alive
  Server: <script>alert('hello from rolisoft.net!')</script>
  Set-Cookie: PHPSESSID=6a9c1ii20c75atbah3sq6j5687; path=/
  Expires: Thu, 19 Nov 1981 08:52:00 GMT
  Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
  Pragma: no-cache

Azok az oldalak amelyek szerver információkat gyűjtenek vagy csak headereket mutatnak, valószínűleg bele fognak esni a csapdába, mivel senki sem gondolt arra, hogy szűrjék a Server mezőt, mikor ideálisan csak a szerver nevét és verzióját kellene tartalmazzák, mint például "Apache/2.2.12" vagy "nginx/0.7.64".

Nos, akkor próbáljuk ki. Az első lesz a kedvenc eszközöm, a hurl.it:

hurl.it xss
Mission accomplished!

Google-ben kerestem még hasonló eszközöket, és a következők XSS-elhetőek még:

Nagyjából az összes eszköz ami a "server header checker" queryre kijön sebezhető. Ouch!

Pár eszköz véletlenül escape-elte a ' karaktert és emiatt a JavaScript nem futott le. Ezt nagyon egyszerűen meg lehet oldani, mindössze nem használunk idézőjeleket az üzenethez:

<script>alert(String.fromCharCode(104,101,108,108,111,32,102,114,111,109,32,114,111,108,105,115,111,102,/*...*/))</script>

Bevezetés a LINQ-be

RoliSoft
2010 február 10, szerda 13:25

Ahhoz, hogy egy adatforrásból adatot nyerjünk ki, az adatforrás saját API-ját kell használnunk. Például egy SQL szervernek az ADO.NET segítségével küldünk egy SQL queryt, és így kapjuk meg a kívánt adatot; XML-ből viszont XPath vagy valamilyen XML DOM API segítségével nyerhetünk ki adatot. Mivel minden adatforrás használatához más API szükséges, ezért ezek megtanulása sok időt és energiát igényel. A LINQ ennek a megoldására lett létrehozva.

A LINQ-t három kategóriába lehet osztani:

  1. LINQ: Language Integrated Query for in memory objects
  2. DINQ: Language Integrated Query for databases
  3. XLINQ: Language Integrated Query for XML

A Microsoft úgy tervezte a LINQ-t, hogy minden olyan adattal tudjon dolgozni, ami implementálta az IEnumerable interfészt.

LINQ 1

A következő példában egy string tömbből válasszuk ki a 4 karakter hosszú stringeket:

string[] gyumolcsok = { "alma", "körte", "eper", "kivi", "barack" };

IEnumerable<string> query = from n in gyumolcsok
                            where n.Length == 4
                            select n;

foreach (string gyumolcs in query) Console.WriteLine(gyumolcs);

LINQ használata objektumokon

Bármikor, amikor tömbökkel vagy listákkal dolgozol használhatsz LINQ kifejezéseket. Az unalmas foreach ciklusokat az if-ekkel bennük, le tudod cserélni egy szépen olvasható LINQ kifejezésre, így szebb, olvashatóbb és rövidebb kódod lesz.

Tegyük fel, hogy meg szeretnéd keresni az összes svchost.exe futó alkalmazást, majd memóriahasználatuk szerint sorrendbe rendezni. Ezt eddig így csináltad meg .Net 2.0-ban:

List<Process> svchostLista = new List<Process>();

foreach (Process p in Process.GetProcesses())
{
	if(p.ProcessName == "svchost")
	{
		svchostLista.Add(p);
	}
}

svchostLista.Sort(delegate(Process p1, Process p2)
{
	return p2.WorkingSet64.CompareTo(p1.WorkingSet64);
});

Le lehet egyszerűsíteni lambda kifejezésekkel:

List<Process> svchostLista = new List<Process>(
	Process.GetProcesses().ToList().FindAll(p => String.Equals(p.ProcessName, "svchost"))
);
svchostLista.Sort((p1, p2) => p2.WorkingSet64.CompareTo(p1.WorkingSet64));

Habár a munkát elvégzi, az utánad következő fejlesztőnek kicsit fejfájás lesz e kódot módosítani. Most viszont itt van ugyan ez, LINQ kifejezésekkel:

IEnumerable<Process> svchostLista = from p in Process.GetProcesses()
                                    where p.ProcessName == "svchost"
                                    orderby p.WorkingSet64 descending
                                    select p;

Könnyen érthető és módosítható.

LINQ használata XML fájlokon

Sok XML API létezik, a LINQ viszont különleges, mivel nem csak XML-t ismer, de az XML-en belül használhatsz XML DOM vagy XPath-et is.
A legelső string tömbös példa XML megfelelője:

XElement gyumolcsok = XElement.Parse(
	@"<gyumolcsok>
		   <gyumolcs>alma</gyumolcs>
		   <gyumolcs>körte</gyumolcs>
		   <gyumolcs>eper</gyumolcs>
		   <gyumolcs>kivi</gyumolcs>
		   <gyumolcs>barack</gyumolcs>
	 </gyumolcsok›"
);

IEnumerable<string> query = from n in gyumolcsok.Elements("gyumolcs")
                            where n.Value.Length == 4
                            select n.Value;

foreach (string gyumolcs in query) Console.WriteLine(gyumolcs);

Ha csak annyit szeretnénk megtudni, hogy az XML fájlban tárolt gyümölcsök közül hány kezdődik k betűvel, akkor a következő kód:

int hanyKBetu = (from i in gyumolcsok.Elements("gyumolcs") where i.Value.StartsWith("k") select i).Count();

Ebben az esetben a következő megoldás viszont rövidebb:

int hanyKBetu = gyumolcsok.Elements("gyumolcs").Where(i => i.Value.StartsWith("k")).Count();

A .Net 3.5-ben be lett vezetve az XElement, amely megengedi az XML fájlok funkcionális készítését:

XElement gyumolcsok =
	new XElement("gyumolcsok",
		new XElement("gyumolcs", "alma"),
		new XElement("gyumolcs", "körte"),
		new XElement("gyumolcs", "eper"),
		new XElement("gyumolcs", "kivi"),
		new XElement("gyumolcs", "barack")
);

Ha a gyumolcsok-re meghívjuk a ToString() függvényt, akkor megkapjuk a fentebb is látható XML fájlt.

LINQ használata SQL adatbázisokon

Ahhoz, hogy LINQ kifejezéseket használhassunk SQL adatbázisokon az első lépés az, hogy elkészítjük a táblánknak megfelelő osztályt.
A Northwind adatbázis Customer táblájának megfelelő osztály így néz ki:

[Table(Name = "dbo.Customers")]
public partial class Customer 
{              
    [Column(IsPrimaryKey = true)]
    public string CustomerID
    {
        get
        {
            return this._CustomerID;
        }
        set
        {
            if ((this._CustomerID != value))
            {
                this._CustomerID = value;
            }
        }                    
    }
    private string _CustomerID;

    [Column]
    public string CompanyName
    {
        get
        {
            return this._CompanyName;
        }
        set
        {
            if ((this._CompanyName != value))
            {
                this._CompanyName = value;
            }
        }
    }

    // és így tovább...
}

Két megoldás van arra, hogy egy osztályt egy táblához kössünk. Az egyik a fenti kód, ahol külön megírjuk a Table és Column részeket, míg a második megoldás a külső XML fájlok használata. Mindkét megoldáshoz vannak automatikus generátorok, mint például az sqlmetal.exe.

A DataContext osztály lefordítja a LINQ kifejezéseket SQL kifejezésekre, majd lefuttatja az adatbázison.
Ha a Northwind adatbázisból le szeretnénk tölteni a francia ügyfeleket, akkor a következő LINQ kifejezést futtatjuk le:

IEnumerable<Customer> ugyfelek = from c in context.Customers
                                 where c.Country == "France"
                                 orderby c.CustomerID ascending
                                 select c;

Ez le lesz fordítva a következő SQL kifejezéssé:

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address],
[t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]
FROM [dbo].[Customers] AS [t0]
WHERE [t0].[Country] = @p0
ORDER BY [t0].[CustomerID]

A LINQ to SQL nagyon komplex kifejezéseket is képes megérteni és lefordítani, például amelyek két táblát join-olnak. Vannak esetek, ahol már túl komplex lesz egy query, és a LINQ to SQL már nem lesz képes megérteni; ilyenkor az Entity Framework-öt kell használni.