Lazy Diary @ Hatena Blog

PowerShell / Java / miscellaneous things about software development, Tips & Gochas. CC BY-SA 4.0/Apache License 2.0

ソフトウェアエンジニアリング左道

「ソフトウェアエンジニアリング左道」という言葉を思いついた。生産性や品質向上を目的としたソフトウェアエンジニアリングでなく、どうしようもない状況をどうにかこうにか切り抜けたり、イヤな落とし穴をわざと作ったりするソフトウェアエンジニアリングを言う。たとえば以下のようなもの。

  • EOLを迎えたソフトウェアを「EULAは遵守してる」とかいって使い続ける
  • GPLOSSの機能を呼び出すアプリをGPLにしなくていいように、WebAPIで疎結合にする
  • TomcatのVirtualWebappLoader*1やWeb application resources*2を使って、顧客がダウンロードしたjarをWARクラスローダでロードする(依存先のOSSを納品物に含めず、顧客にダウンロードしてもらうことで瑕疵担保責任範囲から外すため)
  • ソフトウェアの動作に必須でない(けど見た目には影響する)アセットの著作権を発注元に譲渡しない(テストが大変になるので参入障壁として使える)
  • レガシーマイグレーション用のソースコードコンバータで、変換後のソースがプロプライエタリなライブラリに依存するよう仕込む
  • 開発ツールがファイル数単位で課金されるので、複数のクラスをまとめて巨大なクラスを作る
  • Delegateでなく継承でファサードを作る(責任分界点ごとに親クラスがあって、親クラスに機能が追加されていく)
  • REST APIを供えてます!と言い張ってるけど実際はJSON-RPC
  • ソースの著作権の分界点を明確化する目的でクラスの継承を使う

日本のSIer的なソフトウェア工学が必要とされる前提条件

