Merge branch 'hidden_services' of https://github.com/arrase/orbot into arrase-hidden_services

This commit is contained in:
Nathan Freitas 2017-01-13 22:19:02 -05:00
commit 26b9199378
53 changed files with 3513 additions and 474 deletions

View File

@ -30,5 +30,5 @@ dependencies {
compile project(':orbotservice')
compile 'com.android.support:support-v4:23.4.0'
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0'
}

View File

@ -1,11 +1,13 @@
<?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"/>
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"
@ -15,95 +17,95 @@
<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.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" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application android:name="org.torproject.android.OrbotApp" android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:description="@string/app_description"
android:configChanges="locale|orientation|screenSize"
android:theme="@style/DefaultTheme"
<android:uses-permission android:name="android.permission.READ_PHONE_STATE" />
<application
android:name=".OrbotApp"
android:allowBackup="false"
android:allowClearUserData="true"
android:largeHeap="false"
android:configChanges="locale|orientation|screenSize"
android:description="@string/app_description"
android:hardwareAccelerated="false"
>
<activity android:name=".OrbotMainActivity"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:largeHeap="false"
android:theme="@style/DefaultTheme">
<activity
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>
<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>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="org.torproject.android.REQUEST_HS_PORT" />
</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
-->
<!-- 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: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: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"
/>
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" >
</service>
android:stopWithTask="false"></service>
<service
android:name=".service.vpn.TorVpnService"
android:enabled="true"
android:permission="android.permission.BIND_VPN_SERVICE" >
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService"/>
<action android:name="android.net.VpnService" />
</intent-filter>
</service>
@ -114,24 +116,63 @@
<action android:name="org.torproject.android.intent.action.START" />
</intent-filter>
</receiver>
<receiver android:name=".OnBootReceiver"
android:enabled="true" android:exported="true"
>
<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"/>
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
</receiver>
</application>
<activity
android:name=".ui.hiddenservices.HiddenServicesActivity"
android:label="@string/title_activity_hidden_services"
android:theme="@style/DefaultTheme">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".OrbotMainActivity" />
</activity>
<provider
android:name=".ui.hiddenservices.providers.HSContentProvider"
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"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/hidden_services_paths" />
</provider>
<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>

View File

