Copilot: Add support for proxy settings

For easier testing a docker file is added. You can start
"buildandrun.sh" in copilot/tests/proxy to get a simple
proxy server up and running. The argument "PWDMODE" in
buildandrun.sh can be set to "with" and "without" to get
a proxy server that needs a password or not. The username
and password are user/1234.

Fixes: QTCREATORBUG-29485
Change-Id: I3859c9ad04ebd4f9349e25665ba710e23fb64dea
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Marcus Tillmanns
2023-08-14 09:48:14 +02:00
parent 91596316f2
commit f2d62c6d91
12 changed files with 390 additions and 49 deletions

View File

@@ -44,18 +44,21 @@ AuthWidget::AuthWidget(QWidget *parent)
}.attachTo(this); }.attachTo(this);
// clang-format on // clang-format on
connect(m_button, &QPushButton::clicked, this, [this]() {
if (m_status == Status::SignedIn)
signOut();
else if (m_status == Status::SignedOut)
signIn();
});
auto update = [this] { auto update = [this] {
updateClient(FilePath::fromUserInput(settings().nodeJsPath.volatileValue()), updateClient(FilePath::fromUserInput(settings().nodeJsPath.volatileValue()),
FilePath::fromUserInput(settings().distPath.volatileValue())); FilePath::fromUserInput(settings().distPath.volatileValue()));
}; };
connect(m_button, &QPushButton::clicked, this, [this, update]() {
if (m_status == Status::SignedIn)
signOut();
else if (m_status == Status::SignedOut)
signIn();
else
update();
});
connect(&settings(), &CopilotSettings::applied, this, update);
connect(settings().nodeJsPath.pathChooser(), &PathChooser::textChanged, this, update); connect(settings().nodeJsPath.pathChooser(), &PathChooser::textChanged, this, update);
connect(settings().distPath.pathChooser(), &PathChooser::textChanged, this, update); connect(settings().distPath.pathChooser(), &PathChooser::textChanged, this, update);
@@ -68,35 +71,39 @@ AuthWidget::~AuthWidget()
LanguageClientManager::shutdownClient(m_client); LanguageClientManager::shutdownClient(m_client);
} }
void AuthWidget::setState(const QString &buttonText, bool working) void AuthWidget::setState(const QString &buttonText, const QString &errorText, bool working)
{ {
m_button->setText(buttonText); m_button->setText(buttonText);
m_button->setVisible(true); m_button->setVisible(true);
m_progressIndicator->setVisible(working); m_progressIndicator->setVisible(working);
m_statusLabel->setText(errorText);
m_statusLabel->setVisible(!m_statusLabel->text().isEmpty()); m_statusLabel->setVisible(!m_statusLabel->text().isEmpty());
m_button->setEnabled(!working); m_button->setEnabled(!working);
} }
void AuthWidget::checkStatus() void AuthWidget::checkStatus()
{ {
if (!isEnabled())
return;
QTC_ASSERT(m_client && m_client->reachable(), return); QTC_ASSERT(m_client && m_client->reachable(), return);
setState("Checking status ...", true); setState("Checking status ...", {}, true);
m_client->requestCheckStatus(false, [this](const CheckStatusRequest::Response &response) { m_client->requestCheckStatus(false, [this](const CheckStatusRequest::Response &response) {
if (response.error()) { if (response.error()) {
setState("failed: " + response.error()->message(), false); setState("Failed to authenticate", response.error()->message(), false);
return; return;
} }
const CheckStatusResponse result = *response.result(); const CheckStatusResponse result = *response.result();
if (result.user().isEmpty()) { if (result.user().isEmpty()) {
setState("Sign in", false); setState("Sign in", {}, false);
m_status = Status::SignedOut; m_status = Status::SignedOut;
return; return;
} }
setState("Sign out " + result.user(), false); setState("Sign out " + result.user(), {}, false);
m_status = Status::SignedIn; m_status = Status::SignedIn;
}); });
} }
@@ -105,12 +112,12 @@ void AuthWidget::updateClient(const FilePath &nodeJs, const FilePath &agent)
{ {
LanguageClientManager::shutdownClient(m_client); LanguageClientManager::shutdownClient(m_client);
m_client = nullptr; m_client = nullptr;
setState(Tr::tr("Sign In"), false); setState(Tr::tr("Sign In"), {}, false);
m_button->setEnabled(false); m_button->setEnabled(false);
if (!nodeJs.isExecutableFile() || !agent.exists()) if (!nodeJs.isExecutableFile() || !agent.exists())
return; return;
setState(Tr::tr("Sign In"), true); setState(Tr::tr("Sign In"), {}, true);
m_client = new CopilotClient(nodeJs, agent); m_client = new CopilotClient(nodeJs, agent);
connect(m_client, &Client::initialized, this, &AuthWidget::checkStatus); connect(m_client, &Client::initialized, this, &AuthWidget::checkStatus);
@@ -127,7 +134,7 @@ void AuthWidget::signIn()
qCritical() << "Not implemented"; qCritical() << "Not implemented";
QTC_ASSERT(m_client && m_client->reachable(), return); QTC_ASSERT(m_client && m_client->reachable(), return);
setState("Signing in ...", true); setState("Signing in ...", {}, true);
m_client->requestSignInInitiate([this](const SignInInitiateRequest::Response &response) { m_client->requestSignInInitiate([this](const SignInInitiateRequest::Response &response) {
QTC_ASSERT(!response.error(), return); QTC_ASSERT(!response.error(), return);
@@ -144,19 +151,17 @@ void AuthWidget::signIn()
m_client m_client
->requestSignInConfirm(response.result()->userCode(), ->requestSignInConfirm(response.result()->userCode(),
[this](const SignInConfirmRequest::Response &response) { [this](const SignInConfirmRequest::Response &response) {
m_statusLabel->setText("");
if (response.error()) { if (response.error()) {
QMessageBox::critical(this, QMessageBox::critical(this,
Tr::tr("Login Failed"), Tr::tr("Login Failed"),
Tr::tr( Tr::tr(
"The login request failed: ") "The login request failed: ")
+ response.error()->message()); + response.error()->message());
setState("Sign in", false); setState("Sign in", response.error()->message(), false);
return; return;
} }
setState("Sign Out " + response.result()->user(), false); setState("Sign Out " + response.result()->user(), {}, false);
}); });
}); });
} }
@@ -165,7 +170,7 @@ void AuthWidget::signOut()
{ {
QTC_ASSERT(m_client && m_client->reachable(), return); QTC_ASSERT(m_client && m_client->reachable(), return);
setState("Signing out ...", true); setState("Signing out ...", {}, true);
m_client->requestSignOut([this](const SignOutRequest::Response &response) { m_client->requestSignOut([this](const SignOutRequest::Response &response) {
QTC_ASSERT(!response.error(), return); QTC_ASSERT(!response.error(), return);

View File

@@ -30,7 +30,7 @@ public:
void updateClient(const Utils::FilePath &nodeJs, const Utils::FilePath &agent); void updateClient(const Utils::FilePath &nodeJs, const Utils::FilePath &agent);
private: private:
void setState(const QString &buttonText, bool working); void setState(const QString &buttonText, const QString &errorText, bool working);
void checkStatus(); void checkStatus();

View File

@@ -2,26 +2,32 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "copilotclient.h" #include "copilotclient.h"
#include "copilotconstants.h"
#include "copilotsettings.h" #include "copilotsettings.h"
#include "copilotsuggestion.h" #include "copilotsuggestion.h"
#include "copilottr.h"
#include <app/app_version.h>
#include <languageclient/languageclientinterface.h> #include <languageclient/languageclientinterface.h>
#include <languageclient/languageclientmanager.h> #include <languageclient/languageclientmanager.h>
#include <languageclient/languageclientsettings.h> #include <languageclient/languageclientsettings.h>
#include <languageserverprotocol/lsptypes.h>
#include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
#include <projectexplorer/projectmanager.h> #include <projectexplorer/projectmanager.h>
#include <utils/filepath.h>
#include <texteditor/textdocumentlayout.h> #include <texteditor/textdocumentlayout.h>
#include <texteditor/texteditor.h> #include <texteditor/texteditor.h>
#include <languageserverprotocol/lsptypes.h> #include <utils/checkablemessagebox.h>
#include <utils/filepath.h>
#include <utils/passworddialog.h>
#include <QInputDialog>
#include <QLoggingCategory>
#include <QTimer> #include <QTimer>
#include <QToolButton> #include <QToolButton>
@@ -31,6 +37,8 @@ using namespace Utils;
using namespace ProjectExplorer; using namespace ProjectExplorer;
using namespace Core; using namespace Core;
Q_LOGGING_CATEGORY(copilotClientLog, "qtc.copilot.client", QtWarningMsg)
namespace Copilot::Internal { namespace Copilot::Internal {
static LanguageClient::BaseClientInterface *clientInterface(const FilePath &nodePath, static LanguageClient::BaseClientInterface *clientInterface(const FilePath &nodePath,
@@ -52,6 +60,23 @@ CopilotClient::CopilotClient(const FilePath &nodePath, const FilePath &distPath)
langFilter.filePattern = {"*"}; langFilter.filePattern = {"*"};
setSupportedLanguage(langFilter); setSupportedLanguage(langFilter);
registerCustomMethod("LogMessage", [this](const LanguageServerProtocol::JsonRpcMessage &message) {
QString msg = message.toJsonObject().value("params").toObject().value("message").toString();
qCDebug(copilotClientLog) << message.toJsonObject()
.value("params")
.toObject()
.value("message")
.toString();
if (msg.contains("Socket Connect returned status code,407")) {
qCWarning(copilotClientLog) << "Proxy authentication required";
QMetaObject::invokeMethod(this,
&CopilotClient::proxyAuthenticationFailed,
Qt::QueuedConnection);
}
});
start(); start();
auto openDoc = [this](IDocument *document) { auto openDoc = [this](IDocument *document) {
@@ -68,6 +93,8 @@ CopilotClient::CopilotClient(const FilePath &nodePath, const FilePath &distPath)
closeDocument(textDocument); closeDocument(textDocument);
}); });
connect(this, &LanguageClient::Client::initialized, this, &CopilotClient::requestSetEditorInfo);
for (IDocument *doc : DocumentModel::openedDocuments()) for (IDocument *doc : DocumentModel::openedDocuments())
openDoc(doc); openDoc(doc);
} }
@@ -218,6 +245,32 @@ void CopilotClient::cancelRunningRequest(TextEditor::TextEditorWidget *editor)
m_runningRequests.erase(it); m_runningRequests.erase(it);
} }
static QString currentProxyPassword;
void CopilotClient::requestSetEditorInfo()
{
if (settings().saveProxyPassword())
currentProxyPassword = settings().proxyPassword();
const EditorInfo editorInfo{Core::Constants::IDE_VERSION_DISPLAY, "Qt Creator"};
const EditorPluginInfo editorPluginInfo{Core::Constants::IDE_VERSION_DISPLAY,
"Qt Creator Copilot plugin"};
SetEditorInfoParams params(editorInfo, editorPluginInfo);
if (settings().useProxy()) {
params.setNetworkProxy(
Copilot::NetworkProxy{settings().proxyHost(),
static_cast<int>(settings().proxyPort()),
settings().proxyUser(),
currentProxyPassword,
settings().proxyRejectUnauthorized()});
}
SetEditorInfoRequest request(params);
sendMessage(request);
}
void CopilotClient::requestCheckStatus( void CopilotClient::requestCheckStatus(
bool localChecksOnly, std::function<void(const CheckStatusRequest::Response &response)> callback) bool localChecksOnly, std::function<void(const CheckStatusRequest::Response &response)> callback)
{ {
@@ -269,4 +322,36 @@ bool CopilotClient::isEnabled(Project *project)
return settings.isEnabled(); return settings.isEnabled();
} }
void CopilotClient::proxyAuthenticationFailed()
{
static bool doNotAskAgain = false;
if (m_isAskingForPassword || !settings().enableCopilot())
return;
m_isAskingForPassword = true;
auto answer = Utils::PasswordDialog::getUserAndPassword(
Tr::tr("Copilot"),
Tr::tr("Proxy username and password required:"),
Tr::tr("Do not ask again. This will disable Copilot for now."),
settings().proxyUser(),
&doNotAskAgain,
Core::ICore::dialogParent());
if (answer) {
settings().proxyUser.setValue(answer->first);
currentProxyPassword = answer->second;
} else {
settings().enableCopilot.setValue(false);
}
if (settings().saveProxyPassword())
settings().proxyPassword.setValue(currentProxyPassword);
settings().apply();
m_isAskingForPassword = false;
}
} // namespace Copilot::Internal } // namespace Copilot::Internal

View File

@@ -6,6 +6,7 @@
#include "copilothoverhandler.h" #include "copilothoverhandler.h"
#include "requests/checkstatus.h" #include "requests/checkstatus.h"
#include "requests/getcompletions.h" #include "requests/getcompletions.h"
#include "requests/seteditorinfo.h"
#include "requests/signinconfirm.h" #include "requests/signinconfirm.h"
#include "requests/signininitiate.h" #include "requests/signininitiate.h"
#include "requests/signout.h" #include "requests/signout.h"
@@ -50,7 +51,11 @@ public:
bool isEnabled(ProjectExplorer::Project *project); bool isEnabled(ProjectExplorer::Project *project);
void proxyAuthenticationFailed();
private: private:
void requestSetEditorInfo();
QMap<TextEditor::TextEditorWidget *, GetCompletionRequest> m_runningRequests; QMap<TextEditor::TextEditorWidget *, GetCompletionRequest> m_runningRequests;
struct ScheduleData struct ScheduleData
{ {
@@ -59,6 +64,7 @@ private:
}; };
QMap<TextEditor::TextEditorWidget *, ScheduleData> m_scheduledRequests; QMap<TextEditor::TextEditorWidget *, ScheduleData> m_scheduledRequests;
CopilotHoverHandler m_hoverHandler; CopilotHoverHandler m_hoverHandler;
bool m_isAskingForPassword{false};
}; };
} // namespace Copilot::Internal } // namespace Copilot::Internal