以下のような前提条件が満たされると、日本のSIer的なソフトウェア工学の有用性が高くなるのかな?という想像。

  • 解雇規制が強い(↔カリフォルニア州の雇用法。突然の解雇が合法 *1
  • 雇用の流動性が低い(↔エストニア等で高い。アメリカは中位で、日本は非常に低い *2
  • 派遣法や類似の法律で、派遣労働者の能力に対する責任を派遣元事業主が負う(ここは調べたけど分からなかった)
  • ソフトウェアが無形固定資産になる(これは米国基準でも同じみたい *3
  • ソフトウェアの内製が多い(↔ソフトウェア開発者の所属企業の偏りによる推測。*4
  • 企業会計に継続性原則がある(↔IFRS *5
  • 会計や税務に関して規制当局の力が強い
  • 主な契約決定方法が競争入札
  • Fixed Firm Price(請負契約)による契約が多く、それ以外の契約方法のハードルが高い
  • 受入検査を行う調達部門の独立性が高く、発注内容の変更が認められにくい

あと、コストセンタとプロフィットセンタが部署として分かれているとか、ジョブディスクリプションの文化が薄いとかもあるかもしれないけど、どのくらい寄与しているかはいずれにせよ分かりません。

What is the difference of RFC 5322, RFC 821 and javax.mail.internet.InternetAddress ?

# Address JavaMail RFC 5322 RFC 821
1 abc.def@example.com valid valid valid
2 "abc.def"@example.com valid valid valid
3 "abc.\n def"@example.com valid invalid invalid
4 "abc"."def"@example.com invalid valid valid
5 'abc.def'@example.com valid valid valid
6 'abc.\n def'@example.com invalid invalid invalid
7 'foo'.'bar'@example.com valid valid valid
8 (abc)abc.def@example.com valid valid valid
9 (abc) abc.def@example.com valid valid valid
10 <abc.def@example.com> valid invalid invalid
11 <abc\n .def@example.com> invalid invalid invalid
12 <abc"def"ghi@example.com> invalid invalid invalid
13 foo bar <abc.def@example.com> valid invalid valid
14 'foo bar' <abc.def@example.com> valid invalid valid
15 "foo bar" <abc.def@example.com> valid invalid valid
16 abc.def@[example.com] valid valid valid
17 abc.def@[exa mple.com] invalid valid valid
18 abc.def@[exa\nmple.com] invalid invalid invalid
19 abc.def@[example."hoge".com] valid valid valid
20 ,abc.def@example.com valid invalid invalid
21 ;abc.def@example.com valid invalid invalid
22 abc..def@example.com invalid invalid invalid
23 abc.def.@example.com invalid invalid invalid
24 .abc.def@example.com invalid invalid invalid
25 ⛄bc.def@example.com valid invalid invalid
26 abc.def@localhost valid valid valid
27 abc.def@e.c valid valid valid
28 abc.def@e.co valid valid valid
29 abc.def@-.com valid valid valid
30 abc.def@203.0.113.1 valid valid valid
31 ABC.DEF@example.com valid valid valid
32 "<script>" <abc.def@example.com> valid invalid valid
33 '<script>' <abc.def@example.com> invalid invalid invalid

メールアドレスのバリデーション方法

じゃぁメールアドレスのバリデーションってどうやったらいいのさ、という話。

satob.hatenablog.com

もちろんバリデーションをした後には、メールアドレスの実在性確認のために実際にメールを送信します*1。その前段の、

  • 「じゃぁどんなメールアドレスだったら使っていいの?」と業務チームの開発者ないしユーザから聞かれたときに具体的な例を挙げて答えられるようにする
  • 「画面に表示したときに不要な問い合わせを受けることがないよう、セキュリティ的にもレイアウト的に問題ない(ように見える)メールアドレスだけ使えるようにしておきたい」という業務チームからの問い合わせに答える

みたいなときに使う話ですね。

JavaMailだけではガバガバで、Outlookのアドレス帳から取ってきたような "John Due" <john.doe@example.com> みたいなアドレスも通してしまう。これをそのまま画面表示用に使うのはさすがにマズそうなので(rtlとかltrとか入れ放題になると思う)、 john.doe@example.com の部分だけ使いたいわけです。

じゃぁJavaMailで InternetAddress#getAddress() すればアドレス部分だけ取れるからそれでよいのでは、と考えてしまうのだけれど、それもマズい。JavaMailはdomain-literal(john.doe@[203.0.113.1] みたいなやつ)を通してしまいます。domain-literalは、扱えないMUAやMTAが多いので避けたい。

こう考えると、JavaMailだけでバリデーションするのは筋が悪そうです。

一方、HTML Living Standardの正規表現は「RFC 5322にあえて準拠していないところがあるよ」と言っていますが、上記の観点から見るとそれほど悪くありません。HTML Living Standardをパスするアドレスで、JavaMailがエラーとなるものは、ローカルアドレス部分のlocal-partの先頭・末尾にドットがある場合と、連続するドットがある場合だけ *2 です。

なので、以下のようにするのがよいのではないでしょうか。

  • (A) メールを送信するMTAまで自前で制御可能で、携帯電話のキャリアメールのアドレスを利用可能にしておきたい場合はHTML Living Standardの正規表現でチェックする
  • (B) メール送信に外部のサービスを使うなどの理由で、不正なメールアドレスはトラブルの元になるから利用できなくしておきたい場合は、HTML Living Standardの正規表現とJavaMailの両方でチェックする
  • (A)(B)いずれの場合も InternetAddress#getAddress() を使う必要はなく、入力された文字列をそのままメールアドレスとして使う

*1:それ以外にアドレスの打ち間違いを検知する方法はないため

*2:MTAやMUAによっては不正なアドレスと判定される。昔の携帯電話のキャリアメールだと該当する場合がある。 https://www.rbbtoday.com/article/2009/04/03/59059.html

What does "the syntax of RFC822" means in javax.mail.internet.InternetAddress ?

Javadoc of JavaMail API says InternetAddress class uses "the syntax of RFC822". https://javaee.github.io/javamail/docs/api/javax/mail/internet/InternetAddress.html

Then how about actual implementation? Is it different from some other email validation implementation like <input type="email">?

I have tested following implementation of email address validation and compared results.

  • InternetAddress *1 class in JavaMail *2
  • <input type="email"> implementation described in HTML Living Standard 25 June 2020 *3 , /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
  • "General Email Regex (RFC 5322 Official Standard)" described in Almost Perfect Email Regex *4, (?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])
  • Regexp used in Perl正規表現雑技 *5 , /^(?:[-!#-'*+/-9=?A-Z^-~]+(?:\.[-!#-'*+/-9=?A-Z^-~]+)*|"(?:[!#-\[\]-~]|\\[\x09 -~])*")@[-!#-'*+/-9=?A-Z^-~]+(?:\.[-!#-'*+/-9=?A-Z^-~]+)*$/ (Popular in Japan)

I have used http://www.htmq.com/html5/input_type_email.shtml to test behavior of <input type="email">. Also I have used https://codepen.io/shingorow/pen/oBPZbL to test the regexps.

The results is shown in the table below.

# Test case Address JavaMail HTML Living Standard Almost Perfect Email Regex Perl正規表現雑技
1 ordinal address abc.def@example.com valid valid valid valid
2 quoted by double quote "abc.def"@example.com valid invalid valid invalid
3 quoted by double quote and contain newline + space "abc.\n def"@example.com valid invalid (*) invalid (*) invalid (*)
4 quoted by double quote and joined by "." "abc"."def"@example.com invalid *6 invalid invalid invalid
5 quoted by single quote 'abc.def'@example.com valid valid valid valid
6 quoted by single quote and contain newline + space 'abc.\n def'@example.com invalid *7 invalid (*) invalid (*) invalid (*)
7 quoted by single quote and name joined by "." 'abc'.'def'@example.com valid valid valid valid
8 contains comment with parenthesis (abc)abc.def@example.com valid invalid invalid invalid
9 contains comment with parenthesis and space (abc) abc.def@example.com valid invalid invalid invalid
10 address with angle bracket <abc.def@example.com> valid invalid invalid invalid
11 address with angle bracket and newline <abc\n .def@example.com> invalid *8 invalid (*) invalid (*) invalid (*)
12 address with angle bracket and quote <abc"def"ghi@example.com> invalid *9 invalid invalid invalid
13 address with real name foo bar <abc.def@example.com> valid invalid invalid invalid
14 address with real name (single-quoted) 'foo bar' <abc.def@example.com> valid invalid invalid invalid
15 address with real name (double-quoted) "foo bar" <abc.def@example.com> valid invalid invalid invalid
16 domain with square bracket abc.def@[example.com] valid invalid invalid invalid
17 domain with square bracket and space abc.def@[exa mple.com] invalid *10 invalid invalid invalid
18 domain with square bracket and escaped character abc.def@[exa\nmple.com] invalid *11 invalid (*) invalid (*) invalid (*)
19 domain with square bracket and quote abc.def@[example."hoge".com] valid invalid invalid invalid
20 start with comma ,abc.def@example.com valid invalid invalid invalid
21 start with semicolon ;abc.def@example.com valid invalid invalid invalid
22 contains double-dot abc..def@example.com invalid *12 valid invalid invalid
23 local address ends with dot abc.def.@example.com invalid *13 valid invalid invalid
24 local address starts with dot .abc.def@example.com invalid *14 valid invalid invalid
25 contains character outside of ASCII ⛄bc.def@example.com valid invalid invalid invalid
26 domain without TLD abc.def@localhost valid valid invalid valid
27 domain with 1 char TLD abc.def@e.c valid valid valid valid
28 domain with 2 char TLD abc.def@e.co valid valid valid valid
29 domain not compliant with RFC952 abc.def@-.com valid invalid invalid valid
30 host specified in IP address abc.def@203.0.113.1 valid valid valid valid
31 capital local address ABC.DEF@example.com valid valid invalid valid
32 tags in the double-quoted name "<script>" <abc.def@example.com> valid invalid invalid invalid
33 tags in the single-quoted name '<script>' <abc.def@example.com> invalid *15 invalid invalid invalid

(*) I could not input \n (CRLF) directly, so typed "\n" as ordinal string.

*1:https://javaee.github.io/javamail/docs/api/javax/mail/internet/InternetAddress.html

*2:https://javaee.github.io/javamail/

*3:https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address , Actual behavior of <input type="email"> on Chrome 83 is the same

*4:http://emailregex.com/

*5:http://www.din.or.jp/~ohzaki/mail_regex.htm

*6:Quote not at end of local address

*7:Local address contains control or whitespace

*8:Local address contains control or whitespace

*9:Quote not at start of local address

*10:Domain contains control or whitespace

*11:Domain contains control or whitespace

*12:Local address contains dot-dot

*13:Local address ends with dot

*14:Local address starts with dot

*15:Extra route-addr