@ -4,6 +4,7 @@
package org.torproject.android;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
@ -26,6 +27,11 @@ 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.permissions.PermissionManager;
import org.torproject.android.ui.hiddenservices.providers.HSContentProvider;
import org.torproject.android.vpn.VPNEnableActivity;
import android.annotation.SuppressLint;
@ -34,6 +40,8 @@ import android.app.ActivityManager.RunningServiceInfo;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@ -43,6 +51,7 @@ import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@ -80,6 +89,8 @@ import android.widget.Toast;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
import static android.support.v4.content.FileProvider.getUriForFile;
public class OrbotMainActivity extends AppCompatActivity
implements OrbotConstants, OnLongClickListener, OnTouchListener {
@ -114,8 +125,6 @@ public class OrbotMainActivity extends AppCompatActivity
private final static int REQUEST_SETTINGS = 0x9874;
private final static int REQUEST_VPN_APPS_SELECT = 8889;
private final static boolean mIsLollipop = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
// message types for mStatusUpdateHandler
private final static int STATUS_UPDATE = 1;
private static final int MESSAGE_TRAFFIC_COUNT = 2;
@ -141,12 +150,36 @@ public class OrbotMainActivity extends AppCompatActivity
return null;
}
/** Called when the activity is first created. */
private void migratePreferences() {
String hsPortString = mPrefs.getString("pref_hs_ports", "");
if (hsPortString.length() > 0) {
StringTokenizer st = new StringTokenizer(hsPortString, ",");
ContentResolver cr = getContentResolver();
while (st.hasMoreTokens()) {
int hsPort = Integer.parseInt(st.nextToken().split(" ")[0]);
ContentValues fields = new ContentValues();
fields.put("name", hsPort);
fields.put("port", hsPort);
fields.put("onion_port", hsPort);
cr.insert(HSContentProvider.CONTENT_URI, fields);
}
Editor pEdit = mPrefs.edit();
pEdit.remove("pref_hs_ports");
pEdit.commit();
}
}
/**
* Called when the activity is first created.
*/
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPrefs = TorServiceUtils.getSharedPrefs(getApplicationContext());
migratePreferences(); // Migrate old preferences
/* Create the widgets before registering for broadcasts to guarantee
* that the widgets exist when the status updates try to update them */
doLayout();
@ -478,6 +511,10 @@ 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);
@ -548,7 +585,7 @@ public class OrbotMainActivity extends AppCompatActivity
Prefs.putUseVpn(enable);
if (enable) {
if (mIsLollipop) //let the user choose the apps
if (PermissionManager.isLollipopOrHigher()) //let the user choose the apps
startActivityForResult(new Intent(OrbotMainActivity.this, AppManager.class), REQUEST_VPN_APPS_SELECT);
else
startActivity(new Intent(OrbotMainActivity.this, VPNEnableActivity.class));
@ -556,40 +593,66 @@ public class OrbotMainActivity extends AppCompatActivity
stopVpnService();
}
private void enableHiddenServicePort (int hsPort) throws RemoteException, InterruptedException
{
private void enableHiddenServicePort(
String hsName, final int hsPort, int hsRemotePort,
final String backupToPackage, final Uri hsKeyPath,
final Boolean authCookie
) throws RemoteException, InterruptedException {
Editor pEdit = mPrefs.edit();
String onionHostname = null;
String hsPortString = mPrefs.getString("pref_hs_ports", "");
String onionHostname = mPrefs.getString("pref_hs_hostname","");
if (hsName == null)
hsName = "hs" + hsPort;
if (hsPortString.indexOf(hsPort+"")==-1)
{
if (hsPortString.length() > 0 && hsPortString.indexOf(hsPort+"")==-1)
hsPortString += ',' + hsPort;
else
hsPortString = hsPort + "";
if (hsRemotePort == -1)
hsRemotePort = hsPort;
pEdit.putString("pref_hs_ports", hsPortString);
pEdit.putBoolean("pref_hs_enable", true);
ContentValues fields = new ContentValues();
fields.put(HSContentProvider.HiddenService.NAME, hsName);
fields.put(HSContentProvider.HiddenService.PORT, hsPort);
fields.put(HSContentProvider.HiddenService.ONION_PORT, hsRemotePort);
fields.put(HSContentProvider.HiddenService.AUTH_COOKIE, authCookie);
pEdit.commit();
ContentResolver cr = getContentResolver();
Cursor row = cr.query(
HSContentProvider.CONTENT_URI,
HSContentProvider.PROJECTION,
HSContentProvider.HiddenService.ONION_PORT + "=" + hsPort,
null,
null
);
if (row == null || row.getCount() < 1) {
cr.insert(HSContentProvider.CONTENT_URI, fields);
} else {
onionHostname = row.getString(row.getColumnIndex(HSContentProvider.HiddenService.DOMAIN));
row.close();
}
if (onionHostname == null || onionHostname.length() == 0)
{
requestTorRereadConfig();
if (onionHostname == null || onionHostname.length() < 1) {
new Thread () {
if (hsKeyPath != null) {
BackupUtils hsutils = new BackupUtils(getApplicationContext());
hsutils.restoreKeyBackup(hsPort, hsKeyPath);
}
public void run ()
{
String onionHostname = mPrefs.getString("pref_hs_hostname","");
if (torStatus.equals(TorServiceConstants.STATUS_OFF)) {
startTor();
} else {
stopTor();
Toast.makeText(
this, R.string.start_tor_again_for_finish_the_process, Toast.LENGTH_LONG
).show();
}
while (onionHostname.length() == 0)
{
//we need to stop and start Tor
new Thread() {
public void run() {
String hostname = null;
Intent nResult = new Intent();
while (hostname == null) {
try {
Thread.sleep(3000); //wait three seconds
} catch (Exception e) {
@ -597,29 +660,63 @@ public class OrbotMainActivity extends AppCompatActivity
e.printStackTrace();
}
onionHostname = mPrefs.getString("pref_hs_hostname","");
Cursor onion = getContentResolver().query(
HSContentProvider.CONTENT_URI,
HSContentProvider.PROJECTION,
HSContentProvider.HiddenService.ONION_PORT + "=" + hsPort,
null,
null
);
if (onion != null && onion.getCount() > 0) {
onion.moveToNext();
hostname = onion.getString(onion.getColumnIndex(HSContentProvider.HiddenService.DOMAIN));
if(hostname == null || hostname.length() < 1)
continue;
nResult.putExtra("hs_host", hostname);
if (authCookie) {
nResult.putExtra(
"hs_auth_cookie",
onion.getString(onion.getColumnIndex(HSContentProvider.HiddenService.AUTH_COOKIE_VALUE))
);
}
Intent nResult = new Intent();
nResult.putExtra("hs_host", onionHostname);
if (backupToPackage != null && backupToPackage.length() > 0) {
String servicePath = getFilesDir() + "/" + TorServiceConstants.HIDDEN_SERVICES_DIR + "/hs" + hsPort;
File hidden_service_key = new File(servicePath, "private_key");
Context context = getApplicationContext();
Uri contentUri = getUriForFile(
context,
"org.torproject.android.ui.hiddenservices.storage",
hidden_service_key
);
context.grantUriPermission(backupToPackage, contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
nResult.setData(contentUri);
nResult.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
onion.close();
setResult(RESULT_OK, nResult);
finish();
}
}
}
}.start();
}
else
{
} else {
Intent nResult = new Intent();
nResult.putExtra("hs_host", onionHostname);
setResult(RESULT_OK, nResult);
finish();
}
}
private synchronized void handleIntents ()
{
private synchronized void handleIntents() {
if (getIntent() == null)
return;
@ -633,19 +730,26 @@ public class OrbotMainActivity extends AppCompatActivity
if (action == null)
return;
if (action.equals(INTENT_ACTION_REQUEST_HIDDEN_SERVICE))
{
final int hiddenServicePortRequest = getIntent().getIntExtra("hs_port", -1);
switch (action) {
case INTENT_ACTION_REQUEST_HIDDEN_SERVICE:
final int hiddenServicePort = intent.getIntExtra("hs_port", -1);
final int hiddenServiceRemotePort = intent.getIntExtra("hs_onion_port", -1);
final String hiddenServiceName = intent.getStringExtra("hs_name");
final String backupToPackage = intent.getStringExtra("hs_backup_to_package");
final Boolean authCookie = intent.getBooleanExtra("hs_auth_cookie", false);
final Uri mKeyUri = intent.getData();
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
switch (which){
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
try {
enableHiddenServicePort (hiddenServicePortRequest);
enableHiddenServicePort(
hiddenServiceName, hiddenServicePort,
hiddenServiceRemotePort, backupToPackage,
mKeyUri, authCookie
);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
@ -654,26 +758,19 @@ public class OrbotMainActivity extends AppCompatActivity
e.printStackTrace();
}
break;
case DialogInterface.BUTTON_NEGATIVE:
//No button clicked
finish();
break;
}
}
};
String requestMsg = getString(R.string.hidden_service_request, hiddenServicePortRequest);
String requestMsg = getString(R.string.hidden_service_request, hiddenServicePort);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(requestMsg).setPositiveButton("Allow", dialogClickListener)
.setNegativeButton("Deny", dialogClickListener).show();
return; //don't null the setIntent() as we need it later
}
else if (action.equals(INTENT_ACTION_REQUEST_START_TOR))
{
case INTENT_ACTION_REQUEST_START_TOR:
autoStartFromIntent = true;
startTor();
@ -694,13 +791,11 @@ public class OrbotMainActivity extends AppCompatActivity
finish();
}*/
}
else if (action.equals(Intent.ACTION_VIEW))
{
break;
case Intent.ACTION_VIEW:
String urlString = intent.getDataString();
if (urlString != null)
{
if (urlString != null) {
if (urlString.toLowerCase().startsWith("bridge://"))
@ -708,22 +803,21 @@ public class OrbotMainActivity extends AppCompatActivity
String newBridgeValue = urlString.substring(9); //remove the bridge protocol piece
newBridgeValue = URLDecoder.decode(newBridgeValue); //decode the value here
showAlert(getString(R.string.bridges_updated),getString(R.string.restart_orbot_to_use_this_bridge_) + newBridgeValue,false);
showAlert(getString(R.string.bridges_updated), getString(R.string.restart_orbot_to_use_this_bridge_) + newBridgeValue, false);
setNewBridges(newBridgeValue);
}
}
break;
}
updateStatus(null);
setIntent(null);
}
private void setNewBridges (String newBridgeValue)
{
private void setNewBridges(String newBridgeValue) {
Prefs.setBridgesList(newBridgeValue); //set the string to a preference
Prefs.putBridgesEnabled(true);
@ -1194,10 +1288,6 @@ public class OrbotMainActivity extends AppCompatActivity
{
autoStartFromIntent = false;
Intent resultIntent = lastStatusIntent;
if (resultIntent == null)
resultIntent = new Intent(TorServiceConstants.ACTION_START);
resultIntent.putExtra(TorServiceConstants.EXTRA_STATUS, torStatus);
setResult(RESULT_OK, resultIntent);
finish();

View File

@ -37,9 +37,6 @@ public class SettingsPreferences
private Preference prefTransProxyFlush = null;
private Preference prefTransProxyApps = null;
private CheckBoxPreference prefHiddenServices = null;
private EditTextPreference prefHiddenServicesPorts;
private EditTextPreference prefHiddenServicesHostname;
private CheckBoxPreference prefRequestRoot = null;
private ListPreference prefLocale = null;
@ -104,10 +101,6 @@ public class SettingsPreferences
prefTransProxyApps.setOnPreferenceClickListener(this);
prefCBTransProxy.setOnPreferenceClickListener(this);
prefcBTransProxyAll.setOnPreferenceClickListener(this);
prefHiddenServices = (CheckBoxPreference) findPreference("pref_hs_enable");
prefHiddenServices.setOnPreferenceClickListener(this);
prefHiddenServicesHostname = (EditTextPreference) findPreference("pref_hs_hostname");
prefCBTransProxy.setEnabled(prefRequestRoot.isChecked());
prefcBTransProxyAll.setEnabled(prefCBTransProxy.isChecked());
@ -116,10 +109,6 @@ public class SettingsPreferences
if (prefCBTransProxy.isChecked())
prefTransProxyApps.setEnabled((!prefcBTransProxyAll.isChecked()));
prefHiddenServicesPorts = (EditTextPreference) findPreference("pref_hs_ports");
prefHiddenServicesHostname.setEnabled(prefHiddenServices.isChecked());
prefHiddenServicesPorts.setEnabled(prefHiddenServices.isChecked());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
prefTransProxyApps.setEnabled(true);
@ -153,11 +142,6 @@ public class SettingsPreferences
{
startActivity(new Intent(this, AppManager.class));
}
else if (preference == prefHiddenServices)
{
prefHiddenServicesPorts.setEnabled(prefHiddenServices.isChecked());
prefHiddenServicesHostname.setEnabled(prefHiddenServices.isChecked());
}
else
{

View File

@ -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.ClientCookiesAdapter;
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.permissions.PermissionManager;
import org.torproject.android.ui.hiddenservices.providers.CookieContentProvider;
public class ClientCookiesActivity extends AppCompatActivity {
public final int WRITE_EXTERNAL_STORAGE_FROM_COOKIE_ACTIONBAR = 3;
private ContentResolver mResolver;
private ClientCookiesAdapter 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 ClientCookiesAdapter(
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.isLollipopOrHigher()
&& !PermissionManager.hasExternalWritePermission(this)) {
PermissionManager.requestExternalWritePermissions(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
));
}
}
}

View File

@ -0,0 +1,214 @@
package org.torproject.android.ui.hiddenservices;
import android.content.ContentResolver;
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.v4.view.MenuItemCompat;
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.ArrayAdapter;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.Toast;
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.SelectHSBackupDialog;
import org.torproject.android.ui.hiddenservices.permissions.PermissionManager;
import org.torproject.android.ui.hiddenservices.providers.HSContentProvider;
public class HiddenServicesActivity extends AppCompatActivity {
public final int WRITE_EXTERNAL_STORAGE_FROM_ACTIONBAR = 1;
private ContentResolver mResolver;
private OnionListAdapter mAdapter;
private FloatingActionButton fab;
private String mWhere = HSContentProvider.HiddenService.CREATED_BY_USER + "=1";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_hs_list_view);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mResolver = getContentResolver();
fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
HSDataDialog dialog = new HSDataDialog();
dialog.show(getSupportFragmentManager(), "HSDataDialog");
}
});
mAdapter = new OnionListAdapter(
this,
mResolver.query(
HSContentProvider.CONTENT_URI, HSContentProvider.PROJECTION, mWhere, null, null
),
0
);
mResolver.registerContentObserver(
HSContentProvider.CONTENT_URI, true, new HSObserver(new Handler())
);
ListView onion_list = (ListView) findViewById(R.id.onion_list);
onion_list.setAdapter(mAdapter);
onion_list.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(HSContentProvider.HiddenService._ID))
);
arguments.putString(
"port", item.getString(item.getColumnIndex(HSContentProvider.HiddenService.PORT))
);
arguments.putString(
"onion", item.getString(item.getColumnIndex(HSContentProvider.HiddenService.DOMAIN))
);
arguments.putInt(
"auth_cookie", item.getInt(item.getColumnIndex(HSContentProvider.HiddenService.AUTH_COOKIE))
);
arguments.putString(
"auth_cookie_value", item.getString(item.getColumnIndex(HSContentProvider.HiddenService.AUTH_COOKIE_VALUE))
);
HSActionsDialog dialog = new HSActionsDialog();
dialog.setArguments(arguments);
dialog.show(getSupportFragmentManager(), "HSActionsDialog");
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.hs_menu, menu);
MenuItem item = menu.findItem(R.id.hs_type);
Spinner spinner = (Spinner) MenuItemCompat.getActionView(item);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.array_hs_types, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> arg0, View v, int pos, long id) {
if (pos == 0) {
mWhere = HSContentProvider.HiddenService.CREATED_BY_USER + "=1";
fab.show();
} else {
mWhere = HSContentProvider.HiddenService.CREATED_BY_USER + "=0";
fab.hide();
}
mAdapter.changeCursor(mResolver.query(
HSContentProvider.CONTENT_URI, HSContentProvider.PROJECTION, mWhere, null, null
));
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
// Do nothing
}
});
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.menu_restore_backup) {
if (PermissionManager.isLollipopOrHigher()
&& !PermissionManager.hasExternalWritePermission(this)) {
PermissionManager.requestExternalWritePermissions(this, WRITE_EXTERNAL_STORAGE_FROM_ACTIONBAR);
return true;
}
SelectHSBackupDialog dialog = new SelectHSBackupDialog();
dialog.show(getSupportFragmentManager(), "SelectHSBackupDialog");
}
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_ACTIONBAR: {
SelectHSBackupDialog dialog = new SelectHSBackupDialog();
dialog.show(getSupportFragmentManager(), "SelectHSBackupDialog");
break;
}
case HSActionsDialog.WRITE_EXTERNAL_STORAGE_FROM_ACTION_DIALOG: {
Toast.makeText(this, R.string.click_again_for_backup, Toast.LENGTH_LONG).show();
break;
}
}
}
class HSObserver extends ContentObserver {
HSObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
mAdapter.changeCursor(mResolver.query(
HSContentProvider.CONTENT_URI, HSContentProvider.PROJECTION, mWhere, null, null
));
if (PermissionManager.isLollipopOrHigher()) {
Cursor active = mResolver.query(
HSContentProvider.CONTENT_URI, HSContentProvider.PROJECTION, HSContentProvider.HiddenService.ENABLED + "=1", null, null
);
if (active == null) return;
if (active.getCount() > 0) // Call only if there running services
PermissionManager.requestBatteryPermmssions(HiddenServicesActivity.this, getApplicationContext());
else // Drop whe not needed
PermissionManager.requestDropBatteryPermmssions(HiddenServicesActivity.this, getApplicationContext());
active.close();
}
}
}
}