View File

@@ -44,7 +44,6 @@ CopilotSettings::CopilotSettings()
const FilePath nodeFromPath = FilePath("node").searchInPath(); const FilePath nodeFromPath = FilePath("node").searchInPath();
const FilePaths searchDirs const FilePaths searchDirs
= {FilePath::fromUserInput("~/.vim/pack/github/start/copilot.vim/dist/agent.js"), = {FilePath::fromUserInput("~/.vim/pack/github/start/copilot.vim/dist/agent.js"),
FilePath::fromUserInput("~/.vim/pack/github/start/copilot.vim/copilot/dist/agent.js"), FilePath::fromUserInput("~/.vim/pack/github/start/copilot.vim/copilot/dist/agent.js"),
FilePath::fromUserInput( FilePath::fromUserInput(
@@ -80,13 +79,75 @@ CopilotSettings::CopilotSettings()
autoComplete.setDisplayName(Tr::tr("Auto Complete")); autoComplete.setDisplayName(Tr::tr("Auto Complete"));
autoComplete.setSettingsKey("Copilot.Autocomplete"); autoComplete.setSettingsKey("Copilot.Autocomplete");
autoComplete.setLabelText(Tr::tr("Request completions automatically")); autoComplete.setLabelText(Tr::tr("Auto request"));
autoComplete.setDefaultValue(true); autoComplete.setDefaultValue(true);
autoComplete.setEnabler(&enableCopilot); autoComplete.setEnabler(&enableCopilot);
autoComplete.setToolTip(Tr::tr("Automatically request suggestions for the current text cursor " autoComplete.setToolTip(Tr::tr("Automatically request suggestions for the current text cursor "
"position after changes to the document.")); "position after changes to the document."));
autoComplete.setLabelPlacement(BoolAspect::LabelPlacement::InExtraLabel);
useProxy.setDisplayName(Tr::tr("Use Proxy"));
useProxy.setSettingsKey("Copilot.UseProxy");
useProxy.setLabelText(Tr::tr("Use Proxy"));
useProxy.setDefaultValue(false);
useProxy.setEnabler(&enableCopilot);
useProxy.setToolTip(Tr::tr("Use a proxy to connect to the Copilot servers."));
useProxy.setLabelPlacement(BoolAspect::LabelPlacement::InExtraLabel);
proxyHost.setDisplayName(Tr::tr("Proxy Host"));
proxyHost.setDisplayStyle(StringAspect::LineEditDisplay);
proxyHost.setSettingsKey("Copilot.ProxyHost");
proxyHost.setLabelText(Tr::tr("Proxy Host"));
proxyHost.setDefaultValue("");
proxyHost.setEnabler(&useProxy);
proxyHost.setToolTip(Tr::tr("The host name of the proxy server."));
proxyHost.setHistoryCompleter("Copilot.ProxyHost.History");
proxyPort.setDisplayName(Tr::tr("Proxy Port"));
proxyPort.setSettingsKey("Copilot.ProxyPort");
proxyPort.setLabelText(Tr::tr("Proxy Port"));
proxyPort.setDefaultValue(3128);
proxyPort.setEnabler(&useProxy);
proxyPort.setToolTip(Tr::tr("The port of the proxy server."));
proxyPort.setRange(1, 65535);
proxyUser.setDisplayName(Tr::tr("Proxy User"));
proxyUser.setDisplayStyle(StringAspect::LineEditDisplay);
proxyUser.setSettingsKey("Copilot.ProxyUser");
proxyUser.setLabelText(Tr::tr("Proxy User"));
proxyUser.setDefaultValue("");
proxyUser.setEnabler(&useProxy);
proxyUser.setToolTip(Tr::tr("The user name for the proxy server."));
proxyUser.setHistoryCompleter("Copilot.ProxyUser.History");
saveProxyPassword.setDisplayName(Tr::tr("Save Proxy Password"));
saveProxyPassword.setSettingsKey("Copilot.SaveProxyPassword");
saveProxyPassword.setLabelText(Tr::tr("Save Proxy Password"));
saveProxyPassword.setDefaultValue(false);
saveProxyPassword.setEnabler(&useProxy);
saveProxyPassword.setToolTip(
Tr::tr("Save the password for the proxy server (Password is stored insecurely!)."));
saveProxyPassword.setLabelPlacement(BoolAspect::LabelPlacement::InExtraLabel);
proxyPassword.setDisplayName(Tr::tr("Proxy Password"));
proxyPassword.setDisplayStyle(StringAspect::PasswordLineEditDisplay);
proxyPassword.setSettingsKey("Copilot.ProxyPassword");
proxyPassword.setLabelText(Tr::tr("Proxy Password"));
proxyPassword.setDefaultValue("");
proxyPassword.setEnabler(&saveProxyPassword);
proxyPassword.setToolTip(Tr::tr("The password for the proxy server."));
proxyRejectUnauthorized.setDisplayName(Tr::tr("Reject Unauthorized"));
proxyRejectUnauthorized.setSettingsKey("Copilot.ProxyRejectUnauthorized");
proxyRejectUnauthorized.setLabelText(Tr::tr("Reject Unauthorized"));
proxyRejectUnauthorized.setDefaultValue(true);
proxyRejectUnauthorized.setEnabler(&useProxy);
proxyRejectUnauthorized.setToolTip(Tr::tr("Reject unauthorized certificates from the proxy "
"server. This is a security risk."));
proxyRejectUnauthorized.setLabelPlacement(BoolAspect::LabelPlacement::InExtraLabel);
initEnableAspect(enableCopilot); initEnableAspect(enableCopilot);
enableCopilot.setLabelPlacement(BoolAspect::LabelPlacement::InExtraLabel);
readSettings(); readSettings();
} }
@@ -140,25 +201,24 @@ public:
auto warningLabel = new QLabel; auto warningLabel = new QLabel;
warningLabel->setWordWrap(true); warningLabel->setWordWrap(true);
warningLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse warningLabel->setTextInteractionFlags(
| Qt::LinksAccessibleByKeyboard Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard | Qt::TextSelectableByMouse);
| Qt::TextSelectableByMouse); warningLabel->setText(
warningLabel->setText(Tr::tr( Tr::tr("Enabling %1 is subject to your agreement and abidance with your applicable "
"Enabling %1 is subject to your agreement and abidance with your applicable " "%1 terms. It is your responsibility to know and accept the requirements and "
"%1 terms. It is your responsibility to know and accept the requirements and " "parameters of using tools like %1. This may include, but is not limited to, "
"parameters of using tools like %1. This may include, but is not limited to, " "ensuring you have the rights to allow %1 access to your code, as well as "
"ensuring you have the rights to allow %1 access to your code, as well as " "understanding any implications of your use of %1 and suggestions produced "
"understanding any implications of your use of %1 and suggestions produced " "(like copyright, accuracy, etc.).")
"(like copyright, accuracy, etc.)." ).arg("Copilot")); .arg("Copilot"));
auto authWidget = new AuthWidget(); auto authWidget = new AuthWidget();
auto helpLabel = new QLabel(); auto helpLabel = new QLabel();
helpLabel->setTextFormat(Qt::MarkdownText); helpLabel->setTextFormat(Qt::MarkdownText);
helpLabel->setWordWrap(true); helpLabel->setWordWrap(true);
helpLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse helpLabel->setTextInteractionFlags(
| Qt::LinksAccessibleByKeyboard Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard | Qt::TextSelectableByMouse);
| Qt::TextSelectableByMouse);
helpLabel->setOpenExternalLinks(true); helpLabel->setOpenExternalLinks(true);
connect(helpLabel, &QLabel::linkHovered, [](const QString &link) { connect(helpLabel, &QLabel::linkHovered, [](const QString &link) {
QToolTip::showText(QCursor::pos(), link); QToolTip::showText(QCursor::pos(), link);
@@ -176,14 +236,28 @@ public:
.arg("[agent.js](https://github.com/github/copilot.vim/tree/release/dist)")); .arg("[agent.js](https://github.com/github/copilot.vim/tree/release/dist)"));
Column { Column {
QString("<b>" + Tr::tr("Note:") + "</b>"), br, Group {
warningLabel, br, title(Tr::tr("Note")),
settings().enableCopilot, br, Column {
authWidget, br, warningLabel, br,
settings().nodeJsPath, br, helpLabel, br,
settings().distPath, br, }
settings().autoComplete, br, },
helpLabel, br, Form {
authWidget, br,
settings().enableCopilot, br,
settings().nodeJsPath, br,
settings().distPath, br,
settings().autoComplete, br,
hr, br,
settings().useProxy, br,
settings().proxyHost, br,
settings().proxyPort, br,
settings().proxyRejectUnauthorized, br,
settings().proxyUser, br,
settings().saveProxyPassword, br,
settings().proxyPassword, br,
},
st st
}.attachTo(this); }.attachTo(this);
// clang-format on // clang-format on
@@ -211,4 +285,4 @@ public:
const CopilotSettingsPage settingsPage; const CopilotSettingsPage settingsPage;
} // Copilot } // namespace Copilot

View File

@@ -18,6 +18,15 @@ public:
Utils::FilePathAspect distPath{this}; Utils::FilePathAspect distPath{this};
Utils::BoolAspect autoComplete{this}; Utils::BoolAspect autoComplete{this};
Utils::BoolAspect enableCopilot{this}; Utils::BoolAspect enableCopilot{this};
Utils::BoolAspect useProxy{this};
Utils::StringAspect proxyHost{this};
Utils::IntegerAspect proxyPort{this};
Utils::StringAspect proxyUser{this};
Utils::BoolAspect saveProxyPassword{this};
Utils::StringAspect proxyPassword{this};
Utils::BoolAspect proxyRejectUnauthorized{this};
}; };
CopilotSettings &settings(); CopilotSettings &settings();

