Enable manual configuration of https (#338)

* Allow specifying HTTPS Hosts manually
This is not exposed in the UI, but on the manual host configuration screen, if the address starts with a  "http://" or "https://" this will be stripped and the `isHttps` flag set appropriately.

Additionally, the port can be configured as part of the address as well.
Any port specified as part of the address takes precedence over the explicit HTTP port box, to allow easy copy-pasting of an entire URL for a host.

This enables using Kore with Kodi instances available though HTTPS (e.g. via a reverse proxy).
This commit is contained in:
Aneesh Agrawal 2017-01-27 10:17:39 -05:00 committed by Synced Synapse
parent 42aeccfab3
commit 66c145d778
9 changed files with 107 additions and 171 deletions

View File

@ -193,7 +193,7 @@ public class EventServerConnection {
final HostConnection auxHostConnection = new HostConnection(
new HostInfo(hostInfo.getName(), hostInfo.getAddress(),
HostConnection.PROTOCOL_HTTP, hostInfo.getHttpPort(), hostInfo.getTcpPort(),
hostInfo.getUsername(), hostInfo.getPassword(), false, 0));
hostInfo.getUsername(), hostInfo.getPassword(), false, 0, hostInfo.isHttps));
final Application.GetProperties action = new Application.GetProperties(Application.GetProperties.MUTED);
final Packet mutePacket = new PacketBUTTON(ButtonCodes.MAP_REMOTE, ButtonCodes.REMOTE_MUTE,
false, true, true, (short) 0, (byte) 0);

View File