View File

@ -0,0 +1,50 @@
package org.torproject.android.ui.hiddenservices.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import org.torproject.android.R;
import java.io.File;
import java.util.List;
public class BackupAdapter extends ArrayAdapter<File> {
private int mResource;
public BackupAdapter(Context context, int resource) {
super(context, resource);
mResource = resource;
}
public BackupAdapter(Context context, int resource, List<File> zips) {
super(context, resource, zips);
mResource = resource;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi;
vi = LayoutInflater.from(getContext());
v = vi.inflate(mResource, null);
}
File p = getItem(position);
if (p != null) {
TextView name = (TextView) v.findViewById(R.id.backup_name);
if (name != null)
name.setText(p.getName());
}
return v;
}
}

View File

@ -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 ClientCookiesAdapter extends CursorAdapter {
private LayoutInflater cursorInflater;
public ClientCookiesAdapter(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);
}
}

View File

@ -0,0 +1,68 @@
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.HSContentProvider;
public class OnionListAdapter extends CursorAdapter {
private LayoutInflater cursorInflater;
public OnionListAdapter(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(HSContentProvider.HiddenService._ID));
final String where = HSContentProvider.HiddenService._ID + "=" + id;
TextView port = (TextView) view.findViewById(R.id.hs_port);
port.setText(cursor.getString(cursor.getColumnIndex(HSContentProvider.HiddenService.PORT)));
TextView name = (TextView) view.findViewById(R.id.hs_name);
name.setText(cursor.getString(cursor.getColumnIndex(HSContentProvider.HiddenService.NAME)));
TextView domain = (TextView) view.findViewById(R.id.hs_onion);
domain.setText(cursor.getString(cursor.getColumnIndex(HSContentProvider.HiddenService.DOMAIN)));
Switch enabled = (Switch) view.findViewById(R.id.hs_switch);
enabled.setChecked(
cursor.getInt(cursor.getColumnIndex(HSContentProvider.HiddenService.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(HSContentProvider.HiddenService.ENABLED, isChecked);
resolver.update(
HSContentProvider.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_hs_list_item, parent, false);
}
}

View File

@ -0,0 +1,336 @@
package org.torproject.android.ui.hiddenservices.backup;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.widget.Toast;
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;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
public class BackupUtils {
private final String configFileName = "config.json";
private Context mContext;
private ContentResolver mResolver;
public BackupUtils(Context context) {
mContext = context;
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";
String keyFilePath = mHSBasePath + "/hs" + port + "/private_key";
File storage_path = ExternalStorage.getOrCreateBackupDir();
if (storage_path == null)
return null;
Cursor portData = mResolver.query(
HSContentProvider.CONTENT_URI,
HSContentProvider.PROJECTION,
HSContentProvider.HiddenService.PORT + "=" + port,
null,
null
);
JSONObject config = new JSONObject();
try {
if (portData == null || portData.getCount() != 1)
return null;
portData.moveToNext();
config.put(
HSContentProvider.HiddenService.NAME,
portData.getString(portData.getColumnIndex(HSContentProvider.HiddenService.NAME))
);
config.put(
HSContentProvider.HiddenService.PORT,
portData.getInt(portData.getColumnIndex(HSContentProvider.HiddenService.PORT))
);
config.put(
HSContentProvider.HiddenService.ONION_PORT,
portData.getInt(portData.getColumnIndex(HSContentProvider.HiddenService.ONION_PORT))
);
config.put(
HSContentProvider.HiddenService.DOMAIN,
portData.getString(portData.getColumnIndex(HSContentProvider.HiddenService.DOMAIN))
);
config.put(
HSContentProvider.HiddenService.AUTH_COOKIE,
portData.getInt(portData.getColumnIndex(HSContentProvider.HiddenService.AUTH_COOKIE))
);
config.put(
HSContentProvider.HiddenService.AUTH_COOKIE_VALUE,
portData.getString(portData.getColumnIndex(HSContentProvider.HiddenService.AUTH_COOKIE_VALUE))
);
config.put(
HSContentProvider.HiddenService.CREATED_BY_USER,
portData.getInt(portData.getColumnIndex(HSContentProvider.HiddenService.CREATED_BY_USER))
);
config.put(
HSContentProvider.HiddenService.ENABLED,
portData.getInt(portData.getColumnIndex(HSContentProvider.HiddenService.ENABLED))
);
} catch (JSONException e) {
e.printStackTrace();
return null;
} catch (NullPointerException e) {
e.printStackTrace();
return null;
}
portData.close();
try {
FileWriter file = new FileWriter(configFilePath);
file.write(config.toString());
file.close();
} catch (IOException e) {
e.printStackTrace();
return null;
}
String zip_path = storage_path.getAbsolutePath() + "/hs" + port + ".zip";
String files[] = {hostnameFilePath, keyFilePath, configFilePath};
ZipIt zip = new ZipIt(files, zip_path);
if (!zip.zip())
return null;
return zip_path;
}
public void restoreZipBackup(File backup) {
File mHSBasePath = new File(
mContext.getFilesDir().getAbsolutePath(),
TorServiceConstants.HIDDEN_SERVICES_DIR
);
int port;
Cursor service;
String backupName = backup.getName();
String hsDir = backupName.substring(0, backupName.lastIndexOf('.'));
String configFilePath = mHSBasePath + "/" + hsDir + "/" + configFileName;
String jString = null;
File hsPath = new File(mHSBasePath.getAbsolutePath(), hsDir);
if (!hsPath.isDirectory())
hsPath.mkdirs();
ZipIt zip = new ZipIt(null, backup.getAbsolutePath());
zip.unzip(hsPath.getAbsolutePath());
File config = new File(configFilePath);
FileInputStream stream;
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(
HSContentProvider.HiddenService.NAME,
savedValues.getString(HSContentProvider.HiddenService.NAME)
);
fields.put(
HSContentProvider.HiddenService.ONION_PORT,
savedValues.getInt(HSContentProvider.HiddenService.ONION_PORT)
);
fields.put(
HSContentProvider.HiddenService.DOMAIN,
savedValues.getString(HSContentProvider.HiddenService.DOMAIN)
);
fields.put(
HSContentProvider.HiddenService.AUTH_COOKIE,
savedValues.getInt(HSContentProvider.HiddenService.AUTH_COOKIE)
);
fields.put(
HSContentProvider.HiddenService.CREATED_BY_USER,
savedValues.getInt(HSContentProvider.HiddenService.CREATED_BY_USER)
);
fields.put(
HSContentProvider.HiddenService.ENABLED,
savedValues.getInt(HSContentProvider.HiddenService.ENABLED)
);
port = savedValues.getInt(HSContentProvider.HiddenService.PORT);
fields.put(HSContentProvider.HiddenService.PORT, port);
service = mResolver.query(
HSContentProvider.CONTENT_URI,
HSContentProvider.PROJECTION,
HSContentProvider.HiddenService.PORT + "=" + port,
null,
null
);
if (service == null || service.getCount() == 0) {
mResolver.insert(HSContentProvider.CONTENT_URI, fields);
} else {
mResolver.update(
HSContentProvider.CONTENT_URI,
fields,
HSContentProvider.HiddenService.PORT + "=" + port,
null
);
service.close();
}
} 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 void restoreKeyBackup(int hsPort, Uri hsKeyPath) {
File mHSBasePath = new File(
mContext.getFilesDir().getAbsolutePath(),
TorServiceConstants.HIDDEN_SERVICES_DIR
);
File serviceDir = new File(mHSBasePath, "hs" + hsPort);
if (!serviceDir.isDirectory())
serviceDir.mkdirs();
try {
ParcelFileDescriptor mInputPFD = mContext.getContentResolver().openFileDescriptor(hsKeyPath, "r");
InputStream fileStream = new FileInputStream(mInputPFD.getFileDescriptor());
OutputStream file = new FileOutputStream(serviceDir.getAbsolutePath() + "/private_key");
byte[] buffer = new byte[1024];
int length;
while ((length = fileStream.read(buffer)) > 0) {
file.write(buffer, 0, length);
}
file.close();
} catch (IOException | NullPointerException e) {
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;
}
}

View File

@ -0,0 +1,101 @@
package org.torproject.android.ui.hiddenservices.backup;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class ZipIt {
private static final int BUFFER = 2048;
private String[] _files;
private String _zipFile;
public ZipIt(@Nullable String[] files, @NonNull String zipFile) {
_files = files;
_zipFile = zipFile;
}
public boolean zip() {
try {
BufferedInputStream origin;
FileOutputStream dest = new FileOutputStream(_zipFile);
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest));
byte data[] = new byte[BUFFER];
for (String _file : _files) {
FileInputStream fi = new FileInputStream(_file);
origin = new BufferedInputStream(fi, BUFFER);
ZipEntry entry = new ZipEntry(_file.substring(_file.lastIndexOf("/") + 1));
out.putNextEntry(entry);
int count;
while ((count = origin.read(data, 0, BUFFER)) != -1) {
out.write(data, 0, count);
}
origin.close();
}
out.close();
} catch (Exception e) {
return false;
}
return true;
}
public boolean unzip(String output_path) {
InputStream is;
ZipInputStream zis;
try {
String filename;
is = new FileInputStream(_zipFile);
zis = new ZipInputStream(new BufferedInputStream(is));
ZipEntry ze;
byte[] buffer = new byte[1024];
int count;
while ((ze = zis.getNextEntry()) != null) {
// zapis do souboru
filename = ze.getName();
// Need to create directories if not exists, or
// it will generate an Exception...
if (ze.isDirectory()) {
File fmd = new File(output_path + "/" + filename);
fmd.mkdirs();
continue;
}
FileOutputStream fout = new FileOutputStream(output_path + "/" + filename);
// cteni zipu a zapis
while ((count = zis.read(buffer)) != -1) {
fout.write(buffer, 0, count);
}
fout.close();
zis.closeEntry();
}
zis.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
}

View File

@ -0,0 +1,47 @@
package org.torproject.android.ui.hiddenservices.database;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
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 =
"CREATE TABLE " + HS_DATA_TABLE_NAME + " (" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"name TEXT, " +
"domain TEXT, " +
"onion_port INTEGER, " +
"auth_cookie INTEGER DEFAULT 0, " +
"auth_cookie_value TEXT, " +
"created_by_user INTEGER DEFAULT 0, " +
"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);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(HS_DATA_TABLE_CREATE);
db.execSQL(HS_CLIENT_COOKIE_TABLE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}

View File

@ -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);
}
}

