diff --git a/app/build.gradle b/app/build.gradle index d4fc7c292..bcc651249 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,6 +55,7 @@ dependencies { compile "com.android.support:appcompat-v7:$supportLibrary" compile "com.android.support:design:$supportLibrary" compile 'com.github.amlcurran.showcaseview:library:5.4.3' + compile 'com.github.seven332:android-recaptcha:2bbb3459a8' compile 'com.github.seven332:animator:0.1.0' compile 'com.github.seven332:beerbelly:0.1.4' compile 'com.github.seven332:conaco:0.1.5-eh' @@ -79,4 +80,6 @@ dependencies { compile 'org.greenrobot:greendao:2.2.0' compile 'org.jsoup:jsoup:1.9.2' testCompile 'junit:junit:4.12' + testCompile 'org.robolectric:robolectric:3.4' + testCompile 'org.jooq:joor:0.9.6' } diff --git a/app/src/main/java/com/hippo/database/MSQLiteBuilder.java b/app/src/main/java/com/hippo/database/MSQLiteBuilder.java new file mode 100644 index 000000000..956a881b0 --- /dev/null +++ b/app/src/main/java/com/hippo/database/MSQLiteBuilder.java @@ -0,0 +1,130 @@ +/* + * Copyright 2017 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.database; + +/* + * Created by Hippo on 2017/9/3. + */ + +import android.content.Context; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.SparseArray; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MSQLiteBuilder { + + public static final String COLUMN_ID = "_id"; + + private static final Map JAVA_TYPE_TO_SQLITE_TYPE = new HashMap<>(); + + static { + JAVA_TYPE_TO_SQLITE_TYPE.put(boolean.class, "INTEGER NOT NULL DEFAULT 0"); + JAVA_TYPE_TO_SQLITE_TYPE.put(byte.class, "INTEGER NOT NULL DEFAULT 0"); + JAVA_TYPE_TO_SQLITE_TYPE.put(short.class, "INTEGER NOT NULL DEFAULT 0"); + JAVA_TYPE_TO_SQLITE_TYPE.put(int.class, "INTEGER NOT NULL DEFAULT 0"); + JAVA_TYPE_TO_SQLITE_TYPE.put(long.class, "INTEGER NOT NULL DEFAULT 0"); + JAVA_TYPE_TO_SQLITE_TYPE.put(float.class, "REAL NOT NULL DEFAULT 0"); + JAVA_TYPE_TO_SQLITE_TYPE.put(double.class, "REAL NOT NULL DEFAULT 0"); + JAVA_TYPE_TO_SQLITE_TYPE.put(String.class, "TEXT"); + } + + private static String javaTypeToSQLiteType(Class clazz) { + String type = JAVA_TYPE_TO_SQLITE_TYPE.get(clazz); + if (type == null) { + throw new IllegalStateException("Unknown type: " + clazz); + } + return type; + } + + private int version = 0; + private List statements; + private SparseArray> statementsMap = new SparseArray<>(); + + /** + * Bump database version. + */ + public MSQLiteBuilder version(int version) { + if (version <= this.version) { + throw new IllegalStateException("New version must be bigger than current version. " + + "current version: " + this.version + ", new version: " + version + "."); + } + this.version = version; + this.statements = new ArrayList<>(); + this.statementsMap.put(version, this.statements); + return this; + } + + /** + * Creates a table with int {@link #COLUMN_ID} primary key. + */ + public MSQLiteBuilder createTable(String table) { + return createTable(table, COLUMN_ID, int.class); + } + + /** + * Creates a table. + */ + public MSQLiteBuilder createTable(String table, String column, Class clazz) { + return statement("CREATE TABLE " + table + " (" + column + " " + javaTypeToSQLiteType(clazz) + " PRIMARY KEY);"); + } + + /** + * Drops a table. + */ + public MSQLiteBuilder dropTable(String table) { + return statement("DROP TABLE " + table + ";"); + } + + /** + * Inserts a column to the table. + */ + public MSQLiteBuilder insertColumn(String table, String column, Class clazz) { + return statement("ALTER TABLE " + table + " ADD COLUMN " + column + " " + javaTypeToSQLiteType(clazz) + ";"); + } + + /** + * Add a statement. + */ + public MSQLiteBuilder statement(String statement) { + if (version == 0 || statements == null) { + throw new IllegalStateException("Call version() first!"); + } + statements.add(statement); + return this; + } + + /** + * Build a SQLiteOpenHelper from it. + */ + public SQLiteOpenHelper build(Context context, String name, int version) { + return new MSQLiteOpenHelper(context, name, version, this); + } + + List getStatements(int oldVersion, int newVersion) { + List result = new ArrayList<>(); + for (int i = oldVersion + 1; i <= newVersion; ++i) { + List list = statementsMap.get(i); + if (list != null) { + result.addAll(list); + } + } + return result; + } +} diff --git a/app/src/main/java/com/hippo/database/MSQLiteOpenHelper.java b/app/src/main/java/com/hippo/database/MSQLiteOpenHelper.java new file mode 100644 index 000000000..57cd40b84 --- /dev/null +++ b/app/src/main/java/com/hippo/database/MSQLiteOpenHelper.java @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.database; + +/* + * Created by Hippo on 2017/9/4. + */ + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import com.hippo.util.SqlUtils; + +class MSQLiteOpenHelper extends SQLiteOpenHelper { + + private final int version; + private final MSQLiteBuilder builder; + + public MSQLiteOpenHelper(Context context, String name, int version, MSQLiteBuilder builder) { + super(context, name, null, version); + this.version = version; + this.builder = builder; + } + + @Override + public void onCreate(SQLiteDatabase db) { + onUpgrade(db, 0, version); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + for (String command : builder.getStatements(oldVersion, newVersion)) { + db.execSQL(command); + } + } + + @Override + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + SqlUtils.dropAllTable(db); + onCreate(db); + } +} diff --git a/app/src/main/java/com/hippo/drawable/TextDrawable.java b/app/src/main/java/com/hippo/drawable/TextDrawable.java new file mode 100644 index 000000000..4aefc9f12 --- /dev/null +++ b/app/src/main/java/com/hippo/drawable/TextDrawable.java @@ -0,0 +1,140 @@ +/* + * Copyright 2017 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.drawable; + +/* + * Created by Hippo on 2017/9/6. + */ + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.annotation.IntRange; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +public class TextDrawable extends Drawable { + + private static final float STANDARD_TEXT_SIZE = 1000.0f; + private static final Paint STANDARD_PAINT = new Paint(); + + static { + STANDARD_PAINT.setTextSize(STANDARD_TEXT_SIZE); + } + + private String text; + private float contentPercent; + + private Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + private int textColor = Color.BLACK; + private int backgroundColor = Color.BLACK; + + private float x = 0.0f; + private float y = 0.0f; + private boolean textSizeDirty = false; + private Rect textBounds = new Rect(); + + public TextDrawable(String text, float contentPercent) { + this.text = text; + this.contentPercent = contentPercent; + STANDARD_PAINT.getTextBounds(text, 0, text.length(), textBounds); + } + + public int getTextColor() { + return textColor; + } + + public void setTextColor(int textColor) { + if (this.textColor != textColor) { + this.textColor = textColor; + invalidateSelf(); + } + } + + public int getBackgroundColor() { + return backgroundColor; + } + + public void setBackgroundColor(int backgroundColor) { + if (this.backgroundColor != backgroundColor) { + this.backgroundColor = backgroundColor; + invalidateSelf(); + } + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + textSizeDirty = true; + } + + private void updateTextSizeIfDirty(Rect bounds) { + if (!textSizeDirty) { + return; + } + textSizeDirty = false; + + int contentWidth = (int) (bounds.width() * contentPercent); + int contentHeight = (int) (bounds.height() * contentPercent); + float widthRatio = (float) contentWidth / (float) textBounds.width(); + float heightRatio = (float) contentHeight / (float) textBounds.height(); + float ratio = Math.min(widthRatio, heightRatio); + float textSize = STANDARD_TEXT_SIZE * ratio; + textPaint.setTextSize(textSize); + x = (bounds.width() - textBounds.width() * ratio) / 2 - textBounds.left * ratio; + y = (bounds.height() - textBounds.height() * ratio) / 2 - textBounds.top * ratio; + } + + @Override + public void draw(@NonNull Canvas canvas) { + Rect bounds = getBounds(); + if (!bounds.isEmpty()) { + // Draw background + backgroundPaint.setColor(backgroundColor); + canvas.drawRect(bounds, backgroundPaint); + + if (!TextUtils.isEmpty(text)) { + // Draw text + updateTextSizeIfDirty(bounds); + textPaint.setColor(textColor); + canvas.drawText(text, x, y, textPaint); + } + } + } + + @Override + public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {} + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) { + backgroundPaint.setColorFilter(colorFilter); + textPaint.setColorFilter(colorFilter); + invalidateSelf(); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/EhApplication.java b/app/src/main/java/com/hippo/ehviewer/EhApplication.java index 07f3e798d..4eb273b9f 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhApplication.java +++ b/app/src/main/java/com/hippo/ehviewer/EhApplication.java @@ -32,7 +32,6 @@ import com.hippo.conaco.Conaco; import com.hippo.ehviewer.client.EhClient; import com.hippo.ehviewer.client.EhCookieStore; import com.hippo.ehviewer.client.EhEngine; -import com.hippo.ehviewer.client.EhUrl; import com.hippo.ehviewer.client.data.GalleryDetail; import com.hippo.ehviewer.download.DownloadManager; import com.hippo.ehviewer.spider.SpiderDen; @@ -40,7 +39,6 @@ import com.hippo.ehviewer.ui.CommonOperations; import com.hippo.image.Image; import com.hippo.image.ImageBitmap; import com.hippo.network.StatusCodeException; -import com.hippo.okhttp.CookieDB; import com.hippo.scene.SceneApplication; import com.hippo.text.Html; import com.hippo.unifile.UniFile; @@ -58,8 +56,6 @@ import java.util.HashMap; import java.util.List; import java.util.concurrent.TimeUnit; -import okhttp3.Cookie; -import okhttp3.HttpUrl; import okhttp3.OkHttpClient; public class EhApplication extends SceneApplication implements Thread.UncaughtExceptionHandler { @@ -97,7 +93,6 @@ public class EhApplication extends SceneApplication implements Thread.UncaughtEx super.onCreate(); GetText.initialize(this); - CookieDB.initialize(this); StatusCodeException.initialize(this); Settings.initialize(this); ReadableTime.initialize(this); @@ -159,29 +154,6 @@ public class EhApplication extends SceneApplication implements Thread.UncaughtEx if (version < 52) { Settings.putGuideGallery(true); } - if (version < 56) { // Make cookie long live - HttpUrl eUrl = HttpUrl.parse(EhUrl.HOST_E); - HttpUrl exUrl = HttpUrl.parse(EhUrl.HOST_EX); - EhCookieStore cookieStore = getEhCookieStore(this); - - Cookie c; - c = cookieStore.get(eUrl, EhCookieStore.KEY_IPD_MEMBER_ID); - if (null != c) { - cookieStore.add(EhCookieStore.newCookie(c, c.domain(), true, true, true)); - } - c = cookieStore.get(eUrl, EhCookieStore.KEY_IPD_PASS_HASH); - if (null != c) { - cookieStore.add(EhCookieStore.newCookie(c, c.domain(), true, true, true)); - } - c = cookieStore.get(exUrl, EhCookieStore.KEY_IPD_MEMBER_ID); - if (null != c) { - cookieStore.add(EhCookieStore.newCookie(c, c.domain(), true, true, true)); - } - c = cookieStore.get(exUrl, EhCookieStore.KEY_IPD_PASS_HASH); - if (null != c) { - cookieStore.add(EhCookieStore.newCookie(c, c.domain(), true, true, true)); - } - } } public void clearMemoryCache() { @@ -243,7 +215,7 @@ public class EhApplication extends SceneApplication implements Thread.UncaughtEx public static EhCookieStore getEhCookieStore(@NonNull Context context) { EhApplication application = ((EhApplication) context.getApplicationContext()); if (application.mEhCookieStore == null) { - application.mEhCookieStore = new EhCookieStore(); + application.mEhCookieStore = new EhCookieStore(context); } return application.mEhCookieStore; } diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java index 056ae8395..1ae3385be 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java @@ -153,7 +153,7 @@ public class EhClient { try { switch (mMethod) { case METHOD_SIGN_IN: - return EhEngine.signIn(this, mOkHttpClient, (String) params[0], (String) params[1]); + return EhEngine.signIn(this, mOkHttpClient, (String) params[0], (String) params[1], (String) params[2], (String) params[3]); case METHOD_GET_GALLERY_LIST: return EhEngine.getGalleryList(this, mOkHttpClient, (String) params[0]); case METHOD_FILL_GALLERY_LIST_BY_API: diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhCookieStore.java b/app/src/main/java/com/hippo/ehviewer/client/EhCookieStore.java index 274b5f927..089d43432 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhCookieStore.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhCookieStore.java @@ -16,7 +16,9 @@ package com.hippo.ehviewer.client; -import com.hippo.okhttp.CookieDBStore; +import android.content.Context; + +import com.hippo.network.CookieRepository; import java.util.ArrayList; import java.util.Collections; @@ -26,7 +28,7 @@ import okhttp3.Cookie; import okhttp3.HttpUrl; import okhttp3.Request; -public class EhCookieStore extends CookieDBStore { +public class EhCookieStore extends CookieRepository { public static final String KEY_IPD_MEMBER_ID = "ipb_member_id"; public static final String KEY_IPD_PASS_HASH = "ipb_pass_hash"; @@ -41,13 +43,18 @@ public class EhCookieStore extends CookieDBStore { .expiresAt(Long.MAX_VALUE) .build(); + public EhCookieStore(Context context) { + super(context, "okhttp3-cookie.db"); + } + public void signOut() { - removeAll(); + clear(); } public boolean hasSignedIn() { - return contain(EhUrl.DOMAIN_EX, KEY_IPD_MEMBER_ID) && - contain(EhUrl.DOMAIN_EX, KEY_IPD_PASS_HASH); + HttpUrl url = HttpUrl.parse(EhUrl.HOST_E); + return contains(url, KEY_IPD_MEMBER_ID) && + contains(url, KEY_IPD_PASS_HASH); } public static Cookie newCookie(Cookie cookie, String newDomain, boolean forcePersistent, @@ -82,10 +89,9 @@ public class EhCookieStore extends CookieDBStore { public List loadForRequest(HttpUrl url, Request request) { List cookies = super.loadForRequest(url, request); Object tag = request.tag(); - String host = url.host(); - boolean checkTips = domainMatch(host, EhUrl.DOMAIN_E); - boolean checkUconfig = (domainMatch(host, EhUrl.DOMAIN_E) || domainMatch(host, EhUrl.DOMAIN_EX)) && tag instanceof EhConfig; + boolean checkTips = domainMatch(url, EhUrl.DOMAIN_E); + boolean checkUconfig = (domainMatch(url, EhUrl.DOMAIN_E) || domainMatch(url, EhUrl.DOMAIN_EX)) && tag instanceof EhConfig; if (checkTips || checkUconfig) { List result = new ArrayList<>(cookies.size() + 1); @@ -109,7 +115,7 @@ public class EhCookieStore extends CookieDBStore { Cookie uconfigCookie = new Cookie.Builder() .name(EhConfig.KEY_UCONFIG) .value(ehConfig.uconfig()) - .domain(host) + .domain(url.host()) .path("/") .expiresAt(Long.MAX_VALUE) .build(); @@ -120,26 +126,4 @@ public class EhCookieStore extends CookieDBStore { return cookies; } } - - @Override - public void saveFromResponse(HttpUrl url, List cookies) { - List result = new ArrayList<>(cookies.size() + 2); - - for (Cookie cookie: cookies) { - if (EhUrl.DOMAIN_E.equals(cookie.domain())) { - // Save id and hash for exhentai - if (KEY_IPD_MEMBER_ID.equals(cookie.name()) || - KEY_IPD_PASS_HASH.equals(cookie.name()) || - KEY_IGNEOUS.equals(cookie.name())) { - result.add(newCookie(cookie, EhUrl.DOMAIN_E, true, true, true)); - result.add(newCookie(cookie, EhUrl.DOMAIN_EX, true, true, true)); - continue; - } - } - - result.add(cookie); - } - - super.saveFromResponse(url, Collections.unmodifiableList(result)); - } } diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java index 8857c6fee..f52595aad 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java @@ -17,6 +17,7 @@ package com.hippo.ehviewer.client; import android.support.annotation.Nullable; +import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -120,13 +121,17 @@ public class EhEngine { } public static String signIn(@Nullable EhClient.Task task, OkHttpClient okHttpClient, - String username, String password) throws Exception { + String username, String password, String recaptchaChallenge, String recaptchaResponse) throws Exception { FormBody.Builder builder = new FormBody.Builder() .add("UserName", username) .add("PassWord", password) .add("submit", "Log me in") .add("CookieDate", "1") .add("temporary_https", "off"); + if (!TextUtils.isEmpty(recaptchaChallenge) && !TextUtils.isEmpty(recaptchaResponse)) { + builder.add("recaptcha_challenge_field", recaptchaChallenge); + builder.add("recaptcha_response_field", recaptchaResponse); + } String url = EhUrl.API_SIGN_IN; Log.d(TAG, url); Request request = new EhRequestBuilder(url, null != task ? task.getEhConfig() : Settings.getEhConfig()) diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/CookieSignInScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/CookieSignInScene.java index efd5f8d5d..ab3f72d5c 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/CookieSignInScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/CookieSignInScene.java @@ -30,16 +30,13 @@ import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.TextView; - import com.hippo.ehviewer.EhApplication; import com.hippo.ehviewer.R; import com.hippo.ehviewer.client.EhCookieStore; import com.hippo.ehviewer.client.EhUrl; import com.hippo.ehviewer.client.EhUtils; import com.hippo.yorozuya.ViewUtils; - import junit.framework.Assert; - import okhttp3.Cookie; public class CookieSignInScene extends SolidScene implements EditText.OnEditorActionListener, @@ -241,11 +238,11 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc EhUtils.signOut(context); EhCookieStore store = EhApplication.getEhCookieStore(context); - store.add(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_E)); - store.add(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_EX)); - store.add(newCookie(EhCookieStore.KEY_IPD_PASS_HASH, hash, EhUrl.DOMAIN_E)); - store.add(newCookie(EhCookieStore.KEY_IPD_PASS_HASH, hash, EhUrl.DOMAIN_EX)); - store.add(newCookie(EhCookieStore.KEY_IGNEOUS, igneous, EhUrl.DOMAIN_E)); - store.add(newCookie(EhCookieStore.KEY_IGNEOUS, igneous, EhUrl.DOMAIN_EX)); + store.addCookie(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_E)); + store.addCookie(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_EX)); + store.addCookie(newCookie(EhCookieStore.KEY_IPD_PASS_HASH, hash, EhUrl.DOMAIN_E)); + store.addCookie(newCookie(EhCookieStore.KEY_IPD_PASS_HASH, hash, EhUrl.DOMAIN_EX)); + store.addCookie(newCookie(EhCookieStore.KEY_IGNEOUS, igneous, EhUrl.DOMAIN_E)); + store.addCookie(newCookie(EhCookieStore.KEY_IGNEOUS, igneous, EhUrl.DOMAIN_EX)); } } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/SignInScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/SignInScene.java index c439143ac..d1eb2684f 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/SignInScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/SignInScene.java @@ -40,6 +40,7 @@ import com.hippo.ehviewer.client.EhUrl; import com.hippo.ehviewer.client.EhUtils; import com.hippo.ehviewer.client.parser.ProfileParser; import com.hippo.ehviewer.ui.MainActivity; +import com.hippo.ehviewer.widget.RecaptchaView; import com.hippo.scene.Announcer; import com.hippo.scene.SceneFragment; import com.hippo.yorozuya.IntIdGenerator; @@ -68,6 +69,8 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc private EditText mUsername; @Nullable private EditText mPassword; + private EditText mRecaptcha; + private RecaptchaView mRecaptchaView; @Nullable private View mRegister; @Nullable @@ -125,6 +128,8 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc mPasswordLayout = (TextInputLayout) ViewUtils.$$(loginForm, R.id.password_layout); mPassword = mPasswordLayout.getEditText(); Assert.assertNotNull(mPassword); + mRecaptcha = (EditText) ViewUtils.$$(loginForm, R.id.recaptcha); + mRecaptchaView = (RecaptchaView) ViewUtils.$$(loginForm, R.id.recaptcha_image); mRegister = ViewUtils.$$(loginForm, R.id.register); mSignIn = ViewUtils.$$(loginForm, R.id.sign_in); mSignInViaWebView = (TextView) ViewUtils.$$(loginForm, R.id.sign_in_via_webview); @@ -280,12 +285,15 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc // Clean up for sign in EhUtils.signOut(context); + String challenge = mRecaptchaView.getChallenge(); + String response = mRecaptcha.getText().toString(); + EhCallback callback = new SignInListener(context, activity.getStageId(), getTag()); mRequestId = ((EhApplication) context.getApplicationContext()).putGlobalStuff(callback); EhRequest request = new EhRequest() .setMethod(EhClient.METHOD_SIGN_IN) - .setArgs(username, password) + .setArgs(username, password, challenge, response) .setCallback(callback); EhApplication.getEhClient(context).execute(request); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/WebViewSignInScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/WebViewSignInScene.java index 6b7601670..e33b4b21c 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/WebViewSignInScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/WebViewSignInScene.java @@ -28,18 +28,14 @@ import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import android.webkit.WebView; import android.webkit.WebViewClient; - import com.hippo.ehviewer.EhApplication; import com.hippo.ehviewer.client.EhCookieStore; import com.hippo.ehviewer.client.EhUrl; import com.hippo.ehviewer.client.EhUtils; - -import junit.framework.Assert; - import java.util.ArrayList; import java.util.Collections; import java.util.List; - +import junit.framework.Assert; import okhttp3.Cookie; import okhttp3.HttpUrl; @@ -122,7 +118,7 @@ public class WebViewSignInScene extends SolidScene { } private void addCookie(Context context, String domain, Cookie cookie) { - EhApplication.getEhCookieStore(context).add(EhCookieStore.newCookie(cookie, domain, true, true, true)); + EhApplication.getEhCookieStore(context).addCookie(EhCookieStore.newCookie(cookie, domain, true, true, true)); } @Override @@ -149,6 +145,9 @@ public class WebViewSignInScene extends SolidScene { getHash = true; addCookie(context, EhUrl.DOMAIN_EX, cookie); addCookie(context, EhUrl.DOMAIN_E, cookie); + } else if (EhCookieStore.KEY_IGNEOUS.equals(cookie.name())) { + addCookie(context, EhUrl.DOMAIN_EX, cookie); + addCookie(context, EhUrl.DOMAIN_E, cookie); } } diff --git a/app/src/main/java/com/hippo/ehviewer/widget/RecaptchaView.java b/app/src/main/java/com/hippo/ehviewer/widget/RecaptchaView.java new file mode 100644 index 000000000..8598a4ab1 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/RecaptchaView.java @@ -0,0 +1,183 @@ +/* + * Copyright 2017 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +/* + * Created by Hippo on 2017/8/20. + */ + +import android.content.Context; +import android.graphics.Color; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +import com.hippo.android.recaptcha.RecaptchaV1; +import com.hippo.drawable.TextDrawable; +import com.hippo.ehviewer.R; +import com.hippo.widget.LoadImageView; +import com.hippo.yorozuya.SimpleHandler; + +public class RecaptchaView extends LoadImageView implements RecaptchaV1.RecaptchaCallback, View.OnClickListener { + + private static final String CHALLENGE = "6LdtfgYAAAAAALjIPPiCgPJJah8MhAUpnHcKF8u_"; + + private boolean loading = false; + private String challenge; + private String image; + + private TextDrawable waitingDrawable; + private TextDrawable loadingDrawable; + private TextDrawable failureDrawable; + + public RecaptchaView(Context context) { + super(context); + init(); + } + + public RecaptchaView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + setOnClickListener(this); + if (waitingDrawable == null) { + waitingDrawable = new TextDrawable(getContext().getString(R.string.recaptcha_none), 0.6f); + waitingDrawable.setBackgroundColor(Color.GRAY); + waitingDrawable.setTextColor(Color.WHITE); + } + load(waitingDrawable); + } + + public String getChallenge() { + return challenge; + } + + public void load() { + if (loading) { + return; + } + loading = false; + challenge = null; + image = null; + + if (loadingDrawable == null) { + loadingDrawable = new TextDrawable(getContext().getString(R.string.recaptcha_loading), 0.6f); + loadingDrawable.setBackgroundColor(Color.GRAY); + loadingDrawable.setTextColor(Color.WHITE); + } + load(loadingDrawable); + + RecaptchaV1.recaptcha(getContext(), CHALLENGE, SimpleHandler.getInstance(), this); + } + + @Override + public void onClick(@NonNull View v) { + load(); + } + + @Override + public void onSuccess(@NonNull String challenge, @NonNull String image) { + this.loading = false; + this.challenge = challenge; + this.image = image; + + load(image, image); + } + + @Override + public void onFailure() { + this.loading = false; + this.challenge = null; + this.image = null; + + if (failureDrawable == null) { + failureDrawable = new TextDrawable(getContext().getString(R.string.recaptcha_failure), 0.6f); + failureDrawable.setBackgroundColor(Color.GRAY); + failureDrawable.setTextColor(Color.WHITE); + } + load(failureDrawable); + } + + @Override + protected Parcelable onSaveInstanceState() { + SavedState ss = new SavedState(super.onSaveInstanceState()); + ss.loading = loading; + ss.challenge = challenge; + ss.image = image; + return ss; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof SavedState)) { + super.onRestoreInstanceState(state); + return; + } + + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + + if (ss.loading) { + load(); + } else if (!TextUtils.isEmpty(ss.challenge) && !TextUtils.isEmpty(ss.image)) { + onSuccess(ss.challenge, ss.image); + } + } + + private static class SavedState extends BaseSavedState { + + private boolean loading; + private String challenge; + private String image; + + public SavedState(Parcelable superState) { + super(superState); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeByte(loading ? (byte) 1 : (byte) 0); + out.writeString(challenge); + out.writeString(image); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + + public SavedState(Parcel source) { + super(source); + loading = source.readByte() != 0; + challenge = source.readString(); + image = source.readString(); + } + } +} diff --git a/app/src/main/java/com/hippo/network/CookieDatabase.java b/app/src/main/java/com/hippo/network/CookieDatabase.java new file mode 100644 index 000000000..c67ca35a8 --- /dev/null +++ b/app/src/main/java/com/hippo/network/CookieDatabase.java @@ -0,0 +1,236 @@ +/* + * Copyright 2017 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.network; + +/* + * Created by Hippo on 2017/9/4. + */ + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteStatement; +import android.support.annotation.Nullable; +import android.util.Log; +import com.hippo.database.MSQLiteBuilder; +import com.hippo.util.SqlUtils; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.Cookie; + +class CookieDatabase { + + private static final String LOG_TAG = CookieDatabase.class.getSimpleName(); + + private static final int VERSION_1 = 1; + private static final String TABLE_COOKIE = "OK_HTTP_3_COOKIE"; + private static final String COLUMN_ID = "_id"; + private static final String COLUMN_NAME = "NAME"; + private static final String COLUMN_VALUE = "VALUE"; + private static final String COLUMN_EXPIRES_AT = "EXPIRES_AT"; + private static final String COLUMN_DOMAIN = "DOMAIN"; + private static final String COLUMN_PATH = "PATH"; + private static final String COLUMN_SECURE = "SECURE"; + private static final String COLUMN_HTTP_ONLY = "HTTP_ONLY"; + private static final String COLUMN_PERSISTENT = "PERSISTENT"; + private static final String COLUMN_HOST_ONLY = "HOST_ONLY"; + + private static final int DB_VERSION = VERSION_1; + + private final Map cookieIdMap = new HashMap<>(); + private final SQLiteOpenHelper helper; + private final SQLiteDatabase db; + + public CookieDatabase(Context context, String name) { + helper = new MSQLiteBuilder() + .version(VERSION_1) + .createTable(TABLE_COOKIE) + .insertColumn(TABLE_COOKIE, COLUMN_NAME, String.class) + .insertColumn(TABLE_COOKIE, COLUMN_VALUE, String.class) + .insertColumn(TABLE_COOKIE, COLUMN_EXPIRES_AT, long.class) + .insertColumn(TABLE_COOKIE, COLUMN_DOMAIN, String.class) + .insertColumn(TABLE_COOKIE, COLUMN_PATH, String.class) + .insertColumn(TABLE_COOKIE, COLUMN_SECURE, boolean.class) + .insertColumn(TABLE_COOKIE, COLUMN_HTTP_ONLY, boolean.class) + .insertColumn(TABLE_COOKIE, COLUMN_PERSISTENT, boolean.class) + .insertColumn(TABLE_COOKIE, COLUMN_HOST_ONLY, boolean.class) + .build(context, name, DB_VERSION); + db = helper.getWritableDatabase(); + } + + @Nullable + private static Cookie getCookie(Cursor cursor, long now) { + String name = SqlUtils.getString(cursor, COLUMN_NAME, null); + String value = SqlUtils.getString(cursor, COLUMN_VALUE, null); + long expiresAt = SqlUtils.getLong(cursor, COLUMN_EXPIRES_AT, 0); + String domain = SqlUtils.getString(cursor, COLUMN_DOMAIN, null); + String path = SqlUtils.getString(cursor, COLUMN_PATH, null); + boolean secure = SqlUtils.getBoolean(cursor, COLUMN_SECURE, false); + boolean httpOnly = SqlUtils.getBoolean(cursor, COLUMN_HTTP_ONLY, false); + boolean persistent = SqlUtils.getBoolean(cursor, COLUMN_PERSISTENT, false); + boolean hostOnly = SqlUtils.getBoolean(cursor, COLUMN_HOST_ONLY, false); + + if (name == null || domain == null || path == null) { + return null; + } + + // Check non-persistent or expired + if (!persistent || expiresAt <= now) { + return null; + } + + Cookie.Builder builder = new Cookie.Builder(); + builder.name(name); + builder.value(value); + if (hostOnly) { + builder.hostOnlyDomain(domain); + } else { + builder.domain(domain); + } + builder.path(path); + builder.expiresAt(expiresAt); + if (secure) builder.secure(); + if (httpOnly) builder.httpOnly(); + return builder.build(); + } + + public Map getAllCookies() { + long now = System.currentTimeMillis(); + Map map = new HashMap<>(); + List toRemove = new ArrayList<>(); + + Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_COOKIE + ";", null); + try { + while (cursor.moveToNext()) { + long id = SqlUtils.getLong(cursor, COLUMN_ID, 0); + Cookie cookie = getCookie(cursor, now); + + if (cookie != null) { + // Save id of the cookie in db + cookieIdMap.put(cookie, id); + + // Put cookie to set + CookieSet set = map.get(cookie.domain()); + if (set == null) { + set = new CookieSet(); + map.put(cookie.domain(), set); + } + set.add(cookie); + } else { + // Mark to remove the cookie + toRemove.add(id); + } + } + } finally { + cursor.close(); + } + + // Remove invalid or expired cookie + if (!toRemove.isEmpty()) { + SQLiteStatement statement = db.compileStatement( + "DELETE FROM " + TABLE_COOKIE + " WHERE " + COLUMN_ID + " = ?;"); + db.beginTransaction(); + try { + for (long id : toRemove) { + statement.bindLong(1, id); + statement.executeUpdateDelete(); + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + return map; + } + + public ContentValues toContentValues(Cookie cookie) { + ContentValues contentValues = new ContentValues(9); + contentValues.put(COLUMN_NAME, cookie.name()); + contentValues.put(COLUMN_VALUE, cookie.value()); + contentValues.put(COLUMN_EXPIRES_AT, cookie.expiresAt()); + contentValues.put(COLUMN_DOMAIN, cookie.domain()); + contentValues.put(COLUMN_PATH, cookie.path()); + contentValues.put(COLUMN_SECURE, cookie.secure()); + contentValues.put(COLUMN_HTTP_ONLY, cookie.httpOnly()); + contentValues.put(COLUMN_PERSISTENT, cookie.persistent()); + contentValues.put(COLUMN_HOST_ONLY, cookie.hostOnly()); + return contentValues; + } + + public void add(Cookie cookie) { + long id = db.insert(TABLE_COOKIE, null, toContentValues(cookie)); + if (id != -1L) { + Long oldId = cookieIdMap.put(cookie, id); + if (oldId != null) Log.e(LOG_TAG, "Add a duplicate cookie"); + } else { + Log.e(LOG_TAG, "An error occurred when insert a cookie"); + } + } + + public void update(Cookie from, Cookie to) { + Long id = cookieIdMap.get(from); + if (id == null) { + Log.e(LOG_TAG, "Can't get id when update the cookie"); + return; + } + + ContentValues values = toContentValues(to); + String whereClause = COLUMN_ID + " = ?"; + String[] whereArgs = { id.toString() }; + int count = db.update(TABLE_COOKIE, values, whereClause, whereArgs); + if (count != 1) { + Log.e(LOG_TAG, "Bad result when update cookie: " + count); + } + + // Update it in cookie-id map + cookieIdMap.remove(from); + cookieIdMap.put(to, id); + } + + public void remove(Cookie cookie) { + Long id = cookieIdMap.get(cookie); + if (id == null) { + Log.e(LOG_TAG, "Can't get id when remove the cookie"); + return; + } + + String whereClause = COLUMN_ID + " = ?"; + String[] whereArgs = { id.toString() }; + int count = db.delete(TABLE_COOKIE, whereClause, whereArgs); + if (count != 1) { + Log.e(LOG_TAG, "Bad result when remove cookie: " + count); + } + + // Remove it from cookie-id map + cookieIdMap.remove(cookie); + } + + public void clear() { + db.delete(TABLE_COOKIE, null, null); + cookieIdMap.clear(); + } + + public void close() { + db.close(); + helper.close(); + } +} diff --git a/app/src/main/java/com/hippo/network/CookieRepository.java b/app/src/main/java/com/hippo/network/CookieRepository.java new file mode 100644 index 000000000..9fab98c15 --- /dev/null +++ b/app/src/main/java/com/hippo/network/CookieRepository.java @@ -0,0 +1,188 @@ +/* + * Copyright 2017 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.network; + +/* + * Created by Hippo on 2017/9/4. + */ + +import android.content.Context; +import com.hippo.yorozuya.ObjectUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import okhttp3.Cookie; +import okhttp3.CookieJar; +import okhttp3.HttpUrl; +import okhttp3.Request; + +public class CookieRepository implements CookieJar { + + private CookieDatabase db; + private Map map; + + public CookieRepository(Context context, String name) { + db = new CookieDatabase(context, name); + map = db.getAllCookies(); + } + + public synchronized void addCookie(Cookie cookie) { + // For cookie database + Cookie toAdd = null; + Cookie toUpdate = null; + Cookie toRemove = null; + + CookieSet set = map.get(cookie.domain()); + if (set == null) { + set = new CookieSet(); + map.put(cookie.domain(), set); + } + + if (cookie.expiresAt() <= System.currentTimeMillis()) { + toRemove = set.remove(cookie); + // If the cookie is not persistent, it's not in database + if (toRemove != null && !toRemove.persistent()) { + toRemove = null; + } + } else { + toAdd = cookie; + toUpdate = set.add(cookie); + // If the cookie is not persistent, it's not in database + if (!toAdd.persistent()) toAdd = null; + if (toUpdate != null && !toUpdate.persistent()) toUpdate = null; + // Remove the cookie if it updates to null + if (toAdd == null && toUpdate != null) { + toRemove = toUpdate; + toUpdate = null; + } + } + + if (toRemove != null) { + db.remove(toRemove); + } + if (toAdd != null) { + if (toUpdate != null) { + db.update(toUpdate, toAdd); + } else { + db.add(toAdd); + } + } + } + + public synchronized List getCookies(HttpUrl url) { + List accepted = new ArrayList<>(); + List expired = new ArrayList<>(); + + for (Map.Entry entry : map.entrySet()) { + String domain = entry.getKey(); + CookieSet cookieSet = entry.getValue(); + if (domainMatch(url, domain)) { + cookieSet.get(url, accepted, expired); + } + } + + for (Cookie cookie : expired) { + if (cookie.persistent()) { + db.remove(cookie); + } + } + + // RFC 6265 Section-5.4 step 2, sort the cookie-list + // Cookies with longer paths are listed before cookies with shorter paths. + // Ignore creation-time, we don't store them. + Collections.sort(accepted, new Comparator() { + @Override + public int compare(Cookie o1, Cookie o2) { + return o2.path().length() - o1.path().length(); + } + }); + + return accepted; + } + + public boolean contains(HttpUrl url, String name) { + for (Cookie cookie : getCookies(url)) { + if (ObjectUtils.equal(cookie.name(), name)) { + return true; + } + } + return false; + } + + /** + * Remove all cookies in this {@code CookieRepository}. + */ + public synchronized void clear() { + map.clear(); + db.clear(); + } + + public synchronized void close() { + db.close(); + } + + @Override + public void saveFromResponse(HttpUrl httpUrl, List list) { + for (Cookie cookie : list) { + addCookie(cookie); + } + } + + @Override + public List loadForRequest(HttpUrl httpUrl, Request request) { + return getCookies(httpUrl); + } + + /** + * Quick and dirty pattern to differentiate IP addresses from hostnames. This is an approximation + * of Android's private InetAddress#isNumeric API. + * + * This matches IPv6 addresses as a hex string containing at least one colon, and possibly + * including dots after the first colon. It matches IPv4 addresses as strings containing only + * decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither IP + * addresses nor hostnames; they will be verified as IP addresses (which is a more strict + * verification). + */ + private static final Pattern VERIFY_AS_IP_ADDRESS = Pattern.compile("([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)"); + + /** + * Returns true if `host` is not a host name and might be an IP address. + */ + private static boolean verifyAsIpAddress(String host) { + return VERIFY_AS_IP_ADDRESS.matcher(host).matches(); + } + + // okhttp3.Cookie.domainMatch(HttpUrl, String) + protected static boolean domainMatch(HttpUrl url, String domain) { + String urlHost = url.host(); + + if (urlHost.equals(domain)) { + return true; // As in 'example.com' matching 'example.com'. + } + + if (urlHost.endsWith(domain) + && urlHost.charAt(urlHost.length() - domain.length() - 1) == '.' + && !verifyAsIpAddress(urlHost)) { + return true; // As in 'example.com' matching 'www.example.com'. + } + + return false; + } +} diff --git a/app/src/main/java/com/hippo/network/CookieSet.java b/app/src/main/java/com/hippo/network/CookieSet.java new file mode 100644 index 000000000..0f4a1c9e7 --- /dev/null +++ b/app/src/main/java/com/hippo/network/CookieSet.java @@ -0,0 +1,104 @@ +/* + * Copyright 2017 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.network; + +/* + * Created by Hippo on 2017/9/4. + */ + +import com.hippo.util.HashCodeUtils; +import com.hippo.yorozuya.ObjectUtils; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import okhttp3.Cookie; +import okhttp3.HttpUrl; + +class CookieSet { + + private Map map = new HashMap<>(); + + /** + * Adds a cookie to this {@code CookieSet}. + * Returns a previous cookie with + * the same name, domain and path or {@code null}. + */ + public Cookie add(Cookie cookie) { + return map.put(new Key(cookie), cookie); + } + + /** + * Removes a cookie with the same name, + * domain and path as the cookie. + * Returns the removed cookie or {@code null}. + */ + public Cookie remove(Cookie cookie) { + return map.remove(new Key(cookie)); + } + + /** + * Get cookies for the url. Fill {@code accepted} and {@code expired}. + */ + public void get(HttpUrl url, List accepted, List expired) { + long now = System.currentTimeMillis(); + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Cookie cookie = iterator.next().getValue(); + if (cookie.expiresAt() <= now) { + iterator.remove(); + expired.add(cookie); + } else if (cookie.matches(url)) { + accepted.add(cookie); + } + } + } + + static class Key { + + private String name; + private String domain; + private String path; + + public Key(Cookie cookie) { + this.name = cookie.name(); + this.domain = cookie.domain(); + this.path = cookie.path(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (obj instanceof Key) { + Key key = (Key) obj; + return ObjectUtils.equal(key.name, this.name) && + ObjectUtils.equal(key.domain, this.domain) && + ObjectUtils.equal(key.path, this.path); + } + + return false; + } + + @Override + public int hashCode() { + return HashCodeUtils.hashCode(name, domain, path); + } + } +} diff --git a/app/src/main/java/com/hippo/okhttp/CookieDB.java b/app/src/main/java/com/hippo/okhttp/CookieDB.java deleted file mode 100644 index 1fb1ed6af..000000000 --- a/app/src/main/java/com/hippo/okhttp/CookieDB.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2016 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hippo.okhttp; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; - -import com.hippo.okhttp.dao.DaoMaster; -import com.hippo.okhttp.dao.DaoSession; -import com.hippo.okhttp.dao.OkHttp3CookieDao; -import com.hippo.okhttp.dao.OkHttp3CookieRaw; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import okhttp3.Cookie; - -public class CookieDB { - - private static DaoSession sDaoSession; - - private static class DBOpenHelper extends DaoMaster.OpenHelper { - - public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) { - super(context, name, factory); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - } - } - - public static void initialize(Context context) { - DBOpenHelper helper = new DBOpenHelper( - context.getApplicationContext(), "okhttp3-cookie.db", null); - - SQLiteDatabase db = helper.getWritableDatabase(); - DaoMaster daoMaster = new DaoMaster(db); - - sDaoSession = daoMaster.newSession(); - } - - static Map> getAllCookies() { - OkHttp3CookieDao dao = sDaoSession.getOkHttp3CookieDao(); - List list = dao.queryBuilder().list(); - - long now = System.currentTimeMillis(); - Map> result = new HashMap<>(); - for (OkHttp3CookieRaw raw : list) { - // Check expired - if (!raw.getPersistent() || raw.getExpiresAt() <= now) { - dao.delete(raw); - continue; - } - - // Avoid null error - String name = raw.getName(); - String value = raw.getValue(); - String domain = raw.getDomain(); - if (name == null || value == null || domain == null) { - dao.delete(raw); - continue; - } - - Cookie.Builder builder = new Cookie.Builder(); - builder.name(name); - builder.value(value); - builder.expiresAt(raw.getExpiresAt()); - if (raw.getHostOnly()) { - builder.hostOnlyDomain(domain); - } else { - builder.domain(raw.getDomain()); - } - builder.path(raw.getPath()); - if (raw.getSecure()) { - builder.secure(); - } - if (raw.getHttpOnly()) { - builder.httpOnly(); - } - - List cookies = result.get(domain); - if (cookies == null) { - cookies = new ArrayList<>(); - result.put(domain, cookies); - } - - cookies.add(new CookieWithID(raw.getId(), builder.build())); - } - - return result; - } - - static void removeCookie(long id) { - sDaoSession.getOkHttp3CookieDao().deleteByKey(id); - } - - static void removeAllCookies() { - sDaoSession.getOkHttp3CookieDao().deleteAll(); - } - - static long addCookie(Cookie cookie) { - OkHttp3CookieDao dao = sDaoSession.getOkHttp3CookieDao(); - - OkHttp3CookieRaw raw = new OkHttp3CookieRaw(); - raw.setName(cookie.name()); - raw.setValue(cookie.value()); - raw.setExpiresAt(cookie.expiresAt()); - raw.setDomain(cookie.domain()); - raw.setPath(cookie.path()); - raw.setSecure(cookie.secure()); - raw.setHttpOnly(cookie.httpOnly()); - raw.setPersistent(cookie.persistent()); - raw.setHostOnly(cookie.hostOnly()); - - return dao.insert(raw); - } -} diff --git a/app/src/main/java/com/hippo/okhttp/CookieDBStore.java b/app/src/main/java/com/hippo/okhttp/CookieDBStore.java deleted file mode 100644 index 4cf53a236..000000000 --- a/app/src/main/java/com/hippo/okhttp/CookieDBStore.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright 2016 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hippo.okhttp; - -import android.support.annotation.Nullable; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import okhttp3.Cookie; -import okhttp3.CookieJar; -import okhttp3.HttpUrl; -import okhttp3.Request; - -public class CookieDBStore implements CookieJar { - - private final Map> map; - - public CookieDBStore() { - map = CookieDB.getAllCookies(); - } - - private static boolean hasExpired(Cookie cookie) { - return cookie.expiresAt() < System.currentTimeMillis(); - } - - private static boolean domainMatch(Cookie cookie, String urlHost) { - return cookie.hostOnly() - ? urlHost.equals(cookie.domain()) - : domainMatch(urlHost, cookie.domain()); - } - - public static boolean domainMatch(String urlHost, String cookieDomain) { - if (urlHost.equals(cookieDomain)) { - return true; // As in 'example.com' matching 'example.com'. - } - - return urlHost.endsWith(cookieDomain) - && urlHost.charAt(urlHost.length() - cookieDomain.length() - 1) == '.'; - } - - private static CookieWithID removeCookie(List list, String name) { - for (int i = 0, n = list.size(); i < n; i++) { - CookieWithID cwi = list.get(i); - if (cwi.cookie.name().equals(name)) { - list.remove(i); - return cwi; - } - } - - return null; - } - - private boolean containInternal(String host, String name, List cookies) { - for (int i = 0, n = cookies.size(); i < n; i++) { - Cookie cookie = cookies.get(i).cookie; - if (domainMatch(cookie, host) && cookie.name().equals(name)) { - return true; - } - } - return false; - } - - public synchronized boolean contain(String host, String name) { - for (String key : map.keySet()) { - if (domainMatch(host, key)) { - List cookies = map.get(key); - // cookies can not be null - if (containInternal(host, name, cookies)) { - return true; - } - } - } - return false; - } - - private synchronized void removeInternal(Cookie cookie) { - String domain = cookie.domain(); - List cookies = map.get(domain); - if (cookies != null) { - // Remove from list - CookieWithID cwi = removeCookie(cookies, cookie.name()); - if (cwi != null) { - // Remove from DB - CookieDB.removeCookie(cwi.id); - } - } - } - - private void removeInternal(String host, List cookies) { - for (Iterator i = cookies.iterator(); i.hasNext();) { - CookieWithID cwi = i.next(); - Cookie cookie = cwi.cookie; - // Check match host or has expired - if (domainMatch(cookie, host) || hasExpired(cookie)) { - // Remove from list - i.remove(); - // Remove from DB - CookieDB.removeCookie(cwi.id); - } - } - } - - public synchronized void remove(String host) { - for (String key : map.keySet()) { - if (domainMatch(host, key)) { - List cookies = map.get(key); - // cookies can not be null - removeInternal(host, cookies); - } - } - } - - public synchronized void removeAll() { - map.clear(); - CookieDB.removeAllCookies(); - } - - public synchronized void add(Cookie cookie) { - String domain = cookie.domain(); - - // Check expired - if (hasExpired(cookie)) { - removeInternal(cookie); - return; - } - - List cookies = map.get(domain); - if (cookies == null) { - cookies = new ArrayList<>(); - map.put(domain, cookies); - } else { - // Remove old cookie - // Remove from list - CookieWithID cwi = removeCookie(cookies, cookie.name()); - if (cwi != null) { - // Remove from DB - CookieDB.removeCookie(cwi.id); - } - } - - // Add to DB - long id = CookieDB.addCookie(cookie); - // Add to list - cookies.add(new CookieWithID(id, cookie)); - } - - private void getInternal(HttpUrl url, List cookies, List result) { - for (Iterator i = cookies.iterator(); i.hasNext();) { - CookieWithID cwi = i.next(); - Cookie cookie = cwi.cookie; - // Check expired - if (hasExpired(cookie)) { - // Remove from list - i.remove(); - // Remove from DB - CookieDB.removeCookie(cwi.id); - } else if (cookie.matches(url)) { - result.add(cookie); - } - } - } - - private Cookie getInternal(HttpUrl url, String name, List cookies) { - for (Iterator i = cookies.iterator(); i.hasNext();) { - CookieWithID cwi = i.next(); - Cookie cookie = cwi.cookie; - // Check expired - if (hasExpired(cookie)) { - // Remove from list - i.remove(); - // Remove from DB - CookieDB.removeCookie(cwi.id); - } else if (cookie.name().equals(name) && cookie.matches(url)) { - return cookie; - } - } - return null; - } - - @Nullable - public synchronized Cookie get(HttpUrl url, String name) { - String host = url.host(); - for (String key : map.keySet()) { - if (domainMatch(host, key)) { - List cookies = map.get(key); - return getInternal(url, name, cookies); - } - } - return null; - } - - public synchronized List get(HttpUrl url) { - List result = new ArrayList<>(); - - String host = url.host(); - for (String key : map.keySet()) { - if (domainMatch(host, key)) { - List cookies = map.get(key); - // cookies can not be null - getInternal(url, cookies, result); - } - } - - return Collections.unmodifiableList(result); - } - - @Override - public void saveFromResponse(HttpUrl url, List cookies) { - for (Cookie cookie: cookies) { - add(cookie); - } - } - - @Override - public List loadForRequest(HttpUrl url, Request request) { - return get(url); - } -} diff --git a/app/src/main/java/com/hippo/okhttp/CookieWithID.java b/app/src/main/java/com/hippo/util/HashCodeUtils.java similarity index 51% rename from app/src/main/java/com/hippo/okhttp/CookieWithID.java rename to app/src/main/java/com/hippo/util/HashCodeUtils.java index dd65282ad..705edd915 100644 --- a/app/src/main/java/com/hippo/okhttp/CookieWithID.java +++ b/app/src/main/java/com/hippo/util/HashCodeUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Hippo Seven + * Copyright 2017 Hippo Seven * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,29 @@ * limitations under the License. */ -package com.hippo.okhttp; +package com.hippo.util; +/* + * Created by Hippo on 2017/9/4. + */ -import okhttp3.Cookie; +public class HashCodeUtils { -public class CookieWithID { + private static final int X = 31; - public long id; - public Cookie cookie; - - public CookieWithID(long id, Cookie cookie) { - this.id = id; - this.cookie = cookie; + /** + *
{@code
+   * args[0].hashCode()*31^(n-1) + args[1].hashCode()*31^(n-2) + ... + args[n-1].hashCode()
+   * }
+ * Returns 0 if {@code args == null}. + */ + public static int hashCode(Object... args) { + if (args == null) { + return 0; } + int hash = 0; + for (Object o: args) { + hash = X * hash + (o != null ? o.hashCode() : 0); + } + return hash; + } } diff --git a/app/src/main/java/com/hippo/util/SqlUtils.java b/app/src/main/java/com/hippo/util/SqlUtils.java index 50b18d769..e7ad4734e 100644 --- a/app/src/main/java/com/hippo/util/SqlUtils.java +++ b/app/src/main/java/com/hippo/util/SqlUtils.java @@ -69,4 +69,54 @@ public class SqlUtils { return sb.toString(); } + + public static boolean getBoolean(Cursor cursor, String column, boolean defValue) { + try { + int index = cursor.getColumnIndex(column); + if (index != -1) { + return cursor.getInt(index) != 0; + } + } catch (Throwable e) { /* Ignore */ } + return defValue; + } + + public static int getInt(Cursor cursor, String column, int defValue) { + try { + int index = cursor.getColumnIndex(column); + if (index != -1) { + return cursor.getInt(index); + } + } catch (Throwable e) { /* Ignore */ } + return defValue; + } + + public static long getLong(Cursor cursor, String column, long defValue) { + try { + int index = cursor.getColumnIndex(column); + if (index != -1) { + return cursor.getLong(index); + } + } catch (Throwable e) { /* Ignore */ } + return defValue; + } + + public static float getFloat(Cursor cursor, String column, float defValue) { + try { + int index = cursor.getColumnIndex(column); + if (index != -1) { + return cursor.getFloat(index); + } + } catch (Throwable e) { /* Ignore */ } + return defValue; + } + + public static String getString(Cursor cursor, String column, String defValue) { + try { + int index = cursor.getColumnIndex(column); + if (index != -1) { + return cursor.getString(index); + } + } catch (Throwable e) { /* Ignore */ } + return defValue; + } } diff --git a/app/src/main/res/layout/scene_login.xml b/app/src/main/res/layout/scene_login.xml index 2fc9cddd0..619a186ac 100644 --- a/app/src/main/res/layout/scene_login.xml +++ b/app/src/main/res/layout/scene_login.xml @@ -71,13 +71,40 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/password" - android:imeOptions="actionDone" + android:imeOptions="actionNext" android:inputType="textPassword" android:maxLines="1" android:singleLine="true"/> + + + + + + + + 用户名
密码 + 验证码(非必需) + + 点我 + 加载中… + 失败 登录 注册 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 2d8f9f074..686d1cc55 100755 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -28,6 +28,11 @@ 用户名 密碼 + 驗證碼(非必需) + + 點我 + 加載中… + 失敗 登錄 註冊 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a48608cc6..08d6c25e6 100755 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -28,6 +28,11 @@ 使用者名稱 密碼 + 驗證碼(非必需) + + 點我 + 載入中… + 失敗 登入 註冊 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27437f2bb..fbac2644b 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,6 +44,11 @@ Username Password + Recaptcha (Not required) + + Click me + Loading… + Failed Sign in Register diff --git a/app/src/test/java/com/hippo/database/MSQLiteBuilderTest.java b/app/src/test/java/com/hippo/database/MSQLiteBuilderTest.java new file mode 100644 index 000000000..ee7d49981 --- /dev/null +++ b/app/src/test/java/com/hippo/database/MSQLiteBuilderTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.database; + +/* + * Created by Hippo on 2017/9/4. + */ + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@Config(manifest = Config.NONE) +@RunWith(RobolectricTestRunner.class) +public class MSQLiteBuilderTest { + + @Test + public void testGetStatements() { + MSQLiteBuilder builder = new MSQLiteBuilder() + .version(1) + .statement("11") + .statement("12") + .version(3) + .statement("31") + .statement("32"); + + List list = new ArrayList<>(); + list.add("31"); + list.add("32"); + assertEquals(list, builder.getStatements(1, 3)); + } +} diff --git a/app/src/test/java/com/hippo/network/CookieRepositoryTest.java b/app/src/test/java/com/hippo/network/CookieRepositoryTest.java new file mode 100644 index 000000000..b0c7964ad --- /dev/null +++ b/app/src/test/java/com/hippo/network/CookieRepositoryTest.java @@ -0,0 +1,336 @@ +/* + * Copyright 2017 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.network; + +/* + * Created by Hippo on 2017/9/4. + */ + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import okhttp3.Cookie; +import okhttp3.HttpUrl; +import org.joor.Reflect; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@Config(manifest = Config.NONE) +@RunWith(RobolectricTestRunner.class) +public class CookieRepositoryTest { + + private void equals(CookieSet cookieSet, List cookies) { + assertNotNull(cookieSet); + assertNotNull(cookies); + + Map map = Reflect.on(cookieSet).field("map").get(); + assertEquals(cookies.size(), map.size()); + for (Cookie cookie: cookies) { + assertEquals(cookie, map.get(new CookieSet.Key(cookie))); + } + } + + private void equals(List cookies1, List cookies2) { + assertNotNull(cookies1); + assertNotNull(cookies2); + + assertEquals(cookies1.size(), cookies2.size()); + for (Cookie cookie: cookies1) { + assertTrue(cookies2.contains(cookie)); + } + } + + @Test + public void testPersistent() { + Context app = RuntimeEnvironment.application; + + HttpUrl urlEh = HttpUrl.parse("http://www.ehviewer.com/"); + Cookie cookieEh1 = new Cookie.Builder() + .name("user") + .value("1234567890") + .domain("ehviewer.com") + .path("/") + .build(); + Cookie cookieEh2 = new Cookie.Builder() + .name("level") + .value("999") + .domain("www.ehviewer.com") + .path("/") + .expiresAt(System.currentTimeMillis() + 100000) + .build(); + Cookie cookieEh3 = new Cookie.Builder() + .name("speed") + .value("10") + .domain("www.ehviewer.com") + .path("/") + .expiresAt(System.currentTimeMillis() + 100000) + .build(); + + HttpUrl urlNMB = HttpUrl.parse("http://h.nimingban.com/"); + Cookie cookieNMB = new Cookie.Builder() + .name("hash") + .value("0987654321") + .domain("nimingban.com") + .expiresAt(System.currentTimeMillis() + 100000) + .path("/") + .build(); + + CookieRepository repository = new CookieRepository(app, "cookie.db"); + repository.saveFromResponse(urlEh, Arrays.asList(cookieEh1, cookieEh2, cookieEh3)); + repository.saveFromResponse(urlNMB, Collections.singletonList(cookieNMB)); + + Map map = Reflect.on(repository).field("map").get(); + assertEquals(3, map.size()); + equals(map.get("ehviewer.com"), Collections.singletonList(cookieEh1)); + equals(map.get("www.ehviewer.com"), Arrays.asList(cookieEh2, cookieEh3)); + equals(map.get("nimingban.com"), Collections.singletonList(cookieNMB)); + repository.close(); + + repository = new CookieRepository(app, "cookie.db"); + map = Reflect.on(repository).field("map").get(); + assertEquals(2, map.size()); + equals(map.get("www.ehviewer.com"), Arrays.asList(cookieEh2, cookieEh3)); + equals(map.get("nimingban.com"), Collections.singletonList(cookieNMB)); + repository.close(); + } + + @Test + public void testUpdate() { + Context app = RuntimeEnvironment.application; + + HttpUrl urlEh = HttpUrl.parse("http://www.ehviewer.com/"); + Cookie cookieEh1 = new Cookie.Builder() + .name("level") + .value("999") + .domain("www.ehviewer.com") + .path("/") + .expiresAt(System.currentTimeMillis() + 100000) + .build(); + Cookie cookieEh2 = new Cookie.Builder() + .name("level") + .value("0") + .domain("www.ehviewer.com") + .path("/") + .expiresAt(System.currentTimeMillis() + 100000) + .build(); + + CookieRepository repository = new CookieRepository(app, "cookie.db"); + repository.saveFromResponse(urlEh, Collections.singletonList(cookieEh1)); + repository.saveFromResponse(urlEh, Collections.singletonList(cookieEh2)); + Map map = Reflect.on(repository).field("map").get(); + assertEquals(1, map.size()); + equals(map.get("www.ehviewer.com"), Collections.singletonList(cookieEh2)); + repository.close(); + + repository = new CookieRepository(app, "cookie.db"); + map = Reflect.on(repository).field("map").get(); + assertEquals(1, map.size()); + equals(map.get("www.ehviewer.com"), Collections.singletonList(cookieEh2)); + repository.close(); + } + + @Test + public void testRemoveByExpired() { + Context app = RuntimeEnvironment.application; + + HttpUrl urlEh = HttpUrl.parse("http://www.ehviewer.com/"); + Cookie cookieEh1 = new Cookie.Builder() + .name("level") + .value("999") + .domain("www.ehviewer.com") + .path("/") + .expiresAt(System.currentTimeMillis() + 100000) + .build(); + Cookie cookieEh2 = new Cookie.Builder() + .name("level") + .value("0") + .domain("www.ehviewer.com") + .path("/") + .expiresAt(System.currentTimeMillis() - 100000) + .build(); + + CookieRepository repository = new CookieRepository(app, "cookie.db"); + repository.saveFromResponse(urlEh, Collections.singletonList(cookieEh1)); + repository.saveFromResponse(urlEh, Collections.singletonList(cookieEh2)); + Map map = Reflect.on(repository).field("map").get(); + assertEquals(1, map.size()); + equals(map.get("www.ehviewer.com"), Collections.emptyList()); + repository.close(); + + repository = new CookieRepository(app, "cookie.db"); + map = Reflect.on(repository).field("map").get(); + assertEquals(0, map.size()); + repository.close(); + } + + @Test + public void testRemoveByNonPersistent() { + Context app = RuntimeEnvironment.application; + + HttpUrl urlEh = HttpUrl.parse("http://www.ehviewer.com/"); + Cookie cookieEh1 = new Cookie.Builder() + .name("level") + .value("999") + .domain("www.ehviewer.com") + .path("/") + .expiresAt(System.currentTimeMillis() + 100000) + .build(); + Cookie cookieEh2 = new Cookie.Builder() + .name("level") + .value("0") + .domain("www.ehviewer.com") + .path("/") + .build(); + + CookieRepository repository = new CookieRepository(app, "cookie.db"); + repository.saveFromResponse(urlEh, Collections.singletonList(cookieEh1)); + repository.saveFromResponse(urlEh, Collections.singletonList(cookieEh2)); + Map map = Reflect.on(repository).field("map").get(); + assertEquals(1, map.size()); + equals(map.get("www.ehviewer.com"), Collections.singletonList(cookieEh2)); + repository.close(); + + + repository = new CookieRepository(app, "cookie.db"); + map = Reflect.on(repository).field("map").get(); + assertEquals(0, map.size()); + repository.close(); + } + + @Test + public void testGet() throws InterruptedException { + Context app = RuntimeEnvironment.application; + + HttpUrl urlEh1 = HttpUrl.parse("http://www.ehviewer.com/"); + HttpUrl urlEh2 = HttpUrl.parse("http://ehviewer.com/"); + Cookie cookieEh1 = new Cookie.Builder() + .name("user") + .value("1234567890") + .domain("ehviewer.com") + .path("/") + .expiresAt(System.currentTimeMillis() + 3000) + .build(); + Cookie cookieEh2 = new Cookie.Builder() + .name("level") + .value("999") + .domain("www.ehviewer.com") + .path("/") + .build(); + Cookie cookieEh3 = new Cookie.Builder() + .name("speed") + .value("10") + .domain("ehviewer.com") + .path("/") + .build(); + + HttpUrl urlNMB = HttpUrl.parse("http://h.nimingban.com/"); + Cookie cookieNMB = new Cookie.Builder() + .name("hash") + .value("0987654321") + .domain("nimingban.com") + .path("/") + .build(); + + CookieRepository repository = new CookieRepository(app, "cookie.db"); + repository.saveFromResponse(urlEh1, Arrays.asList(cookieEh1, cookieEh2)); + repository.saveFromResponse(urlEh1, Collections.singletonList(cookieEh3)); + repository.saveFromResponse(urlNMB, Collections.singletonList(cookieNMB)); + equals(Arrays.asList(cookieEh1, cookieEh3), repository.loadForRequest(urlEh2, null)); + Thread.sleep(3000); + equals(Collections.singletonList(cookieEh3), repository.loadForRequest(urlEh2, null)); + repository.close(); + } + + @Test + public void testClear() { + Context app = RuntimeEnvironment.application; + + HttpUrl url = HttpUrl.parse("http://www.ehviewer.com/"); + Cookie cookie = new Cookie.Builder() + .name("user") + .value("1234567890") + .domain("ehviewer.com") + .path("/") + .expiresAt(System.currentTimeMillis() + 3000) + .build(); + + CookieRepository repository = new CookieRepository(app, "cookie.db"); + repository.saveFromResponse(url, Collections.singletonList(cookie)); + Map map = Reflect.on(repository).field("map").get(); + assertEquals(1, map.size()); + equals(map.get("ehviewer.com"), Collections.singletonList(cookie)); + repository.clear(); + map = Reflect.on(repository).field("map").get(); + assertEquals(0, map.size()); + repository.close(); + + repository = new CookieRepository(app, "cookie.db"); + map = Reflect.on(repository).field("map").get(); + assertEquals(0, map.size()); + repository.close(); + } + + @Test + public void testSort() { + Context app = RuntimeEnvironment.application; + + HttpUrl url = HttpUrl.parse("http://www.ehviewer.com/long/long/long/"); + Cookie cookie1 = new Cookie.Builder() + .name("user") + .value("1234567890") + .domain("ehviewer.com") + .path("/") + .build(); + Cookie cookie2 = new Cookie.Builder() + .name("supersuperme") + .value("99999") + .domain("ehviewer.com") + .path("/long/") + .build(); + Cookie cookie3 = new Cookie.Builder() + .name("a") + .value("b") + .domain("ehviewer.com") + .path("/long/long/long/") + .build(); + Cookie cookie4 = new Cookie.Builder() + .name("speed") + .value("100") + .domain("ehviewer.com") + .path("/long/long/") + .build(); + + CookieRepository repository = new CookieRepository(app, "cookie.db"); + repository.saveFromResponse(url, Arrays.asList(cookie1, cookie2, cookie3, cookie4)); + List list = repository.loadForRequest(url, null); + List expected = Arrays.asList(cookie3, cookie4, cookie2, cookie1); + assertEquals(expected.size(), list.size()); + for (int i = 0; i < list.size(); i++) { + assertEquals(expected.get(i), list.get(i)); + } + repository.close(); + } +} diff --git a/build.gradle b/build.gradle index 3eede8f62..99945643d 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { maven { url "https://jitpack.io" } } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:2.3.3' classpath 'com.github.seven332:android-chinese-string-gradle-plugin:0.1.0' // NOTE: Do not place your application dependencies here; they belong diff --git a/daogenerator/src/main/java/com/hippo/ehviewer/daogenerator/CookieDaoGenerator.java b/daogenerator/src/main/java/com/hippo/ehviewer/daogenerator/CookieDaoGenerator.java deleted file mode 100644 index 77bc6a4dd..000000000 --- a/daogenerator/src/main/java/com/hippo/ehviewer/daogenerator/CookieDaoGenerator.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2016 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hippo.ehviewer.daogenerator; - -import java.io.File; - -import de.greenrobot.daogenerator.DaoGenerator; -import de.greenrobot.daogenerator.Entity; -import de.greenrobot.daogenerator.Schema; - -public class CookieDaoGenerator { - - private static final String PACKAGE = "com.hippo.okhttp.dao"; - private static final String OUT_DIR = "../app/src/main/java-gen"; - private static final String DELETE_DIR = "../app/src/main/java-gen/com/hippo/okhttp/dao"; - - public static void generate() throws Exception { - Utilities.deleteContents(new File(DELETE_DIR)); - File outDir = new File(OUT_DIR); - outDir.delete(); - outDir.mkdirs(); - - Schema schema = new Schema(1, PACKAGE); - addOkHttp3Cookie(schema); - new DaoGenerator().generateAll(schema, OUT_DIR); - } - - private static void addOkHttp3Cookie(Schema schema) { - Entity entity = schema.addEntity("OkHttp3CookieRaw"); - entity.setTableName("OK_HTTP_3_COOKIE"); - entity.setClassNameDao("OkHttp3CookieDao"); - - entity.addIdProperty(); - entity.addStringProperty("name"); - entity.addStringProperty("value"); - entity.addLongProperty("expiresAt"); - entity.addStringProperty("domain"); - entity.addStringProperty("path"); - entity.addBooleanProperty("secure"); - entity.addBooleanProperty("httpOnly"); - entity.addBooleanProperty("persistent"); - entity.addBooleanProperty("hostOnly"); - } -} diff --git a/daogenerator/src/main/java/com/hippo/ehviewer/daogenerator/Main.java b/daogenerator/src/main/java/com/hippo/ehviewer/daogenerator/Main.java index f55b22212..189da05b3 100644 --- a/daogenerator/src/main/java/com/hippo/ehviewer/daogenerator/Main.java +++ b/daogenerator/src/main/java/com/hippo/ehviewer/daogenerator/Main.java @@ -19,7 +19,6 @@ package com.hippo.ehviewer.daogenerator; public class Main { public static void main(String args[]) throws Exception { - CookieDaoGenerator.generate(); EhDaoGenerator.generate(); } }