15.1.350. tablet_qt/menulib/serversettingswindow.cpp

    Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com).

    This file is part of CamCOPS.

    CamCOPS is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    CamCOPS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with CamCOPS. If not, see <http://www.gnu.org/licenses/>.

#include "serversettingswindow.h"

#include <QString>

#include "common/aliases_camcops.h"
#include "common/varconst.h"
#include "core/camcopsapp.h"
#include "lib/convert.h"
#include "lib/stringfunc.h"
#include "lib/uifunc.h"
#include "questionnairelib/commonoptions.h"
#include "questionnairelib/namevalueoptions.h"
#include "questionnairelib/questionnaire.h"
#include "questionnairelib/questionnairefunc.h"
#include "questionnairelib/quhorizontalline.h"
#include "questionnairelib/qulineedit.h"
#include "questionnairelib/qulineeditinteger.h"
#include "questionnairelib/qulineeditint64.h"
#include "questionnairelib/qumcq.h"
#include "questionnairelib/qutext.h"
#include "widgets/openablewidget.h"

// ============================================================================
// Server configuration
// ============================================================================

ServerSettingsWindow::ServerSettingsWindow(CamcopsApp& app) :

OpenableWidget* ServerSettingsWindow::editor()
    // The general options here: have the Questionnaire save directly to the
    // StoredVar but in a way that's not permanent and allows "recall/reload"
    // upon cancel; or temporary local storage with writing to the StoredVar
    // on OK. The latter is a generally better principle. Since C++ lambda
    // functions don't extend the life of things they reference (see "dangling
    // references" in the spec), the best way is by having the CamcopsApp
    // (whose lifespan is guaranteed to be long enough) to do the storage,
    // and having a fieldref to a cached storedvar.
    // A third option is to use a database transaction (and forgo or invalidate
    // any copies of storedvars maintained in memory).

    const QString DEPRECATED("(†) ");

    m_app.clearCachedVars();  // ... in case any are left over

    FieldRefPtr address_fr = m_app.storedVarFieldRef(varconst::SERVER_ADDRESS);
    const QString address_t = tr("Server address");
    const QString address_h = tr("host name or IP address");

    FieldRefPtr port_fr = m_app.storedVarFieldRef(varconst::SERVER_PORT);
    const QString port_t = tr("Server port for HTTPS");
    const QString port_h = tr("default 443");

    FieldRefPtr path_fr = m_app.storedVarFieldRef(varconst::SERVER_PATH);
    const QString path_t = tr("Path on server");
    const QString path_h = tr("no leading /; e.g. camcops/database");

    FieldRefPtr timeout_fr = m_app.storedVarFieldRef(varconst::SERVER_TIMEOUT_MS);
    const QString timeout_t = tr("Network timeout (ms)");
    const QString timeout_h = tr("e.g. 50000");

    FieldRefPtr https_fr = m_app.storedVarFieldRef(varconst::DEBUG_USE_HTTPS_TO_SERVER);
    const QString https_t = tr("Use HTTPS to server?");
    const QString https_h = tr("You should <b>only</b> disable this for debugging!");

    FieldRefPtr ssl_fr = m_app.storedVarFieldRef(
    const QString ssl_t = tr("Validate HTTPS (TLS/SSL) certificates?");
    const QString ssl_h = tr("Should always be YES for security-conscious "

    FieldRefPtr ssl_proto_fr = m_app.storedVarFieldRef(varconst::SSL_PROTOCOL);
    const QString ssl_proto_t = tr("HTTPS (TLS/SSL) protocol?");
    const QString ssl_proto_h = tr("Stick with the default unless your server "
                                   "can’t cope with it.");
    const NameValueOptions options_ssl_protocol{
        // http://doc.qt.io/qt-5/qssl.html#SslProtocol-enum
        {tr("Known secure [default]"), convert::SSLPROTODESC_SECUREPROTOCOLS},
        {DEPRECATED + tr("SSL v3"), convert::SSLPROTODESC_SSLV3},
        {DEPRECATED + tr("SSL v2"), convert::SSLPROTODESC_SSLV2},
        {DEPRECATED + tr("TLS v1.0"), convert::SSLPROTODESC_TLSV1_0},
        {DEPRECATED + tr("TLS v1.0 or later"), convert::SSLPROTODESC_TLSV1_0_OR_LATER},
        {tr("TLS v1.1"), convert::SSLPROTODESC_TLSV1_1},
        {tr("TLS v1.1 or later"), convert::SSLPROTODESC_TLSV1_1_OR_LATER},
        {tr("TLS v1.2"), convert::SSLPROTODESC_TLSV1_2},
        {tr("TLS v1.2 or later"), convert::SSLPROTODESC_TLSV1_2_OR_LATER},
        {DEPRECATED + tr("SSLv2, SSLv3, or TLSv1.0"), convert::SSLPROTODESC_ANYPROTOCOL},
        {DEPRECATED + tr("TLS v1.0 or SSL v3"), convert::SSLPROTODESC_TLSV1_SSLV3},
    const QString ssl_proto_explanation = DEPRECATED + "Insecure, deprecated.";

    FieldRefPtr storepw_fr = m_app.storedVarFieldRef(varconst::STORE_SERVER_PASSWORD);
    const QString storepw_t = tr("Store user’s server password?");
    const QString storepw_h = tr(
                "NO = fractionally more secure; YES = more convenient/"
                "fractionally less secure, but still AES-256-encrypted.");

    FieldRefPtr uploadmethod_fr = m_app.storedVarFieldRef(
    const QString uploadmethod_t = tr("Upload method");
    const NameValueOptions options_upload_method{
        {tr("Multi-step (original)"), varconst::UPLOAD_METHOD_MULTISTEP},
        {tr("Always one-step (faster)"), varconst::UPLOAD_METHOD_ONESTEP},
        {tr("One-step if small enough (default)"), varconst::UPLOAD_METHOD_BYSIZE},

    FieldRefPtr maxsizeonestep_fr = m_app.storedVarFieldRef(
    const QString maxsizeonestep_t = tr(
            "Maximum (approximate) database size for one-step upload (bytes)");
    const QString maxsizeonestep_h = tr("e.g. 2000000 for ~2Mb");

    QuPagePtr page(new QuPage{
                stringfunc::makeTitle(address_t, address_h, true),
                (new QuLineEdit(address_fr))->setHint(
                    stringfunc::makeHint(address_t, address_h))
                    ->setWidgetInputMethodHints(Qt::ImhNoAutoUppercase |
                stringfunc::makeTitle(port_t, port_h, true),
                new QuLineEditInteger(port_fr,
                    uiconst::IP_PORT_MIN, uiconst::IP_PORT_MAX)
                stringfunc::makeTitle(path_t, path_h, true),
                (new QuLineEdit(path_fr))->setHint(
                    stringfunc::makeHint(path_t, path_h))
                    ->setWidgetInputMethodHints(Qt::ImhNoAutoUppercase |
                stringfunc::makeTitle(timeout_t, timeout_h, true),
                new QuLineEditInteger(timeout_fr,
        }, 1, 1),

       new QuText(stringfunc::makeTitle(https_t, https_h)),
       (new QuMcq(https_fr, CommonOptions::yesNoBoolean()))

        new QuText(stringfunc::makeTitle(ssl_t, ssl_h)),
        (new QuMcq(ssl_fr, CommonOptions::yesNoBoolean()))

        new QuText(stringfunc::makeTitle(ssl_proto_t, ssl_proto_h)),
        (new QuMcq(ssl_proto_fr, options_ssl_protocol))
        new QuText(ssl_proto_explanation),

        new QuHorizontalLine(),

        new QuText(stringfunc::makeTitle(storepw_t, storepw_h)),
        (new QuMcq(storepw_fr, CommonOptions::yesNoBoolean()))

        new QuHorizontalLine(),

        new QuText(stringfunc::makeTitle(uploadmethod_t)),
        (new QuMcq(uploadmethod_fr, options_upload_method))

                stringfunc::makeTitle(maxsizeonestep_t, maxsizeonestep_h, true),
                (new QuLineEditInt64(maxsizeonestep_fr))->setHint(
                    stringfunc::makeHint(maxsizeonestep_t, maxsizeonestep_h))
        }, 1, 1),
    page->setTitle(tr("Configure server settings"));
                  this, std::placeholders::_1,

    auto questionnaire = new Questionnaire(m_app, {page});
    connect(questionnaire, &Questionnaire::completed,
            this, &ServerSettingsWindow::serverSettingsSaved);
    connect(questionnaire, &Questionnaire::cancelled,
            &m_app, &CamcopsApp::clearCachedVars);
    return questionnaire;

bool ServerSettingsWindow::validateServerSettings(QStringList& errors,
                                                  const QuPage* page)
    // Note that we are using cached server variables.
    const QString hostname = m_app.getCachedVar(varconst::SERVER_ADDRESS).toString();
    if (hostname.contains("/")) {
        errors.append("No forward slashes ('/') permitted in server hostname");
        return false;
    return true;

void ServerSettingsWindow::serverSettingsSaved()
    // User has edited server settings and then clicked OK.
    const bool server_details_changed = (
        || m_app.cachedVarChanged(varconst::SERVER_PORT)
        || m_app.cachedVarChanged(varconst::SERVER_PATH)
    if (server_details_changed) {
            tr("Server details have changed. You should consider "
               "re-registering with the server."),
            tr("Registration advised")
    if (!m_app.storingServerPassword()) {
        // Wipe the stored password
        m_app.setCachedVar(varconst::SERVER_USERPASSWORD_OBSCURED, "");