View File

@ -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.permissions.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.isLollipopOrHigher()
&& !PermissionManager.hasExternalWritePermission(mContext)) {
PermissionManager.requestExternalWritePermissions(
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;
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,130 @@
package org.torproject.android.ui.hiddenservices.dialogs;
import android.app.Dialog;
import android.content.ClipData;
import android.content.ClipboardManager;
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.permissions.PermissionManager;
public class HSActionsDialog extends DialogFragment {
public static final int WRITE_EXTERNAL_STORAGE_FROM_ACTION_DIALOG = 2;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle arguments = getArguments();
final View dialog_view = getActivity().getLayoutInflater().inflate(R.layout.layout_hs_actions, null);
final AlertDialog actionDialog = new AlertDialog.Builder(getActivity())
.setView(dialog_view)
.setTitle(R.string.hidden_services)
.create();
Button backup = (Button) dialog_view.findViewById(R.id.btn_hs_backup);
backup.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Context mContext = v.getContext();
if (PermissionManager.isLollipopOrHigher()
&& !PermissionManager.hasExternalWritePermission(mContext)) {
PermissionManager.requestExternalWritePermissions(
getActivity(), WRITE_EXTERNAL_STORAGE_FROM_ACTION_DIALOG);
return;
}
BackupUtils hsutils = new BackupUtils(mContext);
String backupPath = hsutils.createZipBackup(Integer.parseInt(arguments.getString("port")));
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 copy = (Button) dialog_view.findViewById(R.id.btn_hs_clipboard);
copy.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Context mContext = v.getContext();
ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("onion", arguments.getString("onion"));
clipboard.setPrimaryClip(clip);
Toast.makeText(mContext, R.string.done, Toast.LENGTH_LONG).show();
actionDialog.dismiss();
}
});
Button showAuth = (Button) dialog_view.findViewById(R.id.bt_hs_show_auth);
showAuth.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String auth_cookie_value = arguments.getString("auth_cookie_value");
if (arguments.getInt("auth_cookie") == 1) {
if (auth_cookie_value == null || auth_cookie_value.length() < 1) {
Toast.makeText(
v.getContext(), R.string.please_restart_Orbot_to_enable_the_changes, Toast.LENGTH_LONG
).show();
} else {
HSCookieDialog dialog = new HSCookieDialog();
dialog.setArguments(arguments);
dialog.show(getFragmentManager(), "HSCookieDialog");
}
} else {
Toast.makeText(
v.getContext(), R.string.auth_cookie_was_not_configured, Toast.LENGTH_LONG
).show();
}
actionDialog.dismiss();
}
});
Button delete = (Button) dialog_view.findViewById(R.id.btn_hs_delete);
delete.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
HSDeleteDialog dialog = new HSDeleteDialog();
dialog.setArguments(arguments);
dialog.show(getFragmentManager(), "HSDeleteDialog");
actionDialog.dismiss();
}
});
Button cancel = (Button) dialog_view.findViewById(R.id.btn_hs_cancel);
cancel.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
actionDialog.dismiss();
}
});
return actionDialog;
}
}

