commit 2aa2b4c3703f707d26c44b0b05663cb76cb084a3 Author: arrase arrase@gmail.com Date: Tue Dec 6 23:10:36 2016 +0100
New feature added: HidServAuth manager and QR share --- app/src/main/AndroidManifest.xml | 191 +++++++++++--------- .../org/torproject/android/OrbotMainActivity.java | 3 + .../ui/hiddenservices/ClientCookiesActivity.java | 199 +++++++++++++++++++++ .../ui/hiddenservices/HiddenServicesActivity.java | 11 +- .../adapters/ClienCookiesAdapter.java | 64 +++++++ .../ui/hiddenservices/backup/BackupUtils.java | 93 +++++++++- .../ui/hiddenservices/database/HSDatabase.java | 9 + .../ui/hiddenservices/dialogs/AddCookieDialog.java | 84 +++++++++ .../dialogs/CookieActionsDialog.java | 95 ++++++++++ .../hiddenservices/dialogs/CookieDeleteDialog.java | 50 ++++++ .../ui/hiddenservices/dialogs/HSCookieDialog.java | 25 +++ .../hiddenservices/dialogs/SelectBackupDialog.java | 84 --------- .../dialogs/SelectCookieBackupDialog.java | 84 +++++++++ .../dialogs/SelectHSBackupDialog.java | 84 +++++++++ .../providers/CookieContentProvider.java | 134 ++++++++++++++ .../ui/hiddenservices/storage/ExternalStorage.java | 4 +- .../res/layout/layout_activity_client_cookies.xml | 35 ++++ .../res/layout/layout_add_client_cookie_dialog.xml | 64 +++++++ .../res/layout/layout_client_cookie_list_item.xml | 24 +++ .../res/layout/layout_content_client_cookies.xml | 21 +++ app/src/main/res/layout/layout_cookie_actions.xml | 24 +++ app/src/main/res/layout/layout_hs_cookie.xml | 6 + app/src/main/res/menu/cookie_menu.xml | 11 ++ app/src/main/res/menu/orbot_main.xml | 16 +- app/src/main/res/values-v21/styles.xml | 1 + app/src/main/res/values/strings.xml | 105 ++++++----- .../org/torproject/android/service/TorService.java | 45 ++++- 27 files changed, 1320 insertions(+), 246 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 826ce67..7dd9c1c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,28 +1,31 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="org.torproject.android" - android:versionName="15.2.0-RC-8-multi" - android:versionCode="15208000" - android:installLocation="auto" - > - <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="23"/> - <!-- + package="org.torproject.android" + android:installLocation="auto" + android:versionCode="15208000" + android:versionName="15.2.0-RC-8-multi"> + + <uses-sdk + android:minSdkVersion="16" + android:targetSdkVersion="23" /> + <!-- <permission android:name="org.torproject.android.MANAGE_TOR" android:label="@string/permission_manage_tor_label" android:description="@string/permission_manage_tor_description" android:protectionLevel="signature"/>
<uses-permission android:name="org.torproject.android.MANAGE_TOR"/> - --> - - <uses-permission android:name="android.permission.INTERNET"/> - <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> - <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> - <uses-permission android:name="android.permission.ACCESS_SUPERUSER" /> + -->
+ <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> + <uses-permission android:name="android.permission.ACCESS_SUPERUSER" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <android:uses-permission android:name="android.permission.READ_PHONE_STATE" /> + <application android:name=".OrbotApp" android:allowBackup="false" @@ -38,118 +41,114 @@ android:name=".OrbotMainActivity" android:configChanges="orientation|screenSize" android:excludeFromRecents="true" - android:launchMode="singleTop" - > + android:launchMode="singleTop"> <intent-filter> <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter>
+ <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> <intent-filter> <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="bridge" /> </intent-filter> - - <intent-filter> + <intent-filter> <category android:name="android.intent.category.DEFAULT" /> + <action android:name="org.torproject.android.REQUEST_HS_PORT" /> </intent-filter> - <intent-filter> + <intent-filter> <category android:name="android.intent.category.DEFAULT" /> + <action android:name="org.torproject.android.START_TOR" /> </intent-filter> - </activity>
- <!-- - This is for ensuring the background service still runs when/if the app is swiped away - --> - <activity - android:name=".service.util.DummyActivity" - android:theme="@android:style/Theme.Translucent" - android:enabled="true" - android:allowTaskReparenting="true" - android:noHistory="true" - android:excludeFromRecents="true" - android:alwaysRetainTaskState="false" - android:stateNotNeeded="true" - android:clearTaskOnLaunch="true" - android:finishOnTaskLaunch="true" - - /> - - <activity - android:name=".vpn.VPNEnableActivity" android:label="@string/app_name" android:exported="false" - /> - - - <activity android:name="org.torproject.android.ui.PromoAppsActivity" android:exported="false"/> - - - <activity android:name=".settings.SettingsPreferences" android:label="@string/app_name"/> - <activity android:name=".ui.AppManager" android:label="@string/app_name" - android:theme="@style/Theme.AppCompat" - /> + <!-- This is for ensuring the background service still runs when/if the app is swiped away --> + <activity + android:name=".service.util.DummyActivity" + android:allowTaskReparenting="true" + android:alwaysRetainTaskState="false" + android:clearTaskOnLaunch="true" + android:enabled="true" + android:excludeFromRecents="true" + android:finishOnTaskLaunch="true" + android:noHistory="true" + android:stateNotNeeded="true" + android:theme="@android:style/Theme.Translucent" /> + <activity + android:name=".vpn.VPNEnableActivity" + android:exported="false" + android:label="@string/app_name" /> + <activity + android:name=".ui.PromoAppsActivity" + android:exported="false" /> + <activity + android:name=".settings.SettingsPreferences" + android:label="@string/app_name" /> + <activity + android:name=".ui.AppManager" + android:label="@string/app_name" + android:theme="@style/Theme.AppCompat" />
<service android:name=".service.TorService" android:enabled="true" android:permission="android.permission.BIND_VPN_SERVICE" - android:stopWithTask="false" > + android:stopWithTask="false"></service> + <service + android:name=".service.vpn.TorVpnService" + android:enabled="true" + android:permission="android.permission.BIND_VPN_SERVICE"> + <intent-filter> + <action android:name="android.net.VpnService" /> + </intent-filter> </service>
- - <service - android:name=".service.vpn.TorVpnService" - android:enabled="true" - android:permission="android.permission.BIND_VPN_SERVICE" > - <intent-filter> - <action android:name="android.net.VpnService"/> - </intent-filter> - </service> - <receiver android:name=".service.StartTorReceiver" android:exported="true"> - <intent-filter> - <action android:name="org.torproject.android.intent.action.START" /> - </intent-filter> + <intent-filter> + <action android:name="org.torproject.android.intent.action.START" /> + </intent-filter> </receiver> + <receiver + android:name=".OnBootReceiver" + android:enabled="true" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.BOOT_COMPLETED" /> + + <category android:name="android.intent.category.HOME" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.QUICKBOOT_POWERON" />
- <receiver android:name=".OnBootReceiver" - android:enabled="true" android:exported="true" - - > - <intent-filter> - <action android:name="android.intent.action.BOOT_COMPLETED" /> - <category android:name="android.intent.category.HOME" /> - </intent-filter> - <intent-filter> - <action android:name="android.intent.action.QUICKBOOT_POWERON" /> - <category android:name="android.intent.category.HOME" /> - </intent-filter> - <intent-filter> - <action android:name="android.intent.action.MEDIA_MOUNTED"/> - <category android:name="android.intent.category.HOME" /> - </intent-filter> - </receiver> + <category android:name="android.intent.category.HOME" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.MEDIA_MOUNTED" /> + + <category android:name="android.intent.category.HOME" /> + </intent-filter> + </receiver>
<activity android:name=".ui.hiddenservices.HiddenServicesActivity" android:label="@string/title_activity_hidden_services" - android:theme="@style/DefaultTheme" > + android:theme="@style/DefaultTheme"> <meta-data android:name="android.support.PARENT_ACTIVITY" - android:value="org.torproject.android.OrbotMainActivity" /> + android:value=".OrbotMainActivity" /> </activity>
<provider android:name=".ui.hiddenservices.providers.HSContentProvider" - android:exported="false" - android:authorities="org.torproject.android.ui.hiddenservices.providers" /> - + android:authorities="org.torproject.android.ui.hiddenservices.providers" + android:exported="false" /> <provider android:name="android.support.v4.content.FileProvider" android:authorities="org.torproject.android.ui.hiddenservices.storage" @@ -157,8 +156,22 @@ android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" - android:resource="@xml/hidden_services_paths"/> + android:resource="@xml/hidden_services_paths" /> </provider>
-</application> -</manifest> + <activity + android:name=".ui.hiddenservices.ClientCookiesActivity" + android:label="@string/client_cookies" + android:theme="@style/DefaultTheme"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value=".OrbotMainActivity" /> + </activity> + + <provider + android:name=".ui.hiddenservices.providers.CookieContentProvider" + android:authorities="org.torproject.android.ui.hiddenservices.providers.cookie" + android:exported="false" /> + </application> + +</manifest> \ No newline at end of file diff --git a/app/src/main/java/org/torproject/android/OrbotMainActivity.java b/app/src/main/java/org/torproject/android/OrbotMainActivity.java index caed0c3..3abcdb8 100644 --- a/app/src/main/java/org/torproject/android/OrbotMainActivity.java +++ b/app/src/main/java/org/torproject/android/OrbotMainActivity.java @@ -27,6 +27,7 @@ import org.torproject.android.ui.AppManager; import org.torproject.android.ui.ImageProgressView; import org.torproject.android.ui.PromoAppsActivity; import org.torproject.android.ui.Rotate3dAnimation; +import org.torproject.android.ui.hiddenservices.ClientCookiesActivity; import org.torproject.android.ui.hiddenservices.HiddenServicesActivity; import org.torproject.android.ui.hiddenservices.backup.BackupUtils; import org.torproject.android.ui.hiddenservices.providers.HSContentProvider; @@ -513,6 +514,8 @@ public class OrbotMainActivity extends AppCompatActivity
} else if (item.getItemId() == R.id.menu_hidden_services) { startActivity(new Intent(this, HiddenServicesActivity.class)); + } else if (item.getItemId() == R.id.menu_client_cookies) { + startActivity(new Intent(this, ClientCookiesActivity.class)); }
return super.onOptionsItemSelected(item); diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/ClientCookiesActivity.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/ClientCookiesActivity.java new file mode 100644 index 0000000..0b94812 --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/ClientCookiesActivity.java @@ -0,0 +1,199 @@ +package org.torproject.android.ui.hiddenservices; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.database.Cursor; +import android.os.Bundle; +import android.os.Handler; +import android.support.design.widget.FloatingActionButton; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; +import android.widget.Toast; + +import com.google.zxing.integration.android.IntentIntegrator; +import com.google.zxing.integration.android.IntentResult; + +import org.json.JSONException; +import org.json.JSONObject; +import org.torproject.android.R; +import org.torproject.android.ui.hiddenservices.adapters.ClienCookiesAdapter; +import org.torproject.android.ui.hiddenservices.dialogs.AddCookieDialog; +import org.torproject.android.ui.hiddenservices.dialogs.CookieActionsDialog; +import org.torproject.android.ui.hiddenservices.dialogs.SelectCookieBackupDialog; +import org.torproject.android.ui.hiddenservices.providers.CookieContentProvider; +import org.torproject.android.ui.hiddenservices.storage.PermissionManager; + +public class ClientCookiesActivity extends AppCompatActivity { + public final int WRITE_EXTERNAL_STORAGE_FROM_COOKIE_ACTIONBAR = 3; + + private ContentResolver mResolver; + private ClienCookiesAdapter mAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.layout_activity_client_cookies); + + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + mResolver = getContentResolver(); + + FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + AddCookieDialog dialog = new AddCookieDialog(); + dialog.show(getSupportFragmentManager(), "AddCookieDialog"); + } + }); + + mAdapter = new ClienCookiesAdapter( + this, + mResolver.query(CookieContentProvider.CONTENT_URI, CookieContentProvider.PROJECTION, null, null, null) + , 0); + + mResolver.registerContentObserver( + CookieContentProvider.CONTENT_URI, true, new HSObserver(new Handler()) + ); + + ListView cookies = (ListView) findViewById(R.id.clien_cookies_list); + cookies.setAdapter(mAdapter); + + cookies.setOnItemClickListener(new AdapterView.OnItemClickListener() { + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + Cursor item = (Cursor) parent.getItemAtPosition(position); + + Bundle arguments = new Bundle(); + arguments.putInt( + "_id", item.getInt(item.getColumnIndex(CookieContentProvider.ClientCookie._ID)) + ); + + arguments.putString( + "domain", item.getString(item.getColumnIndex(CookieContentProvider.ClientCookie.DOMAIN)) + ); + + arguments.putString( + "auth_cookie_value", item.getString(item.getColumnIndex(CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE)) + ); + + arguments.putInt( + "enabled", item.getInt(item.getColumnIndex(CookieContentProvider.ClientCookie.ENABLED)) + ); + + CookieActionsDialog dialog = new CookieActionsDialog(); + dialog.setArguments(arguments); + dialog.show(getSupportFragmentManager(), "CookieActionsDialog"); + } + }); + + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.cookie_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + if (id == R.id.cookie_restore_backup) { + if (PermissionManager.usesRuntimePermissions() + && !PermissionManager.hasExternalWritePermission(this)) { + PermissionManager.requestPermissions(this, WRITE_EXTERNAL_STORAGE_FROM_COOKIE_ACTIONBAR); + return true; + } + + SelectCookieBackupDialog dialog = new SelectCookieBackupDialog(); + dialog.show(getSupportFragmentManager(), "SelectCookieBackupDialog"); + + } else if (id == R.id.cookie_from_qr) { + IntentIntegrator integrator = new IntentIntegrator(ClientCookiesActivity.this); + integrator.initiateScan(); + } + + return super.onOptionsItemSelected(item); + } + + @Override + public void onRequestPermissionsResult(int requestCode, + String permissions[], int[] grantResults) { + if (grantResults.length < 1 + || grantResults[0] != PackageManager.PERMISSION_GRANTED) { + return; + } + + switch (requestCode) { + case WRITE_EXTERNAL_STORAGE_FROM_COOKIE_ACTIONBAR: { + SelectCookieBackupDialog dialog = new SelectCookieBackupDialog(); + dialog.show(getSupportFragmentManager(), "SelectCookieBackupDialog"); + break; + } + case CookieActionsDialog.WRITE_EXTERNAL_STORAGE_FROM_COOKIE_ACTION_DIALOG: { + Toast.makeText(this, R.string.click_again_for_backup, Toast.LENGTH_LONG).show(); + break; + } + } + } + + @Override + protected void onActivityResult(int request, int response, Intent data) { + super.onActivityResult(request, response, data); + + IntentResult scanResult = IntentIntegrator.parseActivityResult(request, response, data); + + if (scanResult == null) return; + + String results = scanResult.getContents(); + + if (results == null || results.length() < 1) return; + + try { + JSONObject savedValues = new JSONObject(results); + ContentValues fields = new ContentValues(); + + fields.put( + CookieContentProvider.ClientCookie.DOMAIN, + savedValues.getString(CookieContentProvider.ClientCookie.DOMAIN) + ); + + fields.put( + CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE, + savedValues.getString(CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE) + ); + + mResolver.insert(CookieContentProvider.CONTENT_URI, fields); + + } catch (JSONException e) { + e.printStackTrace(); + Toast.makeText(this, R.string.error, Toast.LENGTH_LONG).show(); + } + } + + class HSObserver extends ContentObserver { + HSObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + mAdapter.changeCursor(mResolver.query( + CookieContentProvider.CONTENT_URI, CookieContentProvider.PROJECTION, null, null, null + )); + } + } + +} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/HiddenServicesActivity.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/HiddenServicesActivity.java index c4f55a1..ae2cd55 100644 --- a/app/src/main/java/org/torproject/android/ui/hiddenservices/HiddenServicesActivity.java +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/HiddenServicesActivity.java @@ -24,7 +24,7 @@ import org.torproject.android.R; import org.torproject.android.ui.hiddenservices.adapters.OnionListAdapter; import org.torproject.android.ui.hiddenservices.dialogs.HSActionsDialog; import org.torproject.android.ui.hiddenservices.dialogs.HSDataDialog; -import org.torproject.android.ui.hiddenservices.dialogs.SelectBackupDialog; +import org.torproject.android.ui.hiddenservices.dialogs.SelectHSBackupDialog; import org.torproject.android.ui.hiddenservices.providers.HSContentProvider; import org.torproject.android.ui.hiddenservices.storage.PermissionManager;
@@ -156,9 +156,8 @@ public class HiddenServicesActivity extends AppCompatActivity { return true; }
- SelectBackupDialog dialog = new SelectBackupDialog(); - dialog.show(getSupportFragmentManager(), "SelectBackupDialog"); - return true; + SelectHSBackupDialog dialog = new SelectHSBackupDialog(); + dialog.show(getSupportFragmentManager(), "SelectHSBackupDialog"); }
return super.onOptionsItemSelected(item); @@ -174,8 +173,8 @@ public class HiddenServicesActivity extends AppCompatActivity {
switch (requestCode) { case WRITE_EXTERNAL_STORAGE_FROM_ACTIONBAR: { - SelectBackupDialog dialog = new SelectBackupDialog(); - dialog.show(getSupportFragmentManager(), "SelectBackupDialog"); + SelectHSBackupDialog dialog = new SelectHSBackupDialog(); + dialog.show(getSupportFragmentManager(), "SelectHSBackupDialog"); break; } case HSActionsDialog.WRITE_EXTERNAL_STORAGE_FROM_ACTION_DIALOG: { diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/adapters/ClienCookiesAdapter.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/adapters/ClienCookiesAdapter.java new file mode 100644 index 0000000..a9d782b --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/adapters/ClienCookiesAdapter.java @@ -0,0 +1,64 @@ +package org.torproject.android.ui.hiddenservices.adapters; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.support.v4.widget.CursorAdapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CompoundButton; +import android.widget.Switch; +import android.widget.TextView; +import android.widget.Toast; + +import org.torproject.android.R; +import org.torproject.android.ui.hiddenservices.providers.CookieContentProvider; + +public class ClienCookiesAdapter extends CursorAdapter { + private LayoutInflater cursorInflater; + + public ClienCookiesAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + + cursorInflater = (LayoutInflater) context.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + final Context mContext = context; + int id = cursor.getInt(cursor.getColumnIndex(CookieContentProvider.ClientCookie._ID)); + final String where = CookieContentProvider.ClientCookie._ID + "=" + id; + + TextView domain = (TextView) view.findViewById(R.id.cookie_onion); + domain.setText(cursor.getString(cursor.getColumnIndex(CookieContentProvider.ClientCookie.DOMAIN))); + + Switch enabled = (Switch) view.findViewById(R.id.cookie_switch); + enabled.setChecked( + cursor.getInt(cursor.getColumnIndex(CookieContentProvider.ClientCookie.ENABLED)) == 1 + ); + + enabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + ContentResolver resolver = mContext.getContentResolver(); + ContentValues fields = new ContentValues(); + fields.put(CookieContentProvider.ClientCookie.ENABLED, isChecked); + resolver.update( + CookieContentProvider.CONTENT_URI, fields, where, null + ); + + Toast.makeText( + mContext, R.string.please_restart_Orbot_to_enable_the_changes, Toast.LENGTH_LONG + ).show(); + } + }); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return cursorInflater.inflate(R.layout.layout_client_cookie_list_item, parent, false); + } +} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/backup/BackupUtils.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/backup/BackupUtils.java index 58911d9..a4790dd 100644 --- a/app/src/main/java/org/torproject/android/ui/hiddenservices/backup/BackupUtils.java +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/backup/BackupUtils.java @@ -12,6 +12,7 @@ import org.json.JSONException; import org.json.JSONObject; import org.torproject.android.R; import org.torproject.android.service.TorServiceConstants; +import org.torproject.android.ui.hiddenservices.providers.CookieContentProvider; import org.torproject.android.ui.hiddenservices.providers.HSContentProvider; import org.torproject.android.ui.hiddenservices.storage.ExternalStorage;
@@ -28,21 +29,19 @@ import java.nio.charset.Charset;
public class BackupUtils { private final String configFileName = "config.json"; - private File mHSBasePath; private Context mContext; private ContentResolver mResolver;
public BackupUtils(Context context) { mContext = context; - mHSBasePath = new File( - mContext.getFilesDir().getAbsolutePath(), - TorServiceConstants.HIDDEN_SERVICES_DIR - ); - mResolver = mContext.getContentResolver(); }
public String createZipBackup(Integer port) { + File mHSBasePath = new File( + mContext.getFilesDir().getAbsolutePath(), + TorServiceConstants.HIDDEN_SERVICES_DIR + );
String configFilePath = mHSBasePath + "/hs" + port + "/" + configFileName; String hostnameFilePath = mHSBasePath + "/hs" + port + "/hostname"; @@ -115,6 +114,8 @@ public class BackupUtils { return null; }
+ portData.close(); + try { FileWriter file = new FileWriter(configFilePath); file.write(config.toString()); @@ -124,8 +125,6 @@ public class BackupUtils { return null; }
- portData.close(); - String zip_path = storage_path.getAbsolutePath() + "/hs" + port + ".zip"; String files[] = {hostnameFilePath, keyFilePath, configFilePath};
@@ -139,6 +138,11 @@ public class BackupUtils {
public void restoreZipBackup(File backup) {
+ File mHSBasePath = new File( + mContext.getFilesDir().getAbsolutePath(), + TorServiceConstants.HIDDEN_SERVICES_DIR + ); + int port; Cursor service; String backupName = backup.getName(); @@ -154,7 +158,7 @@ public class BackupUtils { zip.unzip(hsPath.getAbsolutePath());
File config = new File(configFilePath); - FileInputStream stream = null; + FileInputStream stream;
try { stream = new FileInputStream(config); @@ -236,6 +240,10 @@ public class BackupUtils { }
public void restoreKeyBackup(int hsPort, Uri hsKeyPath) { + File mHSBasePath = new File( + mContext.getFilesDir().getAbsolutePath(), + TorServiceConstants.HIDDEN_SERVICES_DIR + );
File serviceDir = new File(mHSBasePath, "hs" + hsPort);
@@ -258,4 +266,71 @@ public class BackupUtils { e.printStackTrace(); } } + + public void restoreCookieBackup(File p) { + File config = new File(p.getAbsolutePath()); + FileInputStream stream; + String jString = null; + + try { + stream = new FileInputStream(config); + FileChannel fc = stream.getChannel(); + MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); + jString = Charset.defaultCharset().decode(bb).toString(); + stream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + if (jString == null) + Toast.makeText(mContext, R.string.error, Toast.LENGTH_LONG).show(); + + try { + JSONObject savedValues = new JSONObject(jString); + ContentValues fields = new ContentValues(); + + fields.put( + CookieContentProvider.ClientCookie.DOMAIN, + savedValues.getString(CookieContentProvider.ClientCookie.DOMAIN) + ); + + fields.put( + CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE, + savedValues.getString(CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE) + ); + + fields.put( + CookieContentProvider.ClientCookie.ENABLED, + savedValues.getInt(CookieContentProvider.ClientCookie.ENABLED) + ); + + mResolver.insert(CookieContentProvider.CONTENT_URI, fields); + + } catch (JSONException e) { + e.printStackTrace(); + Toast.makeText(mContext, R.string.error, Toast.LENGTH_LONG).show(); + } + + Toast.makeText(mContext, R.string.backup_restored, Toast.LENGTH_LONG).show(); + } + + public String createCookieBackup(String domain, String cookie, Integer enabled) { + File storage_path = ExternalStorage.getOrCreateBackupDir(); + String backupFile = storage_path.getAbsolutePath() + '/' + domain.replace(".onion", ".json"); + + JSONObject backup = new JSONObject(); + try { + backup.put(CookieContentProvider.ClientCookie.DOMAIN, domain); + backup.put(CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE, cookie); + backup.put(CookieContentProvider.ClientCookie.ENABLED, enabled); + FileWriter file = new FileWriter(backupFile); + file.write(backup.toString()); + file.close(); + } catch (JSONException | IOException e) { + e.printStackTrace(); + return null; + } + + return backupFile; + } } diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/database/HSDatabase.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/database/HSDatabase.java index 779c775..18bc7aa 100644 --- a/app/src/main/java/org/torproject/android/ui/hiddenservices/database/HSDatabase.java +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/database/HSDatabase.java @@ -8,6 +8,7 @@ import android.database.sqlite.SQLiteOpenHelper; public class HSDatabase extends SQLiteOpenHelper {
public static final String HS_DATA_TABLE_NAME = "hs_data"; + public static final String HS_CLIENT_COOKIE_TABLE_NAME = "hs_client_cookie"; private static final int DATABASE_VERSION = 2; private static final String DATABASE_NAME = "hidden_services"; private static final String HS_DATA_TABLE_CREATE = @@ -22,6 +23,13 @@ public class HSDatabase extends SQLiteOpenHelper { "enabled INTEGER DEFAULT 1, " + "port INTEGER);";
+ private static final String HS_CLIENT_COOKIE_TABLE_CREATE = + "CREATE TABLE " + HS_CLIENT_COOKIE_TABLE_NAME + " (" + + "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "domain TEXT, " + + "auth_cookie_value TEXT, " + + "enabled INTEGER DEFAULT 1);"; + public HSDatabase(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @@ -29,6 +37,7 @@ public class HSDatabase extends SQLiteOpenHelper { @Override public void onCreate(SQLiteDatabase db) { db.execSQL(HS_DATA_TABLE_CREATE); + db.execSQL(HS_CLIENT_COOKIE_TABLE_CREATE); }
@Override diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/AddCookieDialog.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/AddCookieDialog.java new file mode 100644 index 0000000..7566364 --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/AddCookieDialog.java @@ -0,0 +1,84 @@ +package org.torproject.android.ui.hiddenservices.dialogs; + + +import android.app.Dialog; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +import org.torproject.android.R; +import org.torproject.android.ui.hiddenservices.providers.CookieContentProvider; + +public class AddCookieDialog extends DialogFragment { + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + final View dialog_view = getActivity().getLayoutInflater().inflate(R.layout.layout_add_client_cookie_dialog, null); + + final AlertDialog addCookieDialog = new AlertDialog.Builder(getActivity()) + .setView(dialog_view) + .setTitle(R.string.client_cookies) + .create(); + + Button save = (Button) dialog_view.findViewById(R.id.cookie_dialog_save); + save.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + String onion = ((EditText) dialog_view.findViewById(R.id.cookie_onion)).getText().toString(); + String cookie = ((EditText) dialog_view.findViewById(R.id.cookie_value)).getText().toString(); + + if (checkInput(onion, cookie)) { + saveData(onion, cookie); + Toast.makeText( + v.getContext(), R.string.please_restart_Orbot_to_enable_the_changes, Toast.LENGTH_LONG + ).show(); + addCookieDialog.dismiss(); + } + } + }); + + Button cancel = (Button) dialog_view.findViewById(R.id.cookie_dialog_cancel); + cancel.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + addCookieDialog.cancel(); + } + }); + + return addCookieDialog; + } + + private boolean checkInput(String onion, String cookie) { + + boolean is_set = ((onion != null && onion.length() > 0) && (cookie != null && cookie.length() > 0)); + if (!is_set) { + Toast.makeText(getContext(), R.string.fields_can_t_be_empty, Toast.LENGTH_SHORT).show(); + return false; + } + + if (!onion.matches("([a-z0-9]{16})\.onion")) { + Toast.makeText(getContext(), R.string.invalid_onion_address, Toast.LENGTH_SHORT).show(); + return false; + } + + return true; + } + + private void saveData(String domain, String cookie) { + + ContentValues fields = new ContentValues(); + fields.put(CookieContentProvider.ClientCookie.DOMAIN, domain); + fields.put(CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE, cookie); + + ContentResolver cr = getContext().getContentResolver(); + + cr.insert(CookieContentProvider.CONTENT_URI, fields); + } +} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/CookieActionsDialog.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/CookieActionsDialog.java new file mode 100644 index 0000000..e5683da --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/CookieActionsDialog.java @@ -0,0 +1,95 @@ +package org.torproject.android.ui.hiddenservices.dialogs; + + +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.view.View; +import android.widget.Button; +import android.widget.Toast; + +import org.torproject.android.R; +import org.torproject.android.ui.hiddenservices.backup.BackupUtils; +import org.torproject.android.ui.hiddenservices.storage.PermissionManager; + +public class CookieActionsDialog extends DialogFragment { + public static final int WRITE_EXTERNAL_STORAGE_FROM_COOKIE_ACTION_DIALOG = 4; + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Bundle arguments = getArguments(); + + final View dialog_view = getActivity().getLayoutInflater().inflate(R.layout.layout_cookie_actions, null); + final AlertDialog actionDialog = new AlertDialog.Builder(getActivity()) + .setView(dialog_view) + .setTitle(R.string.client_cookies) + .create(); + + Button backup = (Button) dialog_view.findViewById(R.id.btn_cookie_backup); + backup.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + Context mContext = v.getContext(); + + if (PermissionManager.usesRuntimePermissions() + && !PermissionManager.hasExternalWritePermission(mContext)) { + + PermissionManager.requestPermissions( + getActivity(), WRITE_EXTERNAL_STORAGE_FROM_COOKIE_ACTION_DIALOG); + + return; + } + + BackupUtils backup_utils = new BackupUtils(mContext); + String backupPath = backup_utils.createCookieBackup( + arguments.getString("domain"), + arguments.getString("auth_cookie_value"), + arguments.getInt("enabled") + ); + + if (backupPath == null || backupPath.length() < 1) { + Toast.makeText(mContext, R.string.error, Toast.LENGTH_LONG).show(); + actionDialog.dismiss(); + return; + } + + Toast.makeText(mContext, R.string.backup_saved_at_external_storage, Toast.LENGTH_LONG).show(); + + Uri selectedUri = Uri.parse(backupPath.substring(0, backupPath.lastIndexOf("/"))); + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(selectedUri, "resource/folder"); + + if (intent.resolveActivityInfo(mContext.getPackageManager(), 0) != null) { + startActivity(intent); + } else { + Toast.makeText(mContext, R.string.filemanager_not_available, Toast.LENGTH_LONG).show(); + } + actionDialog.dismiss(); + } + }); + + Button delete = (Button) dialog_view.findViewById(R.id.btn_cookie_delete); + delete.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + CookieDeleteDialog dialog = new CookieDeleteDialog(); + dialog.setArguments(arguments); + dialog.show(getFragmentManager(), "CookieDeleteDialog"); + actionDialog.dismiss(); + } + }); + + Button cancel = (Button) dialog_view.findViewById(R.id.btn_cookie_cancel); + cancel.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + actionDialog.dismiss(); + } + }); + + return actionDialog; + } +} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/CookieDeleteDialog.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/CookieDeleteDialog.java new file mode 100644 index 0000000..60c4d8e --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/CookieDeleteDialog.java @@ -0,0 +1,50 @@ +package org.torproject.android.ui.hiddenservices.dialogs; + + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; + +import org.torproject.android.R; +import org.torproject.android.ui.hiddenservices.providers.CookieContentProvider; + +public class CookieDeleteDialog extends DialogFragment { + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Bundle arguments = getArguments(); + final Context context = getContext(); + + DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + // Delete from db + context.getContentResolver().delete( + CookieContentProvider.CONTENT_URI, + CookieContentProvider.ClientCookie._ID + "=" + arguments.getInt("_id"), + null + ); + + break; + + case DialogInterface.BUTTON_NEGATIVE: + // Do nothing + break; + } + } + }; + + return new AlertDialog.Builder(context) + .setMessage(R.string.confirm_cookie_deletion) + .setPositiveButton(R.string.btn_okay, dialogClickListener) + .setNegativeButton(R.string.btn_cancel, dialogClickListener) + .create(); + } +} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/HSCookieDialog.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/HSCookieDialog.java index ff53cec..39c1b51 100644 --- a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/HSCookieDialog.java +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/HSCookieDialog.java @@ -13,7 +13,12 @@ import android.widget.Button; import android.widget.TextView; import android.widget.Toast;
+import com.google.zxing.integration.android.IntentIntegrator; + +import org.json.JSONException; +import org.json.JSONObject; import org.torproject.android.R; +import org.torproject.android.ui.hiddenservices.providers.CookieContentProvider;
public class HSCookieDialog extends DialogFragment {
@@ -44,6 +49,26 @@ public class HSCookieDialog extends DialogFragment { } });
+ Button shareQR = (Button) dialog_view.findViewById(R.id.hs_cookie_to_qr); + shareQR.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + try { + JSONObject backup = new JSONObject(); + backup.put(CookieContentProvider.ClientCookie.DOMAIN, arguments.getString("onion")); + backup.put(CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE, arguments.getString("auth_cookie_value")); + + IntentIntegrator integrator = new IntentIntegrator(getActivity()); + integrator.shareText(backup.toString()); + + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + cookieDialog.dismiss(); + } + }); + Button cancel = (Button) dialog_view.findViewById(R.id.hs_cookie_cancel); cancel.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectBackupDialog.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectBackupDialog.java deleted file mode 100644 index 0c0d943..0000000 --- a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectBackupDialog.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.torproject.android.ui.hiddenservices.dialogs; - -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AlertDialog; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; - -import org.torproject.android.R; -import org.torproject.android.ui.hiddenservices.adapters.BackupAdapter; -import org.torproject.android.ui.hiddenservices.backup.BackupUtils; -import org.torproject.android.ui.hiddenservices.storage.ExternalStorage; - -import java.io.File; -import java.io.FilenameFilter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class SelectBackupDialog extends DialogFragment { - - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - AlertDialog.Builder backupsDialog = new AlertDialog.Builder(getActivity()); - - backupsDialog.setTitle(R.string.restore_backup); - - File backupDir = ExternalStorage.getOrCreateBackupDir(); - File[] files = null; - - try { - files = backupDir.listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.toLowerCase().endsWith(".zip"); - } - }); - } catch (NullPointerException e) { - // Silent block - } - - if (files == null || files.length < 1) { - backupsDialog.setMessage(R.string.create_a_backup_first); - backupsDialog.setNegativeButton(R.string.btn_cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dialog.dismiss(); - } - }); - - return backupsDialog.create(); - } - - final View dialog_view = getActivity().getLayoutInflater().inflate(R.layout.layout_hs_backups_list, null); - - backupsDialog.setView(dialog_view); - backupsDialog.setPositiveButton(R.string.btn_okay, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dialog.dismiss(); - } - }); - - ListView backups = (ListView) dialog_view.findViewById(R.id.listview_hs_backups); - - List<File> zips = new ArrayList<>(); - Collections.addAll(zips, files); - - backups.setAdapter(new BackupAdapter(getContext(), R.layout.layout_hs_backups_list_item, zips)); - backups.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - BackupUtils backupUtils = new BackupUtils(view.getContext().getApplicationContext()); - File p = (File) parent.getItemAtPosition(position); - backupUtils.restoreZipBackup(p); - } - }); - - return backupsDialog.create(); - } -} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectCookieBackupDialog.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectCookieBackupDialog.java new file mode 100644 index 0000000..de5b784 --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectCookieBackupDialog.java @@ -0,0 +1,84 @@ +package org.torproject.android.ui.hiddenservices.dialogs; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; + +import org.torproject.android.R; +import org.torproject.android.ui.hiddenservices.adapters.BackupAdapter; +import org.torproject.android.ui.hiddenservices.backup.BackupUtils; +import org.torproject.android.ui.hiddenservices.storage.ExternalStorage; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SelectCookieBackupDialog extends DialogFragment { + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder cookieBackupDialog = new AlertDialog.Builder(getActivity()); + + cookieBackupDialog.setTitle(R.string.restore_backup); + + File backupDir = ExternalStorage.getOrCreateBackupDir(); + File[] files = null; + + try { + files = backupDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.toLowerCase().endsWith(".json"); + } + }); + } catch (NullPointerException e) { + // Silent block + } + + if (files == null || files.length < 1) { + cookieBackupDialog.setMessage(R.string.create_a_backup_first); + cookieBackupDialog.setNegativeButton(R.string.btn_cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + } + }); + + return cookieBackupDialog.create(); + } + + final View dialog_view = getActivity().getLayoutInflater().inflate(R.layout.layout_hs_backups_list, null); + + cookieBackupDialog.setView(dialog_view); + cookieBackupDialog.setPositiveButton(R.string.btn_okay, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + } + }); + + ListView backups = (ListView) dialog_view.findViewById(R.id.listview_hs_backups); + + List<File> json_backups = new ArrayList<>(); + Collections.addAll(json_backups, files); + + backups.setAdapter(new BackupAdapter(getContext(), R.layout.layout_hs_backups_list_item, json_backups)); + backups.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + BackupUtils backupUtils = new BackupUtils(view.getContext().getApplicationContext()); + File p = (File) parent.getItemAtPosition(position); + backupUtils.restoreCookieBackup(p); + } + }); + + return cookieBackupDialog.create(); + } +} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectHSBackupDialog.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectHSBackupDialog.java new file mode 100644 index 0000000..a6e3bac --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectHSBackupDialog.java @@ -0,0 +1,84 @@ +package org.torproject.android.ui.hiddenservices.dialogs; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; + +import org.torproject.android.R; +import org.torproject.android.ui.hiddenservices.adapters.BackupAdapter; +import org.torproject.android.ui.hiddenservices.backup.BackupUtils; +import org.torproject.android.ui.hiddenservices.storage.ExternalStorage; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SelectHSBackupDialog extends DialogFragment { + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder backupsDialog = new AlertDialog.Builder(getActivity()); + + backupsDialog.setTitle(R.string.restore_backup); + + File backupDir = ExternalStorage.getOrCreateBackupDir(); + File[] files = null; + + try { + files = backupDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.toLowerCase().endsWith(".zip"); + } + }); + } catch (NullPointerException e) { + // Silent block + } + + if (files == null || files.length < 1) { + backupsDialog.setMessage(R.string.create_a_backup_first); + backupsDialog.setNegativeButton(R.string.btn_cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + } + }); + + return backupsDialog.create(); + } + + final View dialog_view = getActivity().getLayoutInflater().inflate(R.layout.layout_hs_backups_list, null); + + backupsDialog.setView(dialog_view); + backupsDialog.setPositiveButton(R.string.btn_okay, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + } + }); + + ListView backups = (ListView) dialog_view.findViewById(R.id.listview_hs_backups); + + List<File> zips = new ArrayList<>(); + Collections.addAll(zips, files); + + backups.setAdapter(new BackupAdapter(getContext(), R.layout.layout_hs_backups_list_item, zips)); + backups.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + BackupUtils backupUtils = new BackupUtils(view.getContext().getApplicationContext()); + File p = (File) parent.getItemAtPosition(position); + backupUtils.restoreZipBackup(p); + } + }); + + return backupsDialog.create(); + } +} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/providers/CookieContentProvider.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/providers/CookieContentProvider.java new file mode 100644 index 0000000..f420a6d --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/providers/CookieContentProvider.java @@ -0,0 +1,134 @@ +package org.torproject.android.ui.hiddenservices.providers; + +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.provider.BaseColumns; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.torproject.android.ui.hiddenservices.database.HSDatabase; + + +public class CookieContentProvider extends ContentProvider { + public static final String[] PROJECTION = new String[]{ + ClientCookie._ID, + ClientCookie.DOMAIN, + ClientCookie.AUTH_COOKIE_VALUE, + ClientCookie.ENABLED + }; + private static final String AUTH = "org.torproject.android.ui.hiddenservices.providers.cookie"; + public static final Uri CONTENT_URI = + Uri.parse("content://" + AUTH + "/cookie"); + //UriMatcher + private static final int COOKIES = 1; + private static final int COOKIE_ID = 2; + + private static final UriMatcher uriMatcher; + + static { + uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + uriMatcher.addURI(AUTH, "hs", COOKIES); + uriMatcher.addURI(AUTH, "hs/#", COOKIE_ID); + } + + private HSDatabase mServervices; + private Context mContext; + + @Override + public boolean onCreate() { + mContext = getContext(); + mServervices = new HSDatabase(mContext); + return true; + } + + @Nullable + @Override + public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + String where = selection; + if (uriMatcher.match(uri) == COOKIE_ID) { + where = "_id=" + uri.getLastPathSegment(); + } + + SQLiteDatabase db = mServervices.getReadableDatabase(); + + return db.query(HSDatabase.HS_CLIENT_COOKIE_TABLE_NAME, projection, where, + selectionArgs, null, null, sortOrder); + } + + @Nullable + @Override + public String getType(@NonNull Uri uri) { + int match = uriMatcher.match(uri); + + switch (match) { + case COOKIES: + return "vnd.android.cursor.dir/vnd.torproject.cookies"; + case COOKIE_ID: + return "vnd.android.cursor.item/vnd.torproject.cookie"; + default: + return null; + } + } + + @Nullable + @Override + public Uri insert(@NonNull Uri uri, ContentValues values) { + long regId; + + SQLiteDatabase db = mServervices.getWritableDatabase(); + + regId = db.insert(HSDatabase.HS_CLIENT_COOKIE_TABLE_NAME, null, values); + + mContext.getContentResolver().notifyChange(CONTENT_URI, null); + + return ContentUris.withAppendedId(CONTENT_URI, regId); + } + + @Override + public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) { + + String where = selection; + if (uriMatcher.match(uri) == COOKIE_ID) { + where = "_id=" + uri.getLastPathSegment(); + } + + SQLiteDatabase db = mServervices.getWritableDatabase(); + + Integer rows = db.delete(HSDatabase.HS_CLIENT_COOKIE_TABLE_NAME, where, selectionArgs); + + mContext.getContentResolver().notifyChange(CONTENT_URI, null); + + return rows; + + } + + @Override + public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { + SQLiteDatabase db = mServervices.getWritableDatabase(); + + String where = selection; + if (uriMatcher.match(uri) == COOKIE_ID) { + where = "_id=" + uri.getLastPathSegment(); + } + + Integer rows = db.update(HSDatabase.HS_CLIENT_COOKIE_TABLE_NAME, values, where, null); + mContext.getContentResolver().notifyChange(CONTENT_URI, null); + + return rows; + } + + public static final class ClientCookie implements BaseColumns { + public static final String DOMAIN = "domain"; + public static final String AUTH_COOKIE_VALUE = "auth_cookie_value"; + public static final String ENABLED = "enabled"; + + private ClientCookie() { + } + } +} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/storage/ExternalStorage.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/storage/ExternalStorage.java index cda3f8c..c50108b 100644 --- a/app/src/main/java/org/torproject/android/ui/hiddenservices/storage/ExternalStorage.java +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/storage/ExternalStorage.java @@ -5,13 +5,13 @@ import android.os.Environment; import java.io.File;
public class ExternalStorage { - private static final String BACKUPS_DIR = "Orbot-HiddenServices"; + private static final String ORBOT_BACKUPS_DIR = "Orbot";
public static File getOrCreateBackupDir() { if (!isExternalStorageWritable()) return null;
- File dir = new File(Environment.getExternalStorageDirectory(), BACKUPS_DIR); + File dir = new File(Environment.getExternalStorageDirectory(), ORBOT_BACKUPS_DIR);
if (!dir.isDirectory() && !dir.mkdirs()) return null; diff --git a/app/src/main/res/layout/layout_activity_client_cookies.xml b/app/src/main/res/layout/layout_activity_client_cookies.xml new file mode 100644 index 0000000..b8f0626 --- /dev/null +++ b/app/src/main/res/layout/layout_activity_client_cookies.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fitsSystemWindows="true" + tools:context="org.torproject.android.ui.hiddenservices.ClientCookiesActivity"> + + <android.support.design.widget.AppBarLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:theme="@style/DefaultTheme.AppBarOverlay"> + + <android.support.v7.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:background="?attr/colorPrimary" + app:popupTheme="@style/DefaultTheme.PopupOverlay" /> + + </android.support.design.widget.AppBarLayout> + + <include layout="@layout/layout_content_client_cookies" /> + + <android.support.design.widget.FloatingActionButton + android:id="@+id/fab" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|end" + android:layout_margin="@dimen/fab_margin" + app:srcCompat="@android:drawable/stat_notify_more" + app:backgroundTint="@android:color/darker_gray" /> + +</android.support.design.widget.CoordinatorLayout> diff --git a/app/src/main/res/layout/layout_add_client_cookie_dialog.xml b/app/src/main/res/layout/layout_add_client_cookie_dialog.xml new file mode 100644 index 0000000..4669a92 --- /dev/null +++ b/app/src/main/res/layout/layout_add_client_cookie_dialog.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="5dp" + android:paddingRight="5dp" + android:paddingTop="5dp" + android:paddingBottom="5dp"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/label_onion_name" + android:text="@string/onion" + android:textAppearance="@style/TextAppearance.AppCompat.Widget.PopupMenu.Small" + android:paddingLeft="5dp" /> + + <EditText + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="text" + android:ems="10" + android:id="@+id/cookie_onion" /> + + <TextView + android:text="@string/auth_cookie" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/label_cookie_value" + android:textAppearance="@style/TextAppearance.AppCompat.Widget.PopupMenu.Small" + android:paddingLeft="5dp" /> + + <EditText + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ems="10" + android:id="@+id/cookie_value" + android:inputType="text" /> + + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <Button + android:text="@string/btn_cancel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/cookie_dialog_cancel" + android:layout_weight="1" + style="@style/Widget.AppCompat.Button.Borderless.Colored" /> + + <Button + android:text="@string/save" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/cookie_dialog_save" + android:layout_weight="1" + style="@style/Widget.AppCompat.Button.Borderless.Colored" /> + + </LinearLayout> + +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/layout_client_cookie_list_item.xml b/app/src/main/res/layout/layout_client_cookie_list_item.xml new file mode 100644 index 0000000..80eefb9 --- /dev/null +++ b/app/src/main/res/layout/layout_client_cookie_list_item.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:padding="15dp"> + + <TextView + android:id="@+id/cookie_onion" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="18sp" + android:layout_weight="1" /> + + <Switch + android:id="@+id/cookie_switch" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:focusable="false" + android:focusableInTouchMode="false" + android:switchPadding="30dp" /> + +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/layout_content_client_cookies.xml b/app/src/main/res/layout/layout_content_client_cookies.xml new file mode 100644 index 0000000..3ee9a89 --- /dev/null +++ b/app/src/main/res/layout/layout_content_client_cookies.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/content_client_cookies" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + tools:context="org.torproject.android.ui.hiddenservices.ClientCookiesActivity" + tools:showIn="@layout/layout_activity_client_cookies"> + + <ListView + android:id="@+id/clien_cookies_list" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + +</RelativeLayout> diff --git a/app/src/main/res/layout/layout_cookie_actions.xml b/app/src/main/res/layout/layout_cookie_actions.xml new file mode 100644 index 0000000..64d8c1c --- /dev/null +++ b/app/src/main/res/layout/layout_cookie_actions.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent"> + + <Button + android:text="@string/backup_cookie" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/btn_cookie_backup" /> + + <Button + android:text="@string/delete_cookie" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/btn_cookie_delete" /> + + <Button + android:text="@string/btn_cancel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/btn_cookie_cancel" /> + +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/layout_hs_cookie.xml b/app/src/main/res/layout/layout_hs_cookie.xml index 398178f..6d1a893 100644 --- a/app/src/main/res/layout/layout_hs_cookie.xml +++ b/app/src/main/res/layout/layout_hs_cookie.xml @@ -18,6 +18,12 @@ android:id="@+id/hs_cookie_to_clipboard" />
<Button + android:text="@string/share_as_qr" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/hs_cookie_to_qr" /> + + <Button android:text="@string/btn_cancel" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/app/src/main/res/menu/cookie_menu.xml b/app/src/main/res/menu/cookie_menu.xml new file mode 100644 index 0000000..7811025 --- /dev/null +++ b/app/src/main/res/menu/cookie_menu.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto%22%3E + + <item + android:id="@+id/cookie_restore_backup" + android:title="@string/restore_backup" /> + <item + android:id="@+id/cookie_from_qr" + android:title="@string/cookie_from_QR" /> +</menu> \ No newline at end of file diff --git a/app/src/main/res/menu/orbot_main.xml b/app/src/main/res/menu/orbot_main.xml index 179675a..e556b76 100644 --- a/app/src/main/res/menu/orbot_main.xml +++ b/app/src/main/res/menu/orbot_main.xml @@ -44,10 +44,22 @@ </menu> </item>
- <item android:id="@+id/menu_hidden_services" + <item android:title="@string/menu_hidden_services" yourapp:showAsAction="never" - /> + > + <menu> + <item android:id="@+id/menu_hidden_services" + android:title="@string/hosted_services" + yourapp:showAsAction="never" + /> + + <item android:id="@+id/menu_client_cookies" + android:title="@string/client_cookies" + yourapp:showAsAction="never" + /> + </menu> + </item>
<!-- <item android:id="@+id/menu_promo_apps" diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml new file mode 100644 index 0000000..7abc06d --- /dev/null +++ b/app/src/main/res/values-v21/styles.xml @@ -0,0 +1 @@ +<resources></resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4402608..6a07c44 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -128,21 +128,21 @@ <string name="pref_entrance_node_dialog">Enter Entrance Nodes</string> <string name="pref_allow_background_starts_title">Allow Background Starts</string> <string name="pref_allow_background_starts_summary">Let any app tell Orbot to start Tor and related services</string> - + <string name="button_proxy_all">Proxy All</string> <string name="button_proxy_none">Proxy None</string> <string name="button_invert_selection">Invert Selection</string>
<string name="pref_proxy_title">Outbound Network Proxy (Optional)</string> - + <string name="pref_proxy_type_title">Outbound Proxy Type</string> <string name="pref_proxy_type_summary">Protocol to use for proxy server: HTTP, HTTPS, Socks4, Socks5</string> <string name="pref_proxy_type_dialog">Enter Proxy Type</string> - + <string name="pref_proxy_host_title">Outbound Proxy Host</string> <string name="pref_proxy_host_summary">Proxy Server hostname</string> <string name="pref_proxy_host_dialog">Enter Proxy Host</string> - + <string name="pref_proxy_port_title">Outbound Proxy Port</string> <string name="pref_proxy_port_summary">Proxy Server port</string> <string name="pref_proxy_port_dialog">Enter Proxy port</string> @@ -150,13 +150,12 @@ <string name="pref_proxy_username_title">Outbound Proxy Username</string> <string name="pref_proxy_username_summary">Proxy Username (Optional)</string> <string name="pref_proxy_username_dialog">Enter Proxy Username</string> - + <string name="pref_proxy_password_title">Outbound Proxy Password</string> <string name="pref_proxy_password_summary">Proxy Password (Optional)</string> <string name="pref_proxy_password_dialog">Enter Proxy Password</string> - - - + + <string name="status">Status</string> <string name="setting_up_full_transparent_proxying_">Setting up full transparent proxying…</string> <string name="setting_up_app_based_transparent_proxying_">Setting up app-based transparent proxying…</string> @@ -227,17 +226,17 @@ <string name="unable_to_reset_tor">Reboot your device, unable to reset Tor!</string> <string name="pref_use_sys_iptables_title">Use Default Iptables</string> <string name="pref_use_sys_iptables_summary">use the built-in iptables binary instead of the one bundled with Orbot</string> - + <string name="error_installing_binares">The Tor binaries were not able to be installed or upgraded.</string> - + <string name="pref_use_persistent_notifications">Always keep the icon in toolbar when Orbot is connected</string> <string name="pref_use_persistent_notifications_title">Always-On Notifications</string> - + <string name="pref_use_expanded_notifications">Show expanded notification with Tor exit country and IP</string> <string name="pref_use_expanded_notifications_title">Expanded Notifications</string> - + <string name="notification_using_bridges">Bridges enabled!</string> - <string name="default_bridges"/> + <string name="default_bridges" /> <string name="set_locale_title">Language</string> <string name="set_locale_summary">Choose the locale and language for Orbot</string> <string name="wizard_locale_title">Choose Language</string> @@ -251,8 +250,8 @@ <string name="pref_disable_network_title">No Network Auto-Sleep</string> <string name="pref_disable_network_summary">Put Tor to sleep when there is no internet available</string> <string name="newnym">You've switched to a new Tor identity!</string> - - <string name="menu_verify_browser">Browser</string> + + <string name="menu_verify_browser">Browser</string> <string name="menu_use_chatsecure">Use ChatSecure</string>
<string name="permission_manage_tor_label">Manage Tor</string> @@ -262,77 +261,77 @@ <string name="no_network_connectivity_putting_tor_to_sleep_">No network connectivity. Putting Tor to sleep…</string> <string name="network_connectivity_is_good_waking_tor_up_">Network connectivity is good. Waking Tor up…</string> <string name="updating_settings_in_tor_service">updating settings in Tor service</string> - - <string name="pref_socks_title">Tor SOCKS</string> + + <string name="pref_socks_title">Tor SOCKS</string> <string name="pref_socks_summary">Port that Tor offers its SOCKS proxy on (default: 9050 or 0 to disable)</string> <string name="pref_socks_dialog">SOCKS Port Config</string> - - <string name="pref_transport_title">Tor TransProxy Port</string> + + <string name="pref_transport_title">Tor TransProxy Port</string> <string name="pref_transport_summary">Port that Tor offers its Transparent Proxy on (default: 9040 or 0 to disable)</string> <string name="pref_transport_dialog">TransProxy Port Config</string> - - - <string name="pref_dnsport_title">Tor DNS Port</string> + + + <string name="pref_dnsport_title">Tor DNS Port</string> <string name="pref_dnsport_summary">Port that Tor offers its DNS on (default: 5400 or 0 to disable)</string> <string name="pref_dnsport_dialog">DNS Port Config</string> - - - <string name="pref_torrc_title">Torrc Custom Config</string> + + + <string name="pref_torrc_title">Torrc Custom Config</string> <string name="pref_torrc_summary">EXPERTS ONLY: enter direct torrc config lines</string> <string name="pref_torrc_dialog">Custom Torrc</string> - - <string name="wizard_tips_martus">Mobile Martus - Benetech Human Rights Documentation App</string> + + <string name="wizard_tips_martus">Mobile Martus - Benetech Human Rights Documentation App</string> <string name="your_tor_public_ips_">Your Tor Public IPs:</string> <string name="please_disable_this_app_in_android_settings_apps_if_you_are_having_problems_with_orbot_">"Please disable this app in Android->Settings->Apps if you are having problems with Orbot: "</string> <string name="app_conflict">App Conflict</string> - +
<string name="pref_transproxy_refresh_title">Transproxy Auto-Refresh</string> <string name="pref_transproxy_refresh_summary">Re-apply Transproxy rules when the network state changes</string> - + <string name="pref_transproxy_flush_title">Transproxy FORCE REMOVE</string> <string name="pref_transproxy_flush_summary">Tap here to flush all transproxy network rules NOW</string> <string name="transparent_proxy_rules_flushed_">Transparent proxy rules flushed!</string> <string name="you_do_not_have_root_access_enabled">You do not have ROOT access enabled</string> <string name="you_may_need_to_stop_and_start_orbot_for_settings_change_to_be_enabled_">You may need to stop and start Orbot for settings change to be enabled.</string> - + <string name="menu_vpn">Apps</string> - + <string name="kbps">kbps</string> - + <string name="mbps">mbps</string> - + <string name="kb">KB</string> - + <string name="mb">MB</string> - + <string name="bridges_updated">Bridges Updated</string> - + <string name="restart_orbot_to_use_this_bridge_">Please restart Orbot to enable the changes</string> - + <string name="menu_qr">QR Codes</string> - + <string name="if_your_mobile_network_actively_blocks_tor_you_can_use_a_tor_bridge_to_access_the_network_another_way_to_get_bridges_is_to_send_an_email_to_bridges_torproject_org_please_note_that_you_must_send_the_email_using_an_address_from_one_of_the_following_email_providers_riseup_gmail_or_yahoo_">If your mobile network actively blocks Tor, you can use a Bridge to access the network. SELECT one of the bridge types above to enable bridges.</string> - + <string name="bridge_mode">Bridge Mode</string> - + <string name="get_bridges_email">Email</string> <string name="get_bridges_web">Web</string> - + <string name="activate">Activate</string> - + <string name="apps_mode">Apps VPN Mode</string> - + <string name="you_can_enable_all_apps_on_your_device_to_run_through_the_tor_network_using_the_vpn_feature_of_android_">You can enable all apps on your device to run through the Tor network using the VPN feature of Android.\n\n*WARNING* This is a new, experimental feature and in some cases may not start automatically, or may stop. It should NOT be used for anonymity, and ONLY used for getting through firewalls and filters.</string> - + <string name="send_email">Send Email</string> - + <string name="you_must_get_a_bridge_address_by_email_web_or_from_a_friend_once_you_have_this_address_please_paste_it_into_the_bridges_preference_in_orbot_s_setting_and_restart_">You can get a bridge address through email, the web or by scanning a bridge QR code. Select 'Email' or 'Web' below to request a bridge address.\n\nOnce you have an address, copy & paste it into the "Bridges" preference in Orbot's setting and restart.</string> - + <string name="install_orweb">Install Orfox</string> - + <string name="standard_browser">Standard Browser</string> - + <string name="note_only_standard_tor_bridges_work_on_intel_x86_atom_devices">NOTE: Only standard Tor bridges work on Intel X86/ATOM devices</string>
<string name="vpn_default_world">World (Location)</string> @@ -359,6 +358,7 @@ <string name="restore_backup">Restore Backup</string> <string name="create_a_backup_first">Create a backup first</string> <string name="name_can_t_be_empty">Name can't be empty</string> + <string name="fields_can_t_be_empty">Fields can't be empty</string> <string name="start_tor_again_for_finish_the_process">Start Tor again for finish the process</string> <string name="confirm_service_deletion">Confirm service deletion</string> <string name="click_again_for_backup">Click again for backup</string> @@ -367,4 +367,13 @@ <string name="copy_cookie_to_clipboard">Copy cookie to clipboard</string> <string name="auth_cookie_was_not_configured">Auth cookie was not configured</string> <string name="please_restart_Orbot_to_enable_the_changes">Please restart Orbot to enable the changes</string> + <string name="client_cookies">Client cookies</string> + <string name="onion">.onion</string> + <string name="invalid_onion_address">Invalid .onion address</string> + <string name="cookie_from_QR">Read from QR</string> + <string name="backup_cookie">Backup cookie</string> + <string name="delete_cookie">Delete cookie</string> + <string name="confirm_cookie_deletion">Confirm cookie deletion</string> + <string name="hosted_services">Hosted Services</string> + <string name="share_as_qr">Share as QR</string> </resources> diff --git a/orbotservice/src/main/java/org/torproject/android/service/TorService.java b/orbotservice/src/main/java/org/torproject/android/service/TorService.java index 19fb0d1..8956163 100644 --- a/orbotservice/src/main/java/org/torproject/android/service/TorService.java +++ b/orbotservice/src/main/java/org/torproject/android/service/TorService.java @@ -132,7 +132,8 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon private Shell mShellPolipo;
- private static final Uri CONTENT_URI = Uri.parse("content://org.torproject.android.ui.hiddenservices.providers/hs"); + private static final Uri HS_CONTENT_URI = Uri.parse("content://org.torproject.android.ui.hiddenservices.providers/hs"); + private static final Uri COOKIE_CONTENT_URI = Uri.parse("content://org.torproject.android.ui.hiddenservices.providers.cookie/cookie");
public static final class HiddenService implements BaseColumns { public static final String NAME = "name"; @@ -148,7 +149,16 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon } }
- private String[] mProjection = new String[]{ + public static final class ClientCookie implements BaseColumns { + public static final String DOMAIN = "domain"; + public static final String AUTH_COOKIE_VALUE = "auth_cookie_value"; + public static final String ENABLED = "enabled"; + + private ClientCookie() { + } + } + + private String[] hsProjection = new String[]{ HiddenService._ID, HiddenService.NAME, HiddenService.DOMAIN, @@ -158,6 +168,12 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon HiddenService.ONION_PORT, HiddenService.ENABLED};
+ private String[] cookieProjection = new String[]{ + ClientCookie._ID, + ClientCookie.DOMAIN, + ClientCookie.AUTH_COOKIE_VALUE, + ClientCookie.ENABLED}; + public void debug(String msg) { if (Prefs.useDebugLogging()) @@ -778,7 +794,7 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
// Tor is running, update new .onion names at db ContentResolver mCR = getApplicationContext().getContentResolver(); - Cursor hidden_services = mCR.query(CONTENT_URI, mProjection, null, null, null); + Cursor hidden_services = mCR.query(HS_CONTENT_URI, hsProjection, null, null, null); if(hidden_services != null) { try { while (hidden_services.moveToNext()) { @@ -804,7 +820,7 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon fields.put(HiddenService.AUTH_COOKIE_VALUE, aux[1]); } fields.put(HiddenService.DOMAIN, onionHostname); - mCR.update(CONTENT_URI, fields, "port=" + HSLocalPort , null); + mCR.update(HS_CONTENT_URI, fields, "port=" + HSLocalPort , null); } catch (FileNotFoundException e) { logException("unable to read onion hostname file",e); showToolbarNotification(getString(R.string.unable_to_read_hidden_service_name), HS_NOTIFY_ID, R.drawable.ic_stat_notifyerr); @@ -1792,9 +1808,10 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon return false; }
- /* ---- Hidden Services ---- */ ContentResolver mCR = getApplicationContext().getContentResolver(); - Cursor hidden_services = mCR.query(CONTENT_URI, mProjection, HiddenService.ENABLED + "=1", null, null); + + /* ---- Hidden Services ---- */ + Cursor hidden_services = mCR.query(HS_CONTENT_URI, hsProjection, HiddenService.ENABLED + "=1", null, null); if(hidden_services != null) { try { while (hidden_services.moveToNext()) { @@ -1821,6 +1838,22 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon hidden_services.close(); }
+ /* ---- Client Cookies ---- */ + Cursor client_cookies = mCR.query(COOKIE_CONTENT_URI, cookieProjection, ClientCookie.ENABLED + "=1", null, null); + if(client_cookies != null) { + try { + while (client_cookies.moveToNext()) { + String domain = client_cookies.getString(client_cookies.getColumnIndex(ClientCookie.DOMAIN)); + String cookie = client_cookies.getString(client_cookies.getColumnIndex(ClientCookie.AUTH_COOKIE_VALUE)); + extraLines.append("HidServAuth" + ' ' + domain + ' ' + cookie).append('\n'); + } + } catch (Exception e) { + Log.e(OrbotConstants.TAG,"error starting share server",e); + } + + client_cookies.close(); + } + return true; }