タイトル通り、必死こいて汗水垂らしながら実装した。zlib/libpng ライセンスで公開する。
/* * Copyright (c) 2010 eel3 @ TRASH BOX <dov045a@yahoo.co.jp> * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source * distribution. */ /* ********************************************************************** */ /** * @brief メールアドレスのフォーマットチェック関数 && テスト用メインルーチン * @author eel3 @ TRASH BOX * @date 2010/01/30 * * @note * メールアドレスが RFCで規定されている形式に従っているかチェックする。 * 但し Quoted-string, address-literal には未対応。 * あとフォーマットをチェックしているだけなので、現実に無さそうな内容 * (例えば a@b.c)でもOKと判定してしまう。 * * @par 動作確認済み環境: * - Microsoft Windows XP Professional (32bit) SP3 * * @par 確認済みコンパイラ: * - Digital Mars C and C++ Compilers 8.49 * - Microsoft Visual Studio 6.0 * - Microsoft Visual Studio 2005 * - MinGW 5.0.3 (GCC 3.4.2) */ /* ********************************************************************** */ #include <assert.h> #include <ctype.h> #include <stdio.h> #include <string.h> /* ====================================================================== */ /** * @brief [a-zA-Z] なアルファベット文字かチェックする。 * * @param[in] c チェックする文字 * * @retval !=0 アルファベット文字である * @retval 0 アルファベット文字ではない * * @note * 標準ライブラリの isalpha(3) はロケールに左右されるので、 * 自前で判定関数を定義している。 */ /* ====================================================================== */ static int my_isalpha(int c) { return ((c != '\0') && (strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", c) != NULL)); } /* ====================================================================== */ /** * @brief [a-zA-Z] なアルファベット文字ないし数字かチェックする。 * * @param[in] c チェックする文字 * * @retval !=0 アルファベット文字か数字である * @retval 0 アルファベット文字か数字ではない * * @note * 標準ライブラリの isalnum(3) はロケールに左右されるので、 * 自前で判定関数を定義している。 */ /* ====================================================================== */ static int my_isalnum(int c) { return (my_isalpha(c) || isdigit(c)); } /* ====================================================================== */ /** * @brief メールアドレスのLocal-partに使用可能な文字かチェックする。 * * @param[in] c チェックする文字 * * @retval !=0 使用できる * @retval 0 使用できない */ /* ====================================================================== */ static int islocalc(int c) { return (my_isalnum(c) || ((c != '\0') && (strchr(".!#$%&\'*+-/=\?^_`{|}~", c) != NULL))); } /* ====================================================================== */ /** * @brief メールアドレスのLocal-partかどうかチェックする。 * * @param[in] *s チェックする文字列 * @param[in] size 文字列の長さ(単位:byte) * * @retval !=0 Local-partである * @retval 0 Local-partではない */ /* ====================================================================== */ static int islocalpart(const char *s, size_t size) { size_t i; int dot_found; /* 直前の文字が . だったら1 */ assert(s != NULL); if ((s[0] == '\0') || (size == 0)) { return 0; } if (size > 64) { /* RFC 5321 4.5.3.1.1 */ return 0; } if (s[0] == '.') { return 0; } dot_found = 0; for (i = 0; (i < size) && (s[i] != '\0'); ++i) { if (!islocalc((unsigned char) s[i])) { return 0; } if (s[i] == '.') { if (dot_found) { return 0; } else { dot_found = 1; } } else { dot_found = 0; } } return (s[i-1] != '.'); } /* ====================================================================== */ /** * @brief メールアドレスのsub-domainに使用可能な文字かチェックする。 * * @param[in] c チェックする文字 * * @retval !=0 使用できる * @retval 0 使用できない */ /* ====================================================================== */ static int issubdomainc(int c) { return (my_isalnum(c) || (c == '-')); } /* ====================================================================== */ /** * @brief メールアドレスのsub-domainかどうかチェックする。 * * @param[in] *s チェックする文字列 * @param[in] size 文字列の長さ(単位:byte) * * @retval !=0 sub-domainである * @retval 0 sub-domainではない */ /* ====================================================================== */ static int issubdomain(const char *s, size_t size) { size_t i; assert(s != NULL); if ((s[0] == '\0') || (size == 0)) { return 0; } if (size > 63) { /* RFC 1035 2.3.1 */ return 0; } if (!my_isalpha((unsigned char) s[0])) { return 0; } for (i = 1; (i < size) && (s[i] != '\0'); ++i) { if (!issubdomainc((unsigned char) s[i])) { return 0; } } return (s[i-1] != '-'); } /* ====================================================================== */ /** * @brief メールアドレスのDomainかどうかチェックする。 * * @param[in] *s チェックする文字列 * @param[out] *size Domainの長さを返す * * @retval !=0 Domainである * @retval 0 Domainではない */ /* ====================================================================== */ static int isdomain(const char *s, size_t *size) { const char *p, *sepp; size_t nsubdomains; assert(s != NULL); if (s[0] == '\0') { return 0; } nsubdomains = 0; p = s; do { sepp = p + strcspn(p, "."); if (!issubdomain(p, (size_t) (sepp - p))) { return 0; } p = sepp+1; ++nsubdomains; } while (*sepp == '.'); *size = (size_t) (p - s); if (*size > 255) { /* RFC 5321 4.5.3.1.2 */ return 0; } return (nsubdomains >= 2); } /* ====================================================================== */ /** * @brief メールアドレスとして適切なフォーマットかチェックする。 * * @param[in] *s チェックする文字列 * * @retval !=0 適切である * @retval 0 適切ではない * * @par 参考資料 * - RFC 1035, RFC 5321, RFC 5322 * - http://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%BC%E3%83%AB%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9 * * @note * メールアドレスの形式: Local-part@(Domain|address-literal) * * @attention * - 前後の空白文字はトリミングされていること。 * - Local-partはDot-stringにのみ対応。Quoted-string には対応しない。 * - Domainにのみ対応。address-literalには対応しない。 */ /* ====================================================================== */ static int isemailaddr(const char *s) { const char *atmk; size_t lp_size, dmn_size; assert(s != NULL); if (s[0] == '\0') { return 0; } /* Quoted-string には対応しない */ if (s[0] == '\"') { return 0; } atmk = strchr(s, '@'); if (atmk == NULL) { return 0; } lp_size = (size_t) (atmk - s); if (!islocalpart(s, lp_size)) { return 0; } if (!isdomain(atmk+1, &dmn_size)) { return 0; } return ((lp_size + dmn_size) <= 256); /* RFC 5321 4.5.3.1.3 */ } /* ********************************************************************** */ /** * @brief テスト用メインルーチン * * @return 常に0 */ /* ********************************************************************** */ int main(void) { static char buf[1024+1]; while (fgets(buf, (int) sizeof(buf), stdin) != NULL) { size_t len = strlen(buf); if (buf[len-1] == '\n') { buf[len-1] = '\0'; } (void) puts(isemailaddr(buf) ? "OK" : "NG"); } return 0; }
ろくにテストしていないので、取り扱い注意。