diff --git a/app/src/main/java/com/fox2code/mmm/SetupActivity.java b/app/src/main/java/com/fox2code/mmm/SetupActivity.java index e3e430e..295a647 100644 --- a/app/src/main/java/com/fox2code/mmm/SetupActivity.java +++ b/app/src/main/java/com/fox2code/mmm/SetupActivity.java @@ -30,7 +30,10 @@ import com.google.android.material.materialswitch.MaterialSwitch; import com.topjohnwu.superuser.internal.UiThreadHandler; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.security.GeneralSecurityException; import java.util.Objects; @@ -336,14 +339,37 @@ public class SetupActivity extends FoxActivity implements LanguageActivity { }); try { String cookieFileName = "cookies"; - File cookieFile = new File(MainApplication.getINSTANCE().getFilesDir(), cookieFileName); String initialCookie = "is_foxmmm=true; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/; domain=\" + chain.request().url().host() + \"; SameSite=None; Secure;|foxmmm_version=" + BuildConfig.VERSION_CODE + "; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/; domain=\" + chain.request().url().host() + \"; SameSite=None; Secure;"; Context context = getApplicationContext(); - MasterKey mainKeyAlias = new MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(); + MasterKey mainKeyAlias; + mainKeyAlias = new MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(); EncryptedFile encryptedFile = new EncryptedFile.Builder(context, new File(MainApplication.getINSTANCE().getFilesDir(), cookieFileName), mainKeyAlias, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build(); - encryptedFile.openFileOutput().write(initialCookie.getBytes()); - encryptedFile.openFileOutput().flush(); - encryptedFile.openFileOutput().close(); + InputStream inputStream; + try { + inputStream = encryptedFile.openFileInput(); + } catch ( + FileNotFoundException e) { + Timber.d("Cookie file not found, creating new file"); + OutputStream outputStream = encryptedFile.openFileOutput(); + outputStream.write(initialCookie.getBytes()); + outputStream.close(); + outputStream.flush(); + inputStream = encryptedFile.openFileInput(); + } + byte[] buffer = new byte[1024]; + int bytesRead; + StringBuilder outputString = new StringBuilder(); + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputString.append(new String(buffer, 0, bytesRead)); + } + inputStream.close(); + if (outputString.toString().isEmpty()) { + Timber.d("Cookie file is empty, writing initial cookie"); + OutputStream outputStream = encryptedFile.openFileOutput(); + outputStream.write(initialCookie.getBytes()); + outputStream.close(); + outputStream.flush(); + } } catch (GeneralSecurityException | IOException e) { Timber.e(e); diff --git a/app/src/main/java/com/fox2code/mmm/utils/io/AddCookiesInterceptor.java b/app/src/main/java/com/fox2code/mmm/utils/io/AddCookiesInterceptor.java index b49cfc0..0dfc1b2 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/io/AddCookiesInterceptor.java +++ b/app/src/main/java/com/fox2code/mmm/utils/io/AddCookiesInterceptor.java @@ -1,9 +1,5 @@ package com.fox2code.mmm.utils.io; -// Original written by tsuharesu -// Adapted to create a "drop it in and watch it work" approach by Nikhil Jha. -// Just add your package statement and drop it in the folder with all your other classes. - import android.content.Context; import androidx.annotation.NonNull; @@ -12,7 +8,6 @@ import androidx.security.crypto.MasterKey; import com.fox2code.mmm.MainApplication; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -24,8 +19,6 @@ import timber.log.Timber; public class AddCookiesInterceptor implements Interceptor { - // We're storing our stuff in a database made just for cookies called PREF_COOKIES. - // I reccomend you do this, and don't change this default value. private final Context context; public AddCookiesInterceptor(Context context) { @@ -36,44 +29,37 @@ public class AddCookiesInterceptor implements Interceptor { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request.Builder builder = chain.request().newBuilder(); - MasterKey mainKeyAlias; + + // Cookies are stored in an encrypted file in the files directory in our app data + // so we need to decrypt the file before using it + // first, get our decryption key from MasterKey using the AES_256_GCM encryption scheme + // then, create an EncryptedFile object using the key and the file name + // finally, open the file and read the contents into a string + // the string is then split into an array of cookies + // the cookies are then added to the request builder + String cookieFileName = "cookies"; - byte[] plaintext; + String[] cookies = new String[0]; + MasterKey mainKeyAlias; try { - // create cookie file if it doesn't exist - mainKeyAlias = new MasterKey.Builder(context) - .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) - .build(); + mainKeyAlias = new MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(); EncryptedFile encryptedFile = new EncryptedFile.Builder(context, new File(MainApplication.getINSTANCE().getFilesDir(), cookieFileName), mainKeyAlias, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build(); - InputStream inputStream; - inputStream = encryptedFile.openFileInput(); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - int nextByte = inputStream.read(); - while (nextByte != -1) { - byteArrayOutputStream.write(nextByte); - nextByte = inputStream.read(); + InputStream inputStream = encryptedFile.openFileInput(); + byte[] buffer = new byte[1024]; + int bytesRead; + StringBuilder outputString = new StringBuilder(); + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputString.append(new String(buffer, 0, bytesRead)); } - - plaintext = byteArrayOutputStream.toByteArray(); + cookies = outputString.toString().split("\\|"); inputStream.close(); - } catch ( - Exception e) { - Timber.e(e, "Error while reading cookies"); - plaintext = new byte[0]; + } catch (Exception e) { + Timber.e(e, "Error reading cookies from file"); } - String[] preferences = new String(plaintext).split("\\|"); - // Use the following if you need everything in one line. - // Some APIs die if you do it differently. - StringBuilder cookiestring = new StringBuilder(); - for (String cookie : preferences) { - // if cookie doesn't end in a semicolon, add one. - if (!cookie.endsWith(";")) { - cookie = cookie + ";"; - } - cookiestring.append(cookie).append(" "); + + for (String cookie : cookies) { + builder.addHeader("Cookie", cookie); } - Timber.d("Sending cookies: %s", cookiestring.toString()); - builder.addHeader("Cookie", cookiestring.toString()); return chain.proceed(builder.build()); } diff --git a/app/src/main/java/com/fox2code/mmm/utils/io/ReceivedCookiesInterceptor.java b/app/src/main/java/com/fox2code/mmm/utils/io/ReceivedCookiesInterceptor.java index db12a1c..6083e41 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/io/ReceivedCookiesInterceptor.java +++ b/app/src/main/java/com/fox2code/mmm/utils/io/ReceivedCookiesInterceptor.java @@ -1,9 +1,5 @@ package com.fox2code.mmm.utils.io; -// Original written by tsuharesu -// Adapted to create a "drop it in and watch it work" approach by Nikhil Jha. -// Just add your package statement and drop it in the folder with all your other classes. - import android.annotation.SuppressLint; import android.content.Context; @@ -11,14 +7,11 @@ import androidx.annotation.NonNull; import androidx.security.crypto.EncryptedFile; import androidx.security.crypto.MasterKey; -import com.fox2code.mmm.BuildConfig; import com.fox2code.mmm.MainApplication; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.security.GeneralSecurityException; import java.util.Arrays; import java.util.HashSet; @@ -40,48 +33,74 @@ public class ReceivedCookiesInterceptor implements Interceptor { Response originalResponse = chain.proceed(chain.request()); if (!originalResponse.headers("Set-Cookie").isEmpty()) { - MasterKey mainKeyAlias; + StringBuilder cookieBuffer = new StringBuilder(); + for (String header : originalResponse.headers("Set-Cookie")) { + cookieBuffer.append(header).append("|"); + } // for + + int lastPipe = cookieBuffer.lastIndexOf("|"); + if (lastPipe > 0) { + cookieBuffer.deleteCharAt(lastPipe); + } + + // Cookies are stored in an encrypted file in the files directory in our app data + // so we need to decrypt the file before using it + // first, get our decryption key from MasterKey using the AES_256_GCM encryption scheme + // then, create an EncryptedFile object using the key and the file name + // finally, open the file and read the contents into a string + // the string is then split into an array of cookies + String cookieFileName = "cookies"; - byte[] plaintext; + String[] cookies = new String[0]; + MasterKey mainKeyAlias; try { mainKeyAlias = new MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(); EncryptedFile encryptedFile = new EncryptedFile.Builder(context, new File(MainApplication.getINSTANCE().getFilesDir(), cookieFileName), mainKeyAlias, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build(); InputStream inputStream = encryptedFile.openFileInput(); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - int nextByte = inputStream.read(); - while (nextByte != -1) { - byteArrayOutputStream.write(nextByte); - nextByte = inputStream.read(); + byte[] buffer = new byte[1024]; + int bytesRead; + StringBuilder outputString = new StringBuilder(); + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputString.append(new String(buffer, 0, bytesRead)); } - - plaintext = byteArrayOutputStream.toByteArray(); + cookies = outputString.toString().split("\\|"); inputStream.close(); - } catch ( - Exception e) { - e.printStackTrace(); - plaintext = new byte[0]; + } catch (Exception e) { + Timber.e(e, "Error reading cookies from file"); } - HashSet cookies = new HashSet<>(Arrays.asList(new String(plaintext).split("\\|"))); - HashSet cookieSet = new HashSet<>(originalResponse.headers("Set-Cookie")); - if (BuildConfig.DEBUG_HTTP) { - Timber.d("Received cookies: %s", cookieSet); + + // Logic to merge our cookies with received cookies + // We need to check if the cookie we received is already in our file + // If it is, we need to replace it with the new one + // If it isn't, we need to add it to the end of the file + HashSet cookieSet = new HashSet<>(Arrays.asList(cookies)); + String[] newCookies = cookieBuffer.toString().split("\\|"); + for (String cookie : newCookies) { + cookieSet.remove(cookie); + cookieSet.add(cookie); + } + + // convert the set back into a string + StringBuilder newCookieBuffer = new StringBuilder(); + for (String cookie : cookieSet) { + newCookieBuffer.append(cookie).append("|"); } - // if we already have the cooki in cookies, remove the one in cookies - cookies.removeIf(cookie -> cookieSet.toString().contains(cookie.split(";")[0])); - // add the new cookies to the cookies - cookies.addAll(cookieSet); - // write the cookies to the file + + // remove the last pipe + lastPipe = newCookieBuffer.lastIndexOf("|"); + if (lastPipe > 0) { + newCookieBuffer.deleteCharAt(lastPipe); + } + + // write the new cookies to the file try { mainKeyAlias = new MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(); EncryptedFile encryptedFile = new EncryptedFile.Builder(context, new File(MainApplication.getINSTANCE().getFilesDir(), cookieFileName), mainKeyAlias, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build(); - encryptedFile.openFileOutput().write(String.join("|", cookies).getBytes()); - encryptedFile.openFileOutput().flush(); - encryptedFile.openFileOutput().close(); - Timber.d("Storing encrypted cookies: %s", String.join("|", cookies)); - } catch ( - GeneralSecurityException e) { - throw new IllegalStateException("Unable to get master key", e); + encryptedFile.openFileOutput().write(newCookieBuffer.toString().getBytes()); + } catch (Exception e) { + Timber.e(e, "Error writing cookies to file"); } + } return originalResponse;