Four Diseases



Piotr Przybył
DevFest Toulouse MMXIX

© 2019 Piotr Przybył. Licensed under Simplified BSD license.

$ whoami

Piotr Przybył
Remote Freelance Software Gardener
piotr[at]przybyl.org
piotrprz

4 diseases

common DDDosis

regex diarrhoea

not-made-here syndrome

malicious stringosis

Legendary IO

Von Neumann Architecture

https://www.computerscience.gcse.guru/theory/von-neumann-architecture

Von Neumann Architecture

http://www.derbildungsblog.ch/4-fachliche-fitness-sich-die-dinge-zu-eigen-machen-weil-verstehen-spass-macht/

Chicken

https://starecat.com/plowing-fields-chicken-hen/

Disease one

common DDDosis

lat. DDDosis silvestris

PIT
Helion

https://helion.pl/users/rejestracja.cgi

Cooland

http://www.coolandsimple.pl/rejestracja/

Fest

http://www.fest.olsztyn.pl/register

Poczta Polska address sample Poczta Polska address sample

https://www.poczta-polska.pl/hermes/uploads/2013/10/Poradnik-nt.-poprawnego-adresowania-2016-12-30.pdf

Poczta Polska address sample

https://www.poczta-polska.pl/hermes/uploads/2013/10/Poradnik-nt.-poprawnego-adresowania-2016-12-30.pdf

Selgros address

https://www.selgros.pl/kontakt/wroclaw-dlugoleka

Selgros invoice

Piotr Przybył

ul. Polna 1A

55-095 MIRKÓW

Field street

https://www.google.pl/maps/search/polna/@51.1611543,17.1445451,13z

Długołęka Commune Address

http://gmina.dlugoleka.pl/kontakty/

PKO address

https://inteligo.pl/client/open_account_1/submit

Let's skip the other end...

why get dirty ;-)

There are villages in Poland without streets

There are states without formal streets

There are states without postal codes

Why???

(5 WHY principle)

DDD
Event sourcing

Poczta Polska druk 11

Poczta Polska S.A. nr 11

Allegro address

http://allegro.pl

Prescription:

Don't excuse yourself with DDD.