View File

@ -0,0 +1,81 @@
package org.torproject.android.ui.hiddenservices.dialogs;
import android.app.Dialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
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.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 {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final View dialog_view = getActivity().getLayoutInflater().inflate(R.layout.layout_hs_cookie, null);
final Bundle arguments = getArguments();
final String auth_cookie_value = arguments.getString("auth_cookie_value");
final AlertDialog cookieDialog = new AlertDialog.Builder(getActivity())
.setView(dialog_view)
.create();
TextView cookie = (TextView) dialog_view.findViewById(R.id.hs_cookie);
cookie.setText(auth_cookie_value);
Button clipboard = (Button) dialog_view.findViewById(R.id.hs_cookie_to_clipboard);
clipboard.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Context mContext = v.getContext();
ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("cookie", auth_cookie_value);
clipboard.setPrimaryClip(clip);
Toast.makeText(mContext, R.string.done, Toast.LENGTH_LONG).show();
cookieDialog.dismiss();
}
});
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) {
cookieDialog.dismiss();
}
});
return cookieDialog;
}
}

View File

@ -0,0 +1,102 @@
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.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
import org.torproject.android.R;
import org.torproject.android.ui.hiddenservices.providers.HSContentProvider;
public class HSDataDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Get the layout
final View dialog_view = getActivity().getLayoutInflater().inflate(R.layout.layout_hs_data_dialog, null);
// Use the Builder class for convenient dialog construction
final AlertDialog serviceDataDialog = new AlertDialog.Builder(getActivity())
.setView(dialog_view)
.setTitle(R.string.hidden_services)
.create();
// Buttons action
Button save = (Button) dialog_view.findViewById(R.id.HSDialogSave);
save.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String serverName = ((EditText) dialog_view.findViewById(R.id.hsName)).getText().toString();
Integer localPort = Integer.parseInt(
((EditText) dialog_view.findViewById(R.id.hsLocalPort)).getText().toString()
);
Integer onionPort = Integer.parseInt(
((EditText) dialog_view.findViewById(R.id.hsOnionPort)).getText().toString()
);
Boolean authCookie = ((CheckBox) dialog_view.findViewById(R.id.hsAuth)).isChecked();
if (checkInput(serverName, localPort, onionPort)) {
saveData(serverName, localPort, onionPort, authCookie);
Toast.makeText(
v.getContext(), R.string.please_restart_Orbot_to_enable_the_changes, Toast.LENGTH_LONG
).show();
serviceDataDialog.dismiss();
}
}
});
Button cancel = (Button) dialog_view.findViewById(R.id.HSDialogCancel);
cancel.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
serviceDataDialog.cancel();
}
});
return serviceDataDialog;
}
private boolean checkInput(String serverName, Integer local, Integer remote) {
boolean is_ok = true;
Integer error_msg = 0;
if ((local < 1 || local > 65535) || (remote < 1 || remote > 65535)) {
error_msg = R.string.invalid_port;
is_ok = false;
}
if (serverName == null || serverName.length() < 1) {
error_msg = R.string.name_can_t_be_empty;
is_ok = false;
}
if (!is_ok) {
Toast.makeText(getContext(), error_msg, Toast.LENGTH_SHORT).show();
}
return is_ok;
}
private void saveData(String name, Integer local, Integer remote, Boolean authCookie) {
ContentValues fields = new ContentValues();
fields.put(HSContentProvider.HiddenService.NAME, name);
fields.put(HSContentProvider.HiddenService.PORT, local);
fields.put(HSContentProvider.HiddenService.ONION_PORT, remote);
fields.put(HSContentProvider.HiddenService.AUTH_COOKIE, authCookie);
fields.put(HSContentProvider.HiddenService.CREATED_BY_USER, 1);
ContentResolver cr = getContext().getContentResolver();
cr.insert(HSContentProvider.CONTENT_URI, fields);
}
}

View File

@ -0,0 +1,65 @@
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.service.TorServiceConstants;
import org.torproject.android.ui.hiddenservices.providers.HSContentProvider;
import java.io.File;
public class HSDeleteDialog 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(
HSContentProvider.CONTENT_URI,
HSContentProvider.HiddenService._ID + "=" + arguments.getInt("_id"),
null
);
// Delete from interal storage
String base = context.getFilesDir().getAbsolutePath() + "/" + TorServiceConstants.HIDDEN_SERVICES_DIR;
File dir = new File(base, "hs" + arguments.getString("port"));
if (dir.isDirectory()) {
String[] children = dir.list();
for (String aChildren : children) {
new File(dir, aChildren).delete();
}
dir.delete();
}
break;
case DialogInterface.BUTTON_NEGATIVE:
// Do nothing
break;
}
}
};
return new AlertDialog.Builder(context)
.setMessage(R.string.confirm_service_deletion)
.setPositiveButton(R.string.btn_okay, dialogClickListener)
.setNegativeButton(R.string.btn_cancel, dialogClickListener)
.create();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,103 @@
package org.torproject.android.ui.hiddenservices.permissions;
import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.PowerManager;
import android.provider.Settings;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import org.torproject.android.R;
public class PermissionManager {
public static int VERY_LONG_LENGTH = 6000;
public static boolean isLollipopOrHigher() {
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP);
}
@SuppressLint("NewApi")
public static boolean hasExternalWritePermission(Context context) {
return (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
}
public static void requestExternalWritePermissions(FragmentActivity activity, int action) {
final int mAction = action;
final FragmentActivity mActivity = activity;
if (ActivityCompat.shouldShowRequestPermissionRationale
(mActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
Snackbar.make(mActivity.findViewById(android.R.id.content),
R.string.please_grant_permissions_for_external_storage,
Snackbar.LENGTH_INDEFINITE).setAction(R.string.activate,
new View.OnClickListener() {
@Override
public void onClick(View v) {
ActivityCompat.requestPermissions(mActivity,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
mAction);
}
}).show();
} else {
ActivityCompat.requestPermissions(mActivity,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
mAction);
}
}
@TargetApi(Build.VERSION_CODES.M)
public static void requestBatteryPermmssions(FragmentActivity activity, Context context) {
final Context mContext = context;
final String packageName = mContext.getPackageName();
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
if (pm.isIgnoringBatteryOptimizations(packageName))
return;
Snackbar.make(activity.findViewById(android.R.id.content),
R.string.consider_disable_battery_optimizations,
VERY_LONG_LENGTH).setAction(R.string.disable,
new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
mContext.startActivity(intent);
}
}).show();
}
@TargetApi(Build.VERSION_CODES.M)
public static void requestDropBatteryPermmssions(FragmentActivity activity, Context context) {
final Context mContext = context;
final String packageName = context.getPackageName();
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(packageName))
return;
Snackbar.make(activity.findViewById(android.R.id.content),
R.string.consider_enable_battery_optimizations,
VERY_LONG_LENGTH).setAction(R.string.enable,
new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
mContext.startActivity(intent);
}
}).show();
}
}

