Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F27224490
webserver.cpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
10 KB
Subscribers
None
webserver.cpp
View Options
// SPDX-FileCopyrightText: 2023 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include
"webserver.h"
#include
<QDebug>
#include
<QFile>
#include
<QJsonDocument>
#include
<QJsonObject>
#include
<QJsonArray>
#include
<QJsonDocument>
#include
<QHttpServer>
#include
<QHttpServerResponse>
#include
<QSslCertificate>
#include
<QSslKey>
#include
<QWebSocketServer>
#include
<QWebSocket>
#include
<QStandardPaths>
#include
"controllers/registrationcontroller.h"
#include
"controllers/staticcontroller.h"
#include
"controllers/emailcontroller.h"
using
namespace
Qt
::
Literals
::
StringLiterals
;
WebServer
WebServer
::
s_instance
=
WebServer
();
WebServer
&
WebServer
::
self
()
{
return
s_instance
;
}
WebServer
::
WebServer
()
:
QObject
(
nullptr
)
,
m_httpServer
(
new
QHttpServer
(
this
))
,
m_webSocketServer
(
new
QWebSocketServer
(
u
"GPGOL"
_s
,
QWebSocketServer
::
SslMode
::
SecureMode
,
this
))
{
}
WebServer
::~
WebServer
()
=
default
;
bool
WebServer
::
run
()
{
auto
keyPath
=
QStandardPaths
::
locate
(
QStandardPaths
::
AppLocalDataLocation
,
QStringLiteral
(
"certificate-key.pem"
));
auto
certPath
=
QStandardPaths
::
locate
(
QStandardPaths
::
AppLocalDataLocation
,
QStringLiteral
(
"certificate.pem"
));
Q_ASSERT
(
!
keyPath
.
isEmpty
());
Q_ASSERT
(
!
certPath
.
isEmpty
());
QFile
privateKeyFile
(
keyPath
);
if
(
!
privateKeyFile
.
open
(
QIODevice
::
ReadOnly
))
{
qWarning
()
<<
u
"Couldn't open file for reading: %1"
_s
.
arg
(
privateKeyFile
.
errorString
());
return
false
;
}
const
QSslKey
sslKey
(
&
privateKeyFile
,
QSsl
::
Rsa
);
privateKeyFile
.
close
();
const
auto
sslCertificateChain
=
QSslCertificate
::
fromPath
(
certPath
);
if
(
sslCertificateChain
.
isEmpty
())
{
qWarning
()
<<
u
"Couldn't retrieve SSL certificate from file."
_s
;
return
false
;
}
// Static assets controller
m_httpServer
->
route
(
u
"/home"
_s
,
&
StaticController
::
homeAction
);
m_httpServer
->
route
(
u
"/assets/"
_s
,
&
StaticController
::
assetsAction
);
// Registration controller
m_httpServer
->
route
(
u
"/register"
_s
,
&
RegistrationController
::
registerAction
);
// Email controller
m_httpServer
->
route
(
u
"/view"
_s
,
&
EmailController
::
viewEmailAction
);
m_httpServer
->
route
(
u
"/info"
_s
,
&
EmailController
::
infoEmailAction
);
m_httpServer
->
route
(
u
"/reply"
_s
,
&
EmailController
::
replyEmailAction
);
m_httpServer
->
route
(
u
"/forward"
_s
,
&
EmailController
::
forwardEmailAction
);
m_httpServer
->
route
(
u
"/new"
_s
,
&
EmailController
::
newEmailAction
);
m_httpServer
->
route
(
u
"/socket-web"
_s
,
&
EmailController
::
socketWebAction
);
m_httpServer
->
route
(
u
"/draft/<arg>"
_s
,
&
EmailController
::
draftAction
);
m_httpServer
->
sslSetup
(
sslCertificateChain
.
front
(),
sslKey
);
const
auto
port
=
m_httpServer
->
listen
(
QHostAddress
::
Any
,
WebServer
::
Port
);
if
(
!
port
)
{
qWarning
()
<<
"Server failed to listen on a port."
;
return
false
;
}
qWarning
()
<<
u
"Running http server on https://127.0.0.1:%1/ (Press CTRL+C to quit)"
_s
.
arg
(
port
);
QSslConfiguration
sslConfiguration
;
sslConfiguration
.
setPeerVerifyMode
(
QSslSocket
::
VerifyNone
);
sslConfiguration
.
setLocalCertificate
(
sslCertificateChain
.
front
());
sslConfiguration
.
setPrivateKey
(
sslKey
);
m_webSocketServer
->
setSslConfiguration
(
sslConfiguration
);
if
(
m_webSocketServer
->
listen
(
QHostAddress
::
Any
,
WebServer
::
WebSocketPort
))
{
qWarning
()
<<
u
"Running websocket server on wss://127.0.0.1:%1/ (Press CTRL+C to quit)"
_s
.
arg
(
WebServer
::
Port
+
1
);
connect
(
m_webSocketServer
,
&
QWebSocketServer
::
newConnection
,
this
,
&
WebServer
::
onNewConnection
);
}
return
true
;
}
void
WebServer
::
onNewConnection
()
{
auto
pSocket
=
m_webSocketServer
->
nextPendingConnection
();
if
(
!
pSocket
)
{
return
;
}
qDebug
()
<<
"Client connected:"
<<
pSocket
->
peerName
()
<<
pSocket
->
origin
();
connect
(
pSocket
,
&
QWebSocket
::
textMessageReceived
,
this
,
&
WebServer
::
processTextMessage
);
connect
(
pSocket
,
&
QWebSocket
::
binaryMessageReceived
,
this
,
&
WebServer
::
processBinaryMessage
);
connect
(
pSocket
,
&
QWebSocket
::
disconnected
,
this
,
&
WebServer
::
socketDisconnected
);
m_clients
<<
pSocket
;
}
void
WebServer
::
processTextMessage
(
QString
message
)
{
auto
webClient
=
qobject_cast
<
QWebSocket
*>
(
sender
());
if
(
webClient
)
{
QJsonParseError
error
;
const
auto
doc
=
QJsonDocument
::
fromJson
(
message
.
toUtf8
(),
&
error
);
if
(
error
.
error
!=
QJsonParseError
::
NoError
)
{
qWarning
()
<<
"Error parsing json"
<<
error
.
errorString
();
return
;
}
if
(
!
doc
.
isObject
())
{
qWarning
()
<<
"Invalid json received"
;
return
;
}
const
auto
object
=
doc
.
object
();
if
(
!
object
.
contains
(
"command"
_L1
)
||
!
object
[
"command"
_L1
].
isString
()
||
!
object
.
contains
(
"arguments"
_L1
)
||
!
object
[
"arguments"
_L1
].
isObject
())
{
qWarning
()
<<
"Invalid json received: no command or arguments set"
;
return
;
}
static
QHash
<
QString
,
WebServer
::
Command
>
commandMapping
{
{
"register"
_L1
,
WebServer
::
Command
::
Register
},
{
"email-sent"
_L1
,
WebServer
::
Command
::
EmailSent
},
};
const
auto
command
=
commandMapping
[
doc
[
"command"
_L1
].
toString
()];
processCommand
(
command
,
object
[
"arguments"
_L1
].
toObject
(),
webClient
);
}
}
void
WebServer
::
processCommand
(
Command
command
,
const
QJsonObject
&
arguments
,
QWebSocket
*
socket
)
{
switch
(
command
)
{
case
Command
::
Register
:
{
const
auto
type
=
arguments
[
"type"
_L1
].
toString
();
qDebug
()
<<
"Register"
<<
arguments
;
if
(
type
.
isEmpty
())
{
qWarning
()
<<
"empty client type given when registering"
;
return
;
}
const
auto
emails
=
arguments
[
"emails"
_L1
].
toArray
();
if
(
type
==
"webclient"
_L1
)
{
if
(
emails
.
isEmpty
())
{
qWarning
()
<<
"empty email given"
;
}
for
(
const
auto
&
email
:
emails
)
{
m_webClientsMappingToEmail
[
email
.
toString
()]
=
socket
;
qWarning
()
<<
"email"
<<
email
.
toString
()
<<
"mapped to a web client"
;
const
auto
nativeClient
=
m_nativeClientsMappingToEmail
[
email
.
toString
()];
if
(
nativeClient
)
{
QJsonDocument
doc
(
QJsonObject
{
{
"type"
_L1
,
"connection"
_L1
},
{
"payload"
_L1
,
QJsonObject
{
{
"client_type"
_L1
,
"web_client"
_L1
}
}}
});
nativeClient
->
sendTextMessage
(
QString
::
fromUtf8
(
doc
.
toJson
()));
}
}
}
else
{
if
(
emails
.
isEmpty
())
{
qWarning
()
<<
"empty email given"
;
}
for
(
const
auto
&
email
:
emails
)
{
m_nativeClientsMappingToEmail
[
email
.
toString
()]
=
socket
;
qWarning
()
<<
"email"
<<
email
.
toString
()
<<
"mapped to a native client"
;
const
auto
webClient
=
m_webClientsMappingToEmail
[
email
.
toString
()];
if
(
webClient
)
{
QJsonDocument
doc
(
QJsonObject
{
{
"type"
_L1
,
"connection"
_L1
},
{
"payload"
_L1
,
QJsonObject
{
{
"client_type"
_L1
,
"native_client"
_L1
}
}}
});
webClient
->
sendTextMessage
(
QString
::
fromUtf8
(
doc
.
toJson
()));
}
}
}
return
;
}
case
Command
::
EmailSent
:
{
const
auto
email
=
arguments
[
"email"
_L1
].
toString
();
const
auto
socket
=
m_nativeClientsMappingToEmail
[
email
];
if
(
!
socket
)
{
return
;
}
QJsonDocument
doc
(
QJsonObject
{
{
"type"
_L1
,
"email-sent"
_L1
},
{
"arguments"
_L1
,
arguments
},
});
socket
->
sendTextMessage
(
QString
::
fromUtf8
(
doc
.
toJson
()));
return
;
}
case
Command
::
Undefined
:
qWarning
()
<<
"Invalid json received: invalid command"
;
return
;
}
}
bool
WebServer
::
sendMessageToWebClient
(
const
QString
&
email
,
const
QByteArray
&
payload
)
{
auto
socket
=
m_webClientsMappingToEmail
[
email
];
if
(
!
socket
)
{
return
false
;
}
socket
->
sendTextMessage
(
QString
::
fromUtf8
(
payload
));
return
true
;
}
void
WebServer
::
processBinaryMessage
(
QByteArray
message
)
{
qWarning
()
<<
"got binary message"
<<
message
;
QWebSocket
*
pClient
=
qobject_cast
<
QWebSocket
*>
(
sender
());
if
(
pClient
)
{
pClient
->
sendBinaryMessage
(
message
);
}
}
void
WebServer
::
socketDisconnected
()
{
QWebSocket
*
pClient
=
qobject_cast
<
QWebSocket
*>
(
sender
());
if
(
pClient
)
{
qDebug
()
<<
"Client disconnected"
<<
pClient
;
// Web client was disconnected
{
const
auto
it
=
std
::
find_if
(
m_webClientsMappingToEmail
.
cbegin
(),
m_webClientsMappingToEmail
.
cend
(),
[
pClient
](
QWebSocket
*
webSocket
)
{
return
pClient
==
webSocket
;
});
if
(
it
!=
m_webClientsMappingToEmail
.
cend
())
{
const
auto
email
=
it
.
key
();
const
auto
nativeClient
=
m_nativeClientsMappingToEmail
[
email
];
qDebug
()
<<
"webclient with email disconnected"
<<
email
<<
nativeClient
;
if
(
nativeClient
)
{
QJsonDocument
doc
(
QJsonObject
{
{
"type"
_L1
,
"disconnection"
_L1
},
});
nativeClient
->
sendTextMessage
(
QString
::
fromUtf8
(
doc
.
toJson
()));
}
m_webClientsMappingToEmail
.
removeIf
([
pClient
](
auto
socket
)
{
return
pClient
==
socket
.
value
();
});
}
}
// Native client was disconnected
const
auto
emails
=
m_nativeClientsMappingToEmail
.
keys
();
for
(
const
auto
&
email
:
emails
)
{
const
auto
webSocket
=
m_nativeClientsMappingToEmail
[
email
];
if
(
webSocket
!=
pClient
)
{
qDebug
()
<<
"webSocket not equal"
<<
email
<<
webSocket
<<
pClient
;
continue
;
}
qDebug
()
<<
"native client for"
<<
email
<<
"was disconnected."
;
QJsonDocument
doc
(
QJsonObject
{
{
"type"
_L1
,
"disconnection"
_L1
},
});
sendMessageToWebClient
(
email
,
doc
.
toJson
());
}
m_nativeClientsMappingToEmail
.
removeIf
([
pClient
](
auto
socket
)
{
return
pClient
==
socket
.
value
();
});
m_clients
.
removeAll
(
pClient
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Mon, Aug 25, 11:02 PM (1 d, 6 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
83/d6/600d2b8533bb9268498b4b84ae3b
Attached To
rOJ GpgOL.js
Event Timeline
Log In to Comment