(Especially if you have no idea what you're talking about.)

Disease two

regex diarrhoea

lat. Diarrhoea regexis

Let's go to e-mail

Orange email

https://www.orange.pl/rejestracja.phtml?_DARGS=/ocp/gear/profile/rwdRegistration/registration_form.jsp.registration-form

PUE

https://www.zus.pl/portal/riu/riuRejFormularze.npi

Email in Starostwo of Kępno

https://www.powiatkepno.pl/


function validateEmail(email, form){
		if(email.length == 0){
				return true;
		}
		if (email.match("^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$")) {
			return true;
		}
		form.wynik.value = "Niepoprawny format adresu email";
		return false;
}
					

https://rezerwacja.powiatkepno.pl:8443/WAN/js/form_validate.js

A few correct e-mail addresses

email@domain.com
firstname.lastname@domain.com
email@subdomain.domain.com
firstname+lastname@domain.com
email@123.123.123.123
email@[123.123.123.123]
"email"@domain.com
1234567890@domain.com
email@domain-one.com
_______@domain.com
email@domain.name
email@domain.co.jp
firstname-lastname@domain.com
あいうえお@example.com //not valid before 2012
					

http://codefool.tumblr.com/post/15288874550/list-of-valid-and-invalid-email-addresses

A few incorrect e-mail addresses

plainaddress
#@%^%#$@#$@#.com
@example.com
Joe Smith 
email.example.com
email@example@example.com
.email@example.com
email.@example.com
email..email@example.com
email@example.com (Joe Smith)
					

http://codefool.tumblr.com/post/15288874550/list-of-valid-and-invalid-email-addresses https://stackoverflow.com/a/37320735/1271372

Is it possible to validate an e-mail address with a regex?

(?:(?:\r\n)?[ \t])*(?:(?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)*\<(?:(?:\r\n)?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*(?:,@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*)*:(?:(?:\r\n)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*\>(?:(?:\r\n)?[ \t])*)|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)*:(?:(?:\r\n)?[ \t])*(?:(?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)*\<(?:(?:\r\n)?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*(?:,@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*)*:(?:(?:\r\n)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*\>(?:(?:\r\n)?[ \t])*)(?:,\s*(?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)*\<(?:(?:\r\n)?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*(?:,@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*)*:(?:(?:\r\n)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*\>(?:(?:\r\n)?[ \t])*))*)?;\s*)

https://stackoverflow.com/a/13013056/1271372

Email in ePUAP

https://epuap.gov.pl/wps/portal/rejestracja-konta

RFC maybe?

RFC 5321, RFC 5322, RFC 6531, etc.

Maybe a server doesn't support it?

Maybe test MX/SMTP?

Maybe there's no such mailbox?

Maybe "antivirus" will stop it?

Maybe the mailbox doesn't belong to the user?

Or maybe "validation" of a URL?


$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
if ($match) {
	return true;
}
return false;
					

https://github.com/pfsense/pfsense/blob/5fed4bf20fab6aeb3a4e2556d684acb4a93bfd9f/src/etc/inc/util.inc#L2300

What can pass it?

"s:/example.com" "_:/example.com" "www.example.com"
" http://example.com" "this is madness _:/sparta.com"

If you have a problem and you solve it with a regex...

you end up with two problems.

(Doesn't apply to Perl)

Prescription:

If you collect e-mail addresses to send something using them...

then maybe send the first message to verify.

Disease three

Not-made-here syndrome

lat. Syndroma non-fecit-a-nobis


CREATE TABLE an-important-table {
  id long,
  created long
}
					

CREATE TABLE an-important-table {
  id long,
  created varchar(20)
}
					

CREATE TABLE an-important-table {
  id long,
  created_date long,
  created_hour int
}
					

Why not simply


CREATE TABLE an-important-table {
  id long,
  created timestamp with time zone
}
					

private static DateTime? ParseDate(string raw)
{
    // YYYY-MM-DDTHH:MM:SSZ
    if (String.IsNullOrEmpty(raw)) return null;
    bool utcInd = false, lenGood;
    if (raw.Length == 10) raw += "T00:00:00";
    if (raw.Length == 20 && (raw[19] == 'Z' || raw[19] == 'z')) { utcInd = true; lenGood = true; } else lenGood = raw.Length == 19;
    if (!lenGood) throw new FormatException();
    int year, month, day, hour, minute, second;
    try
    {
        year = Int32.Parse(raw.Substring(0, 4), _formatProvider);
        month = Int32.Parse(raw.Substring(5, 2), _formatProvider);
        day = Int32.Parse(raw.Substring(8, 2), _formatProvider);
        hour = Int32.Parse(raw.Substring(11, 2), _formatProvider);
        minute = Int32.Parse(raw.Substring(14, 2), _formatProvider);
        second = Int32.Parse(raw.Substring(17, 2), _formatProvider);
    }
    catch { throw new FormatException(); }
    return new DateTime(year, month, day, hour, minute, second, utcInd ? DateTimeKind.Utc : DateTimeKind.Unspecified);
}
					

http://thedailywtf.com/articles/Stringify-All-the-Things!


string Stdt = String.Empty;
string FromDt = stDtTime.ToShortDateString();
string[] varFromDate = FromDt.Split('/');

string mth = Convert.ToInt32(varFromDate[0]) < 10 ?  varFromDate[0] : varFromDate[0];
string date = Convert.ToInt32(varFromDate[1]) < 10 ?  varFromDate[1] : varFromDate[1];
FromDate = mth + "/" + date + "/" + varFromDate[2];
Stdt = FromDate;
					

https://thedailywtf.com/articles/is-this-terning-into-a-date

...because no project is crappy enough in its crappines...

without its own date parser.

Somewhere on http://thedailywtf.com

DB in DB


CREATE TABLE meta-table-for-everything {
  id long,
  col01 varchar(2000),
  col01_type varchar(20),
  col02 varchar(2000),
  col02_type varchar(20),
  ...
}
					

Generating CSV in DB


 SELECT this, that, something-else FROM tableX JOIN lots_of_metadata
 -- generate header
  FOREACH ROW
    FOREACH COLUMN
    IF column_type = 'timestamp'
      THEN format_date(a_column)
    ELSE
      format_varchar(a_column) -- "don't forget to escape"
    END;
  -- new lines, formatting, etc.
					

Generating CSV in DB


psql -c "COPY \
( SELECT * FROM TABLE ORDER BY id limit 10 ) \
TO STDOUT WITH CSV HEADER " > CSV_FILE.csv
					

http://blogs.harvard.edu/dlarochelle/2011/12/11/outputing-to-csv-in-postgresql/

Rolling your own [encryption]













https://twitter.com/MisterCh0c/status/951772327049646080

If your have itchy fingers to

parse dates your own way

implement DB in DB

develop own DNS resolver

hash passwords

generate CSV

you should remember someone, somewhere already did that.

Probably better.

Prescription

If you're holding a golden hammer and you want to hammer everything into code...

ask your friend to hit you (softly)

Disease four

malicious stringosis

lat. Stringoza malignum


CREATE TABLE clients {
  id char(36) CHECK  -- regex checks UUID format,
  created varchar(40),
  name varchar(200),
  taxid long,
  balance double CHECK -- checks precision
}
					

class Client {
  String id;
  String created;
  String name;
  Long taxid;
  Double balance;
}
					

And then in each and every method...


function taxidRelatedOperation(Long taxid) {
  if (!ValidationUtils.taxidCorrect(taxid)) {
    throw new IllegalArgumentException("Incorrect tax ID!");
  }
  // business logyc
}
function clientRelatedOperation(String id) {
  if (!ValidationUtils.idCorrect(id)) {
    throw new IllegalArgumentException("Incorrect ID!");
  }
  // business logyc
}
					

Let's try again...


CREATE TABLE clients {
  id UUID,
  created timestamp with time zone,
  name varchar(200),
  taxid char(10) CHECK --checksum,
  balance money
}
					

class Client {
  UUID id;
  Instant created;
  String name;
  TaxID taxid;
  MonetaryAmount balance;
}
					

Maybe even better?


class Client {
  ClientID id;
  Instant created;
  String name;
  TaxID taxid;
}
					

And then in each and every method...


function taxidRelatedOperation(TaxID taxid) {
  notNull(taxid, "taxid");
  // business logyc
}
function clientRelatedOperation(ClientID id) {
  notNull(id, "client ID");
  // business logyc
}
					

We get rid of API absurds

client.id.charAt(0);
(client.id + " " + client.taxid).getBytes("UTF-8");
//if ID is long and date is kept as long too
if (Math.min(client.id, client.created)) {}

Premature optimisation → root of all evil.

Mutable objects are optimisation too.

"Easy to generate JSON" isn't an argument.

Tests

https://twitter.com/ZackKorman/status/993054858344304641

Tests

https://twitter.com/mariofusco/status/993367261506224129

Prescription

Every time you keep stuff in Java/C#/... as String*

think really, really hard

if you're not turning Java/C#

into JavaScript/PHP.

Conclusion

Four Diseases


Merci beaucoup


Stay fit!

Piotr Przybył
piotr[at]przybyl.org
piotrprz
DevFest Toulouse MMXIX
http://bit.ly/DFT4Dis

Always

Everyone

Feedback!

How was it?

http://bit.ly/DFT4DisPoll
http://bit.ly/DFT4DisPoll

piotrprz

http://bit.ly/DFT4Dis
http://bit.ly/DFT4Dis