Webアプリ等で、入力テキストが「URLを含んだ文字」かどうかを判定する必要があった。
正規表現だと視覚的じゃないので、何かライブラリないかな?と思ってurllibを使ってみた。
そしたら一部の表記だとIPv6URLエラーが出てアプリが落ちた。
問題の概要・原因と解決策を提示する
目次
問題の概要
文字列をURLかどうか判定すると、予期しないエラーが発生した。
問題コード
from urllib import parse def is_url(text) : url_param = parse.urlparse(text) return len(url_param.scheme) > 0
上記のコードに、以下のような条件を入力した
print(is_url("http://googcle.com")) # True print(is_url("HTTPリクエスト")) # False print(is_url("abcd:efgh")) # True print(is_url("http://[")) # ValueError: Invalid IPv6 URL print(is_url("http://]")) # ValueError: Invalid IPv6 URL print(is_url("http://[]")) # True
実際に入力された文字
http://[
起きたエラー
ValueError: Invalid IPv6 URL
原因
ライブラリのソースコードでは意図は不明だけどこの条件が入っている。
入力文字の「http://」以降が、①または②の条件を満たす為にraise ValueError(“Invalid IPv6 URL”)となる。
- ①[は含むが]を含まない
- ②]は含むが[を含まない
つまり、角括弧の片方のみ含んでいる状態である。
詳細は実際のスタックトレースを参照ください(ちょっと長いです)
ValueError Traceback (most recent call last) <ipython-input-19-b3b7afb7aa88> in <module> 5 return len(url_param.scheme) > 0 6 ----> 7 print(is_url("http://[")) <ipython-input-19-b3b7afb7aa88> in is_url(text) 2 3 def is_url(text) : ----> 4 url_param = parse.urlparse(text) 5 return len(url_param.scheme) > 0 6 /usr/lib/python3.5/urllib/parse.py in urlparse(url, scheme, allow_fragments) 293 (e.g. netloc is a single string) and we don't expand % escapes.""" 294 url, scheme, _coerce_result = _coerce_args(url, scheme) --> 295 splitresult = urlsplit(url, scheme, allow_fragments) 296 scheme, netloc, url, query, fragment = splitresult 297 if scheme in uses_params and ';' in url: /usr/lib/python3.5/urllib/parse.py in urlsplit(url, scheme, allow_fragments) 343 if (('[' in netloc and ']' not in netloc) or 344 (']' in netloc and '[' not in netloc)): --> 345 raise ValueError("Invalid IPv6 URL") 346 if allow_fragments and '#' in url: 347 url, fragment = url.split('#', 1) ValueError: Invalid IPv6 URL
bug tracker
python bug trucker:課題33342
事象自体は本件とは別件ですが、起きているエラーは同一です。
課題として挙がっているのは、パスフレーズに[を含んだ場合であるらしい。
仕様ではどう扱われるか
公式:URL を解析して構成要素にする
公式リファレンスを覗くと、以下のように記されている。
なので、今回のValueErrorは必然であることが分かる。
netloc 属性にマッチしなかった角括弧があると ValueError を送出します。
その他の問題
ValueErrorも十分問題だが、もう1点問題がある。
それは、コロンが入った文字も「URLである」という判定になってしまう事です。
本来このような文字列はURLでもなんでもないので、URLとしてはFalseとなるべきです。
print(is_url("abcd:efgh")) # True
なぜそうなるか
またソースコードを覗きにいく。
urlparseは、内部でurlsplitを行っている。
分割する文字は「:」である為、「abcd:efgh」などの文字列でも正しくparseされる。
→schemeである[abcd]とnetlocの[efgh]へ分割
その結果、urlパラメータのスキーマ部分として、「abcd」が抽出され、実装コードのサイズ判定でTrueとなる。
return len(url_param.scheme) > 0
問題の解決
問題なのはわかった。
ではどうすべきか。
解決コード
import re URL_PTN = re.compile(r"^(http|https)://") def is_url(text) : return URL_PTN.match(text)
これで正しく判定できるようになった。
アドレス部分は今回興味がないので省いているが、参考を元にアドレスまで見ても良いかもしれない。
この場合、文字の先頭がURLであるかどうかを判定している。
参考
正規表現でURLを削除
正規表現で参考にしました。
【Python】URLかどうか調べる方法
今回の問題ソースを書く際に参考にしました。
まとめ
URLかどうかを判定したい場合、場合にもよるがurllibを使うべきではない。
単純に正規表現などで文字列比較をした方が、まだ求めたい結果が得られる。
ただ、こういうバリデーションはライブラリとかであってもいいのになぁと思う。