View File

@@ -0,0 +1,126 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include "checkstatus.h"
#include <languageserverprotocol/jsonrpcmessages.h>
#include <languageserverprotocol/lsptypes.h>
namespace Copilot {
class EditorPluginInfo : public LanguageServerProtocol::JsonObject
{
static constexpr char version[] = "version";
static constexpr char name[] = "name";
public:
using JsonObject::JsonObject;
EditorPluginInfo(const QString &version, const QString &name)
{
setEditorVersion(version);
setEditorName(name);
}
void setEditorVersion(const QString &v) { insert(version, v); }
void setEditorName(const QString &n) { insert(name, n); }
};
class EditorInfo : public LanguageServerProtocol::JsonObject
{
static constexpr char version[] = "version";
static constexpr char name[] = "name";
public:
using JsonObject::JsonObject;
EditorInfo(const QString &version, const QString &name)
{
setEditorVersion(version);
setEditorName(name);
}
void setEditorVersion(const QString &v) { insert(version, v); }
void setEditorName(const QString &n) { insert(name, n); }
};
class NetworkProxy : public LanguageServerProtocol::JsonObject
{
static constexpr char host[] = "host";
static constexpr char port[] = "port";
static constexpr char user[] = "username";
static constexpr char password[] = "password";
static constexpr char rejectUnauthorized[] = "rejectUnauthorized";
public:
using JsonObject::JsonObject;
NetworkProxy(const QString &host,
int port,
const QString &user,
const QString &password,
bool rejectUnauthorized)
{
setHost(host);
setPort(port);
setUser(user);
setPassword(password);
setRejectUnauthorized(rejectUnauthorized);
}
void insertIfNotEmpty(const std::string_view key, const QString &value)
{
if (!value.isEmpty())
insert(key, value);
}
void setHost(const QString &h) { insert(host, h); }
void setPort(int p) { insert(port, p); }
void setUser(const QString &u) { insertIfNotEmpty(user, u); }
void setPassword(const QString &p) { insertIfNotEmpty(password, p); }
void setRejectUnauthorized(bool r) { insert(rejectUnauthorized, r); }
};
class SetEditorInfoParams : public LanguageServerProtocol::JsonObject
{
static constexpr char editorInfo[] = "editorInfo";
static constexpr char editorPluginInfo[] = "editorPluginInfo";
static constexpr char networkProxy[] = "networkProxy";
public:
using JsonObject::JsonObject;
SetEditorInfoParams(const EditorInfo &editorInfo, const EditorPluginInfo &editorPluginInfo)
{
setEditorInfo(editorInfo);
setEditorPluginInfo(editorPluginInfo);
}
SetEditorInfoParams(const EditorInfo &editorInfo,
const EditorPluginInfo &editorPluginInfo,
const NetworkProxy &networkProxy)
{
setEditorInfo(editorInfo);
setEditorPluginInfo(editorPluginInfo);
setNetworkProxy(networkProxy);
}
void setEditorInfo(const EditorInfo &info) { insert(editorInfo, info); }
void setEditorPluginInfo(const EditorPluginInfo &info) { insert(editorPluginInfo, info); }
void setNetworkProxy(const NetworkProxy &proxy) { insert(networkProxy, proxy); }
};
class SetEditorInfoRequest
: public LanguageServerProtocol::Request<CheckStatusResponse, std::nullptr_t, SetEditorInfoParams>
{
public:
explicit SetEditorInfoRequest(const SetEditorInfoParams &params)
: Request(methodName, params)
{}
using Request::Request;
constexpr static const char methodName[] = "setEditorInfo";
};
} // namespace Copilot