View File

@ -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() {
}
}
}

View File

@ -0,0 +1,144 @@
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 HSContentProvider extends ContentProvider {
public static final String[] PROJECTION = new String[]{
HiddenService._ID,
HiddenService.NAME,
HiddenService.PORT,
HiddenService.DOMAIN,
HiddenService.ONION_PORT,
HiddenService.AUTH_COOKIE,
HiddenService.AUTH_COOKIE_VALUE,
HiddenService.CREATED_BY_USER,
HiddenService.ENABLED
};
private static final String AUTH = "org.torproject.android.ui.hiddenservices.providers";
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTH + "/hs");
//UriMatcher
private static final int ONIONS = 1;
private static final int ONION_ID = 2;
private static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTH, "hs", ONIONS);
uriMatcher.addURI(AUTH, "hs/#", ONION_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) == ONION_ID) {
where = "_id=" + uri.getLastPathSegment();
}
SQLiteDatabase db = mServervices.getReadableDatabase();
return db.query(HSDatabase.HS_DATA_TABLE_NAME, projection, where,
selectionArgs, null, null, sortOrder);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
int match = uriMatcher.match(uri);
switch (match) {
case ONIONS:
return "vnd.android.cursor.dir/vnd.torproject.onions";
case ONION_ID:
return "vnd.android.cursor.item/vnd.torproject.onion";
default:
return null;
}
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
long regId;
SQLiteDatabase db = mServervices.getWritableDatabase();
regId = db.insert(HSDatabase.HS_DATA_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) == ONION_ID) {
where = "_id=" + uri.getLastPathSegment();
}
SQLiteDatabase db = mServervices.getWritableDatabase();
Integer rows = db.delete(HSDatabase.HS_DATA_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) == ONION_ID) {
where = "_id=" + uri.getLastPathSegment();
}
Integer rows = db.update(HSDatabase.HS_DATA_TABLE_NAME, values, where, null);
mContext.getContentResolver().notifyChange(CONTENT_URI, null);
return rows;
}
public static final class HiddenService implements BaseColumns {
public static final String NAME = "name";
public static final String PORT = "port";
public static final String ONION_PORT = "onion_port";
public static final String DOMAIN = "domain";
public static final String AUTH_COOKIE = "auth_cookie";
public static final String AUTH_COOKIE_VALUE = "auth_cookie_value";
public static final String CREATED_BY_USER = "created_by_user";
public static final String ENABLED = "enabled";
private HiddenService() {
}
}
}

View File

