summaryrefslogtreecommitdiffstats
path: root/Plugins/DbAndroid/dbandroidjsonconnection.cpp
diff options
context:
space:
mode:
authorLibravatarUnit 193 <unit193@ubuntu.com>2015-11-25 16:48:41 -0500
committerLibravatarUnit 193 <unit193@ubuntu.com>2015-11-25 16:48:41 -0500
commit8e640722c62692818ab840d50b3758f89a41a54e (patch)
tree38197eb1688a5afc338081ea17e15f938976e422 /Plugins/DbAndroid/dbandroidjsonconnection.cpp
parent9618f0ebbf4b88045247c01ce8c8f58203508ebf (diff)
Imported Upstream version 3.0.7upstream/3.0.7
Diffstat (limited to 'Plugins/DbAndroid/dbandroidjsonconnection.cpp')
-rw-r--r--Plugins/DbAndroid/dbandroidjsonconnection.cpp430
1 files changed, 430 insertions, 0 deletions
diff --git a/Plugins/DbAndroid/dbandroidjsonconnection.cpp b/Plugins/DbAndroid/dbandroidjsonconnection.cpp
new file mode 100644
index 0000000..05d6469
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidjsonconnection.cpp
@@ -0,0 +1,430 @@
+#include "dbandroidjsonconnection.h"
+#include "dbandroid.h"
+#include "adbmanager.h"
+#include "services/notifymanager.h"
+#include "common/blockingsocket.h"
+#include "db/sqlerrorcodes.h"
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QtConcurrent/QtConcurrent>
+
+DbAndroidJsonConnection::DbAndroidJsonConnection(DbAndroid* plugin, QObject *parent) :
+ DbAndroidConnection(parent), plugin(plugin)
+{
+ socket = new BlockingSocket(this);
+ adbManager = plugin->getAdbManager();
+ connect(socket, SIGNAL(disconnected()), this, SLOT(handlePossibleDisconnection()));
+}
+
+DbAndroidJsonConnection::~DbAndroidJsonConnection()
+{
+ cleanUp();
+}
+
+bool DbAndroidJsonConnection::connectToAndroid(const DbAndroidUrl& url)
+{
+ if (isConnected())
+ {
+ qWarning() << "Already connected while calling DbAndroidConnection::connect().";
+ return false;
+ }
+
+ dbUrl = url;
+ mode = url.getMode();
+
+ switch (mode)
+ {
+ case DbAndroidMode::NETWORK:
+ return connectToNetwork();
+ case DbAndroidMode::USB:
+ return connectToDevice();
+ case DbAndroidMode::SHELL:
+ qCritical() << "SHELL mode encountered in DbAndroidJsonConnection";
+ break;
+ case DbAndroidMode::null:
+ qCritical() << "Null mode encountered in DbAndroidJsonConnection";
+ break;
+ }
+
+ qCritical() << "Invalid Android db mode while connecting:" << static_cast<int>(mode);
+ return false;
+}
+
+void DbAndroidJsonConnection::disconnectFromAndroid()
+{
+ socket->disconnectFromHost();
+ connectedState = false;
+}
+
+bool DbAndroidJsonConnection::isConnected() const
+{
+ if (!socket)
+ return false;
+
+ return connectedState;
+}
+
+QByteArray DbAndroidJsonConnection::send(const QByteArray& data)
+{
+ QByteArray bytes = sizeToBytes(data.size());
+ bytes.append(data);
+ return sendBytes(bytes);
+}
+
+QString DbAndroidJsonConnection::getDbName() const
+{
+ return dbUrl.getDbName();
+}
+
+QByteArray DbAndroidJsonConnection::sendBytes(const QByteArray& data)
+{
+ //qDebug() << "Sending" << data;
+ bool success = socket->send(data);
+ if (!success)
+ {
+ qCritical() << "Error writing bytes to Android socket:" << socket->getErrorText();
+ return QByteArray();
+ }
+
+ QByteArray sizeBytes = socket->read(4, 5000, &success);
+ if (!success)
+ {
+ qCritical() << "Error reading response size from Android socket:" << socket->getErrorText();
+ return QByteArray();
+ }
+
+ qint32 size = bytesToSize(sizeBytes);
+ QByteArray responseBytes = socket->read(size, 5000, &success);
+ if (!success)
+ {
+ qCritical() << "Error reading response from Android socket:" << socket->getErrorText();
+ return QByteArray();
+ }
+ //qDebug() << "Received" << responseBytes;
+ return responseBytes;
+}
+
+void DbAndroidJsonConnection::handleSocketError()
+{
+ qWarning() << "Blocking socket error in Android connection:" << socket->getErrorText();
+ handlePossibleDisconnection();
+}
+
+void DbAndroidJsonConnection::handlePossibleDisconnection()
+{
+ if (connectedState && !socket->isConnected())
+ {
+ connectedState = false;
+ emit disconnected();
+ }
+}
+
+QByteArray DbAndroidJsonConnection::sizeToBytes(qint32 size)
+{
+ QByteArray bytes;
+ for (int i = 0; i < 4; i++)
+ bytes.append((size >> (8*i)) & 0xff);
+
+ return bytes;
+}
+
+qint32 DbAndroidJsonConnection::bytesToSize(const QByteArray& bytes)
+{
+ int size = (((unsigned char)bytes[3]) << 24) |
+ (((unsigned char)bytes[2]) << 16) |
+ (((unsigned char)bytes[1]) << 8) |
+ ((unsigned char)bytes[0]);
+
+ return size;
+}
+
+QVariant DbAndroidJsonConnection::convertJsonValue(const QJsonValue& value)
+{
+ if (value.isArray())
+ {
+ // BLOB
+ QJsonArray blobContainer = value.toArray();
+ if (blobContainer.size() < 1)
+ {
+ qCritical() << "Invalid blob value from Android - empty array.";
+ return QByteArray();
+ }
+
+ return convertBlob(blobContainer.first().toString());
+ }
+
+ // Regular value
+ return value.toVariant();
+}
+
+bool DbAndroidJsonConnection::connectToNetwork()
+{
+ if (!dbUrl.isHostValid())
+ return false;
+
+ return connectToTcp(dbUrl.getHost(), dbUrl.getPort());
+}
+
+bool DbAndroidJsonConnection::connectToDevice()
+{
+ if (!plugin->isAdbValid())
+ return false;
+
+ if (!plugin->getAdbManager()->getDevices().contains(dbUrl.getDevice()))
+ {
+ notifyWarn(tr("Cannot connect to device %1, because it's not visible to your computer.").arg(dbUrl.getDevice()));
+ return false;
+ }
+
+ int localPort = plugin->getAdbManager()->makeForwardFor(dbUrl.getDevice(), dbUrl.getPort());
+ if (localPort < 0)
+ {
+ notifyError(tr("Failed to create port forwarding for device %1 for port %2.")
+ .arg(dbUrl.getDevice(), QString::number(dbUrl.getPort())));
+ return false;
+ }
+
+ return connectToTcp("127.0.0.1", localPort);
+}
+
+bool DbAndroidJsonConnection::connectToTcp(const QString& ip, int port)
+{
+ bool success = socket->connectToHost(ip, port);
+ if (!success)
+ {
+ qWarning() << "Could not connect to network host for Android DB:" << ip << ":" << port << ", details:" << socket->getErrorText();
+ notifyWarn(tr("Could not connect to network host: %1:%2").arg(ip, QString::number(port)));
+ return false;
+ }
+
+ connectedState = true;
+
+ // Evaluate the connection and send the security token
+ static_qstring(pingCmd, "{ping:\"%1\"}");
+ QDate date = QDateTime::currentDateTime().date();
+ QString tokenString = "06fn43" + QString::number(date.dayOfYear()) + "3ig7ws" + QString::number(date.year()) + "53";
+ QByteArray md5 = QCryptographicHash::hash(tokenString.toUtf8(), QCryptographicHash::Md5);
+ QByteArray token = md5.toBase64();
+
+ QByteArray response = send(pingCmd.arg(QString::fromLatin1(token)).toUtf8());
+ if (response != PING_RESPONSE_OK)
+ {
+ qWarning() << "Connection to" << ip << ":" << port << "has failed, because response to PING was:" << response;
+ notifyWarn(tr("Cannot connect to SQLiteStudio service on Android device. The remote service is unavailable or invalid."));
+ handleConnectionFailed();
+ return false;
+ }
+
+ // Authenticate
+ QString pass = dbUrl.getPassword();
+ if (!pass.isEmpty())
+ {
+ static_qstring(passPharse, "{auth:\"%1\"}");
+ response = send(passPharse.arg(pass.replace("\"", "\\\"")).toUtf8());
+ if (response != PASS_RESPONSE_OK)
+ {
+ notifyWarn(tr("Cannot connect to %1:%2, because password is invalid.").arg(ip, QString::number(port)));
+ handleConnectionFailed();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void DbAndroidJsonConnection::handleConnectionFailed()
+{
+ connectedState = false;
+ socket->disconnectFromHost();
+}
+
+void DbAndroidJsonConnection::cleanUp()
+{
+ disconnectFromAndroid();
+ safe_delete(socket);
+}
+
+QStringList DbAndroidJsonConnection::getDbList()
+{
+ if (!isConnected())
+ {
+ qWarning() << "Called DbAndroidJsonConnection::getDbList() on closed connection.";
+ return QStringList();
+ }
+
+ QByteArray result = send(LIST_CMD);
+ return handleDbListResult(result);
+}
+
+QStringList DbAndroidJsonConnection::getAppList()
+{
+ return QStringList();
+}
+
+bool DbAndroidJsonConnection::isAppOkay() const
+{
+ return true;
+}
+
+QStringList DbAndroidJsonConnection::handleDbListResult(const QByteArray& results)
+{
+ QJsonParseError jsonError;
+ QJsonDocument jsonResponse = QJsonDocument::fromJson(results, &jsonError);
+ if (jsonError.error != QJsonParseError::NoError)
+ {
+ qCritical() << "Error while parsing response from Android:" << jsonError.errorString();
+ return QStringList();
+ }
+
+ QJsonObject responseObject = jsonResponse.object();
+ if (responseObject.contains("generic_error"))
+ {
+ qCritical() << "Generic error from Android:" << responseObject["generic_error"].toInt();
+ return QStringList();
+ }
+
+ if (!responseObject.contains("list"))
+ {
+ qCritical() << "Missing 'list' in response from Android.";
+ return QStringList();
+ }
+
+ QStringList dbNames;
+ for (const QVariant& name : responseObject["list"].toArray().toVariantList())
+ dbNames << name.toString();
+
+ return dbNames;
+}
+
+bool DbAndroidJsonConnection::deleteDatabase(const QString& dbName)
+{
+ if (!isConnected())
+ {
+ qWarning() << "Called DbAndroidConnection::deleteDatabase() on closed database.";
+ return false;
+ }
+
+ QByteArray result = send(QString(DELETE_DB_CMD).arg(dbName).toUtf8());
+ return handleStdResult(result);
+}
+
+DbAndroidConnection::ExecutionResult DbAndroidJsonConnection::executeQuery(const QString& query)
+{
+ DbAndroidConnection::ExecutionResult executionResults;
+
+ QJsonDocument json = wrapQueryInJson(query);
+ QByteArray responseBytes = send(json.toJson(QJsonDocument::Compact));
+
+ QJsonParseError jsonError;
+ QJsonDocument jsonResponse = QJsonDocument::fromJson(responseBytes, &jsonError);
+ if (jsonError.error != QJsonParseError::NoError)
+ {
+ executionResults.wasError = true;
+ executionResults.errorMsg = tr("Error while parsing response from Android: %1").arg(jsonError.errorString());
+ return executionResults;
+ }
+
+ QJsonObject responseObject = jsonResponse.object();
+ if (responseObject.contains("generic_error"))
+ {
+ executionResults.wasError = true;
+ executionResults.errorMsg = tr("Generic error from Android: %1").arg(responseObject["generic_error"].toInt());
+ return executionResults;
+ }
+
+ if (responseObject.contains("error_code"))
+ {
+ executionResults.errorCode = responseObject["error_code"].toInt();
+ executionResults.errorMsg = responseObject["error_message"].toString();
+ return executionResults;
+ }
+
+ if (!responseObject.contains("columns"))
+ {
+ executionResults.wasError = true;
+ executionResults.errorMsg = tr("Missing 'columns' in response from Android.");
+ return executionResults;
+ }
+
+ if (!responseObject.contains("data"))
+ {
+ executionResults.wasError = true;
+ executionResults.errorMsg = tr("Missing 'columns' in response from Android.");
+ return executionResults;
+ }
+
+ for (const QVariant& col : responseObject["columns"].toArray().toVariantList())
+ executionResults.resultColumns << col.toString();
+
+ QJsonArray jsonRows = responseObject["data"].toArray();
+ QJsonObject jsonRow;
+ QJsonValue jsonValue;
+ QVariantHash rowAsMap;
+ QVariantList rowAsList;
+ QVariant cellValue;
+ for (int i = 0, total = jsonRows.size(); i < total; ++i)
+ {
+ jsonRow = jsonRows[i].toObject();
+ for (const QString& colName : executionResults.resultColumns)
+ {
+ if (!jsonRow.contains(colName))
+ {
+ executionResults.wasError = true;
+ executionResults.errorMsg = tr("Response from Android has missing data for column '%1' in row %2.").arg(colName, QString::number(i+1));
+ return executionResults;
+ }
+
+ jsonValue = jsonRow[colName];
+ cellValue = convertJsonValue(jsonValue);
+ rowAsMap[colName] = cellValue;
+ rowAsList << cellValue;
+ }
+
+ executionResults.resultDataMap << rowAsMap;
+ executionResults.resultDataList << rowAsList;
+
+ rowAsMap.clear();
+ rowAsList.clear();
+ }
+
+ return executionResults;
+}
+
+QJsonDocument DbAndroidJsonConnection::wrapQueryInJson(const QString& query)
+{
+ QJsonDocument doc;
+
+ QJsonObject rootObj;
+ rootObj["cmd"] = "QUERY";
+ rootObj["db"] = dbUrl.getDbName();
+ rootObj["query"] = query;
+
+ doc.setObject(rootObj);
+ return doc;
+}
+
+bool DbAndroidJsonConnection::handleStdResult(const QByteArray& results)
+{
+ QJsonParseError jsonError;
+ QJsonDocument jsonResponse = QJsonDocument::fromJson(results, &jsonError);
+ if (jsonError.error != QJsonParseError::NoError)
+ {
+ qCritical() << "Error while parsing response from Android:" << jsonError.errorString();
+ return false;
+ }
+
+ QJsonObject responseObject = jsonResponse.object();
+ if (responseObject.contains("generic_error"))
+ {
+ qCritical() << "Generic error from Android:" << responseObject["generic_error"].toInt();
+ return false;
+ }
+
+ if (!responseObject.contains("result"))
+ {
+ qCritical() << "Missing 'result' in response from Android.";
+ return false;
+ }
+
+ return (responseObject["result"].toString() == "ok");
+}