View File

@@ -0,0 +1,20 @@
ARG PWDMODE=with
FROM ubuntu:20.04 AS base
ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=Etc/UTC
RUN apt-get update && apt-get install -y squid apache2-utils && rm -rf /var/lib/apt/lists/*
COPY run.sh /
RUN chmod +x /run.sh
FROM base as image-with-pwd
RUN echo 1234 | htpasswd -i -c /etc/squid/pswds user
COPY userauth.conf /etc/squid/conf.d/
FROM base as image-without-pwd
COPY noauth.conf /etc/squid/conf.d/
FROM image-${PWDMODE}-pwd AS final
CMD [ "/run.sh" ]

View File

@@ -0,0 +1,4 @@
#!/bin/bash
docker build --build-arg PWDMODE=with -t copilot-proxy-test . && \
docker run --rm -it -p 3128:3128 copilot-proxy-test

View File

@@ -0,0 +1 @@
http_access allow all

View File

@@ -0,0 +1,7 @@
#!/bin/sh
touch /var/log/squid/access.log
chmod 640 /var/log/squid/access.log
chown proxy:proxy /var/log/squid/access.log
tail -f /var/log/squid/access.log &
exec squid --foreground

View File

@@ -0,0 +1,4 @@
auth_param basic program /usr/lib/squid3/basic_ncsa_auth /etc/squid/pswds
auth_param basic realm proxy
acl authenticated proxy_auth REQUIRED
http_access allow authenticated