<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
- <version>1.1</version>
+ <version>1.1.1</version>
<configuration>
<rules>
<requireMavenVersion>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
- <version>2.12</version>
+ <version>2.12.1</version>
<configuration>
<skipTests>false</skipTests>
<enableAssertions>true</enableAssertions>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
- <version>2.3.3</version> <!-- 2.4.0 has BUG-->
+ <version>2.5.2</version>
<configuration>
<effort>Max</effort>
<threshold>Low</threshold>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
- <version>2.4</version>
+ <version>2.5</version>
<configuration>
<linkOnly>true</linkOnly>
<offline>true</offline>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
- <version>2.12</version>
+ <version>2.12.1</version>
<configuration>
<showSuccess>false</showSuccess>
</configuration>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
- <version>2.3.3</version> <!-- 2.4.0 has BUG-->
+ <version>2.5.2</version>
<configuration>
- <skip>true</skip>
+ <skip>false</skip>
<effort>Max</effort>
<threshold>Low</threshold>
<!-- excludeFilterFile/ -->
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
-import java.text.DateFormatSymbols;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.Locale;
-import java.util.TimeZone;
/**
* 人狼BBSアカウント管理用のCookie。
- * JRE1.6 HttpCookie の代用品。
+ * <p>JRE1.6 java.net.HttpCookie の代用品。
+ * <p>人狼BBSではCookieヘッダに"Set-Cookie"が用いられる。
+ * (Set-Cookie2ではない)
+ * <p>人狼BBSではCookieの寿命管理に"Expires"が用いられる。(Max-Ageではない)
+ * <p>人狼BBSではセッション管理のCookie名に"login"が用いられる。
+ * @see <a href="http://www.ietf.org/rfc/rfc6265.txt">RFC6265</a>
*/
class AccountCookie{ // TODO JRE 1.6対応とともにHttpCookieへ移行予定
- // 人狼BBSのCookie期限表記例: 「Thu, 26 Jun 2008 06:44:34 GMT」
- private static final String DATE_FORM = "EEE, dd MMM yyyy HH:mm:ss z";
- private static final SimpleDateFormat FORMAT;
-
- static{
- Calendar calendar = new GregorianCalendar();
- TimeZone zoneGMT = TimeZone.getTimeZone("GMT");
- DateFormatSymbols customSyms = new DateFormatSymbols();
- String[] sweekdays = customSyms.getShortWeekdays();
- sweekdays[Calendar.SUNDAY] = "Sun";
- sweekdays[Calendar.MONDAY] = "Mon";
- sweekdays[Calendar.TUESDAY] = "Tue";
- sweekdays[Calendar.WEDNESDAY] = "Wed";
- sweekdays[Calendar.THURSDAY] = "Thu";
- sweekdays[Calendar.FRIDAY] = "Fri";
- sweekdays[Calendar.SATURDAY] = "Sat";
- customSyms.setShortWeekdays(sweekdays);
- String[] months = customSyms.getShortMonths();
- months[Calendar.JANUARY] = "Jan";
- months[Calendar.FEBRUARY] = "Feb";
- months[Calendar.MARCH] = "Mar";
- months[Calendar.APRIL] = "Apr";
- months[Calendar.MAY] = "May";
- months[Calendar.JUNE] = "Jun";
- months[Calendar.JULY] = "Jul";
- months[Calendar.AUGUST] = "Aug";
- months[Calendar.SEPTEMBER] = "Sep";
- months[Calendar.OCTOBER] = "Oct";
- months[Calendar.NOVEMBER] = "Nov";
- months[Calendar.DECEMBER] = "Dec";
- customSyms.setShortMonths(months);
-
- FORMAT = new SimpleDateFormat(DATE_FORM, Locale.JAPAN);
- FORMAT.setCalendar(calendar);
- FORMAT.setTimeZone(zoneGMT);
- FORMAT.setDateFormatSymbols(customSyms);
- FORMAT.setLenient(true);
- }
+ private static final String HEADER_COOKIE = "Set-Cookie";
+ private static final String SEPARATOR = ";";
+ private static final String COOKIE_PATH = "Path";
+ private static final String COOKIE_EXPIRES = "Expires";
+ private static final String BBS_IDENTITY = "login";
+
private final String loginData;
private final URI pathURI;
- private final Date expireDate;
+ private final long expireDate;
/**
* 認証クッキーの生成。
* @throws java.lang.NullPointerException 引数がnull
* @throws java.lang.IllegalArgumentException パスが変
*/
- public AccountCookie(String loginData, String path, Date expireDate)
+ public AccountCookie(String loginData, String path, long expireDate)
throws NullPointerException, IllegalArgumentException{
super();
- if(loginData == null || path == null || expireDate == null){
+ if(loginData == null || path == null){
throw new NullPointerException();
}
}
/**
- * Cookie期限が切れてないか判定する。
- * @return 期限が切れていたらtrue
+ * 文字列両端に連続するWSP(空白もしくはタブ)を取り除く。
+ * @param txt テキスト
+ * @return 取り除いた結果。引数がnullならnull
*/
- public boolean hasExpired(){
- long nowMs = System.currentTimeMillis();
- long expireMs = this.expireDate.getTime();
- if(expireMs < nowMs) return true;
- return false;
+ static String chopWsp(String txt){
+ if(txt == null) return null;
+
+ int startPt = -1;
+ int endPt = -1;
+
+ int len = txt.length();
+ for(int idx = 0; idx < len; idx++){
+ char ch = txt.charAt(idx);
+ if(ch != '\u0020' && ch != '\t'){
+ if(startPt < 0) startPt = idx;
+ endPt = idx + 1;
+ }
+ }
+
+ if(startPt < 0) startPt = 0;
+ if(endPt < 0) endPt = 0;
+
+ String result = txt.substring(startPt, endPt);
+
+ return result;
}
/**
- * Cookieパスを返す。
- * @return Cookieパス
+ * Cookie属性を名前と値に分割する。
+ * '='が無い属性(例:Secure)は値がnullになり、=付き空文字列とは区別される。
+ * 余分なWSP空白はトリミングされる。
+ * @param pair '='を挟む名前と値のペア。
+ * @return [0]名前 [1]値 ※空文字列の名前およびnullな値がありうる。
*/
- public URI getPathURI(){
- return this.pathURI;
+ static String[] splitPair(String pair){
+ String[] result = new String[2];
+
+ String[] split = pair.split("=", 2);
+
+ if(split.length >= 1){
+ result[0] = chopWsp(split[0]);
+ }
+
+ if(split.length >= 2){
+ result[1] = chopWsp(split[1]);
+ }
+
+ return result;
}
/**
- * 認証データを返す。
- * @return 認証データ
+ * 人狼BBS用ログインデータを抽出する。
+ * Cookie名は"login"。
+ * @param nameValue '='で区切られたCookie name-value構造
+ * @return 人狼BBS用ログインデータ。見つからなければnull
*/
- public String getLoginData(){
- return this.loginData;
+ private static String parseLoginData(String nameValue){
+ String[] nvPair = splitPair(nameValue);
+ String name = nvPair[0];
+ String value = nvPair[1];
+ if(name.length() <= 0) return null; // 名前なし
+ if(value == null) return null; // '=' なし
+
+ if( ! BBS_IDENTITY.equals(name) ) return null;
+
+ return value;
}
/**
* 認証Cookieを抽出する。
- * @param cookieSource HTTPヘッダ 「Cookie=」の値
+ * @param cookieSource HTTPヘッダ 「Set-Cookie」の値
* @return 認証Cookie
*/
public static AccountCookie createCookie(String cookieSource){
- String[] cookieParts = cookieSource.split("; ");
- if(cookieParts.length <= 0) return null;
+ String[] cookiePart = cookieSource.split(SEPARATOR);
- String login = null;
+ String login = parseLoginData(cookiePart[0]);
String path = null;
String expires = null;
- for(String part : cookieParts){
- String[] nmval = part.split("=", 2);
- if(nmval == null) continue;
- if(nmval.length != 2) continue;
- String name = nmval[0];
- String value = nmval[1];
-
- if(name.equals("login")){
- login = value;
- }else if(name.equals("path")){
- path = value;
- }else if(name.equals("expires")){
- expires = value;
+
+ int partNo = cookiePart.length;
+ for(int idx = 0 + 1; idx < partNo; idx++){
+ String pair = cookiePart[idx];
+ String[] attr = splitPair(pair);
+ String attrName = attr[0];
+ String attrValue = attr[1];
+
+ if(COOKIE_PATH.equalsIgnoreCase(attrName)){
+ path = attrValue;
+ }else if(COOKIE_EXPIRES.equalsIgnoreCase(attrName)){
+ expires = attrValue;
}
}
- if(login == null || path == null || expires == null) return null;
- Date date;
- try{
- date = FORMAT.parse(expires);
- }catch(ParseException e){
- return null;
- }
+ if(login == null) return null;
+ if(path == null) return null;
+ if(expires == null) return null;
- AccountCookie cookie = new AccountCookie(login, path, date);
+ long expTime = CookieDateParser.parseToEpoch(expires);
+ if(expTime < 0L) return null;
+
+ AccountCookie cookie = new AccountCookie(login, path, expTime);
return cookie;
}
* @return 認証Cookie
*/
public static AccountCookie createCookie(HttpURLConnection connection){
- String cookieHeader = connection.getHeaderField("Set-Cookie");
+ String cookieHeader = connection.getHeaderField(HEADER_COOKIE);
if(cookieHeader == null) return null;
AccountCookie cookie = createCookie(cookieHeader);
return cookie;
}
/**
- * 認証Cookieの文字列表記。
- * @return String文字列
+ * 認証データを返す。
+ * 取り扱い注意!人狼BBSでは機密情報だよ!
+ * @return 認証データ
*/
- @Override
- public String toString(){
+ public String getLoginData(){
return this.loginData;
}
+ /**
+ * Cookieパスを返す。
+ * @return Cookieパス
+ */
+ public URI getPathURI(){
+ return this.pathURI;
+ }
+
+ /**
+ * Cookie期限をエポック時刻で返す。
+ * @return Cookieが期限切れを起こす日時。(msec)
+ */
+ public long getExpiredTime(){
+ return this.expireDate;
+ }
+
+ /**
+ * Cookie期限が切れてないか判定する。
+ * @param nowMs 比較時刻(msec)
+ * @return 期限が切れていたらtrue
+ */
+ public boolean hasExpired(long nowMs){
+ long expireMs = getExpiredTime();
+ if(expireMs < nowMs) return true;
+ return false;
+ }
+
+ /**
+ * 現時点でCookie期限が切れてないか判定する。
+ * @return 期限が切れていたらtrue
+ */
+ public boolean hasExpired(){
+ long nowMs = System.currentTimeMillis();
+ boolean result = hasExpired(nowMs);
+ return result;
+ }
+
}
--- /dev/null
+/*
+ * parser for Cookie-Date
+ *
+ * License : The MIT License
+ * Copyright(c) 2012 olyutorskii
+ */
+
+package jp.sfjp.jindolf.net;
+
+import java.text.DateFormatSymbols;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * 人狼BBS用Cookie日付のパースを行う。
+ * <p>デフォルトのロケールやタイムゾーンに依存しないよう設計される。
+ * <p>人狼BBSのCookie期限表記はRFC2616で"rfc-1123-date"として定義される。
+ * <p>例: 「Thu, 26 Jun 2008 06:44:34 GMT」
+ * @see <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>
+ */
+final class CookieDateParser{
+
+ private static final String DATE_PATTERN = "EEE, dd MMM yyyy HH:mm:ss z";
+ private static final Locale LOCALE_ROOT = new Locale("__", "", "");
+ private static final SimpleDateFormat PARSER;
+
+ static{
+ TimeZone zoneGMT = TimeZone.getTimeZone("GMT");
+ Calendar gcal = new GregorianCalendar(zoneGMT, LOCALE_ROOT);
+ DateFormatSymbols customSyms = buildSymbols();
+
+ PARSER = new SimpleDateFormat(DATE_PATTERN, LOCALE_ROOT);
+ PARSER.setTimeZone(zoneGMT);
+ PARSER.setCalendar(gcal);
+ PARSER.setDateFormatSymbols(customSyms);
+ PARSER.setLenient(true);
+ }
+
+
+ /**
+ * 隠しコンストラクタ。
+ */
+ private CookieDateParser(){
+ assert false;
+ }
+
+
+ /**
+ * 日付表記要素を定義する。
+ * @return 日付表記要素
+ */
+ private static DateFormatSymbols buildSymbols(){
+ DateFormatSymbols customSyms = new DateFormatSymbols(LOCALE_ROOT);
+
+ String[] sweekdays = customSyms.getShortWeekdays();
+ String[] smonths = customSyms.getShortMonths();
+
+ sweekdays[Calendar.SUNDAY ] = "Sun";
+ sweekdays[Calendar.MONDAY ] = "Mon";
+ sweekdays[Calendar.TUESDAY ] = "Tue";
+ sweekdays[Calendar.WEDNESDAY] = "Wed";
+ sweekdays[Calendar.THURSDAY ] = "Thu";
+ sweekdays[Calendar.FRIDAY ] = "Fri";
+ sweekdays[Calendar.SATURDAY ] = "Sat";
+
+ smonths[Calendar.JANUARY ] = "Jan";
+ smonths[Calendar.FEBRUARY ] = "Feb";
+ smonths[Calendar.MARCH ] = "Mar";
+ smonths[Calendar.APRIL ] = "Apr";
+ smonths[Calendar.MAY ] = "May";
+ smonths[Calendar.JUNE ] = "Jun";
+ smonths[Calendar.JULY ] = "Jul";
+ smonths[Calendar.AUGUST ] = "Aug";
+ smonths[Calendar.SEPTEMBER] = "Sep";
+ smonths[Calendar.OCTOBER ] = "Oct";
+ smonths[Calendar.NOVEMBER ] = "Nov";
+ smonths[Calendar.DECEMBER ] = "Dec";
+
+ customSyms.setShortWeekdays(sweekdays);
+ customSyms.setShortMonths(smonths);
+
+ return customSyms;
+ }
+
+ /**
+ * 日付文字列をパースする。
+ * @param txt 文字列
+ * @return エポック時刻(msec)。不正な文字列の場合は負の数を返す。
+ */
+ public static long parseToEpoch(String txt){
+ Date date = parseToDate(txt);
+ if(date == null) return -1L;
+
+ long result = date.getTime();
+
+ return result;
+ }
+
+ /**
+ * 日付文字列をパースする。
+ * @param txt 文字列
+ * @return 日付。不正な文字列の場合はnullを返す。
+ */
+ public static Date parseToDate(String txt){
+ Date result;
+
+ try{
+ synchronized(PARSER){
+ result = PARSER.parse(txt);
+ }
+ }catch(ParseException e){
+ return null;
+ }
+
+ return result;
+ }
+
+}
--- /dev/null
+/*
+ * AccountCookie Test
+ *
+ * Copyright(c) 2012 olyutorskii
+ */
+package jp.sfjp.jindolf.net;
+
+import java.net.URI;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class AccountCookieTest {
+
+ public AccountCookieTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() {
+ }
+
+ @AfterClass
+ public static void tearDownClass() {
+ }
+
+ @Before
+ public void setUp() {
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ /**
+ * Test of chopWsp method, of class AccountCookie.
+ */
+ @Test
+ public void testChopWsp_String() {
+ System.out.println("chopWsp");
+
+ String result;
+
+ result = AccountCookie.chopWsp(" abc ");
+ assertEquals("abc", result);
+
+ result = AccountCookie.chopWsp("abc");
+ assertEquals("abc", result);
+
+ result = AccountCookie.chopWsp(" abc");
+ assertEquals("abc", result);
+
+ result = AccountCookie.chopWsp("abc ");
+ assertEquals("abc", result);
+
+ result = AccountCookie.chopWsp("a b c");
+ assertEquals("a b c", result);
+
+ result = AccountCookie.chopWsp("\t abc \t");
+ assertEquals("abc", result);
+
+ result = AccountCookie.chopWsp("");
+ assertEquals("", result);
+
+ result = AccountCookie.chopWsp(" ");
+ assertEquals("", result);
+
+ result = AccountCookie.chopWsp(null);
+ assertNull(result);
+
+ return;
+ }
+
+ /**
+ * Test of splitPair method, of class AccountCookie.
+ */
+ @Test
+ public void testSplitPair_String() {
+ System.out.println("splitPair");
+
+ String[] result;
+
+ result = AccountCookie.splitPair("a=b");
+ assertEquals(2, result.length);
+ assertEquals("a", result[0]);
+ assertEquals("b", result[1]);
+
+ result = AccountCookie.splitPair("=b");
+ assertEquals(2, result.length);
+ assertEquals("", result[0]);
+ assertEquals("b", result[1]);
+
+ result = AccountCookie.splitPair("a=");
+ assertEquals(2, result.length);
+ assertEquals("a", result[0]);
+ assertEquals("", result[1]);
+
+ result = AccountCookie.splitPair("=");
+ assertEquals(2, result.length);
+ assertEquals("", result[0]);
+ assertEquals("", result[1]);
+
+ result = AccountCookie.splitPair("==");
+ assertEquals(2, result.length);
+ assertEquals("", result[0]);
+ assertEquals("=", result[1]);
+
+ result = AccountCookie.splitPair("a===b");
+ assertEquals(2, result.length);
+ assertEquals("a", result[0]);
+ assertEquals("==b", result[1]);
+
+ result = AccountCookie.splitPair(" a = b ");
+ assertEquals(2, result.length);
+ assertEquals("a", result[0]);
+ assertEquals("b", result[1]);
+
+ result = AccountCookie.splitPair("a");
+ assertEquals(2, result.length);
+ assertEquals("a", result[0]);
+ assertNull(result[1]);
+
+ result = AccountCookie.splitPair("");
+ assertEquals(2, result.length);
+ assertEquals("", result[0]);
+ assertNull(result[1]);
+
+ return;
+ }
+
+ /**
+ * Test of createCookie method, of class AccountCookie.
+ */
+ @Test
+ public void testCreateCookie_String() {
+ System.out.println("createCookie");
+
+ String cookieSource;
+ AccountCookie result;
+
+ cookieSource =
+ "login=%22XXX%22;"
+ + " path=/;"
+ + " expires=Sun, 09 Sep 2012 14:16:26 GMT";
+
+ result = AccountCookie.createCookie(cookieSource);
+
+ assertEquals("%22XXX%22", result.getLoginData());
+ assertEquals(URI.create("/"), result.getPathURI());
+ assertEquals(1347200186000L, result.getExpiredTime());
+
+ assertFalse(result.hasExpired(1347200185999L));
+ assertFalse(result.hasExpired(1347200186000L));
+ assertTrue (result.hasExpired(1347200186001L));
+
+ return;
+ }
+
+}
--- /dev/null
+/*
+ * CookieDateParser Test
+ *
+ * Copyright(c) 2012 olyutorskii
+ */
+package jp.sfjp.jindolf.net;
+
+import java.util.Date;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class CookieDateParserTest {
+
+ public CookieDateParserTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() {
+ }
+
+ @AfterClass
+ public static void tearDownClass() {
+ }
+
+ @Before
+ public void setUp() {
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ /**
+ * Test of parseToEpoch method, of class CookieDateParser.
+ */
+ @Test
+ public void testParseToEpoch() {
+ System.out.println("parseToEpoch");
+
+ long result;
+
+ result = CookieDateParser
+ .parseToEpoch("Thu, 1 Jan 1970 00:00:00 GMT");
+ assertEquals(0L, result);
+
+ result = CookieDateParser
+ .parseToEpoch("Thu, 26 Jun 2008 06:44:34 GMT");
+ assertEquals(1214462674000L, result);
+
+ result = CookieDateParser
+ .parseToEpoch("Thu, 26 Jun 2008 06:44:34 JST");
+ assertEquals(1214430274000L, result);
+
+ result = CookieDateParser
+ .parseToEpoch("ThuX, 26 Jun 2008 06:44:34 JST");
+ assertTrue(result < 0L);
+
+ return;
+ }
+
+ /**
+ * Test of parseToDate method, of class CookieDateParser.
+ */
+ @Test
+ public void testParseToDate() {
+ System.out.println("parseToDate");
+
+ Date result;
+
+ result = CookieDateParser
+ .parseToDate("Thu, 1 Jan 1970 00:00:00 GMT");
+ assertEquals(new Date(0L), result);
+
+ result = CookieDateParser
+ .parseToDate("Thu, 26 Jun 2008 06:44:34 GMT");
+ assertEquals(new Date(1214462674000L), result);
+
+ result = CookieDateParser
+ .parseToDate("ThuX, 26 Jun 2008 06:44:34 GMT");
+ assertNull(result);
+
+ return;
+ }
+
+}