@ -0,0 +1,34 @@
package org.torproject.android.ui.hiddenservices.storage;
import android.os.Environment;
import java.io.File;
public class ExternalStorage {
private static final String ORBOT_BACKUPS_DIR = "Orbot";
public static File getOrCreateBackupDir() {
if (!isExternalStorageWritable())
return null;
File dir = new File(Environment.getExternalStorageDirectory(), ORBOT_BACKUPS_DIR);
if (!dir.isDirectory() && !dir.mkdirs())
return null;
return dir;
}
/* Checks if external storage is available for read and write */
public static boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
/* Checks if external storage is available to at least read */
public static boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,36 @@
<?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/copy_address_to_clipboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_hs_clipboard" />
<Button
android:text="@string/show_auth_cookie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/bt_hs_show_auth" />
<Button
android:text="@string/backup_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_hs_backup" />
<Button
android:text="@string/delete_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_hs_delete" />
<Button
android:text="@string/btn_cancel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_hs_cancel" />
</LinearLayout>

View File

@ -0,0 +1,12 @@
<?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:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/listview_hs_backups"
android:layout_marginTop="15dp" />
</LinearLayout>

View File

@ -0,0 +1,25 @@
<?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:id="@+id/backup_list_item"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="15dp"
tools:paddingLeft="15dp"
android:paddingRight="15dp"
tools:paddingRight="15dp">
<TextView
android:id="@+id/backup_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="30sp"
android:layout_marginBottom="10dp" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,31 @@
<?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">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/hs_cookie"
android:paddingBottom="5dp"
android:paddingLeft="5dp"
android:paddingTop="5dp" />
<Button
android:text="@string/copy_cookie_to_clipboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
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"
android:id="@+id/hs_cookie_cancel" />
</LinearLayout>

View File

@ -0,0 +1,87 @@
<?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/HSNameLabel"
android:text="@string/name"
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/hsName" />
<TextView
android:text="@string/local_port"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/HSLocalPortLabel"
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/hsLocalPort"
android:inputType="number" />
<TextView
android:text="@string/onion_port"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/HSOnionPortLabel"
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/hsOnionPort"
android:inputType="number" />
<CheckBox
android:text="@string/auth_cookie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/hsAuth"
android:paddingTop="5dp"
android:paddingBottom="10dp"/>
<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/HSDialogCancel"
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/HSDialogSave"
android:layout_weight="1"
style="@style/Widget.AppCompat.Button.Borderless.Colored" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,59 @@
<?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="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="15dp"
android:paddingRight="15dp"
tools:paddingLeft="15dp"
tools:paddingRight="15dp">
<TextView
android:id="@+id/hs_port"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingRight="10dp"
android:paddingTop="10dp"
android:textSize="35sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/hs_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:paddingLeft="10dp"
android:textSize="24sp" />
<TextView
android:id="@+id/hs_onion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:paddingLeft="10dp"
android:textSize="18sp" />
</LinearLayout>
<Switch
android:id="@+id/hs_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:focusableInTouchMode="false"
android:switchPadding="30dp" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,33 @@
<?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.HiddenServicesActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/layout_hs_list_view_main" />
<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>

View File

@ -0,0 +1,20 @@
<?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_main"
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.HiddenServicesActivity"
tools:showIn="@layout/layout_hs_list_view">
<ListView
android:id="@+id/onion_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>

View File

@ -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">
<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>

View File

@ -0,0 +1,14 @@
<?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">
<item
android:id="@+id/hs_type"
android:background="#ff00"
android:title="@string/service_type"
app:actionViewClass="android.widget.Spinner"
app:showAsAction="always" />
<item
android:id="@+id/menu_restore_backup"
android:title="@string/restore_backup" />
</menu>

View File

@ -44,6 +44,22 @@
</menu>
</item>
<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"

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="bridge_options">
<item>Obfs4 (Preferido)</item>
<item>Túnel a través de Azure</item>
<item>Túnel a través de Amazon</item>
<item>Obtener nuevos puentes</item>
<item></item>
</string-array>
<string-array name="array_hs_types">
<item>Usuarios</item>
<item>Aplicaciones</item>
</string-array>
</resources>

View File

@ -291,4 +291,36 @@ direcciones (o rangos). No prevalecen sobre las configuraciones de exclusión de
<string name="standard_browser">Navegador estándar</string>
<string name="note_only_standard_tor_bridges_work_on_intel_x86_atom_devices">NOTA: Sólo los repetidores puente (bridges) estándar de Tor funcionan en dispositivos Intel X86/ATOM</string>
<string name="vpn_default_world">Mundo</string>
<string name="hidden_services">Servicios Ocultos</string>
<string name="title_activity_hidden_services">Servicios Ocultos</string>
<string name="menu_hidden_services">Servicios Ocultos</string>
<string name="save">Guardar</string>
<string name="ports">Puertos</string>
<string name="local_port">Puerto Local</string>
<string name="onion_port">Puerto Onion</string>
<string name="name">Nombre</string>
<string name="done">Hecho!</string>
<string name="invalid_port">Puerto no vàlido</string>
<string name="copy_address_to_clipboard">Copiar dirección a portapapeles</string>
<string name="show_auth_cookie">Mostra cookie de autenticación</string>
<string name="backup_service">Crear backup</string>
<string name="delete_service">Borrar servicio</string>
<string name="backup_saved_at_external_storage">Backup guardado en almacenamiento externo</string>
<string name="backup_restored">Backup restaurado</string>
<string name="filemanager_not_available">Gestor de archivos no disponible</string>
<string name="please_grant_permissions_for_external_storage">Permitir permisos para almacenamiento externo</string>
<string name="permission_granted">Permisos concedidos</string>
<string name="permission_denied">Permisos denegados</string>
<string name="restore_backup">Restaurar Backup</string>
<string name="create_a_backup_first">Crear primero una copia de seguridad</string>
<string name="name_can_t_be_empty">Nombre no puede estar vacio</string>
<string name="start_tor_again_for_finish_the_process">Inicie Tor de nuevo para finalizar el proceso</string>
<string name="confirm_service_deletion">Confirmar eliminación de servicio</string>
<string name="click_again_for_backup">Haga clic de nuevo para realizar copias de seguridad</string>
<string name="service_type">Tipo de servicio</string>
<string name="auth_cookie">Cookie de autenticación</string>
<string name="copy_cookie_to_clipboard">Copiar cookie al portapapeles</string>
<string name="auth_cookie_was_not_configured">La cookie de autenticación no estaba configurada</string>
<string name="please_restart_Orbot_to_enable_the_changes">Reinicie Orbot para habilitar los cambios</string>
</resources>

View File

@ -0,0 +1 @@
<resources></resources>

View File

@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@ -9,5 +9,8 @@
<item>Get New Bridges</item>
<item></item>
</string-array>
<string-array name="array_hs_types">
<item>User services</item>
<item>App services</item>
</string-array>
</resources>

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
/* //device/apps/common/assets/res/any/dimens.xml
**
** Copyright 2006, The Android Open Source Project
@ -118,26 +117,27 @@
<!-- The platform's desired minimum size for a dialog's width when it
is along the major axis (that is the screen is landscape). This may
be either a fraction or a dimension. -->
<item type="dimen" name="dialog_min_width_major">65%</item>
<item name="dialog_min_width_major" type="dimen">65%</item>
<!-- The platform's desired fixed width for a dialog along the major axis
(the screen is in landscape). This may be either a fraction or a dimension.-->
<item type="dimen" name="dialog_fixed_width_major">320dp</item>
<item name="dialog_fixed_width_major" type="dimen">320dp</item>
<!-- The platform's desired fixed width for a dialog along the minor axis
(the screen is in portrait). This may be either a fraction or a dimension.-->
<item type="dimen" name="dialog_fixed_width_minor">320dp</item>
<item name="dialog_fixed_width_minor" type="dimen">320dp</item>
<!-- The platform's desired fixed height for a dialog along the major axis
(the screen is in portrait). This may be either a fraction or a dimension.-->
<item type="dimen" name="dialog_fixed_height_major">80%</item>
<item name="dialog_fixed_height_major" type="dimen">80%</item>
<!-- The platform's desired fixed height for a dialog along the minor axis
(the screen is in landscape). This may be either a fraction or a dimension.-->
<item type="dimen" name="dialog_fixed_height_minor">100%</item>
<item name="dialog_fixed_height_minor" type="dimen">100%</item>
<!-- Preference activity, vertical padding for the header list -->
<dimen name="preference_screen_header_vertical_padding">0dp</dimen>
<dimen name="preference_screen_header_padding_side">16dip</dimen>
<integer name="preference_screen_header_scrollbarStyle">0x02000000</integer> <!-- outsideOverlay -->
<integer name="preference_screen_header_scrollbarStyle">0x02000000
</integer> <!-- outsideOverlay -->
<integer name="preference_fragment_scrollbarStyle">0x02000000</integer> <!-- outsideOverlay -->
@ -148,7 +148,7 @@
<!-- The platform's desired minimum size for a dialog's width when it
is along the minor axis (that is the screen is portrait). This may
be either a fraction or a dimension. -->
<item type="dimen" name="dialog_min_width_minor">95%</item>
<item name="dialog_min_width_minor" type="dimen">95%</item>
<!-- The width of the big icons in notifications. -->
<dimen name="notification_large_icon_width">64dp</dimen>
@ -358,4 +358,8 @@
<!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
<dimen name="immersive_mode_cling_width">-1px</dimen>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
</resources>

View File

@ -156,7 +156,6 @@
<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&#8230;</string>
<string name="setting_up_app_based_transparent_proxying_">Setting up app-based transparent proxying&#8230;</string>
@ -237,7 +236,7 @@
<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>
@ -336,4 +335,49 @@
<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>
<string name="hidden_services">Hidden Services</string>
<string name="title_activity_hidden_services">Hidden Services</string>
<string name="menu_hidden_services">Hidden Services</string>
<string name="save">Save</string>
<string name="ports">Ports</string>
<string name="local_port">Local Port</string>
<string name="onion_port">Onion Port</string>
<string name="name">Name</string>
<string name="done">Done!</string>
<string name="invalid_port">Invalid Port</string>
<string name="copy_address_to_clipboard">Copy address to clipboard</string>
<string name="show_auth_cookie">Show auth cookie</string>
<string name="backup_service">Backup Service</string>
<string name="delete_service">Delete Service</string>
<string name="backup_saved_at_external_storage">Backup saved at external storage</string>
<string name="backup_restored">Backup restored</string>
<string name="filemanager_not_available">Filemanager not available</string>
<string name="please_grant_permissions_for_external_storage">Please grant permissions for external storage</string>
<string name="permission_granted">Permission granted</string>
<string name="permission_denied">Permission denied</string>
<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>
<string name="service_type">Service type</string>
<string name="auth_cookie">Auth cookie</string>
<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>
<string name="disable">Disable</string>
<string name="enable">Enable</string>
<string name="consider_disable_battery_optimizations">Consider disable battery optimizations</string>
<string name="consider_enable_battery_optimizations">Consider enable battery optimizations</string>
</resources>

View File

@ -1,5 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="NotificationText" parent="android:TextAppearance.StatusBar.EventContent" />
<style name="NotificationTitle" parent="android:TextAppearance.StatusBar.EventContent.Title" />
<style name="DefaultTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="DefaultTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="hidden-services" path="hidden_services/" />
</paths>

View File

@ -157,17 +157,6 @@ android:dialogTitle="@string/enter_ports"
/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_hs_group">
<CheckBoxPreference android:title="@string/enable_hidden_services"
android:summary="@string/run_servers_accessible_via_the_tor_network" android:key="pref_hs_enable"></CheckBoxPreference>
<EditTextPreference android:summary="@string/enter_localhost_ports_for_hidden_services"
android:title="@string/hidden_service_ports" android:enabled="false" android:key="pref_hs_ports"></EditTextPreference>
<EditTextPreference android:key="pref_hs_hostname"
android:summary="@string/the_addressable_name_for_your_hidden_service_generated_automatically_"
android:title=".Onion Hostname"></EditTextPreference>
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_proxy_title">
<EditTextPreference android:key="pref_proxy_type"
android:title="@string/pref_proxy_type_title"

View File

@ -16,19 +16,22 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.BaseColumns;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
@ -62,7 +65,6 @@ import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.text.Normalizer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
@ -124,10 +126,54 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
public static File fileObfsclient;
public static File fileXtables;
public static File fileTorRc;
private File mHSBasePath;
private Shell mShell;
private Shell mShellPolipo;
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";
public static final String PORT = "port";
public static final String ONION_PORT = "onion_port";
public static final String DOMAIN = "domain";
public static final String AUTH_COOKIE = "auth_cookie";
public static final String AUTH_COOKIE_VALUE = "auth_cookie_value";
public static final String CREATED_BY_USER = "created_by_user";
public static final String ENABLED = "enabled";
private HiddenService() {
}
}
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,
HiddenService.PORT,
HiddenService.AUTH_COOKIE,
HiddenService.AUTH_COOKIE_VALUE,
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())
@ -441,75 +487,6 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
sendCallbackStatus(STATUS_OFF);
}
private String getHiddenServiceHostname ()
{
SharedPreferences prefs = TorServiceUtils.getSharedPrefs(getApplicationContext());
boolean enableHiddenServices = prefs.getBoolean("pref_hs_enable", false);
StringBuffer result = new StringBuffer();
if (enableHiddenServices)
{
String hsPorts = prefs.getString("pref_hs_ports","");
StringTokenizer st = new StringTokenizer (hsPorts,",");
String hsPortConfig = null;
while (st.hasMoreTokens())
{
int hsPort = Integer.parseInt(st.nextToken().split(" ")[0]);;
File fileDir = new File(appCacheHome, "hs" + hsPort);
File file = new File(fileDir, "hostname");
if (file.exists())
{
try {
String onionHostname = Utils.readString(new FileInputStream(file)).trim();
if (result.length() > 0)
result.append(",");
result.append(onionHostname);
} 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);
return null;
}
}
else
{
showToolbarNotification(getString(R.string.unable_to_read_hidden_service_name), HS_NOTIFY_ID, R.drawable.ic_stat_notifyerr);
return null;
}
}
if (result.length() > 0)
{
String onionHostname = result.toString();
showToolbarNotification(getString(R.string.hidden_service_on) + ' ' + onionHostname, HS_NOTIFY_ID, R.drawable.ic_stat_tor);
Editor pEdit = prefs.edit();
pEdit.putString("pref_hs_hostname",onionHostname);
pEdit.commit();
return onionHostname;
}
}
return null;
}
private void killAllDaemons() throws Exception {
if (conn != null) {
logNotice("Using control port to shutdown Tor");
@ -577,6 +554,14 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
fileXtables = new File(appBinHome, TorServiceConstants.IPTABLES_ASSET_KEY);
fileTorRc = new File(appBinHome, TorServiceConstants.TORRC_ASSET_KEY);
mHSBasePath = new File(
getFilesDir().getAbsolutePath(),
TorServiceConstants.HIDDEN_SERVICES_DIR
);
if (!mHSBasePath.isDirectory())
mHSBasePath.mkdirs();
mEventHandler = new TorEventHandler(this);
if (mNotificationManager == null)
@ -809,7 +794,56 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
enableTransparentProxy();
}
getHiddenServiceHostname ();
// Tor is running, update new .onion names at db
ContentResolver mCR = getApplicationContext().getContentResolver();
Cursor hidden_services = mCR.query(HS_CONTENT_URI, hsProjection, null, null, null);
if(hidden_services != null) {
try {
while (hidden_services.moveToNext()) {
String HSDomain = hidden_services.getString(hidden_services.getColumnIndex(HiddenService.DOMAIN));
Integer HSLocalPort = hidden_services.getInt(hidden_services.getColumnIndex(HiddenService.PORT));
Integer HSAuthCookie = hidden_services.getInt(hidden_services.getColumnIndex(HiddenService.AUTH_COOKIE));
String HSAuthCookieValue = hidden_services.getString(hidden_services.getColumnIndex(HiddenService.AUTH_COOKIE_VALUE));
// Update only new domains or restored from backup with auth cookie
if((HSDomain == null || HSDomain.length() < 1) || (HSAuthCookie == 1 && (HSAuthCookieValue == null || HSAuthCookieValue.length() < 1))) {
String hsDirPath = new File(mHSBasePath.getAbsolutePath(),"hs" + HSLocalPort).getCanonicalPath();
File file = new File(hsDirPath, "hostname");
if (file.exists())
{
ContentValues fields = new ContentValues();
try {
String onionHostname = Utils.readString(new FileInputStream(file)).trim();
if(HSAuthCookie == 1) {
String[] aux = onionHostname.split(" ");
onionHostname = aux[0];
fields.put(HiddenService.AUTH_COOKIE_VALUE, aux[1]);
}
fields.put(HiddenService.DOMAIN, onionHostname);
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);
}
}
else
{
showToolbarNotification(getString(R.string.unable_to_read_hidden_service_name), HS_NOTIFY_ID, R.drawable.ic_stat_notifyerr);
}
}
}
} catch (NumberFormatException e) {
Log.e(OrbotConstants.TAG,"error parsing hsport",e);
} catch (Exception e) {
Log.e(OrbotConstants.TAG,"error starting share server",e);
}
hidden_services.close();
}
} catch (Exception e) {
logException("Unable to start Tor: " + e.toString(), e);
@ -1574,7 +1608,6 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
boolean becomeRelay = prefs.getBoolean(OrbotConstants.PREF_OR, false);
boolean ReachableAddresses = prefs.getBoolean(OrbotConstants.PREF_REACHABLE_ADDRESSES,false);
boolean enableHiddenServices = prefs.getBoolean("pref_hs_enable", false);
boolean enableStrictNodes = prefs.getBoolean("pref_strict_nodes", false);
String entranceNodes = prefs.getString("pref_entrance_nodes", "");
@ -1777,47 +1810,50 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
return false;
}
if (enableHiddenServices)
{
logNotice("hidden services are enabled");
ContentResolver mCR = getApplicationContext().getContentResolver();
//updateConfiguration("RendPostPeriod", "600 seconds", false); //possible feature to investigate
/* ---- 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()) {
String HSname = hidden_services.getString(hidden_services.getColumnIndex(HiddenService.NAME));
Integer HSLocalPort = hidden_services.getInt(hidden_services.getColumnIndex(HiddenService.PORT));
Integer HSOnionPort = hidden_services.getInt(hidden_services.getColumnIndex(HiddenService.ONION_PORT));
Integer HSAuthCookie = hidden_services.getInt(hidden_services.getColumnIndex(HiddenService.AUTH_COOKIE));
String hsDirPath = new File(mHSBasePath.getAbsolutePath(),"hs" + HSLocalPort).getCanonicalPath();
String hsPorts = prefs.getString("pref_hs_ports","");
StringTokenizer st = new StringTokenizer (hsPorts,",");
String hsPortConfig = null;
int hsPort = -1;
while (st.hasMoreTokens())
{
try
{
hsPortConfig = st.nextToken().trim();
if (hsPortConfig.indexOf(":")==-1) //setup the port to localhost if not specifed
{
hsPortConfig = hsPortConfig + " 127.0.0.1:" + hsPortConfig;
}
hsPort = Integer.parseInt(hsPortConfig.split(" ")[0]);
String hsDirPath = new File(appCacheHome,"hs" + hsPort).getCanonicalPath();
debug("Adding hidden service on port: " + hsPortConfig);
debug("Adding hidden service on port: " + HSLocalPort);
extraLines.append("HiddenServiceDir" + ' ' + hsDirPath).append('\n');
extraLines.append("HiddenServicePort" + ' ' + hsPortConfig).append('\n');
extraLines.append("HiddenServicePort" + ' ' + HSOnionPort + " 127.0.0.1:" + HSLocalPort).append('\n');
if(HSAuthCookie == 1)
extraLines.append("HiddenServiceAuthorizeClient stealth " + HSname).append('\n');
}
} catch (NumberFormatException e) {
Log.e(OrbotConstants.TAG,"error parsing hsport",e);
} catch (Exception e) {
Log.e(OrbotConstants.TAG,"error starting share server",e);
}
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;

View File

@ -161,4 +161,6 @@ public interface TorServiceConstants {
"meek_lite 0.0.2.0:3 url=https://az668014.vo.msecnd.net/ front=ajax.aspnetcdn.com"
};
public static final String HIDDEN_SERVICES_DIR = "hidden_services";
}