@ -30,6 +30,11 @@ public class HostInfo {
private static final String JSON_RPC_ENDPOINT = "/jsonrpc";
/**
* Default HTTPS port
*/
public static final int DEFAULT_HTTPS_PORT = 443;
/**
* Default HTTP port for XBMC (80 on Windows, 8080 on others)
*/
@ -58,28 +63,29 @@ public class HostInfo {
/**
* Internal id of the host
*/
private int id;
private final int id;
/**
* Friendly name of the host
*/
private String name;
private final String name;
/**
* Connection information
*/
private String address;
private int httpPort;
private int tcpPort;
private final String address;
private final int httpPort;
private final int tcpPort;
public final boolean isHttps;
private boolean useEventServer;
private int eventServerPort;
private final int eventServerPort;
/**
* Authentication information
*/
private String username;
private String password;
private final String username;
private final String password;
/**
* Mac address and Wake On Lan port
@ -104,9 +110,9 @@ public class HostInfo {
/**
* Last time updated (in millis)
*/
private long updated;
private final long updated;
private String auxImageHttpAddress;
private final String auxImageHttpAddress;
/**
* Full constructor. This constructor should be used when instantiating from the database
@ -124,7 +130,7 @@ public class HostInfo {
String username, String password, String macAddress, int wolPort,
boolean useEventServer, int eventServerPort,
int kodiVersionMajor, int kodiVersionMinor, String kodiVersionRevision, String kodiVersionTag,
long updated) {
long updated, boolean isHttps) {
this.id = id;
this.name = name;
this.address = address;
@ -133,6 +139,7 @@ public class HostInfo {
}
this.protocol = protocol;
this.httpPort = httpPort;
this.isHttps = isHttps;
this.tcpPort = tcpPort;
this.username = username;
this.password = password;
@ -165,12 +172,12 @@ public class HostInfo {
*/
public HostInfo(String name, String address, int protocol, int httpPort,
int tcpPort, String username, String password,
boolean useEventServer, int eventServerPort) {
boolean useEventServer, int eventServerPort, boolean isHttps) {
this(-1, name, address, protocol, httpPort, tcpPort, username,
password, null, DEFAULT_WOL_PORT, useEventServer, eventServerPort,
DEFAULT_KODI_VERSION_MAJOR, DEFAULT_KODI_VERSION_MINOR,
DEFAULT_KODI_VERSION_REVISION, DEFAULT_KODI_VERSION_TAG,
0);
0, isHttps);
}
public int getId() {
@ -289,7 +296,8 @@ public class HostInfo {
* @return HTTP URL eg. http://192.168.1.1:8080
*/
public String getHttpURL() {
return "http://" + address + ":" + httpPort;
String scheme = isHttps ? "https://" : "http://";
return scheme + address + ":" + httpPort;
}
/**
@ -297,7 +305,7 @@ public class HostInfo {
* @return HTTP URL eg. http://192.168.1.1:8080/jsonrpc
*/
public String getJsonRpcHttpEndpoint() {
return "http://" + address + ":" + httpPort + JSON_RPC_ENDPOINT;
return getHttpURL() + JSON_RPC_ENDPOINT;
}
/**

View File

@ -155,12 +155,13 @@ public class HostManager {
int kodiVersionMinor = cursor.getInt(idx++);
String kodiVersionRevision = cursor.getString(idx++);
String kodiVersionTag = cursor.getString(idx++);
boolean isHttps = (cursor.getInt(idx++) != 0);
hosts.add(new HostInfo(
id, name, address, protocol, httpPort, tcpPort,
username, password, macAddress, wolPort, useEventServer, eventServerPort,
kodiVersionMajor, kodiVersionMinor, kodiVersionRevision, kodiVersionTag,
updated));
updated, isHttps));
}
}
cursor.close();
@ -328,7 +329,8 @@ public class HostManager {
hostInfo.getMacAddress(), hostInfo.getWolPort(),
hostInfo.getUseEventServer(), hostInfo.getEventServerPort(),
hostInfo.getKodiVersionMajor(), hostInfo.getKodiVersionMinor(),
hostInfo.getKodiVersionRevision(), hostInfo.getKodiVersionTag());
hostInfo.getKodiVersionRevision(), hostInfo.getKodiVersionTag(),
hostInfo.isHttps);
}
/**
@ -345,7 +347,8 @@ public class HostManager {
public HostInfo addHost(String name, String address, int protocol, int httpPort, int tcpPort,
String username, String password, String macAddress, int wolPort,
boolean useEventServer, int eventServerPort,
int kodiVersionMajor, int kodiVersionMinor, String kodiVersionRevision, String kodiVersionTag) {
int kodiVersionMajor, int kodiVersionMinor, String kodiVersionRevision, String kodiVersionTag,
boolean isHttps) {
ContentValues values = new ContentValues();
values.put(MediaContract.HostsColumns.NAME, name);
@ -359,11 +362,12 @@ public class HostManager {
values.put(MediaContract.HostsColumns.WOL_PORT, wolPort);
values.put(MediaContract.HostsColumns.USE_EVENT_SERVER, useEventServer);
values.put(MediaContract.HostsColumns.EVENT_SERVER_PORT, eventServerPort);
values.put(MediaContract.HostsColumns.KODI_VERSION_MAJOR, kodiVersionMajor);
values.put(MediaContract.HostsColumns.KODI_VERSION_MINOR, kodiVersionMinor);
values.put(MediaContract.HostsColumns.KODI_VERSION_REVISION, kodiVersionRevision);
values.put(MediaContract.HostsColumns.KODI_VERSION_TAG, kodiVersionTag);
values.put(MediaContract.HostsColumns.IS_HTTPS, isHttps);
Uri newUri = context.getContentResolver()
.insert(MediaContract.Hosts.CONTENT_URI, values);
@ -400,11 +404,11 @@ public class HostManager {
values.put(MediaContract.HostsColumns.WOL_PORT, newHostInfo.getWolPort());
values.put(MediaContract.HostsColumns.USE_EVENT_SERVER, newHostInfo.getUseEventServer());
values.put(MediaContract.HostsColumns.EVENT_SERVER_PORT, newHostInfo.getEventServerPort());
values.put(MediaContract.HostsColumns.KODI_VERSION_MAJOR, newHostInfo.getKodiVersionMajor());
values.put(MediaContract.HostsColumns.KODI_VERSION_MINOR, newHostInfo.getKodiVersionMinor());
values.put(MediaContract.HostsColumns.KODI_VERSION_REVISION, newHostInfo.getKodiVersionRevision());
values.put(MediaContract.HostsColumns.KODI_VERSION_TAG, newHostInfo.getKodiVersionTag());
values.put(MediaContract.HostsColumns.IS_HTTPS, newHostInfo.isHttps);
context.getContentResolver()
.update(MediaContract.Hosts.buildHostUri(hostId), values, null, null);

View File

@ -308,141 +308,8 @@ public class HostConnection {
};
executorService.execute(command);
//new Thread(command).start();
}
// /**
// * Sends the JSON RPC request through HTTP
// */
// private <T> void executeThroughHttp(final ApiMethod<T> method, final ApiCallback<T> callback,
// final Handler handler) {
// String jsonRequest = method.toJsonString();
// try {
// HttpURLConnection connection = openHttpConnection(hostInfo);
// sendHttpRequest(connection, jsonRequest);
// // Read response and convert it
// final T result = method.resultFromJson(parseJsonResponse(readHttpResponse(connection)));
//
// if ((handler != null) && (callback != null)) {
// handler.post(new Runnable() {
// @Override
// public void run() {
// callback.onSuccess(result);
// }
// });
// }
// } catch (final ApiException e) {
// // Got an error, call error handler
//
// if ((handler != null) && (callback != null)) {
// handler.post(new Runnable() {
// @Override
// public void run() {
// callback.onError(e.getCode(), e.getMessage());
// }
// });
// }
// }
// }
//
// /**
// * Auxiliary method to open a HTTP connection.
// * This method calls connect() so that any errors are cathced
// * @param hostInfo Host info
// * @return Connection set up
// * @throws ApiException
// */
// private HttpURLConnection openHttpConnection(HostInfo hostInfo) throws ApiException {
// try {
//// LogUtils.LOGD(TAG, "Opening HTTP connection.");
// HttpURLConnection connection = (HttpURLConnection) new URL(hostInfo.getJsonRpcHttpEndpoint()).openConnection();
// connection.setRequestMethod("POST");
// connection.setConnectTimeout(connectTimeout);
// //connection.setReadTimeout(connectTimeout);
// connection.setRequestProperty("Content-Type", "application/json");
// connection.setDoOutput(true);
//
// // http basic authorization
// if ((hostInfo.getUsername() != null) && !hostInfo.getUsername().isEmpty() &&
// (hostInfo.getPassword() != null) && !hostInfo.getPassword().isEmpty()) {
// final String token = Base64.encodeToString((hostInfo.getUsername() + ":" +
// hostInfo.getPassword()).getBytes(), Base64.DEFAULT);
// connection.setRequestProperty("Authorization", "Basic " + token);
// }
//
// // Check the connection
// connection.connect();
// return connection;
// } catch (ProtocolException e) {
// // Won't try to catch this
// LogUtils.LOGE(TAG, "Got protocol exception while opening HTTP connection.", e);
// throw new RuntimeException(e);
// } catch (IOException e) {
// LogUtils.LOGW(TAG, "Failed to open HTTP connection.", e);
// throw new ApiException(ApiException.IO_EXCEPTION_WHILE_CONNECTING, e);
// }
// }
//
// /**
// * Send an HTTP POST request
// * @param connection Open connection
// * @param request Request to send
// * @throws ApiException
// */
// private void sendHttpRequest(HttpURLConnection connection, String request) throws ApiException {
// try {
// LogUtils.LOGD(TAG, "Sending request via HTTP: " + request);
// OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
// out.write(request);
// out.flush();
// out.close();
// } catch (IOException e) {
// LogUtils.LOGW(TAG, "Failed to send HTTP request.", e);
// throw new ApiException(ApiException.IO_EXCEPTION_WHILE_SENDING_REQUEST, e);
// }
// }
//
// /**
// * Reads the response from the server
// * @param connection Connection
// * @return Response read
// * @throws ApiException
// */
// private String readHttpResponse(HttpURLConnection connection) throws ApiException {
// try {
//// LogUtils.LOGD(TAG, "Reading HTTP response.");
// int responseCode = connection.getResponseCode();
//
// switch (responseCode) {
// case 200:
// // All ok, read response
// BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
// StringBuilder response = new StringBuilder();
// String inputLine;
// while ((inputLine = in.readLine()) != null)
// response.append(inputLine);
// in.close();
// LogUtils.LOGD(TAG, "HTTP response: " + response.toString());
// return response.toString();
// case 401:
// LogUtils.LOGD(TAG, "HTTP response read error. Got a 401.");
// throw new ApiException(ApiException.HTTP_RESPONSE_CODE_UNAUTHORIZED,
// "Server returned response code: " + responseCode);
// case 404:
// LogUtils.LOGD(TAG, "HTTP response read error. Got a 404.");
// throw new ApiException(ApiException.HTTP_RESPONSE_CODE_NOT_FOUND,
// "Server returned response code: " + responseCode);
// default:
// LogUtils.LOGD(TAG, "HTTP response read error. Got: " + responseCode);
// throw new ApiException(ApiException.HTTP_RESPONSE_CODE_UNKNOWN,
// "Server returned response code: " + responseCode);
// }
// } catch (IOException e) {
// LogUtils.LOGW(TAG, "Failed to read HTTP response.", e);
// throw new ApiException(ApiException.IO_EXCEPTION_WHILE_READING_RESPONSE, e);
// }
// }
/**
* Sends the JSON RPC request through HTTP (using OkHttp library)
*/

View File

@ -76,6 +76,7 @@ public class MediaContract {
String KODI_VERSION_MINOR = "kodi_version_minor";
String KODI_VERSION_REVISION = "kodi_version_revision";
String KODI_VERSION_TAG = "kodi_version_tag";
String IS_HTTPS = "is_https";
}
public static class Hosts implements BaseColumns, SyncColumns, HostsColumns {
@ -100,7 +101,8 @@ public class MediaContract {
public final static String[] ALL_COLUMNS = {
_ID, UPDATED, NAME, ADDRESS, PROTOCOL, HTTP_PORT, TCP_PORT, USERNAME, PASSWORD,
MAC_ADDRESS, WOL_PORT, USE_EVENT_SERVER, EVENT_SERVER_PORT,
KODI_VERSION_MAJOR, KODI_VERSION_MINOR, KODI_VERSION_REVISION, KODI_VERSION_TAG
KODI_VERSION_MAJOR, KODI_VERSION_MINOR, KODI_VERSION_REVISION, KODI_VERSION_TAG,
IS_HTTPS
};
}

View File

@ -36,7 +36,8 @@ public class MediaDatabase extends SQLiteOpenHelper {
DB_VERSION_PRE_SONG_DISPLAY_ARTIST = 6,
DB_VERSION_PRE_SONG_DISC = 7,
DB_VERSION_PRE_HOST_VERSION = 8,
DB_VERSION = 9;
DB_VERSION_PRE_HOST_HTTPS = 9,
DB_VERSION = 10;
/**
* Tables exposed
@ -153,11 +154,11 @@ public class MediaDatabase extends SQLiteOpenHelper {
MediaContract.HostsColumns.WOL_PORT + " INTEGER, " +
MediaContract.HostsColumns.USE_EVENT_SERVER + " INTEGER, " +
MediaContract.HostsColumns.EVENT_SERVER_PORT + " INTEGER, " +
MediaContract.HostsColumns.KODI_VERSION_MAJOR + " INTEGER, " +
MediaContract.HostsColumns.KODI_VERSION_MINOR + " INTEGER, " +
MediaContract.HostsColumns.KODI_VERSION_REVISION + " TEXT, " +
MediaContract.HostsColumns.KODI_VERSION_TAG + " TEXT)"
MediaContract.HostsColumns.KODI_VERSION_TAG + " TEXT, " +
MediaContract.HostsColumns.IS_HTTPS + " INTEGER)"
);
// Movies
@ -515,6 +516,10 @@ public class MediaDatabase extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + Tables.HOSTS +
" ADD COLUMN " + MediaContract.HostsColumns.KODI_VERSION_TAG +
" TEXT DEFAULT " + HostInfo.DEFAULT_KODI_VERSION_TAG + ";");
case DB_VERSION_PRE_HOST_HTTPS:
db.execSQL("ALTER TABLE " + Tables.HOSTS +
" ADD COLUMN " + MediaContract.HostsColumns.IS_HTTPS +
" INTEGER DEFAULT 0;");
}
}

View File

@ -283,7 +283,7 @@ public class AddHostFragmentZeroconf extends Fragment {
String hostAddress = addresses[0];
int hostHttpPort = selectedServiceInfo.getPort();
HostInfo selectedHostInfo = new HostInfo(hostName, hostAddress, HostConnection.PROTOCOL_TCP,
hostHttpPort, HostInfo.DEFAULT_TCP_PORT, null, null, true, HostInfo.DEFAULT_EVENT_SERVER_PORT);
hostHttpPort, HostInfo.DEFAULT_TCP_PORT, null, null, true, HostInfo.DEFAULT_EVENT_SERVER_PORT, false);
listener.onAddHostZeroconfFoundHost(selectedHostInfo);
}

View File

@ -43,6 +43,9 @@ import org.xbmc.kore.jsonrpc.type.ApplicationType;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.NetUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import butterknife.ButterKnife;
import butterknife.InjectView;
@ -212,6 +215,10 @@ public class HostFragmentManualConfiguration extends Fragment {
}
}
private static boolean isValidPort(int port) {
return port > 0 && port <= 65535;
}
/**
* Tests a connection with the values set in the UI.
* Checks whether the values are correctly set, and then tries to make
@ -220,14 +227,60 @@ public class HostFragmentManualConfiguration extends Fragment {
*/
private void testConnection() {
String xbmcName = xbmcNameEditText.getText().toString();
String xbmcAddress = xbmcIpAddressEditText.getText().toString();
int xbmcHttpPort;
boolean isHttps = false;
String xbmcAddress = xbmcIpAddressEditText.getText().toString();
if (xbmcAddress.startsWith("https://")) {
xbmcAddress = xbmcAddress.substring("https://".length());
LogUtils.LOGD(TAG, "Stripped https:// on address to get: " + xbmcAddress);
isHttps = true;
} else if (xbmcAddress.startsWith("http://")) {
xbmcAddress = xbmcAddress.substring("http://".length());
LogUtils.LOGD(TAG, "Stripped http:// on address to get: " + xbmcAddress);
}
int xbmcHttpPort = isHttps ? HostInfo.DEFAULT_HTTPS_PORT : HostInfo.DEFAULT_HTTP_PORT;
Integer implicitPort = null;
Matcher m = Pattern.compile("^.*:(\\d{1,5})\\z").matcher(xbmcAddress);
if (m.matches()) {
// Minus one character for the colon
xbmcAddress = xbmcAddress.substring(0, m.start(1) - 1);
LogUtils.LOGD(TAG, "Stripped port on address to get: " + xbmcAddress);
try {
implicitPort = Integer.valueOf(m.group(1));
} catch (NumberFormatException e) {
LogUtils.LOGW(
TAG,
"Value matching port regex couldn't be parsed as integer: " + m.group(1)
);
implicitPort = -1;
}
}
Integer explicitPort = null;
String aux = xbmcHttpPortEditText.getText().toString();
try {
xbmcHttpPort = TextUtils.isEmpty(aux) ? HostInfo.DEFAULT_HTTP_PORT : Integer.valueOf(aux);
} catch (NumberFormatException exc) {
xbmcHttpPort = -1;
if (!TextUtils.isEmpty(aux)) {
try {
explicitPort = Integer.valueOf(aux);
} catch (NumberFormatException e) {
explicitPort = -1;
}
}
if (implicitPort != null) {
if (!isValidPort(implicitPort)) {
Toast.makeText(getActivity(), R.string.wizard_invalid_http_port_specified, Toast.LENGTH_SHORT);
xbmcIpAddressEditText.requestFocus();
return;
}
xbmcHttpPort = implicitPort;
} else if (explicitPort != null) {
if (!isValidPort(explicitPort)) {
Toast.makeText(getActivity(), R.string.wizard_invalid_http_port_specified, Toast.LENGTH_SHORT);
xbmcHttpPortEditText.requestFocus();
return;
}
xbmcHttpPort = explicitPort;
}
String xbmcUsername = xbmcUsernameEditText.getText().toString();
@ -270,10 +323,6 @@ public class HostFragmentManualConfiguration extends Fragment {
Toast.makeText(getActivity(), R.string.wizard_no_address_specified, Toast.LENGTH_SHORT).show();
xbmcIpAddressEditText.requestFocus();
return;
} else if (xbmcHttpPort <= 0) {
Toast.makeText(getActivity(), R.string.wizard_invalid_http_port_specified, Toast.LENGTH_SHORT).show();
xbmcHttpPortEditText.requestFocus();
return;
} else if (xbmcTcpPort <= 0) {
Toast.makeText(getActivity(), R.string.wizard_invalid_tcp_port_specified, Toast.LENGTH_SHORT).show();
xbmcTcpPortEditText.requestFocus();
@ -294,7 +343,7 @@ public class HostFragmentManualConfiguration extends Fragment {
final HostInfo checkedHostInfo = new HostInfo(xbmcName, xbmcAddress, xbmcProtocol,
xbmcHttpPort, xbmcTcpPort,
xbmcUsername, xbmcPassword,
xbmcUseEventServer, xbmcEventServerPort);
xbmcUseEventServer, xbmcEventServerPort, isHttps);
checkedHostInfo.setMacAddress(macAddress);
checkedHostInfo.setWolPort(xbmcWolPort);

View File

@ -62,7 +62,8 @@ public class Database {
return HostManager.getInstance(context).addHost("TestHost", "127.0.0.1", 1, 80, 9090, null,
null, "52:54:00:12:35:02", 9, false, 9777,
HostInfo.DEFAULT_KODI_VERSION_MAJOR, HostInfo.DEFAULT_KODI_VERSION_MINOR,
HostInfo.DEFAULT_KODI_VERSION_REVISION, HostInfo.DEFAULT_KODI_VERSION_TAG);
HostInfo.DEFAULT_KODI_VERSION_REVISION, HostInfo.DEFAULT_KODI_VERSION_TAG,
false);
}
public static void insertMovies(Context context, ContentResolver contentResolver, int hostId)