Compare commits
No commits in common. "master" and "v0.0.3" have entirely different histories.
File diff suppressed because it is too large
Load Diff
40
Cargo.toml
40
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "aira"
|
||||
version = "0.1.1"
|
||||
version = "0.0.3"
|
||||
authors = ["Hardcore Sushi <hardcore.sushi@disroot.org>"]
|
||||
edition = "2018"
|
||||
exclude = ["src/frontend"]
|
||||
|
@ -9,41 +9,37 @@ exclude = ["src/frontend"]
|
|||
rand = "0.8"
|
||||
rand-7 = { package = "rand", version = "0.7.3" }
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "net", "io-util"] }
|
||||
async-psec = { version = "0.4", features = ["split"] }
|
||||
async-psec = { version = "0.3", features = ["split"] }
|
||||
lazy_static = "1.4"
|
||||
socket2 = "0.4"
|
||||
rusqlite = { version = "0.27", features = ["bundled"] }
|
||||
ed25519-dalek = "1" #for singatures
|
||||
sha2 = "0.10"
|
||||
rusqlite = { version = "0.25.1", features = ["bundled"] }
|
||||
ed25519-dalek = "1" #for singing
|
||||
sha2 = "0.9"
|
||||
aes-gcm = "0.9"
|
||||
aes-gcm-siv = "0.10" #database encryption
|
||||
hkdf = "0.12"
|
||||
aes-gcm-siv = "0.10" #Database
|
||||
hkdf = "0.11"
|
||||
hex = "0.4"
|
||||
actix-web = "4"
|
||||
env_logger = "0.9"
|
||||
actix-multipart = "0.4"
|
||||
time = "0.3" #needed for actix cookies
|
||||
actix-web = "3"
|
||||
actix-multipart = "0.3"
|
||||
time = "0.2" #needed for actix cookies
|
||||
futures = "0.3"
|
||||
tungstenite = "0.17" #websocket
|
||||
serde = { version = "1.0", features = ["derive"] } #serialization
|
||||
tungstenite = "0.13" #websocket
|
||||
serde = "1.0" #serialization
|
||||
html-escape = "0.2"
|
||||
sanitize-filename = "0.3"
|
||||
platform-dirs = "0.3"
|
||||
uuid = { version = "1.0", features = ["v4"] }
|
||||
webbrowser = "0.7"
|
||||
uuid = { version = "0.8", features = ["v4"] }
|
||||
webbrowser = "0.5"
|
||||
libmdns = "0.6" #mDNS advertiser
|
||||
multicast_dns = "0.5" #mDNS browser
|
||||
if-addrs = "0.7"
|
||||
if-addrs = "0.6"
|
||||
base64 = "0.13"
|
||||
scrypt = "0.10"
|
||||
zeroize = "1.5"
|
||||
image = "0.24"
|
||||
scrypt = "0.7"
|
||||
zeroize = "1.2"
|
||||
image = "0.23"
|
||||
yaml-rust = "0.4" #only in debug mode
|
||||
|
||||
[build-dependencies]
|
||||
html-minifier = "3.0"
|
||||
yaml-rust = "0.4"
|
||||
linked-hash-map = "0.5"
|
||||
|
||||
[profile.dev.package.scrypt]
|
||||
opt-level = 3
|
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 60 KiB |
|
@ -4,5 +4,4 @@ pub const APPLICATION_FOLDER: &str = "AIRA";
|
|||
pub const DB_NAME: &str = "AIRA.db";
|
||||
pub const HTTP_COOKIE_NAME: &str = "aira_auth";
|
||||
pub const MSG_LOADING_COUNT: usize = 20;
|
||||
pub const FILE_CHUNK_SIZE: usize = 1_023_996;
|
||||
pub const MAX_RECV_SIZE: usize = 16_383_996;
|
||||
pub const FILE_CHUNK_SIZE: usize = 1023996;
|
|
@ -34,8 +34,8 @@ label {
|
|||
|
||||
.avatar {
|
||||
margin-right: .5em;
|
||||
width: 2.5em;
|
||||
height: 2.5em;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
<svg width="191.7mm" height="168.39mm" version="1.1" viewBox="0 0 191.7 168.39" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(-9.1475 -20.806)">
|
||||
<path d="m81.908 189.19c14.507-12.545 13.641-67.907 13.641-67.907l18.56 2e-5s-0.86524 55.362 13.641 67.907z" fill="#803300"/>
|
||||
<g fill="#19f52c">
|
||||
<g fill-rule="evenodd">
|
||||
<circle cx="19.053" cy="67.31" r="8.6808"/>
|
||||
<circle cx="40.006" cy="88.061" r="8.6808"/>
|
||||
<circle cx="79.997" cy="99.739" r="8.6808"/>
|
||||
<circle cx="68.203" cy="133.44" r="8.6808"/>
|
||||
<circle cx="105.86" cy="74.321" r="8.6808"/>
|
||||
<circle cx="38.754" cy="131.9" r="8.6808"/>
|
||||
<circle cx="127.11" cy="124.1" r="8.6808"/>
|
||||
<circle cx="97.819" cy="119.49" r="8.6808"/>
|
||||
<circle cx="19.67" cy="109.42" r="8.6808"/>
|
||||
<circle cx="48.138" cy="59.715" r="8.6808"/>
|
||||
<circle cx="95.703" cy="44.581" r="8.6808"/>
|
||||
<circle cx="77.134" cy="67.671" r="8.6808"/>
|
||||
<circle cx="121.04" cy="29.486" r="8.6808"/>
|
||||
<circle cx="119.73" cy="98.423" r="8.6808"/>
|
||||
<circle cx="51.945" cy="108.83" r="8.6808"/>
|
||||
</g>
|
||||
<g stroke="#19f52c" stroke-width="1.8939">
|
||||
<path d="m38.754 131.9-19.084-22.481"/>
|
||||
<path d="m19.67 109.42 20.336-21.356"/>
|
||||
<path d="m19.053 67.31 20.953 20.75"/>
|
||||
<path d="m51.945 108.83 16.258 24.602"/>
|
||||
<path d="m51.945 108.83 28.052-9.0944"/>
|
||||
<path d="m48.138 59.715-8.132 28.346"/>
|
||||
<path d="m48.138 59.715 28.996 7.956"/>
|
||||
<path d="m77.134 67.671 28.73 6.6502"/>
|
||||
<path d="m77.134 67.671 18.569-23.089"/>
|
||||
<path d="m95.703 44.581 25.333-15.095"/>
|
||||
<path d="m105.86 74.321 13.863 24.103"/>
|
||||
<path d="m73.582 133.41 24.237-13.916"/>
|
||||
<path d="m97.819 119.49 21.907-21.071"/>
|
||||
<path d="m97.819 119.49 29.289 4.6056"/>
|
||||
<path d="m17.828 153.33 20.926-21.434"/>
|
||||
</g>
|
||||
<circle cx="17.828" cy="153.33" r="8.6808" fill-rule="evenodd"/>
|
||||
<circle cx="53.865" cy="159.2" r="8.6808" fill-rule="evenodd"/>
|
||||
<path d="m38.754 131.9 15.111 27.303" stroke="#19f52c" stroke-width="1.8939"/>
|
||||
<path d="m127.11 124.1 13.863 24.103" stroke="#19f52c" stroke-width="1.8939"/>
|
||||
<circle cx="140.97" cy="148.2" r="8.6808" fill-rule="evenodd"/>
|
||||
<path d="m140.97 148.2 20.559-17.279" stroke="#19f52c" stroke-width="1.8939"/>
|
||||
<g fill-rule="evenodd">
|
||||
<circle cx="161.53" cy="130.92" r="8.6808"/>
|
||||
<circle cx="148.77" cy="105.42" r="8.6808"/>
|
||||
<circle cx="161.93" cy="80.961" r="8.6808"/>
|
||||
<circle cx="134.44" cy="73.351" r="8.6808"/>
|
||||
<circle cx="172.46" cy="44.584" r="8.6808"/>
|
||||
<circle cx="192.17" cy="132.75" r="8.6808"/>
|
||||
<circle cx="175.39" cy="155.03" r="8.6808"/>
|
||||
<circle cx="68.229" cy="33.867" r="8.6808"/>
|
||||
<circle cx="143.95" cy="45.376" r="8.6808"/>
|
||||
</g>
|
||||
<g stroke="#19f52c" stroke-width="1.8939">
|
||||
<path d="m161.53 130.92 13.863 24.103"/>
|
||||
<path d="m175.39 155.03 16.778-22.271"/>
|
||||
<path d="m119.73 98.423 29.041 6.9934"/>
|
||||
<path d="m148.77 105.42 12.763 25.506"/>
|
||||
<path d="m105.86 74.321 28.58-0.96983"/>
|
||||
<path d="m148.77 105.42 29.455-1.4191"/>
|
||||
<path d="m143.95 45.376 28.511-0.79158"/>
|
||||
<path d="m121.04 29.486 22.914 15.89"/>
|
||||
<path d="m38.754 131.9 29.449 1.5377"/>
|
||||
<path d="m77.134 67.671 2.8632 32.068"/>
|
||||
<path d="m68.229 33.867 27.474 10.715"/>
|
||||
<path d="m48.138 59.715 20.091-25.848"/>
|
||||
<path d="m143.95 45.376-9.5064 27.975"/>
|
||||
</g>
|
||||
<circle cx="178.22" cy="104" r="8.6808" fill-rule="evenodd"/>
|
||||
<path d="m172.46 44.584 16.663 26.606" stroke="#19f52c" stroke-width="1.8939"/>
|
||||
<path d="m134.44 73.351 27.488 7.6103" stroke="#19f52c" stroke-width="1.8939"/>
|
||||
<circle cx="189.12" cy="71.19" r="8.6808" fill-rule="evenodd"/>
|
||||
<path d="m189.12 71.19-27.193 9.7712" stroke="#19f52c" stroke-width="1.8939"/>
|
||||
<path d="m161.93 80.961 16.292 23.036" stroke="#19f52c" stroke-width="1.8939"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.8 KiB |
|
@ -120,7 +120,7 @@ button:hover::after {
|
|||
position: relative;
|
||||
}
|
||||
#avatarContainer .avatar {
|
||||
font-size: 2.5em;
|
||||
font-size: 4em;
|
||||
}
|
||||
#removeAvatar {
|
||||
position: absolute;
|
||||
|
@ -149,7 +149,8 @@ button:hover::after {
|
|||
font-size: 0.9em;
|
||||
}
|
||||
#session_info .avatar {
|
||||
font-size: 2.5em;
|
||||
width: 6em;
|
||||
height: 6em;
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
|
@ -197,6 +198,7 @@ button:hover::after {
|
|||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.7em;
|
||||
cursor: pointer;
|
||||
}
|
||||
#me>div {
|
||||
|
@ -205,7 +207,6 @@ button:hover::after {
|
|||
flex-grow: 1;
|
||||
}
|
||||
#me p {
|
||||
font-size: 1.7em;
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
display: inline;
|
||||
|
@ -213,9 +214,6 @@ button:hover::after {
|
|||
#me>div:hover p {
|
||||
color: var(--accent);
|
||||
}
|
||||
#me .avatar {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
#left_panel ul:last-of-type, #msg_log {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
@ -237,7 +235,8 @@ button:hover::after {
|
|||
flex-grow: 1;
|
||||
}
|
||||
#left_panel ul li .avatar {
|
||||
font-size: .9em;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
}
|
||||
#left_panel ul li:hover, #left_panel ul li.current {
|
||||
background-color: #333940;
|
||||
|
@ -255,8 +254,7 @@ button:hover::after {
|
|||
#left_panel ul li.is_verified p::after {
|
||||
content: url("/static/imgs/icons/verified/ACCENT_COLOR");
|
||||
}
|
||||
#left_panel ul li.not_seen::after {
|
||||
content: "";
|
||||
#left_panel ul li .not_seen_marker {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: var(--accent);
|
||||
|
@ -297,6 +295,7 @@ button:hover::after {
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 20px 20px;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
#chat_header>div {
|
||||
display: flex;
|
||||
|
@ -308,7 +307,6 @@ button:hover::after {
|
|||
color: var(--accent);
|
||||
}
|
||||
#chat_header>div>p { /*name*/
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
@ -356,6 +354,7 @@ button:hover::after {
|
|||
}
|
||||
#file_transfer {
|
||||
border-top: 2px solid var(--accent);
|
||||
display: none;
|
||||
position: relative;
|
||||
}
|
||||
#file_transfer.active {
|
||||
|
@ -402,46 +401,31 @@ button:hover::after {
|
|||
height: 100%;
|
||||
background-color: var(--accent);
|
||||
}
|
||||
#message_box {
|
||||
border-top: 2px solid var(--accent);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
#msg_log {
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
#msg_log li {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
#msg_log li>div {
|
||||
flex-grow: 1;
|
||||
}
|
||||
#msg_log li .timestamp {
|
||||
opacity: .5;
|
||||
font-family: "Liberation Sans", Arial, sans-serif;
|
||||
font-size: .8em;
|
||||
font-size: 1.1em;
|
||||
overflow-y: scroll;
|
||||
white-space: pre;
|
||||
}
|
||||
#msg_log p {
|
||||
font-size: 1.1em;
|
||||
margin: 0;
|
||||
}
|
||||
#msg_log .avatar {
|
||||
font-size: .8em;
|
||||
}
|
||||
#msg_log li .header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 15px;
|
||||
}
|
||||
#msg_log li .header p {
|
||||
color: var(--accent);
|
||||
font-weight: bold;
|
||||
margin-left: .5em;
|
||||
}
|
||||
#msg_log li .content {
|
||||
margin-left: 3em;
|
||||
}
|
||||
#msg_log li .content p {
|
||||
word-break: break-word;
|
||||
margin-left: 2em;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#msg_log a {
|
||||
color: #238cf5;
|
||||
|
@ -468,149 +452,6 @@ button:hover::after {
|
|||
width: 2em;
|
||||
margin-left: 15px;
|
||||
}
|
||||
#message_box, #message_box.online #offline_warning, #chat_header, #msg_log, #file_transfer {
|
||||
#message_box, #chat_header, #msg_log {
|
||||
display: none;
|
||||
}
|
||||
#message_box.active {
|
||||
display: block;
|
||||
}
|
||||
#message_box {
|
||||
border-top: 2px solid red;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
#message_box>div:nth-child(2) {
|
||||
display: flex;
|
||||
}
|
||||
#message_box.online {
|
||||
border-top-color: var(--accent);
|
||||
}
|
||||
#offline_warning {
|
||||
margin-left: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 25px;
|
||||
}
|
||||
#offline_warning::before {
|
||||
content: url("/static/imgs/icons/warning/ff0000");
|
||||
display: block;
|
||||
width: 2em;
|
||||
}
|
||||
#offline_warning h3 {
|
||||
color: red;
|
||||
display: inline-block;
|
||||
margin-bottom: .3em;
|
||||
}
|
||||
#offline_warning p {
|
||||
margin-top: 0;
|
||||
}
|
||||
#msg_log li.pending_msgs_divider {
|
||||
border-top: 1px solid grey;
|
||||
padding-top: 10px;
|
||||
margin-top: 30px;
|
||||
margin-left: 100px;
|
||||
margin-right: 100px;
|
||||
}
|
||||
#msg_log li.pending_msgs_divider h4 {
|
||||
margin: auto;
|
||||
opacity: .5;
|
||||
}
|
||||
.lds-spinner {
|
||||
color: official;
|
||||
position: relative;
|
||||
width: 82px;
|
||||
height: 82px;
|
||||
}
|
||||
.lds-spinner div {
|
||||
transform-origin: 40px 40px;
|
||||
animation: lds-spinner 1.2s linear infinite;
|
||||
}
|
||||
.lds-spinner div:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 37px;
|
||||
width: 6px;
|
||||
height: 18px;
|
||||
border-radius: 20%;
|
||||
background: #fff;
|
||||
}
|
||||
.lds-spinner div:nth-child(1) {
|
||||
transform: rotate(0deg);
|
||||
animation-delay: -1.1s;
|
||||
}
|
||||
.lds-spinner div:nth-child(2) {
|
||||
transform: rotate(30deg);
|
||||
animation-delay: -1s;
|
||||
}
|
||||
.lds-spinner div:nth-child(3) {
|
||||
transform: rotate(60deg);
|
||||
animation-delay: -0.9s;
|
||||
}
|
||||
.lds-spinner div:nth-child(4) {
|
||||
transform: rotate(90deg);
|
||||
animation-delay: -0.8s;
|
||||
}
|
||||
.lds-spinner div:nth-child(5) {
|
||||
transform: rotate(120deg);
|
||||
animation-delay: -0.7s;
|
||||
}
|
||||
.lds-spinner div:nth-child(6) {
|
||||
transform: rotate(150deg);
|
||||
animation-delay: -0.6s;
|
||||
}
|
||||
.lds-spinner div:nth-child(7) {
|
||||
transform: rotate(180deg);
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
.lds-spinner div:nth-child(8) {
|
||||
transform: rotate(210deg);
|
||||
animation-delay: -0.4s;
|
||||
}
|
||||
.lds-spinner div:nth-child(9) {
|
||||
transform: rotate(240deg);
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
.lds-spinner div:nth-child(10) {
|
||||
transform: rotate(270deg);
|
||||
animation-delay: -0.2s;
|
||||
}
|
||||
.lds-spinner div:nth-child(11) {
|
||||
transform: rotate(300deg);
|
||||
animation-delay: -0.1s;
|
||||
}
|
||||
.lds-spinner div:nth-child(12) {
|
||||
transform: rotate(330deg);
|
||||
animation-delay: 0s;
|
||||
}
|
||||
@keyframes lds-spinner {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
#pending_msgs_indicator {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
#pending_msgs_indicator.sending {
|
||||
display: flex;
|
||||
}
|
||||
#disconnected {
|
||||
display: none;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
#disconnected.disconnected {
|
||||
display: flex;
|
||||
}
|
||||
#disconnected img {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
}
|
|
@ -3,7 +3,6 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>AIRA</title>
|
||||
<link rel="icon" type="image/svg" href="/static/imgs/icons/logo">
|
||||
<link rel="stylesheet" href="/static/commons/style.css">
|
||||
<link rel="stylesheet" href="/static/index.css">
|
||||
</head>
|
||||
|
@ -42,10 +41,6 @@
|
|||
</div>
|
||||
<ul id="msg_log">
|
||||
</ul>
|
||||
<div id="pending_msgs_indicator">
|
||||
<div class="lds-spinner"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div>
|
||||
<h3>Sending pending messages...</h3>
|
||||
</div>
|
||||
<div id="file_transfer">
|
||||
<div id="file_control">
|
||||
<button id="file_cancel" title="Cancel"></button>
|
||||
|
@ -61,22 +56,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="message_box">
|
||||
<div id="offline_warning">
|
||||
<div>
|
||||
<h3>Your contact seems to be offline.</h3>
|
||||
<p>Sent messages will be stored until a connection is established.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" id="message_input" placeholder="Send a message...">
|
||||
<label title="Send file" class="file_picker">
|
||||
<input type="file" id="attach_file" multiple>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="disconnected">
|
||||
<img src="/static/imgs/icons/warning/ff0000">
|
||||
<h1>Websocket connection closed</h1>
|
||||
<input type="text" id="message_input" placeholder="Send a message...">
|
||||
<label title="Send file" class="file_picker">
|
||||
<input type="file" id="attach_file" multiple>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
@ -93,3 +76,4 @@
|
|||
<script src="/static/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
"use strict";
|
||||
|
||||
let identityName = undefined;
|
||||
let socket = new WebSocket("ws://"+location.hostname+":"+websocketPort+"/ws");;
|
||||
let socket = null;
|
||||
let notificationAllowed = false;
|
||||
let localIps = [];
|
||||
let currentSessionId = -1;
|
||||
let sessionsData = new Map();
|
||||
let msgHistory = new Map();
|
||||
let pendingMsgs = new Map();
|
||||
let pendingFilesTransfers = new Map();
|
||||
let avatarTimestamps = new Map([
|
||||
["self", Date.now()]
|
||||
|
@ -15,7 +14,7 @@ let avatarTimestamps = new Map([
|
|||
|
||||
function onClickSession(event) {
|
||||
let sessionId = event.currentTarget.getAttribute("data-sessionId");
|
||||
if (sessionId != null && socket.readyState === WebSocket.OPEN) {
|
||||
if (sessionId != null) {
|
||||
currentSessionId = sessionId;
|
||||
let session = sessionsData.get(sessionId);
|
||||
if (!session.seen) {
|
||||
|
@ -81,7 +80,6 @@ document.getElementById("delete_conversation").onclick = function() {
|
|||
document.getElementById("add_contact").onclick = function() {
|
||||
socket.send("contact "+currentSessionId);
|
||||
sessionsData.get(currentSessionId).isContact = true;
|
||||
pendingMsgs.set(currentSessionId, []);
|
||||
displayHeader();
|
||||
displaySessions();
|
||||
};
|
||||
|
@ -105,9 +103,7 @@ document.getElementById("remove_contact").onclick = function() {
|
|||
if (!session.isOnline) {
|
||||
sessionsData.delete(currentSessionId);
|
||||
msgHistory.get(currentSessionId).length = 0;
|
||||
displayChatBottom();
|
||||
}
|
||||
pendingMsgs.delete(currentSessionId);
|
||||
displayHeader();
|
||||
displaySessions();
|
||||
displayHistory();
|
||||
|
@ -178,7 +174,7 @@ document.getElementById("attach_file").onchange = function(event) {
|
|||
let files = event.target.files;
|
||||
let useLargeFileTransfer = false;
|
||||
for (let i=0; i<files.length; ++i) {
|
||||
if (files[i].size > 16380000) {
|
||||
if (files[i].size > 32760000) {
|
||||
useLargeFileTransfer = true;
|
||||
break;
|
||||
}
|
||||
|
@ -219,11 +215,7 @@ document.getElementById("attach_file").onchange = function(event) {
|
|||
formData.append("", files[i]);
|
||||
fetch("/send_file", {method: "POST", body: formData}).then(response => {
|
||||
if (response.ok) {
|
||||
response.text().then(text => {
|
||||
if (text === "pending") {
|
||||
newPendingMsg(currentSessionId, true, files[i].name);
|
||||
}
|
||||
});
|
||||
response.text().then(uuid => onFileSent(currentSessionId, uuid, files[i].name));
|
||||
} else {
|
||||
console.log(response);
|
||||
}
|
||||
|
@ -234,19 +226,16 @@ document.getElementById("attach_file").onchange = function(event) {
|
|||
document.getElementById("file_cancel").onclick = function() {
|
||||
socket.send("abort "+currentSessionId);
|
||||
};
|
||||
let msgLog = document.getElementById("msg_log");
|
||||
msgLog.onscroll = function() {
|
||||
let session = sessionsData.get(currentSessionId);
|
||||
if (typeof session !== "undefined") {
|
||||
if (session.isContact) {
|
||||
if (msgLog.scrollTop < 30) {
|
||||
socket.send("load_msgs "+currentSessionId);
|
||||
}
|
||||
let msg_log = document.getElementById("msg_log");
|
||||
msg_log.onscroll = function() {
|
||||
if (sessionsData.get(currentSessionId).isContact) {
|
||||
if (msg_log.scrollTop < 30) {
|
||||
socket.send("load_msgs "+currentSessionId);
|
||||
}
|
||||
}
|
||||
};
|
||||
let profileDiv = document.querySelector("#me>div");
|
||||
profileDiv.onclick = function() {
|
||||
let profile_div = document.querySelector("#me>div");
|
||||
profile_div.onclick = function() {
|
||||
let mainDiv = document.createElement("div");
|
||||
mainDiv.id = "profile_info";
|
||||
let avatarContainer = document.createElement("div");
|
||||
|
@ -335,35 +324,32 @@ profileDiv.onclick = function() {
|
|||
changePasswordButton.textContent = "Change password";
|
||||
changePasswordButton.onclick = function() {
|
||||
let inputs = document.querySelectorAll("input[type=\"password\"]");
|
||||
let newPassword, newPasswordConfirm, oldPassword;
|
||||
let newPassword, newPasswordConfirm;
|
||||
if (isIdentityProtected) {
|
||||
oldPassword = inputs[0].value;
|
||||
newPassword = inputs[1].value;
|
||||
newPasswordConfirm = inputs[2].value;
|
||||
newPassword = inputs[1];
|
||||
newPasswordConfirm = inputs[2];
|
||||
} else {
|
||||
newPassword = inputs[0].value;
|
||||
newPasswordConfirm = inputs[1].value;
|
||||
newPassword = inputs[0];
|
||||
newPasswordConfirm = inputs[1];
|
||||
}
|
||||
if (newPassword == newPasswordConfirm) {
|
||||
let newPasswordSet = newPassword.length > 0;
|
||||
if (isIdentityProtected && oldPassword.length == 0) {
|
||||
errorMsg.textContent = "Current password cannot be empty.";
|
||||
} else if (isIdentityProtected || newPasswordSet) { //don't change password if identity is not protected and new password is blank
|
||||
if (newPassword.value == newPasswordConfirm.value) {
|
||||
let newPassword_set = newPassword.value.length > 0;
|
||||
if (isIdentityProtected || newPassword_set) { //don't change password if identity is not protected and new password is blank
|
||||
let msg = "change_password";
|
||||
if (isIdentityProtected) {
|
||||
msg += " "+b64EncodeUnicode(inputs[0].value);
|
||||
}
|
||||
if (newPasswordSet) {
|
||||
msg += " "+b64EncodeUnicode(newPassword);
|
||||
if (newPassword_set) {
|
||||
msg += " "+b64EncodeUnicode(newPassword.value);
|
||||
}
|
||||
socket.send(msg);
|
||||
} else {
|
||||
removePopup();
|
||||
}
|
||||
} else {
|
||||
newPassword = "";
|
||||
newPasswordConfirm = "";
|
||||
errorMsg.textContent = "Passwords don't match.";
|
||||
newPassword.value = "";
|
||||
newPasswordConfirm.value = "";
|
||||
errorMsg.textContent = "Passwords don't match";
|
||||
}
|
||||
};
|
||||
sectionPassword.appendChild(changePasswordButton);
|
||||
|
@ -450,17 +436,8 @@ function getCookie(cname) {
|
|||
}
|
||||
return "";
|
||||
}
|
||||
function parseTimestamp(timestamp) {
|
||||
return new Date(Number(timestamp) * 1000);
|
||||
}
|
||||
function toTwoNumbers(n) {
|
||||
if (n < 10) {
|
||||
return '0'+n;
|
||||
} else {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
socket = new WebSocket("ws://"+location.hostname+":"+websocketPort+"/ws");
|
||||
socket.onopen = function() {
|
||||
console.log("Connected");
|
||||
socket.send(getCookie("aira_auth")); //authenticating websocket connection
|
||||
|
@ -491,10 +468,10 @@ socket.onmessage = function(msg) {
|
|||
onNewSession(args[1], args[2] === "true", args[3], args[4], msg.data.slice(args[0].length+args[1].length+args[2].length+args[3].length+args[4].length+5));
|
||||
break;
|
||||
case "new_message":
|
||||
onNewMessage(args[1], args[2] === "true", parseTimestamp(args[3]), msg.data.slice(args[0].length+args[1].length+args[2].length+args[3].length+4));
|
||||
onNewMessage(args[1], args[2] === "true", msg.data.slice(args[0].length+args[1].length+args[2].length+3));
|
||||
break;
|
||||
case "file":
|
||||
onNewFile(args[1], args[2] === "true", parseTimestamp(args[3]), args[4], msg.data.slice(args[0].length+args[1].length+args[2].length+args[3].length+args[4].length+5));
|
||||
onFileReceived(args[1], args[2], msg.data.slice(args[0].length+args[1].length+args[2].length+3));
|
||||
break;
|
||||
case "files_transfer":
|
||||
onNewFilesTransfer(args[1], args[2], msg.data.slice(args[0].length+args[1].length+args[2].length+3));
|
||||
|
@ -523,15 +500,6 @@ socket.onmessage = function(msg) {
|
|||
case "not_seen":
|
||||
setNotSeen(msg.data.slice(args[0].length+1));
|
||||
break;
|
||||
case "pending":
|
||||
newPendingMsg(args[1], args[2] === "true", msg.data.slice(args[0].length+args[1].length+args[2].length+3));
|
||||
break;
|
||||
case "sending_pending_msgs":
|
||||
onSendingPendingMsgs(args[1]);
|
||||
break;
|
||||
case "pending_msgs_sent":
|
||||
onPendingMsgsSent(args[1]);
|
||||
break;
|
||||
case "local_ips":
|
||||
setLocalIps(msg.data.slice(args[0].length+1));
|
||||
break;
|
||||
|
@ -551,12 +519,6 @@ socket.onmessage = function(msg) {
|
|||
};
|
||||
socket.onclose = function() {
|
||||
console.log("Disconnected");
|
||||
currentSessionId = -1;
|
||||
displayHistory();
|
||||
displayHeader();
|
||||
displayChatBottom();
|
||||
displaySessions();
|
||||
document.getElementById("disconnected").classList.add("disconnected");
|
||||
};
|
||||
|
||||
function onNewSession(sessionId, outgoing, fingerprint, ip, name) {
|
||||
|
@ -602,22 +564,6 @@ function setNotSeen(strSessionIds) {
|
|||
}
|
||||
displaySessions();
|
||||
}
|
||||
function newPendingMsg(sessionId, isFile, data) {
|
||||
pendingMsgs.get(sessionId).push([isFile, data]);
|
||||
if (sessionId == currentSessionId) {
|
||||
displayHistory();
|
||||
}
|
||||
}
|
||||
function onSendingPendingMsgs(sessionId) {
|
||||
document.getElementById("pending_msgs_indicator").classList.add("sending");
|
||||
pendingMsgs.get(sessionId).length = 0;
|
||||
if (sessionId == currentSessionId) {
|
||||
displayHistory();
|
||||
}
|
||||
}
|
||||
function onPendingMsgsSent(sessionId) {
|
||||
document.getElementById("pending_msgs_indicator").classList.remove("sending");
|
||||
}
|
||||
function setLocalIps(strIPs) {
|
||||
localIps = strIPs.split(' ');
|
||||
}
|
||||
|
@ -630,7 +576,6 @@ function onIsContact(sessionId, verified, fingerprint, name) {
|
|||
} else {
|
||||
addSession(sessionId, name, undefined, fingerprint, undefined, true, verified, false);
|
||||
}
|
||||
pendingMsgs.set(sessionId, []);
|
||||
}
|
||||
function onMsgOrFileReceived(sessionId, outgoing, body) {
|
||||
if (currentSessionId == sessionId) {
|
||||
|
@ -644,22 +589,16 @@ function onMsgOrFileReceived(sessionId, outgoing, body) {
|
|||
}
|
||||
if (document.hidden && !outgoing) {
|
||||
if (notificationAllowed) {
|
||||
let sessionName = sessionsData.get(sessionId).name;
|
||||
new Notification(sessionName, {
|
||||
"body": body,
|
||||
"icon": "/avatar/"+sessionId+"/"+sessionName+"?"+avatarTimestamps.get(sessionId)
|
||||
new Notification(sessionsData.get(sessionId).name, {
|
||||
"body": body
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
function onNewMessage(sessionId, outgoing, timestamp, msg) {
|
||||
msgHistory.get(sessionId).push([outgoing, timestamp, false, msg]);
|
||||
function onNewMessage(sessionId, outgoing, msg) {
|
||||
msgHistory.get(sessionId).push([outgoing, false, msg]);
|
||||
onMsgOrFileReceived(sessionId, outgoing, msg);
|
||||
}
|
||||
function onNewFile(sessionId, outgoing, timestamp, uuid, filename) {
|
||||
msgHistory.get(sessionId).push([outgoing, timestamp, true, [uuid, filename]]);
|
||||
onMsgOrFileReceived(sessionId, outgoing, filename);
|
||||
}
|
||||
function onNewFilesTransfer(sessionId, index, filesInfo) {
|
||||
let split = filesInfo.split(' ');
|
||||
let files = [];
|
||||
|
@ -744,8 +683,7 @@ function onAskLargeFiles(sessionId, encodedDownloadLocation, filesInfo) {
|
|||
showPopup(mainDiv, false);
|
||||
if (document.hidden && notificationAllowed) {
|
||||
new Notification(sessionName, {
|
||||
"body": "Files download request",
|
||||
"icon": "/avatar/"+sessionId+"/"+sessionName+"?"+avatarTimestamps.get(sessionId)
|
||||
"body": "Files download request"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -788,32 +726,29 @@ function onIncFilesTransfer(sessionId, chunkSize) {
|
|||
}
|
||||
function onMsgsLoad(sessionId, strMsgs) {
|
||||
let msgs = strMsgs.split(' ');
|
||||
if (msgs.length > 3) {
|
||||
let n = 0;
|
||||
while (n < msgs.length) {
|
||||
let outgoing = msgs[n+1] === "true";
|
||||
let timestamp = parseTimestamp(msgs[n+2]);
|
||||
switch (msgs[n]) {
|
||||
case 'm':
|
||||
let msg = b64DecodeUnicode(msgs[n+3]);
|
||||
msgHistory.get(sessionId).unshift([outgoing, timestamp, false, msg]);
|
||||
n += 4;
|
||||
break;
|
||||
case 'f':
|
||||
let uuid = msgs[n+3];
|
||||
let fileName = b64DecodeUnicode(msgs[n+4]);
|
||||
msgHistory.get(sessionId).unshift([outgoing, timestamp, true, [uuid, fileName]]);
|
||||
n += 5;
|
||||
}
|
||||
let n = 0;
|
||||
while (n < msgs.length) {
|
||||
let outgoing = msgs[n+1] === "true";
|
||||
switch (msgs[n]) {
|
||||
case 'm':
|
||||
let msg = b64DecodeUnicode(msgs[n+2]);
|
||||
msgHistory.get(sessionId).unshift([outgoing, false, msg]);
|
||||
n += 3;
|
||||
break;
|
||||
case 'f':
|
||||
let uuid = msgs[n+2];
|
||||
let fileName = b64DecodeUnicode(msgs[n+3]);
|
||||
msgHistory.get(sessionId).unshift([outgoing, true, [uuid, fileName]]);
|
||||
n += 4;
|
||||
}
|
||||
if (currentSessionId == sessionId) {
|
||||
if (msgLog.scrollHeight - msgLog.scrollTop === msgLog.clientHeight) {
|
||||
displayHistory();
|
||||
} else {
|
||||
let backupHeight = msgLog.scrollHeight;
|
||||
displayHistory(false);
|
||||
msgLog.scrollTop = msgLog.scrollHeight-backupHeight;
|
||||
}
|
||||
}
|
||||
if (currentSessionId == sessionId) {
|
||||
if (msg_log.scrollHeight - msg_log.scrollTop === msg_log.clientHeight) {
|
||||
displayHistory();
|
||||
} else {
|
||||
let backupHeight = msg_log.scrollHeight;
|
||||
displayHistory(false);
|
||||
msg_log.scrollTop = msg_log.scrollHeight-backupHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -827,7 +762,6 @@ function onDisconnected(sessionId) {
|
|||
}
|
||||
if (currentSessionId == sessionId) {
|
||||
displayChatBottom();
|
||||
scrollHistoryToBottom();
|
||||
}
|
||||
if (currentSessionId == sessionId && !session.isContact) {
|
||||
currentSessionId = -1;
|
||||
|
@ -835,6 +769,16 @@ function onDisconnected(sessionId) {
|
|||
}
|
||||
displaySessions();
|
||||
}
|
||||
function onFileReceived(sessionId, uuid, file_name) {
|
||||
msgHistory.get(sessionId).push([false, true, [uuid, file_name]]);
|
||||
onMsgOrFileReceived(sessionId, false, file_name);
|
||||
}
|
||||
function onFileSent(sessionId, uuid, file_name) {
|
||||
msgHistory.get(sessionId).push([true, true, [uuid, file_name]]);
|
||||
if (currentSessionId == sessionId) {
|
||||
displayHistory();
|
||||
}
|
||||
}
|
||||
function onNameSet(newName) {
|
||||
removePopup();
|
||||
identityName = newName;
|
||||
|
@ -981,11 +925,11 @@ function logout() {
|
|||
window.location = "/logout";
|
||||
}
|
||||
function displayProfile() {
|
||||
profileDiv.innerHTML = "";
|
||||
profileDiv.appendChild(generateSelfAvatar(avatarTimestamps.get("self")));
|
||||
profile_div.innerHTML = "";
|
||||
profile_div.appendChild(generateSelfAvatar(avatarTimestamps.get("self")));
|
||||
let p = document.createElement("p");
|
||||
p.textContent = identityName;
|
||||
profileDiv.appendChild(p);
|
||||
profile_div.appendChild(p);
|
||||
}
|
||||
function displayHeader() {
|
||||
chatHeader.children[0].innerHTML = "";
|
||||
|
@ -1058,7 +1002,9 @@ function generateSession(sessionId, session) {
|
|||
li.classList.add("is_verified");
|
||||
}
|
||||
if (!session.seen) {
|
||||
li.classList.add("not_seen");
|
||||
let marker = document.createElement("div");
|
||||
marker.classList.add("not_seen_marker");
|
||||
li.appendChild(marker);
|
||||
}
|
||||
if (sessionId == currentSessionId) {
|
||||
li.classList.add("current");
|
||||
|
@ -1081,27 +1027,20 @@ function generateMsgHeader(name, sessionId) {
|
|||
div.appendChild(p);
|
||||
return div;
|
||||
}
|
||||
function generateMessageTimestamp(timestamp) {
|
||||
let p = document.createElement("p");
|
||||
p.classList.add("timestamp");
|
||||
p.title = timestamp;
|
||||
p.textContent = toTwoNumbers(timestamp.getHours())+":"+toTwoNumbers(timestamp.getMinutes());
|
||||
return p;
|
||||
}
|
||||
function generateMessage(name, sessionId, msg) {
|
||||
let p = document.createElement("p");
|
||||
p.appendChild(document.createTextNode(msg));
|
||||
let div = document.createElement("div");
|
||||
div.classList.add("content");
|
||||
div.appendChild(linkifyElement(p));
|
||||
let divContainer = document.createElement("div");
|
||||
let li = document.createElement("li");
|
||||
if (typeof name !== "undefined") {
|
||||
divContainer.appendChild(generateMsgHeader(name, sessionId));
|
||||
li.appendChild(generateMsgHeader(name, sessionId));
|
||||
}
|
||||
divContainer.appendChild(div);
|
||||
return divContainer;
|
||||
li.appendChild(div);
|
||||
return li;
|
||||
}
|
||||
function generateFile(name, sessionId, outgoing, fileInfo) {
|
||||
function generateFile(name, sessionId, outgoing, file_info) {
|
||||
let div1 = document.createElement("div");
|
||||
div1.classList.add("file");
|
||||
div1.classList.add("content");
|
||||
|
@ -1113,24 +1052,20 @@ function generateFile(name, sessionId, outgoing, fileInfo) {
|
|||
h4.textContent = "File received:";
|
||||
}
|
||||
div2.appendChild(h4);
|
||||
div1.appendChild(div2);
|
||||
let p = document.createElement("p");
|
||||
if (typeof fileInfo === "string") { //pending
|
||||
p.textContent = fileInfo;
|
||||
} else {
|
||||
p.textContent = fileInfo[1];
|
||||
let a = document.createElement("a");
|
||||
a.href = "/load_file?uuid="+fileInfo[0]+"&file_name="+encodeURIComponent(fileInfo[1]);
|
||||
a.target = "_blank";
|
||||
div1.appendChild(a);
|
||||
}
|
||||
p.textContent = file_info[1];
|
||||
div2.appendChild(p);
|
||||
let divContainer = document.createElement("div");
|
||||
div1.appendChild(div2);
|
||||
let a = document.createElement("a");
|
||||
a.href = "/load_file?uuid="+file_info[0]+"&file_name="+encodeURIComponent(file_info[1]);
|
||||
a.target = "_blank";
|
||||
div1.appendChild(a);
|
||||
let li = document.createElement("li");
|
||||
if (typeof name !== "undefined") {
|
||||
divContainer.appendChild(generateMsgHeader(name, sessionId));
|
||||
li.appendChild(generateMsgHeader(name, sessionId));
|
||||
}
|
||||
divContainer.appendChild(div1);
|
||||
return divContainer;
|
||||
li.appendChild(div1);
|
||||
return li;
|
||||
}
|
||||
function generateFileInfo(fileName, fileSize, p) {
|
||||
let span = document.createElement("span");
|
||||
|
@ -1143,16 +1078,13 @@ function displayChatBottom(speed = undefined) {
|
|||
let fileTransfer = document.getElementById("file_transfer");
|
||||
let session = sessionsData.get(currentSessionId);
|
||||
if (typeof session === "undefined") {
|
||||
msgBox.classList.remove("active");
|
||||
msgBox.removeAttribute("style");
|
||||
fileTransfer.classList.remove("active");
|
||||
} else {
|
||||
if (session.isContact || session.isOnline) {
|
||||
msgBox.classList.add("active");
|
||||
}
|
||||
if (session.isOnline) {
|
||||
msgBox.classList.add("online");
|
||||
msgBox.style.display = "flex";
|
||||
} else {
|
||||
msgBox.classList.remove("online");
|
||||
msgBox.removeAttribute("style");
|
||||
}
|
||||
if (pendingFilesTransfers.has(currentSessionId)) {
|
||||
let fileInfo = document.getElementById("file_info");
|
||||
|
@ -1197,71 +1129,34 @@ function displayChatBottom(speed = undefined) {
|
|||
}
|
||||
}
|
||||
}
|
||||
function scrollHistoryToBottom() {
|
||||
msgLog.scrollTop = msgLog.scrollHeight;
|
||||
}
|
||||
function displayHistory(scrollToBottom = true) {
|
||||
msgLog.innerHTML = "";
|
||||
msg_log.style.display = "block";
|
||||
msg_log.innerHTML = "";
|
||||
let session = sessionsData.get(currentSessionId);
|
||||
if (typeof session === "undefined") {
|
||||
msgLog.style.display = "none";
|
||||
} else {
|
||||
msgLog.style.display = "block";
|
||||
let previousOutgoing = undefined;
|
||||
msgHistory.get(currentSessionId).forEach(entry => {
|
||||
let name = undefined;
|
||||
let sessionId = undefined;
|
||||
if (previousOutgoing != entry[0]) {
|
||||
previousOutgoing = entry[0];
|
||||
if (entry[0]) { //outgoing msg
|
||||
name = identityName;
|
||||
} else {
|
||||
name = session.name;
|
||||
sessionId = currentSessionId;
|
||||
}
|
||||
}
|
||||
let div;
|
||||
if (entry[2]) { //is file
|
||||
div = generateFile(name, sessionId, entry[0], entry[3]);
|
||||
let previousOutgoing = undefined;
|
||||
msgHistory.get(currentSessionId).forEach(entry => {
|
||||
let name = undefined;
|
||||
let sessionId = undefined;
|
||||
if (previousOutgoing != entry[0]) {
|
||||
previousOutgoing = entry[0];
|
||||
if (entry[0]) { //outgoing msg
|
||||
name = identityName;
|
||||
} else {
|
||||
div = generateMessage(name, sessionId, entry[3]);
|
||||
}
|
||||
let li = document.createElement("li");
|
||||
li.appendChild(div);
|
||||
li.appendChild(generateMessageTimestamp(entry[1]));
|
||||
msgLog.appendChild(li);
|
||||
});
|
||||
if (session.isContact) {
|
||||
let msgs = pendingMsgs.get(currentSessionId);
|
||||
if (msgs.length > 0) {
|
||||
let li = document.createElement("li");
|
||||
li.classList.add("pending_msgs_divider");
|
||||
let h4 = document.createElement("h4");
|
||||
h4.textContent = "Pending messages:";
|
||||
li.appendChild(h4);
|
||||
msgLog.appendChild(li);
|
||||
msgs.forEach(entry => {
|
||||
let name = undefined;
|
||||
if (previousOutgoing != true) {
|
||||
previousOutgoing = true;
|
||||
name = identityName;
|
||||
}
|
||||
let div;
|
||||
if (entry[0]) { //is file
|
||||
div = generateFile(name, undefined, true, entry[1]);
|
||||
} else {
|
||||
div = generateMessage(name, undefined, entry[1]);
|
||||
}
|
||||
let li = document.createElement("li");
|
||||
li.appendChild(div);
|
||||
msgLog.appendChild(li);
|
||||
});
|
||||
name = session.name;
|
||||
sessionId = currentSessionId;
|
||||
}
|
||||
}
|
||||
if (scrollToBottom) {
|
||||
scrollHistoryToBottom();
|
||||
if (entry[1]) { //is file
|
||||
msg_log.appendChild(generateFile(name, sessionId, entry[0], entry[2]));
|
||||
} else {
|
||||
msg_log.appendChild(generateMessage(name, sessionId, entry[2]));
|
||||
}
|
||||
if (msgLog.scrollHeight <= msgLog.clientHeight && session.isContact) {
|
||||
});
|
||||
if (scrollToBottom) {
|
||||
msg_log.scrollTop = msg_log.scrollHeight;
|
||||
}
|
||||
if (typeof session !== "undefined") {
|
||||
if (msg_log.scrollHeight <= msg_log.clientHeight && session.isContact) {
|
||||
socket.send("load_msgs "+currentSessionId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>AIRA - Login</title>
|
||||
<link rel="icon" type="image/svg" href="/static/imgs/icons/logo">
|
||||
<link rel="stylesheet" href="/static/commons/style.css">
|
||||
<style>
|
||||
body {
|
||||
|
@ -59,7 +58,8 @@
|
|||
padding: 10px 50px;
|
||||
}
|
||||
.avatar {
|
||||
font-size: 3em;
|
||||
width: 7em;
|
||||
height: 7em;
|
||||
}
|
||||
#identity h2 {
|
||||
text-align: center;
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>AIRA - Logged out</title>
|
||||
<link rel="icon" type="image/svg" href="/static/imgs/icons/logo">
|
||||
<link rel="stylesheet" href="/static/commons/style.css">
|
||||
<style>
|
||||
body {
|
||||
|
|
124
src/identity.rs
124
src/identity.rs
|
@ -43,11 +43,12 @@ fn get_database_path() -> String {
|
|||
AppDirs::new(Some(constants::APPLICATION_FOLDER), false).unwrap().data_dir.join(constants::DB_NAME).to_str().unwrap().to_owned()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Message {
|
||||
pub outgoing: bool,
|
||||
pub timestamp: u64,
|
||||
pub data: Vec<u8>,
|
||||
struct EncryptedIdentity {
|
||||
name: String,
|
||||
encrypted_keypair: Vec<u8>,
|
||||
salt: Vec<u8>,
|
||||
encrypted_master_key: Vec<u8>,
|
||||
encrypted_use_padding: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct Contact {
|
||||
|
@ -59,14 +60,6 @@ pub struct Contact {
|
|||
pub seen: bool,
|
||||
}
|
||||
|
||||
struct EncryptedIdentity {
|
||||
name: String,
|
||||
encrypted_keypair: Vec<u8>,
|
||||
salt: Vec<u8>,
|
||||
encrypted_master_key: Vec<u8>,
|
||||
encrypted_use_padding: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct Identity {
|
||||
pub name: String,
|
||||
pub keypair: Keypair,
|
||||
|
@ -94,8 +87,8 @@ impl Identity {
|
|||
};
|
||||
Ok(Contact {
|
||||
uuid: contact_uuid,
|
||||
public_key,
|
||||
name,
|
||||
public_key: public_key,
|
||||
name: name,
|
||||
avatar: avatar_uuid,
|
||||
verified: false,
|
||||
seen: true,
|
||||
|
@ -249,17 +242,16 @@ impl Identity {
|
|||
Ok(file_uuid)
|
||||
}
|
||||
|
||||
pub fn store_msg(&self, contact_uuid: &Uuid, message: &Message) -> Result<usize, rusqlite::Error> {
|
||||
pub fn store_msg(&self, contact_uuid: &Uuid, outgoing: bool, data: &[u8]) -> Result<usize, rusqlite::Error> {
|
||||
let db = Connection::open(get_database_path())?;
|
||||
db.execute(&format!("CREATE TABLE IF NOT EXISTS \"{}\" (outgoing BLOB, timestamp BLOB, data BLOB)", contact_uuid), [])?;
|
||||
let outgoing_byte: u8 = bool_to_byte(message.outgoing);
|
||||
db.execute(&format!("CREATE TABLE IF NOT EXISTS \"{}\" (outgoing BLOB, data BLOB)", contact_uuid), [])?;
|
||||
let outgoing_byte: u8 = bool_to_byte(outgoing);
|
||||
let encrypted_outgoing = crypto::encrypt_data(&[outgoing_byte], &self.master_key).unwrap();
|
||||
let encrypted_timestamp = crypto::encrypt_data(&message.timestamp.to_be_bytes(), &self.master_key).unwrap();
|
||||
let encrypted_data = crypto::encrypt_data(&message.data, &self.master_key).unwrap();
|
||||
db.execute(&format!("INSERT INTO \"{}\" (outgoing, timestamp, data) VALUES (?1, ?2, ?3)", contact_uuid), params![encrypted_outgoing, encrypted_timestamp, encrypted_data])
|
||||
let encrypted_data = crypto::encrypt_data(data, &self.master_key).unwrap();
|
||||
db.execute(&format!("INSERT INTO \"{}\" (outgoing, data) VALUES (?1, ?2)", contact_uuid), params![encrypted_outgoing, encrypted_data])
|
||||
}
|
||||
|
||||
pub fn load_msgs(&self, contact_uuid: &Uuid, offset: usize, mut count: usize) -> Option<Vec<Message>> {
|
||||
pub fn load_msgs(&self, contact_uuid: &Uuid, offset: usize, mut count: usize) -> Option<Vec<(bool, Vec<u8>)>> {
|
||||
match Connection::open(get_database_path()) {
|
||||
Ok(db) => {
|
||||
if let Ok(mut stmt) = db.prepare(&format!("SELECT count(*) FROM \"{}\"", contact_uuid)) {
|
||||
|
@ -270,30 +262,24 @@ impl Identity {
|
|||
if offset+count >= total {
|
||||
count = total-offset;
|
||||
}
|
||||
let mut stmt = db.prepare(&format!("SELECT outgoing, timestamp, data FROM \"{}\" LIMIT {} OFFSET {}", contact_uuid, count, total-offset-count)).unwrap();
|
||||
let mut stmt = db.prepare(&format!("SELECT outgoing, data FROM \"{}\" LIMIT {} OFFSET {}", contact_uuid, count, total-offset-count)).unwrap();
|
||||
let mut rows = stmt.query([]).unwrap();
|
||||
let mut msgs = Vec::new();
|
||||
while let Ok(Some(row)) = rows.next() {
|
||||
let encrypted_outgoing: Vec<u8> = row.get(0).unwrap();
|
||||
match crypto::decrypt_data(encrypted_outgoing.as_slice(), &self.master_key){
|
||||
Ok(outgoing) => {
|
||||
if let Ok(outgoing) = byte_to_bool(outgoing[0]) {
|
||||
let encrypted_timestamp: Vec<u8> = row.get(1).unwrap();
|
||||
match crypto::decrypt_data(&encrypted_timestamp, &self.master_key) {
|
||||
Ok(timestamp) => {
|
||||
let encrypted_data: Vec<u8> = row.get(2).unwrap();
|
||||
match crypto::decrypt_data(encrypted_data.as_slice(), &self.master_key) {
|
||||
Ok(data) => msgs.push(Message {
|
||||
outgoing,
|
||||
timestamp: u64::from_be_bytes(timestamp.try_into().unwrap()),
|
||||
data,
|
||||
}),
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
match byte_to_bool(outgoing[0]) {
|
||||
Ok(outgoing) => {
|
||||
let encrypted_data: Vec<u8> = row.get(1).unwrap();
|
||||
match crypto::decrypt_data(encrypted_data.as_slice(), &self.master_key) {
|
||||
Ok(data) => msgs.push((outgoing, data)),
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
|
@ -378,8 +364,14 @@ impl Identity {
|
|||
pub fn load_identity(password: Option<&[u8]>) -> Result<Identity, String> {
|
||||
match Identity::load_encrypted_identity() {
|
||||
Ok(encrypted_identity) => {
|
||||
let master_key: [u8; crypto::MASTER_KEY_LEN] = match password {
|
||||
Some(password) => match crypto::decrypt_master_key(&encrypted_identity.encrypted_master_key, password, &encrypted_identity.salt) {
|
||||
let master_key: [u8; crypto::MASTER_KEY_LEN] = if password.is_none() {
|
||||
if encrypted_identity.encrypted_master_key.len() == crypto::MASTER_KEY_LEN {
|
||||
encrypted_identity.encrypted_master_key.try_into().unwrap()
|
||||
} else {
|
||||
return Err(String::from(DATABASE_CORRUPED_ERROR))
|
||||
}
|
||||
} else {
|
||||
match crypto::decrypt_master_key(&encrypted_identity.encrypted_master_key, password.unwrap(), &encrypted_identity.salt) {
|
||||
Ok(master_key) => master_key,
|
||||
Err(e) => return Err(
|
||||
match e {
|
||||
|
@ -388,11 +380,6 @@ impl Identity {
|
|||
}
|
||||
)
|
||||
}
|
||||
None => if encrypted_identity.encrypted_master_key.len() == crypto::MASTER_KEY_LEN {
|
||||
encrypted_identity.encrypted_master_key.try_into().unwrap()
|
||||
} else {
|
||||
return Err(String::from(DATABASE_CORRUPED_ERROR))
|
||||
}
|
||||
};
|
||||
match crypto::decrypt_data(&encrypted_identity.encrypted_keypair, &master_key) {
|
||||
Ok(keypair) => {
|
||||
|
@ -438,16 +425,13 @@ impl Identity {
|
|||
let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
|
||||
db.set(DBKeys::NAME, name.as_bytes())?;
|
||||
db.set(DBKeys::KEYPAIR, &encrypted_keypair)?;
|
||||
let salt = match password {
|
||||
Some(password) => {
|
||||
let (salt, encrypted_master_key) = crypto::encrypt_master_key(master_key, password);
|
||||
db.set(DBKeys::MASTER_KEY, &encrypted_master_key)?;
|
||||
salt
|
||||
}
|
||||
None => {
|
||||
db.set(DBKeys::MASTER_KEY, &master_key)?; //storing master_key in plaintext
|
||||
[0; crypto::SALT_LEN]
|
||||
}
|
||||
let salt = if password.is_none() { //no password
|
||||
db.set(DBKeys::MASTER_KEY, &master_key)?; //storing master_key in plaintext
|
||||
[0; crypto::SALT_LEN]
|
||||
} else {
|
||||
let (salt, encrypted_master_key) = crypto::encrypt_master_key(master_key, password.unwrap());
|
||||
db.set(DBKeys::MASTER_KEY, &encrypted_master_key)?;
|
||||
salt
|
||||
};
|
||||
db.set(DBKeys::SALT, &salt)?;
|
||||
let encrypted_use_padding = crypto::encrypt_data(&[bool_to_byte(true)], &master_key).unwrap();
|
||||
|
@ -462,16 +446,13 @@ impl Identity {
|
|||
|
||||
fn update_master_key(master_key: [u8; crypto::MASTER_KEY_LEN], new_password: Option<&[u8]>) -> Result<usize, rusqlite::Error> {
|
||||
let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
|
||||
let salt = match new_password {
|
||||
Some(new_password) => {
|
||||
let (salt, encrypted_master_key) = crypto::encrypt_master_key(master_key, new_password);
|
||||
db.update(DBKeys::MASTER_KEY, &encrypted_master_key)?;
|
||||
salt
|
||||
}
|
||||
None => {
|
||||
db.update(DBKeys::MASTER_KEY, &master_key)?;
|
||||
[0; crypto::SALT_LEN]
|
||||
}
|
||||
let salt = if new_password.is_none() { //no password
|
||||
db.update(DBKeys::MASTER_KEY, &master_key)?;
|
||||
[0; crypto::SALT_LEN]
|
||||
} else {
|
||||
let (salt, encrypted_master_key) = crypto::encrypt_master_key(master_key, new_password.unwrap());
|
||||
db.update(DBKeys::MASTER_KEY, &encrypted_master_key)?;
|
||||
salt
|
||||
};
|
||||
db.update(DBKeys::SALT, &salt)
|
||||
}
|
||||
|
@ -479,19 +460,20 @@ impl Identity {
|
|||
pub fn change_password(old_password: Option<&[u8]>, new_password: Option<&[u8]>) -> Result<bool, String> {
|
||||
match Identity::load_encrypted_identity() {
|
||||
Ok(encrypted_identity) => {
|
||||
let master_key: [u8; crypto::MASTER_KEY_LEN] = match old_password {
|
||||
Some(old_password) => match crypto::decrypt_master_key(&encrypted_identity.encrypted_master_key, old_password, &encrypted_identity.salt) {
|
||||
let master_key: [u8; crypto::MASTER_KEY_LEN] = if old_password.is_none() {
|
||||
if encrypted_identity.encrypted_master_key.len() == crypto::MASTER_KEY_LEN {
|
||||
encrypted_identity.encrypted_master_key.try_into().unwrap()
|
||||
} else {
|
||||
return Err(String::from(DATABASE_CORRUPED_ERROR))
|
||||
}
|
||||
} else {
|
||||
match crypto::decrypt_master_key(&encrypted_identity.encrypted_master_key, old_password.unwrap(), &encrypted_identity.salt) {
|
||||
Ok(master_key) => master_key,
|
||||
Err(e) => return match e {
|
||||
CryptoError::DecryptionFailed => Ok(false),
|
||||
CryptoError::InvalidLength => Err(String::from(DATABASE_CORRUPED_ERROR))
|
||||
}
|
||||
}
|
||||
None => if encrypted_identity.encrypted_master_key.len() == crypto::MASTER_KEY_LEN {
|
||||
encrypted_identity.encrypted_master_key.try_into().unwrap()
|
||||
} else {
|
||||
return Err(String::from(DATABASE_CORRUPED_ERROR))
|
||||
}
|
||||
};
|
||||
match Identity::update_master_key(master_key, new_password) {
|
||||
Ok(_) => Ok(true),
|
||||
|
|
|
@ -12,7 +12,7 @@ impl<'a> KeyValueTable<'a> {
|
|||
Ok(KeyValueTable {db, table_name})
|
||||
}
|
||||
pub fn set(&self, key: &str, value: &[u8]) -> Result<usize, Error> {
|
||||
self.db.execute(&format!("INSERT INTO {} (key, value) VALUES (?1, ?2)", self.table_name), params![key, value])
|
||||
Ok(self.db.execute(&format!("INSERT INTO {} (key, value) VALUES (?1, ?2)", self.table_name), params![key, value])?)
|
||||
}
|
||||
pub fn get(&self, key: &str) -> Result<Vec<u8>, Error> {
|
||||
let mut stmt = self.db.prepare(&format!("SELECT value FROM {} WHERE key=\"{}\"", self.table_name, key))?;
|
||||
|
|
305
src/main.rs
305
src/main.rs
|
@ -8,12 +8,12 @@ mod ui_interface;
|
|||
mod constants;
|
||||
mod discovery;
|
||||
|
||||
use std::{env, fs, io::{self, Cursor}, net::SocketAddr, str::{FromStr, from_utf8}, sync::{Arc, RwLock}, cmp::Ordering};
|
||||
use std::{env, fs, io, net::SocketAddr, str::{FromStr, from_utf8}, sync::{Arc, RwLock}};
|
||||
use image::GenericImageView;
|
||||
use tokio::{net::TcpListener, runtime::Handle, sync::mpsc, task::JoinError};
|
||||
use tungstenite::Message;
|
||||
use actix_web::{App, HttpRequest, HttpResponse, HttpServer, http::header, cookie::CookieBuilder, web, web::Data};
|
||||
use tokio::{net::TcpListener, runtime::Handle, sync::mpsc};
|
||||
use actix_web::{App, HttpMessage, HttpRequest, HttpResponse, HttpServer, http::{header, CookieBuilder}, web, web::Data};
|
||||
use actix_multipart::Multipart;
|
||||
use tungstenite::Message;
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -25,36 +25,41 @@ use identity::Identity;
|
|||
use session_manager::{SessionManager, SessionCommand};
|
||||
use ui_interface::UiConnection;
|
||||
|
||||
async fn start_websocket_server(ui_auth_token: Arc<RwLock<Option<String>>>, session_manager: Arc<SessionManager>) -> u16 {
|
||||
let websocket_bind_addr = env::var("AIRA_WEBSOCKET_ADDR").unwrap_or_else(|_| "127.0.0.1".to_owned());
|
||||
let websocket_port = env::var("AIRA_WEBSOCKET_PORT").unwrap_or_else(|_| "0".to_owned());
|
||||
async fn start_websocket_server(global_vars: Arc<RwLock<GlobalVars>>) -> u16 {
|
||||
let websocket_bind_addr = env::var("AIRA_WEBSOCKET_ADDR").unwrap_or("127.0.0.1".to_owned());
|
||||
let websocket_port = env::var("AIRA_WEBSOCKET_PORT").unwrap_or("0".to_owned());
|
||||
let server = TcpListener::bind(websocket_bind_addr+":"+&websocket_port).await.unwrap();
|
||||
let websocket_port = server.local_addr().unwrap().port();
|
||||
tokio::spawn(async move {
|
||||
let worker_done = Arc::new(RwLock::new(true));
|
||||
loop {
|
||||
let (stream, _addr) = server.accept().await.unwrap();
|
||||
let ui_auth_token = {
|
||||
ui_auth_token.read().unwrap().clone()
|
||||
};
|
||||
if let Some(ui_auth_token) = ui_auth_token {
|
||||
let stream = stream.into_std().unwrap();
|
||||
stream.set_nonblocking(false).unwrap();
|
||||
match tungstenite::accept(stream) {
|
||||
Ok(mut websocket) => {
|
||||
if let Ok(message) = websocket.read_message() { //waiting for auth token
|
||||
match message.into_text() {
|
||||
Ok(token) => {
|
||||
if token == ui_auth_token {
|
||||
let ui_connection = UiConnection::new(websocket);
|
||||
session_manager.set_ui_connection(ui_connection.clone());
|
||||
websocket_worker(ui_connection, session_manager.clone()).await.unwrap();
|
||||
if *worker_done.read().unwrap() {
|
||||
let ui_auth_token = {
|
||||
global_vars.clone().read().unwrap().ui_auth_token.clone()
|
||||
};
|
||||
if let Some(ui_auth_token) = ui_auth_token {
|
||||
let stream = stream.into_std().unwrap();
|
||||
stream.set_nonblocking(false).unwrap();
|
||||
match tungstenite::accept(stream.try_clone().unwrap()) {
|
||||
Ok(mut websocket) => {
|
||||
if let Ok(message) = websocket.read_message() { //waiting for auth token
|
||||
match message.into_text() {
|
||||
Ok(token) => {
|
||||
if token == ui_auth_token {
|
||||
let ui_connection = UiConnection::new(websocket);
|
||||
let global_vars = global_vars.clone();
|
||||
global_vars.read().unwrap().session_manager.set_ui_connection(ui_connection.clone());
|
||||
*worker_done.write().unwrap() = false;
|
||||
websocket_worker(ui_connection, global_vars, worker_done.clone()).await;
|
||||
}
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +70,7 @@ async fn start_websocket_server(ui_auth_token: Arc<RwLock<Option<String>>>, sess
|
|||
fn discover_peers(session_manager: Arc<SessionManager>) {
|
||||
tokio::spawn(async move {
|
||||
discovery::discover_peers(move |discovery_manager, ip| {
|
||||
println!("New peer discovered: {}", ip);
|
||||
let session_manager = session_manager.clone();
|
||||
if session_manager.is_identity_loaded() {
|
||||
tokio::spawn( async move {
|
||||
|
@ -79,18 +85,19 @@ fn discover_peers(session_manager: Arc<SessionManager>) {
|
|||
});
|
||||
}
|
||||
|
||||
fn load_msgs(session_manager: &SessionManager, ui_connection: &mut UiConnection, session_id: &usize) {
|
||||
fn load_msgs(session_manager: Arc<SessionManager>, ui_connection: &mut UiConnection, session_id: &usize) {
|
||||
if let Some(msgs) = session_manager.load_msgs(session_id, constants::MSG_LOADING_COUNT) {
|
||||
ui_connection.load_msgs(session_id, &msgs);
|
||||
}
|
||||
}
|
||||
|
||||
async fn websocket_worker(mut ui_connection: UiConnection, session_manager: Arc<SessionManager>) -> Result<(), JoinError> {
|
||||
async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLock<GlobalVars>>, worker_done: Arc<RwLock<bool>>) {
|
||||
let session_manager = global_vars.read().unwrap().session_manager.clone();
|
||||
ui_connection.set_name(&session_manager.identity.read().unwrap().as_ref().unwrap().name);
|
||||
session_manager.list_contacts().into_iter().for_each(|contact|{
|
||||
ui_connection.set_as_contact(contact.0, &contact.1, contact.2, &crypto::generate_fingerprint(&contact.3));
|
||||
session_manager.last_loaded_msg_offsets.write().unwrap().insert(contact.0, 0);
|
||||
load_msgs(&session_manager, &mut ui_connection, &contact.0);
|
||||
load_msgs(session_manager.clone(), &mut ui_connection, &contact.0);
|
||||
});
|
||||
session_manager.sessions.read().unwrap().iter().for_each(|session| {
|
||||
ui_connection.on_new_session(
|
||||
|
@ -105,28 +112,14 @@ async fn websocket_worker(mut ui_connection: UiConnection, session_manager: Arc<
|
|||
{
|
||||
let not_seen = session_manager.not_seen.read().unwrap();
|
||||
if not_seen.len() > 0 {
|
||||
ui_connection.set_not_seen(¬_seen);
|
||||
ui_connection.set_not_seen(not_seen.clone());
|
||||
}
|
||||
}
|
||||
session_manager.get_saved_msgs().into_iter().for_each(|msgs| {
|
||||
if !msgs.1.is_empty() {
|
||||
if msgs.1.len() > 0 {
|
||||
ui_connection.load_msgs(&msgs.0, &msgs.1);
|
||||
}
|
||||
});
|
||||
session_manager.pending_msgs.lock().unwrap().iter().for_each(|entry| {
|
||||
entry.1.iter().for_each(|buff| {
|
||||
match buff[0] {
|
||||
protocol::Headers::MESSAGE => match from_utf8(&buff[1..]) {
|
||||
Ok(msg) => ui_connection.new_pending_msg(entry.0, false, msg),
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
protocol::Headers::FILE => if let Some(filename) = protocol::get_file_name(buff) {
|
||||
ui_connection.new_pending_msg(entry.0, true, filename);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
});
|
||||
let mut ips = Vec::new();
|
||||
match if_addrs::get_if_addrs() {
|
||||
Ok(ifaces) => for iface in ifaces {
|
||||
|
@ -136,10 +129,10 @@ async fn websocket_worker(mut ui_connection: UiConnection, session_manager: Arc<
|
|||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
ui_connection.set_local_ips(&ips);
|
||||
ui_connection.set_local_ips(ips);
|
||||
discover_peers(session_manager.clone());
|
||||
let handle = Handle::current();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
std::thread::spawn(move || { //new thread needed to block on read_message() without blocking tokio tasks
|
||||
loop {
|
||||
match ui_connection.websocket.read_message() {
|
||||
Ok(msg) => {
|
||||
|
@ -147,12 +140,10 @@ async fn websocket_worker(mut ui_connection: UiConnection, session_manager: Arc<
|
|||
ui_connection.write_message(Message::Pong(Vec::new())); //not sure if I'm doing this right
|
||||
} else if msg.is_text() {
|
||||
let msg = msg.into_text().unwrap();
|
||||
#[cfg(debug_assertions)]
|
||||
println!("Message: {}", msg);
|
||||
let mut ui_connection = ui_connection.clone();
|
||||
let session_manager = session_manager.clone();
|
||||
handle.spawn(async move {
|
||||
let args: Vec<&str> = msg.split_whitespace().collect();
|
||||
let args: Vec<&str> = msg.split(" ").collect();
|
||||
match args[0] {
|
||||
"set_seen" => {
|
||||
let session_id: usize = args[1].parse().unwrap();
|
||||
|
@ -169,14 +160,11 @@ async fn websocket_worker(mut ui_connection: UiConnection, session_manager: Arc<
|
|||
"refresh" => discover_peers(session_manager.clone()),
|
||||
"send" => {
|
||||
let session_id: usize = args[1].parse().unwrap();
|
||||
let msg_content = &msg[args[0].len()+args[1].len()+2..];
|
||||
let buffer = protocol::new_message(msg_content);
|
||||
#[allow(unused_must_use)] {
|
||||
if let Ok(sent) = session_manager.send_or_add_to_pending(&session_id, buffer).await {
|
||||
if !sent {
|
||||
ui_connection.new_pending_msg(&session_id, false, msg_content);
|
||||
}
|
||||
}
|
||||
let buffer = protocol::new_message(msg[args[0].len()+args[1].len()+2..].to_string());
|
||||
if session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
buff: buffer.clone()
|
||||
}).await {
|
||||
session_manager.store_msg(&session_id, true, buffer);
|
||||
}
|
||||
}
|
||||
"large_files" => {
|
||||
|
@ -185,9 +173,9 @@ async fn websocket_worker(mut ui_connection: UiConnection, session_manager: Arc<
|
|||
for n in (2..args.len()).step_by(2) {
|
||||
file_info.push((args[n].parse::<u64>().unwrap(), base64::decode(args[n+1]).unwrap()));
|
||||
}
|
||||
#[allow(unused_must_use)] {
|
||||
session_manager.send_or_add_to_pending(&session_id, protocol::ask_large_files(file_info)).await;
|
||||
}
|
||||
session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
buff: protocol::ask_large_files(file_info)
|
||||
}).await;
|
||||
}
|
||||
"download" => {
|
||||
let session_id: usize = args[1].parse().unwrap();
|
||||
|
@ -207,7 +195,7 @@ async fn websocket_worker(mut ui_connection: UiConnection, session_manager: Arc<
|
|||
}
|
||||
"load_msgs" => {
|
||||
let session_id: usize = args[1].parse().unwrap();
|
||||
load_msgs(&session_manager, &mut ui_connection, &session_id);
|
||||
load_msgs(session_manager.clone(), &mut ui_connection, &session_id);
|
||||
}
|
||||
"contact" => {
|
||||
let session_id: usize = args[1].parse().unwrap();
|
||||
|
@ -218,7 +206,7 @@ async fn websocket_worker(mut ui_connection: UiConnection, session_manager: Arc<
|
|||
}
|
||||
"uncontact" => {
|
||||
let session_id: usize = args[1].parse().unwrap();
|
||||
match session_manager.remove_contact(&session_id) {
|
||||
match session_manager.remove_contact(session_id) {
|
||||
Ok(_) => {},
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
|
@ -271,11 +259,11 @@ async fn websocket_worker(mut ui_connection: UiConnection, session_manager: Arc<
|
|||
(None, Some(base64::decode(args[1]).unwrap()))
|
||||
};
|
||||
let result = Identity::change_password(old_password.as_deref(), new_password.as_deref());
|
||||
if let Some(mut old_password) = old_password {
|
||||
old_password.zeroize();
|
||||
if old_password.is_some() {
|
||||
old_password.unwrap().zeroize();
|
||||
}
|
||||
let is_identity_protected = if let Some(mut new_password) = new_password {
|
||||
new_password.zeroize();
|
||||
let is_identity_protected = if new_password.is_some() {
|
||||
new_password.unwrap().zeroize();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
@ -299,6 +287,7 @@ async fn websocket_worker(mut ui_connection: UiConnection, session_manager: Arc<
|
|||
Err(e) => {
|
||||
match e {
|
||||
tungstenite::Error::ConnectionClosed => {
|
||||
*worker_done.write().unwrap() = true;
|
||||
break;
|
||||
}
|
||||
_ => print_error!(e)
|
||||
|
@ -306,13 +295,13 @@ async fn websocket_worker(mut ui_connection: UiConnection, session_manager: Arc<
|
|||
}
|
||||
}
|
||||
}
|
||||
}).await
|
||||
});
|
||||
}
|
||||
|
||||
fn is_authenticated(req: &HttpRequest) -> bool {
|
||||
if let Some(cookie) = req.cookie(constants::HTTP_COOKIE_NAME) {
|
||||
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||
if let Some(token) = global_vars.ui_auth_token.read().unwrap().as_ref() {
|
||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
||||
if let Some(token) = &global_vars.read().unwrap().ui_auth_token {
|
||||
return token == cookie.value();
|
||||
}
|
||||
}
|
||||
|
@ -321,7 +310,7 @@ fn is_authenticated(req: &HttpRequest) -> bool {
|
|||
|
||||
async fn handle_set_avatar(req: HttpRequest, mut payload: Multipart) -> HttpResponse {
|
||||
if let Ok(Some(mut field)) = payload.try_next().await {
|
||||
let content_disposition = field.content_disposition();
|
||||
let content_disposition = field.content_disposition().unwrap();
|
||||
if let Some(name) = content_disposition.get_name() {
|
||||
if name == "avatar" {
|
||||
let mut buffer = Vec::new();
|
||||
|
@ -333,15 +322,17 @@ async fn handle_set_avatar(req: HttpRequest, mut payload: Multipart) -> HttpResp
|
|||
match image::load_from_memory_with_format(&buffer, format) {
|
||||
Ok(image) => {
|
||||
let (width, height) = image.dimensions();
|
||||
let image = match width.cmp(&height) {
|
||||
Ordering::Greater => image.crop_imm((width-height)/2, 0, height, height),
|
||||
Ordering::Less => image.crop_imm(0, (height-width)/2, width, width),
|
||||
Ordering::Equal => image,
|
||||
let image = if width < height {
|
||||
image.crop_imm(0, (height-width)/2, width, width)
|
||||
} else if width > height {
|
||||
image.crop_imm((width-height)/2, 0, height, height)
|
||||
} else {
|
||||
image
|
||||
};
|
||||
let mut avatar = Vec::new();
|
||||
image.write_to(&mut Cursor::new(&mut avatar), format).unwrap();
|
||||
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||
let session_manager = &global_vars.session_manager;
|
||||
image.write_to(&mut avatar, format).unwrap();
|
||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
||||
let session_manager = &global_vars.read().unwrap().session_manager;
|
||||
let is_authenticated = is_authenticated(&req);
|
||||
let is_running = session_manager.is_identity_loaded();
|
||||
if is_authenticated || !is_running {
|
||||
|
@ -379,23 +370,23 @@ fn reply_with_avatar(avatar: Option<Vec<u8>>, name: Option<&str>) -> HttpRespons
|
|||
let svg = include_str!(concat!(env!("OUT_DIR"), "/text_avatar.svg"));
|
||||
#[cfg(debug_assertions)]
|
||||
let svg = replace_fields("src/frontend/imgs/text_avatar.svg");
|
||||
HttpResponse::Ok().content_type("image/svg+xml").body(svg.replace("LETTER", &name.chars().next().unwrap_or('?').to_string()))
|
||||
HttpResponse::Ok().content_type("image/svg+xml").body(svg.replace("LETTER", &name.chars().nth(0).unwrap_or('?').to_string()))
|
||||
}
|
||||
None => HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_avatar(req: HttpRequest) -> HttpResponse {
|
||||
let splits: Vec<&str> = req.path()[1..].split('/').collect();
|
||||
fn handle_avatar(req: HttpRequest) -> HttpResponse {
|
||||
let splits: Vec<&str> = req.path()[1..].split("/").collect();
|
||||
if splits.len() == 2 {
|
||||
if splits[1] == "self" {
|
||||
return reply_with_avatar(Identity::get_identity_avatar().ok(), Identity::get_identity_name().ok().as_deref());
|
||||
}
|
||||
} else if splits.len() == 3 && is_authenticated(&req) {
|
||||
if let Ok(session_id) = splits[1].parse() {
|
||||
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||
return reply_with_avatar(global_vars.session_manager.get_avatar(&session_id), Some(splits[2]));
|
||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
||||
return reply_with_avatar(global_vars.read().unwrap().session_manager.get_avatar(&session_id), Some(splits[2]));
|
||||
}
|
||||
}
|
||||
HttpResponse::BadRequest().finish()
|
||||
|
@ -406,13 +397,16 @@ struct FileInfo {
|
|||
uuid: String,
|
||||
file_name: String,
|
||||
}
|
||||
async fn handle_load_file(req: HttpRequest, file_info: web::Query<FileInfo>) -> HttpResponse {
|
||||
fn handle_load_file(req: HttpRequest, file_info: web::Query<FileInfo>) -> HttpResponse {
|
||||
if is_authenticated(&req) {
|
||||
match Uuid::from_str(&file_info.uuid) {
|
||||
Ok(uuid) => {
|
||||
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||
if let Some(buffer) = global_vars.session_manager.identity.read().unwrap().as_ref().unwrap().load_file(uuid) {
|
||||
return HttpResponse::Ok().append_header(("Content-Disposition", format!("attachment; filename=\"{}\"", escape_double_quote(html_escape::decode_html_entities(&file_info.file_name).to_string())))).content_type("application/octet-stream").body(buffer);
|
||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
||||
match global_vars.read().unwrap().session_manager.identity.read().unwrap().as_ref().unwrap().load_file(uuid) {
|
||||
Some(buffer) => {
|
||||
return HttpResponse::Ok().header("Content-Disposition", format!("attachment; filename=\"{}\"", escape_double_quote(html_escape::decode_html_entities(&file_info.file_name).to_string()))).content_type("application/octet-stream").body(buffer);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
|
@ -425,27 +419,33 @@ async fn handle_send_file(req: HttpRequest, mut payload: Multipart) -> HttpRespo
|
|||
if is_authenticated(&req) {
|
||||
let mut session_id: Option<usize> = None;
|
||||
while let Ok(Some(mut field)) = payload.try_next().await {
|
||||
let content_disposition = field.content_disposition();
|
||||
let content_disposition = field.content_disposition().unwrap();
|
||||
if let Some(name) = content_disposition.get_name() {
|
||||
if name == "session_id" {
|
||||
if let Some(Ok(raw_id)) = field.next().await {
|
||||
session_id = Some(from_utf8(&raw_id).unwrap().parse().unwrap());
|
||||
}
|
||||
} else if session_id.is_some() {
|
||||
let filename = content_disposition.get_filename().unwrap().to_owned();
|
||||
let filename = content_disposition.get_filename().unwrap();
|
||||
let session_id = session_id.unwrap();
|
||||
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
||||
let global_vars_read = global_vars.read().unwrap();
|
||||
if req.path() == "/send_file" {
|
||||
let mut buffer = Vec::new();
|
||||
while let Some(Ok(chunk)) = field.next().await {
|
||||
buffer.extend(chunk);
|
||||
}
|
||||
if let Ok(sent) = global_vars.session_manager.send_or_add_to_pending(&session_id, protocol::file(&filename, &buffer)).await {
|
||||
return if sent {
|
||||
HttpResponse::Ok().finish()
|
||||
} else {
|
||||
HttpResponse::Ok().body("pending")
|
||||
};
|
||||
if global_vars_read.session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
buff: protocol::file(filename, &buffer)
|
||||
}).await {
|
||||
match global_vars_read.session_manager.store_file(&session_id, &buffer) {
|
||||
Ok(file_uuid) => {
|
||||
let msg = [&[protocol::Headers::FILE][..], file_uuid.as_bytes(), filename.as_bytes()].concat();
|
||||
global_vars_read.session_manager.store_msg(&session_id, true, msg);
|
||||
return HttpResponse::Ok().body(file_uuid.to_string());
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let (ack_sender, mut ack_receiver) = mpsc::channel(1);
|
||||
|
@ -469,7 +469,7 @@ async fn handle_send_file(req: HttpRequest, mut payload: Multipart) -> HttpRespo
|
|||
break;
|
||||
}
|
||||
}
|
||||
if !global_vars.session_manager.send_command(&session_id, SessionCommand::EncryptFileChunk{
|
||||
if !global_vars_read.session_manager.send_command(&session_id, SessionCommand::EncryptFileChunk{
|
||||
plain_text: chunk_buffer.clone()
|
||||
}).await {
|
||||
return HttpResponse::InternalServerError().finish();
|
||||
|
@ -477,7 +477,7 @@ async fn handle_send_file(req: HttpRequest, mut payload: Multipart) -> HttpRespo
|
|||
if !match ack_receiver.recv().await {
|
||||
Some(should_continue) => {
|
||||
//send previous encrypted chunk even if transfert is aborted to keep PSEC nonces syncrhonized
|
||||
if global_vars.session_manager.send_command(&session_id, SessionCommand::SendEncryptedFileChunk {
|
||||
if global_vars_read.session_manager.send_command(&session_id, SessionCommand::SendEncryptedFileChunk {
|
||||
ack_sender: ack_sender.clone()
|
||||
}).await {
|
||||
should_continue
|
||||
|
@ -506,31 +506,29 @@ async fn handle_send_file(req: HttpRequest, mut payload: Multipart) -> HttpRespo
|
|||
|
||||
async fn handle_logout(req: HttpRequest) -> HttpResponse {
|
||||
if is_authenticated(&req) {
|
||||
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||
if global_vars.session_manager.is_identity_loaded() {
|
||||
*global_vars.ui_auth_token.write().unwrap() = None;
|
||||
global_vars.session_manager.stop().await;
|
||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
||||
let mut global_vars_write = global_vars.write().unwrap();
|
||||
if global_vars_write.session_manager.is_identity_loaded() {
|
||||
global_vars_write.ui_auth_token = None;
|
||||
global_vars_write.session_manager.stop().await;
|
||||
}
|
||||
if Identity::is_protected().unwrap_or(true) {
|
||||
HttpResponse::Found().append_header((header::LOCATION, "/")).finish()
|
||||
HttpResponse::Found().header(header::LOCATION, "/").finish()
|
||||
} else {
|
||||
#[cfg(debug_assertions)]
|
||||
let html = fs::read_to_string("src/frontend/logout.html").unwrap();
|
||||
#[cfg(not(debug_assertions))]
|
||||
let html = include_str!("frontend/logout.html");
|
||||
HttpResponse::Ok().body(html)
|
||||
HttpResponse::Ok().body(include_str!("frontend/logout.html"))
|
||||
}
|
||||
} else {
|
||||
HttpResponse::Unauthorized().finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn login(identity: Identity, global_vars: &GlobalVars) -> HttpResponse {
|
||||
let session_manager = global_vars.session_manager.clone();
|
||||
fn login(identity: Identity, global_vars: &Arc<RwLock<GlobalVars>>) -> HttpResponse {
|
||||
let mut global_vars_write = global_vars.write().unwrap();
|
||||
let session_manager = global_vars_write.session_manager.clone();
|
||||
if !session_manager.is_identity_loaded() {
|
||||
session_manager.set_identity(Some(identity));
|
||||
global_vars.tokio_handle.spawn(async move {
|
||||
if SessionManager::start_listener(session_manager).await.is_err() {
|
||||
global_vars_write.session_manager.set_identity(Some(identity));
|
||||
global_vars_write.tokio_handle.clone().spawn(async move {
|
||||
if SessionManager::start_listener(session_manager.clone()).await.is_err() {
|
||||
print_error!("You won't be able to receive incomming connections from other peers.");
|
||||
}
|
||||
});
|
||||
|
@ -538,15 +536,15 @@ fn login(identity: Identity, global_vars: &GlobalVars) -> HttpResponse {
|
|||
let mut raw_cookie = [0; 32];
|
||||
OsRng.fill_bytes(&mut raw_cookie);
|
||||
let cookie_value = base64::encode(raw_cookie);
|
||||
*global_vars.ui_auth_token.write().unwrap() = Some(cookie_value.clone());
|
||||
global_vars_write.ui_auth_token = Some(cookie_value.clone());
|
||||
let cookie = CookieBuilder::new(constants::HTTP_COOKIE_NAME, cookie_value).max_age(time::Duration::hours(4)).finish();
|
||||
HttpResponse::Found()
|
||||
.append_header((header::LOCATION, "/"))
|
||||
.insert_header((header::SET_COOKIE, cookie.to_string()))
|
||||
.header(header::LOCATION, "/")
|
||||
.set_header(header::SET_COOKIE, cookie.to_string())
|
||||
.finish()
|
||||
}
|
||||
|
||||
fn on_identity_loaded(identity: Identity, global_vars: &Arc<GlobalVars>) -> HttpResponse {
|
||||
fn on_identity_loaded(identity: Identity, global_vars: &Arc<RwLock<GlobalVars>>) -> HttpResponse {
|
||||
match Identity::clear_cache() {
|
||||
Ok(_) => {},
|
||||
Err(e) => print_error!(e)
|
||||
|
@ -558,13 +556,13 @@ fn on_identity_loaded(identity: Identity, global_vars: &Arc<GlobalVars>) -> Http
|
|||
struct LoginParams {
|
||||
password: String,
|
||||
}
|
||||
async fn handle_login(req: HttpRequest, mut params: web::Form<LoginParams>) -> HttpResponse {
|
||||
fn handle_login(req: HttpRequest, mut params: web::Form<LoginParams>) -> HttpResponse {
|
||||
let response = match Identity::load_identity(Some(params.password.as_bytes())) {
|
||||
Ok(identity) => {
|
||||
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
||||
on_identity_loaded(identity, global_vars)
|
||||
}
|
||||
Err(e) => generate_login_response(Some(&e))
|
||||
Err(e) => generate_login_response(Some(&e.to_string()))
|
||||
};
|
||||
params.password.zeroize();
|
||||
response
|
||||
|
@ -613,15 +611,15 @@ async fn handle_create(req: HttpRequest, mut params: web::Form<CreateParams>) ->
|
|||
let response = if params.password == params.password_confirm {
|
||||
match Identity::create_identidy(
|
||||
¶ms.name,
|
||||
if params.password.is_empty() { //no password
|
||||
if params.password.len() == 0 { //no password
|
||||
None
|
||||
} else {
|
||||
Some(params.password.as_bytes())
|
||||
}
|
||||
) {
|
||||
Ok(identity) => {
|
||||
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||
login(identity, global_vars)
|
||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
||||
login(identity, global_vars.get_ref())
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
|
@ -636,7 +634,7 @@ async fn handle_create(req: HttpRequest, mut params: web::Form<CreateParams>) ->
|
|||
response
|
||||
}
|
||||
|
||||
fn index_not_logged_in(global_vars: &Arc<GlobalVars>) -> HttpResponse {
|
||||
fn index_not_logged_in(global_vars: &Arc<RwLock<GlobalVars>>) -> HttpResponse {
|
||||
if Identity::is_protected().unwrap_or(true) {
|
||||
generate_login_response(None)
|
||||
} else {
|
||||
|
@ -648,21 +646,22 @@ fn index_not_logged_in(global_vars: &Arc<GlobalVars>) -> HttpResponse {
|
|||
}
|
||||
|
||||
async fn handle_index(req: HttpRequest) -> HttpResponse {
|
||||
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
||||
if is_authenticated(&req) {
|
||||
let global_vars_read = global_vars.read().unwrap();
|
||||
#[cfg(debug_assertions)]
|
||||
let html = fs::read_to_string("src/frontend/index.html").unwrap()
|
||||
.replace("AIRA_VERSION", env!("CARGO_PKG_VERSION"));
|
||||
#[cfg(not(debug_assertions))]
|
||||
let html = include_str!(concat!(env!("OUT_DIR"), "/index.html"));
|
||||
let identity = global_vars.session_manager.identity.read().unwrap();
|
||||
let identity = identity.as_ref().unwrap();
|
||||
let public_key = global_vars_read.session_manager.identity.read().unwrap().as_ref().unwrap().get_public_key();
|
||||
let use_padding = global_vars_read.session_manager.identity.read().unwrap().as_ref().unwrap().use_padding.to_string();
|
||||
HttpResponse::Ok().body(
|
||||
html
|
||||
.replace("IDENTITY_FINGERPRINT", &crypto::generate_fingerprint(&identity.get_public_key()))
|
||||
.replace("WEBSOCKET_PORT", &global_vars.websocket_port.to_string())
|
||||
.replace("IDENTITY_FINGERPRINT", &crypto::generate_fingerprint(&public_key))
|
||||
.replace("WEBSOCKET_PORT", &global_vars_read.websocket_port.to_string())
|
||||
.replace("IS_IDENTITY_PROTECTED", &Identity::is_protected().unwrap().to_string())
|
||||
.replace("PSEC_PADDING", &identity.use_padding.to_string())
|
||||
.replace("PSEC_PADDING", &use_padding)
|
||||
)
|
||||
} else {
|
||||
index_not_logged_in(global_vars)
|
||||
|
@ -683,8 +682,8 @@ fn replace_fields(file_path: &str) -> String {
|
|||
content
|
||||
}
|
||||
|
||||
async fn handle_static(req: HttpRequest) -> HttpResponse {
|
||||
let splits: Vec<&str> = req.path()[1..].split('/').collect();
|
||||
fn handle_static(req: HttpRequest) -> HttpResponse {
|
||||
let splits: Vec<&str> = req.path()[1..].split("/").collect();
|
||||
if splits[0] == "static" {
|
||||
let mut response_builder = HttpResponse::Ok();
|
||||
match splits[1] {
|
||||
|
@ -708,8 +707,7 @@ async fn handle_static(req: HttpRequest) -> HttpResponse {
|
|||
} else {
|
||||
"none"
|
||||
};
|
||||
if let Some(body) = match splits[3] {
|
||||
"logo" => Some(include_str!("frontend/imgs/icons/logo.svg")),
|
||||
match match splits[3] {
|
||||
"verified" => Some(include_str!("frontend/imgs/icons/verified.svg")),
|
||||
"add_contact" => Some(include_str!("frontend/imgs/icons/add_contact.svg")),
|
||||
"remove_contact" => Some(include_str!("frontend/imgs/icons/remove_contact.svg")),
|
||||
|
@ -724,8 +722,11 @@ async fn handle_static(req: HttpRequest) -> HttpResponse {
|
|||
"profile" => Some(include_str!("frontend/imgs/icons/profile.svg")),
|
||||
_ => None
|
||||
} {
|
||||
response_builder.content_type("image/svg+xml");
|
||||
return response_builder.body(body.replace("FILL_COLOR", color))
|
||||
Some(body) => {
|
||||
response_builder.content_type("image/svg+xml");
|
||||
return response_builder.body(body.replace("FILL_COLOR", color))
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
} else if splits.len() == 3 {
|
||||
match splits[2] {
|
||||
|
@ -765,12 +766,13 @@ async fn handle_static(req: HttpRequest) -> HttpResponse {
|
|||
}
|
||||
"libs" => {
|
||||
if splits.len() == 3 {
|
||||
if let Some(body) = match splits[2] {
|
||||
match match splits[2] {
|
||||
"linkify.min.js" => Some(include_str!("frontend/libs/linkify.min.js")),
|
||||
"linkify-element.min.js" => Some(include_str!("frontend/libs/linkify-element.min.js")),
|
||||
_ => None
|
||||
} {
|
||||
return response_builder.content_type(JS_CONTENT_TYPE).body(body);
|
||||
Some(body) => return response_builder.content_type(JS_CONTENT_TYPE).body(body),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -780,15 +782,17 @@ async fn handle_static(req: HttpRequest) -> HttpResponse {
|
|||
HttpResponse::NotFound().finish()
|
||||
}
|
||||
|
||||
async fn start_http_server(global_vars: GlobalVars) -> io::Result<()> {
|
||||
let http_addr = env::var("AIRA_HTTP_ADDR").unwrap_or_else(|_| "127.0.0.1".to_owned()).parse().expect("AIRA_HTTP_ADDR invalid");
|
||||
#[actix_web::main]
|
||||
async fn start_http_server(global_vars: Arc<RwLock<GlobalVars>>) -> io::Result<()> {
|
||||
let http_addr = env::var("AIRA_HTTP_ADDR").unwrap_or("127.0.0.1".to_owned()).parse().expect("AIRA_HTTP_ADDR invalid");
|
||||
let http_port = match env::var("AIRA_HTTP_PORT") {
|
||||
Ok(port) => port.parse().expect("AIRA_HTTP_PORT invalid"),
|
||||
Err(_) => constants::UI_PORT
|
||||
};
|
||||
let server = HttpServer::new(move || {
|
||||
let global_vars_clone = global_vars.clone();
|
||||
App::new()
|
||||
.app_data(Data::new(global_vars.clone()))
|
||||
.data(global_vars_clone)
|
||||
.service(web::resource("/")
|
||||
.route(web::get().to(handle_index))
|
||||
.route(web::post().to(handle_create))
|
||||
|
@ -798,8 +802,8 @@ async fn start_http_server(global_vars: GlobalVars) -> io::Result<()> {
|
|||
.route("/send_large_file", web::post().to(handle_send_file))
|
||||
.route("/load_file", web::get().to(handle_load_file))
|
||||
.route("/set_avatar", web::post().to(handle_set_avatar))
|
||||
.route("/avatar/{_}*", web::get().to(handle_avatar))
|
||||
.route("/static/{_}*", web::get().to(handle_static))
|
||||
.route("/avatar/*", web::get().to(handle_avatar))
|
||||
.route("/static/.*", web::get().to(handle_static))
|
||||
.route("/logout", web::get().to(handle_logout))
|
||||
}
|
||||
).bind(SocketAddr::new(http_addr, http_port))?;
|
||||
|
@ -811,11 +815,10 @@ async fn start_http_server(global_vars: GlobalVars) -> io::Result<()> {
|
|||
server.run().await
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct GlobalVars {
|
||||
session_manager: Arc<SessionManager>,
|
||||
websocket_port: u16,
|
||||
ui_auth_token: Arc<RwLock<Option<String>>>,
|
||||
ui_auth_token: Option<String>,
|
||||
tokio_handle: Handle,
|
||||
}
|
||||
|
||||
|
@ -826,13 +829,13 @@ async fn main() {
|
|||
print_error!(e);
|
||||
}
|
||||
}
|
||||
let ui_auth_token = Arc::new(RwLock::new(None));
|
||||
let session_manager = Arc::new(SessionManager::new());
|
||||
let websocket_port = start_websocket_server(ui_auth_token.clone(), session_manager.clone()).await;
|
||||
start_http_server(GlobalVars {
|
||||
session_manager,
|
||||
websocket_port,
|
||||
ui_auth_token,
|
||||
let global_vars = Arc::new(RwLock::new(GlobalVars {
|
||||
session_manager: Arc::new(SessionManager::new()),
|
||||
websocket_port: 0,
|
||||
ui_auth_token: None,
|
||||
tokio_handle: Handle::current(),
|
||||
}).await.unwrap();
|
||||
}));
|
||||
let websocket_port = start_websocket_server(global_vars.clone()).await;
|
||||
global_vars.write().unwrap().websocket_port = websocket_port;
|
||||
start_http_server(global_vars).unwrap();
|
||||
}
|
|
@ -17,7 +17,7 @@ impl Headers {
|
|||
pub const ABORT_FILES_TRANSFER: u8 = 0x0a;
|
||||
}
|
||||
|
||||
pub fn new_message(message: &str) -> Vec<u8> {
|
||||
pub fn new_message(message: String) -> Vec<u8> {
|
||||
[&[Headers::MESSAGE], message.as_bytes()].concat()
|
||||
}
|
||||
|
||||
|
@ -33,21 +33,17 @@ pub fn file(file_name: &str, buffer: &[u8]) -> Vec<u8> {
|
|||
[&[Headers::FILE], &(file_name.len() as u16).to_be_bytes()[..], file_name.as_bytes(), buffer].concat()
|
||||
}
|
||||
|
||||
pub fn get_file_name(buffer: &[u8]) -> Option<&str> {
|
||||
pub fn parse_file<'a>(buffer: &'a [u8]) -> Option<(&'a [u8], &'a [u8])> {
|
||||
if buffer.len() > 3 {
|
||||
let file_name_len = u16::from_be_bytes([buffer[1], buffer[2]]) as usize;
|
||||
if buffer.len() > 3+file_name_len {
|
||||
return from_utf8(&buffer[3..3+file_name_len]).ok();
|
||||
let file_name = &buffer[3..3+file_name_len];
|
||||
return Some((file_name, &buffer[3+file_name_len..]));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn parse_file(buffer: &[u8]) -> Option<(&str, &[u8])> {
|
||||
let file_name = get_file_name(buffer)?;
|
||||
Some((file_name, &buffer[3+file_name.len()..]))
|
||||
}
|
||||
|
||||
pub fn ask_large_files(file_info: Vec<(u64, Vec<u8>)>) -> Vec<u8> {
|
||||
let mut buff = vec![Headers::ASK_LARGE_FILES];
|
||||
file_info.into_iter().for_each(|info| {
|
||||
|
|
|
@ -4,7 +4,8 @@ use libmdns::Service;
|
|||
use uuid::Uuid;
|
||||
use platform_dirs::UserDirs;
|
||||
use async_psec::{PUBLIC_KEY_LENGTH, Session, SessionWriteHalf, PsecWriter, PsecReader, PsecError};
|
||||
use crate::{constants, crypto, discovery, identity::{Contact, Identity, Message}, ui_interface::UiConnection, print_error, protocol, utils::{get_not_used_path, get_unix_timestamp_ms, get_unix_timestamp_sec}};
|
||||
use crate::{constants, protocol, crypto, discovery, identity::{Contact, Identity}, print_error, utils::{get_unix_timestamp, get_not_used_path}};
|
||||
use crate::ui_interface::UiConnection;
|
||||
|
||||
pub enum SessionCommand {
|
||||
Send {
|
||||
|
@ -54,8 +55,7 @@ pub struct SessionManager {
|
|||
ui_connection: Mutex<Option<UiConnection>>,
|
||||
loaded_contacts: RwLock<HashMap<usize, Contact>>,
|
||||
pub last_loaded_msg_offsets: RwLock<HashMap<usize, usize>>,
|
||||
saved_msgs: RwLock<HashMap<usize, Vec<Message>>>,
|
||||
pub pending_msgs: Mutex<HashMap<usize, Vec<Vec<u8>>>>,
|
||||
pub saved_msgs: RwLock<HashMap<usize, Vec<(bool, Vec<u8>)>>>,
|
||||
pub not_seen: RwLock<Vec<usize>>,
|
||||
mdns_service: Mutex<Option<Service>>,
|
||||
listener_stop_signal: Mutex<Option<Sender<()>>>,
|
||||
|
@ -65,10 +65,11 @@ impl SessionManager {
|
|||
|
||||
fn with_ui_connection<F>(&self, f: F) where F: FnOnce(&mut UiConnection) {
|
||||
let mut ui_connection_opt = self.ui_connection.lock().unwrap();
|
||||
if let Some(ui_connection) = ui_connection_opt.as_mut() {
|
||||
if ui_connection.is_valid {
|
||||
match ui_connection_opt.as_mut() {
|
||||
Some(ui_connection) => if ui_connection.is_valid {
|
||||
f(ui_connection);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,11 +88,11 @@ impl SessionManager {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn store_msg(&self, session_id: &usize, message: Message) {
|
||||
pub fn store_msg(&self, session_id: &usize, outgoing: bool, buffer: Vec<u8>) {
|
||||
let mut msg_saved = false;
|
||||
if let Some(contact) = self.loaded_contacts.read().unwrap().get(session_id) {
|
||||
let mut offsets = self.last_loaded_msg_offsets.write().unwrap(); //locking mutex before modifying the DB to prevent race conditions with load_msgs()
|
||||
match self.identity.read().unwrap().as_ref().unwrap().store_msg(&contact.uuid, &message) {
|
||||
match self.identity.read().unwrap().as_ref().unwrap().store_msg(&contact.uuid, outgoing, &buffer) {
|
||||
Ok(_) => {
|
||||
*offsets.get_mut(session_id).unwrap() += 1;
|
||||
msg_saved = true;
|
||||
|
@ -100,16 +101,16 @@ impl SessionManager {
|
|||
}
|
||||
}
|
||||
if !msg_saved {
|
||||
//can be None if session disconnected
|
||||
if let Some(saved_msgs) = self.saved_msgs.write().unwrap().get_mut(&session_id) {
|
||||
saved_msgs.push(message)
|
||||
}
|
||||
self.saved_msgs.write().unwrap().get_mut(&session_id).unwrap().push((outgoing, buffer));
|
||||
}
|
||||
}
|
||||
|
||||
fn get_session_sender(&self, session_id: &usize) -> Option<Sender<SessionCommand>> {
|
||||
let sessions = self.sessions.read().unwrap();
|
||||
sessions.get(session_id).map(|session_data| session_data.sender.clone())
|
||||
let mut sessions = self.sessions.write().unwrap();
|
||||
match sessions.get_mut(session_id) {
|
||||
Some(session_data) => Some(session_data.sender.clone()),
|
||||
None => None
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_command(&self, session_id: &usize, session_command: SessionCommand) -> bool {
|
||||
|
@ -126,21 +127,6 @@ impl SessionManager {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn send_or_add_to_pending(&self, session_id: &usize, buff: Vec<u8>) -> Result<bool, ()> {
|
||||
if let Some(sender) = self.get_session_sender(session_id) {
|
||||
match sender.send(SessionCommand::Send { buff }).await {
|
||||
Ok(_) => Ok(true),
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.pending_msgs.lock().unwrap().get_mut(session_id).unwrap().push(buff);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_session(&self, session_id: &usize) {
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_disconnected(&session_id);
|
||||
|
@ -165,66 +151,23 @@ impl SessionManager {
|
|||
});
|
||||
}
|
||||
|
||||
async fn send_store_and_inform<T: PsecWriter>(&self, session_id: usize, session_writer: &mut T, buff: Vec<u8>) -> Result<Option<Vec<u8>>, PsecError> {
|
||||
self.encrypt_and_send(session_writer, &buff).await?;
|
||||
let timestamp = get_unix_timestamp_sec();
|
||||
Ok(match buff[0] {
|
||||
protocol::Headers::MESSAGE => {
|
||||
let msg = Message {
|
||||
outgoing: true,
|
||||
timestamp,
|
||||
data: buff,
|
||||
};
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_new_msg(&session_id, &msg);
|
||||
});
|
||||
self.store_msg(&session_id, msg);
|
||||
None
|
||||
}
|
||||
protocol::Headers::FILE => {
|
||||
if let Some((filename, content)) = protocol::parse_file(&buff) {
|
||||
match self.store_file(&session_id, content) {
|
||||
Ok(file_uuid) => {
|
||||
let msg = [&[protocol::Headers::FILE][..], file_uuid.as_bytes(), filename.as_bytes()].concat();
|
||||
self.store_msg(&session_id, Message {
|
||||
outgoing: true,
|
||||
timestamp,
|
||||
data: msg,
|
||||
});
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_new_file(&session_id, true, timestamp, filename, file_uuid);
|
||||
});
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
async fn send_msg(&self, session_id: usize, session_write: &mut SessionWriteHalf, buff: &[u8], is_sending: &mut bool, file_ack_sender: &mut Option<Sender<bool>>) -> Result<(), PsecError> {
|
||||
self.encrypt_and_send(session_write, &buff).await?;
|
||||
if buff[0] == protocol::Headers::ACCEPT_LARGE_FILES {
|
||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download.as_mut().unwrap().accepted = true;
|
||||
} else if buff[0] == protocol::Headers::ABORT_FILES_TRANSFER {
|
||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download = None;
|
||||
*is_sending = false;
|
||||
if let Some(ack_sender) = file_ack_sender {
|
||||
if let Err(e) = ack_sender.send(false).await {
|
||||
print_error!(e);
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => Some(buff)
|
||||
})
|
||||
}
|
||||
|
||||
async fn send_msg(&self, session_id: usize, session_write: &mut SessionWriteHalf, buff: Vec<u8>, is_sending: &mut bool, file_ack_sender: &mut Option<Sender<bool>>) -> Result<(), PsecError> {
|
||||
if let Some(buff) = self.send_store_and_inform(session_id, session_write, buff).await? {
|
||||
//not a message or a file
|
||||
match buff[0] {
|
||||
protocol::Headers::ACCEPT_LARGE_FILES => self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download.as_mut().unwrap().accepted = true,
|
||||
protocol::Headers::ABORT_FILES_TRANSFER => {
|
||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download = None;
|
||||
*is_sending = false;
|
||||
if let Some(ack_sender) = file_ack_sender {
|
||||
if let Err(e) = ack_sender.send(false).await {
|
||||
print_error!(e);
|
||||
}
|
||||
*file_ack_sender = None;
|
||||
}
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_file_transfer_aborted(&session_id);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
*file_ack_sender = None;
|
||||
}
|
||||
}
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_msg_sent(session_id, &buff);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -280,7 +223,7 @@ impl SessionManager {
|
|||
let mut sessions = self.sessions.write().unwrap();
|
||||
let files_transfer = sessions.get_mut(&session_id).unwrap().files_download.as_mut().unwrap();
|
||||
let file_transfer = &mut files_transfer.files[files_transfer.index];
|
||||
file_transfer.last_chunk = get_unix_timestamp_ms();
|
||||
file_transfer.last_chunk = get_unix_timestamp();
|
||||
file_transfer.transferred += chunk_size;
|
||||
if file_transfer.transferred >= file_transfer.file_size { //we downloaded all the file
|
||||
if files_transfer.index+1 == files_transfer.files.len() {
|
||||
|
@ -338,7 +281,7 @@ impl SessionManager {
|
|||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download = None;
|
||||
local_file_handle = None;
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_file_transfer_aborted(&session_id);
|
||||
ui_connection.on_received(&session_id, &buffer);
|
||||
});
|
||||
}
|
||||
protocol::Headers::ASK_LARGE_FILES => {
|
||||
|
@ -350,7 +293,7 @@ impl SessionManager {
|
|||
file_name: info.1,
|
||||
file_size: info.0,
|
||||
transferred: 0,
|
||||
last_chunk: get_unix_timestamp_ms(),
|
||||
last_chunk: get_unix_timestamp(),
|
||||
}
|
||||
}).collect();
|
||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download = Some(LargeFilesDownload {
|
||||
|
@ -426,52 +369,46 @@ impl SessionManager {
|
|||
protocol::Headers::REMOVE_AVATAR => self.set_avatar_uuid(&session_id, None),
|
||||
_ => {
|
||||
let header = buffer[0];
|
||||
let timestamp = get_unix_timestamp_sec();
|
||||
match header {
|
||||
protocol::Headers::MESSAGE => {
|
||||
let msg = Message {
|
||||
outgoing: false,
|
||||
timestamp,
|
||||
data: buffer,
|
||||
};
|
||||
self.set_seen(session_id, false);
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_new_msg(&session_id, &msg);
|
||||
});
|
||||
self.store_msg(&session_id, msg);
|
||||
}
|
||||
let buffer = match header {
|
||||
protocol::Headers::FILE => {
|
||||
if let Some((filename, content)) = protocol::parse_file(&buffer) {
|
||||
if let Some((file_name, content)) = protocol::parse_file(&buffer) {
|
||||
match self.store_file(&session_id, content) {
|
||||
Ok(file_uuid) => {
|
||||
self.set_seen(session_id, false);
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_new_file(&session_id, false, timestamp, filename, file_uuid);
|
||||
});
|
||||
self.store_msg(&session_id, Message {
|
||||
outgoing: false,
|
||||
timestamp,
|
||||
data: [&[protocol::Headers::FILE][..], file_uuid.as_bytes(), filename.as_bytes()].concat(),
|
||||
});
|
||||
Some([&[protocol::Headers::FILE][..], file_uuid.as_bytes(), file_name].concat())
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
protocol::Headers::ACCEPT_LARGE_FILES => {
|
||||
_ => {
|
||||
Some(buffer)
|
||||
}
|
||||
};
|
||||
if buffer.is_some() {
|
||||
let is_classical_message = header == protocol::Headers::MESSAGE || header == protocol::Headers::FILE;
|
||||
if is_classical_message {
|
||||
self.set_seen(session_id, false);
|
||||
} else if header == protocol::Headers::ACCEPT_LARGE_FILES {
|
||||
is_sending = true;
|
||||
last_chunks_sizes = Some(Vec::new());
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_large_files_accepted(&session_id);
|
||||
})
|
||||
}
|
||||
_ => {}
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_received(&session_id, buffer.as_ref().unwrap());
|
||||
});
|
||||
if is_classical_message {
|
||||
self.store_msg(&session_id, false, buffer.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if e != PsecError::BrokenPipe && e != PsecError::ConnectionReset {
|
||||
if e != PsecError::BrokenPipe && e != PsecError::ConnectionReset && e != PsecError::BufferTooLarge {
|
||||
print_error!(e);
|
||||
}
|
||||
break;
|
||||
|
@ -484,9 +421,11 @@ impl SessionManager {
|
|||
//don't send msg if we already encrypted a file chunk (keep PSEC nonces synchronized)
|
||||
if is_sending {
|
||||
msg_queue.push(buff);
|
||||
} else if let Err(e) = self.send_msg(session_id, &mut session_write, buff, &mut is_sending, &mut file_ack_sender).await {
|
||||
print_error!(e);
|
||||
break;
|
||||
} else {
|
||||
if let Err(e) = self.send_msg(session_id, &mut session_write, &buff, &mut is_sending, &mut file_ack_sender).await {
|
||||
print_error!(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SessionCommand::EncryptFileChunk { plain_text } => {
|
||||
|
@ -499,9 +438,9 @@ impl SessionManager {
|
|||
Ok(_) => {
|
||||
file_ack_sender = Some(ack_sender);
|
||||
//once the pre-encrypted chunk is sent, we can send the pending messages
|
||||
while !msg_queue.is_empty() {
|
||||
while msg_queue.len() > 0 {
|
||||
let msg = msg_queue.remove(0);
|
||||
if let Err(e) = self.send_msg(session_id, &mut session_write, msg, &mut is_sending, &mut file_ack_sender).await {
|
||||
if let Err(e) = self.send_msg(session_id, &mut session_write, &msg, &mut is_sending, &mut file_ack_sender).await {
|
||||
print_error!(e);
|
||||
break;
|
||||
}
|
||||
|
@ -522,28 +461,9 @@ impl SessionManager {
|
|||
}
|
||||
}
|
||||
|
||||
async fn on_session_initialized(&self, session: &mut Session, session_id: usize, is_contact: bool) -> Result<(), PsecError> {
|
||||
if is_contact {
|
||||
let pending_msgs = self.pending_msgs.lock().unwrap().get_mut(&session_id).unwrap().split_off(0);
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_sending_pending_msgs(&session_id);
|
||||
});
|
||||
for buff in pending_msgs {
|
||||
self.send_store_and_inform(session_id, session, buff).await?;
|
||||
}
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_pending_msgs_sent(&session_id);
|
||||
});
|
||||
Ok(())
|
||||
} else {
|
||||
self.encrypt_and_send(session, &protocol::ask_profile_info()).await
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_new_session(session_manager: Arc<SessionManager>, mut session: Session, outgoing: bool) {
|
||||
tokio::spawn(async move {
|
||||
let mut peer_public_key = [0; PUBLIC_KEY_LENGTH];
|
||||
session.set_max_recv_size(constants::MAX_RECV_SIZE, false);
|
||||
let session = {
|
||||
let identity = {
|
||||
session_manager.identity.read().unwrap().clone()
|
||||
|
@ -588,7 +508,7 @@ impl SessionManager {
|
|||
outgoing,
|
||||
peer_public_key,
|
||||
ip,
|
||||
sender,
|
||||
sender: sender,
|
||||
files_download: None,
|
||||
};
|
||||
let mut session_id = None;
|
||||
|
@ -618,10 +538,17 @@ impl SessionManager {
|
|||
session_manager.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_new_session(&session_id, &ip.to_string(), outgoing, &crypto::generate_fingerprint(&peer_public_key), ip, None);
|
||||
});
|
||||
match session_manager.on_session_initialized(&mut session, session_id, is_contact).await {
|
||||
Ok(_) => session_manager.session_worker(session_id, receiver, session).await,
|
||||
Err(e) => print_error!(e)
|
||||
if !is_contact {
|
||||
match session_manager.encrypt_and_send(&mut session, &protocol::ask_profile_info()).await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
session_manager.remove_session(&session_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
session_manager.session_worker(session_id, receiver, session).await;
|
||||
session_manager.remove_session(&session_id);
|
||||
}
|
||||
}
|
||||
|
@ -656,7 +583,7 @@ impl SessionManager {
|
|||
self.loaded_contacts.read().unwrap().iter().map(|c| (*c.0, c.1.name.clone(), c.1.verified, c.1.public_key)).collect()
|
||||
}
|
||||
|
||||
pub fn get_saved_msgs(&self) -> HashMap<usize, Vec<Message>> {
|
||||
pub fn get_saved_msgs(&self) -> HashMap<usize, Vec<(bool, Vec<u8>)>> {
|
||||
self.saved_msgs.read().unwrap().clone()
|
||||
}
|
||||
|
||||
|
@ -669,13 +596,16 @@ impl SessionManager {
|
|||
}
|
||||
|
||||
let mut loaded_contacts = self.loaded_contacts.write().unwrap();
|
||||
if let Some(contact) = loaded_contacts.get_mut(&session_id) {
|
||||
if contact.seen != seen {
|
||||
match self.identity.read().unwrap().as_ref().unwrap().set_contact_seen(&contact.uuid, seen) {
|
||||
Ok(_) => contact.seen = seen,
|
||||
Err(e) => print_error!(e)
|
||||
match loaded_contacts.get_mut(&session_id) {
|
||||
Some(contact) => {
|
||||
if contact.seen != seen {
|
||||
match self.identity.read().unwrap().as_ref().unwrap().set_contact_seen(&contact.uuid, seen) {
|
||||
Ok(_) => contact.seen = seen,
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -685,22 +615,20 @@ impl SessionManager {
|
|||
let contact = self.identity.read().unwrap().as_ref().unwrap().add_contact(session.name.clone(), session.avatar, session.peer_public_key)?;
|
||||
self.loaded_contacts.write().unwrap().insert(session_id, contact);
|
||||
self.last_loaded_msg_offsets.write().unwrap().insert(session_id, 0);
|
||||
self.pending_msgs.lock().unwrap().insert(session_id, Vec::new());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_contact(&self, session_id: &usize) -> Result<usize, rusqlite::Error> {
|
||||
pub fn remove_contact(&self, session_id: usize) -> Result<usize, rusqlite::Error> {
|
||||
let mut loaded_contacts = self.loaded_contacts.write().unwrap();
|
||||
let result = Identity::remove_contact(&loaded_contacts.get(session_id).unwrap().uuid);
|
||||
let result = Identity::remove_contact(&loaded_contacts.get(&session_id).unwrap().uuid);
|
||||
if result.is_ok() {
|
||||
if let Some(contact) = loaded_contacts.remove(session_id) {
|
||||
if let Some(session) = self.sessions.write().unwrap().get_mut(session_id) {
|
||||
if let Some(contact) = loaded_contacts.remove(&session_id) {
|
||||
if let Some(session) = self.sessions.write().unwrap().get_mut(&session_id) {
|
||||
session.name = contact.name;
|
||||
session.avatar = contact.avatar;
|
||||
}
|
||||
}
|
||||
self.last_loaded_msg_offsets.write().unwrap().remove(session_id);
|
||||
self.pending_msgs.lock().unwrap().remove(session_id);
|
||||
self.last_loaded_msg_offsets.write().unwrap().remove(&session_id);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
@ -725,13 +653,13 @@ impl SessionManager {
|
|||
}
|
||||
|
||||
pub fn store_file(&self, session_id: &usize, data: &[u8]) -> Result<Uuid, rusqlite::Error> {
|
||||
self.identity.read().unwrap().as_ref().unwrap().store_file(
|
||||
self.loaded_contacts.read().unwrap().get(session_id).map(|contact| contact.uuid),
|
||||
data
|
||||
)
|
||||
self.identity.read().unwrap().as_ref().unwrap().store_file(match self.loaded_contacts.read().unwrap().get(session_id) {
|
||||
Some(contact) => Some(contact.uuid),
|
||||
None => None
|
||||
}, data)
|
||||
}
|
||||
|
||||
pub fn load_msgs(&self, session_id: &usize, count: usize) -> Option<Vec<Message>> {
|
||||
pub fn load_msgs(&self, session_id: &usize, count: usize) -> Option<Vec<(bool, Vec<u8>)>> {
|
||||
let mut offsets = self.last_loaded_msg_offsets.write().unwrap();
|
||||
let msgs = self.identity.read().unwrap().as_ref().unwrap().load_msgs(
|
||||
&self.loaded_contacts.read().unwrap().get(session_id)?.uuid,
|
||||
|
@ -815,18 +743,20 @@ impl SessionManager {
|
|||
}
|
||||
*identity_guard = identity;
|
||||
if identity_guard.is_some() { //login
|
||||
if let Some(contacts) = identity_guard.as_ref().unwrap().load_contacts() {
|
||||
let mut loaded_contacts = self.loaded_contacts.write().unwrap();
|
||||
let mut session_counter = self.session_counter.write().unwrap();
|
||||
let mut not_seen = self.not_seen.write().unwrap();
|
||||
contacts.into_iter().for_each(|contact| {
|
||||
if !contact.seen {
|
||||
not_seen.push(*session_counter);
|
||||
}
|
||||
loaded_contacts.insert(*session_counter, contact);
|
||||
self.pending_msgs.lock().unwrap().insert(*session_counter, Vec::new());
|
||||
*session_counter += 1;
|
||||
});
|
||||
match identity_guard.as_ref().unwrap().load_contacts() {
|
||||
Some(contacts) => {
|
||||
let mut loaded_contacts = self.loaded_contacts.write().unwrap();
|
||||
let mut session_counter = self.session_counter.write().unwrap();
|
||||
let mut not_seen = self.not_seen.write().unwrap();
|
||||
contacts.into_iter().for_each(|contact| {
|
||||
if !contact.seen {
|
||||
not_seen.push(*session_counter);
|
||||
}
|
||||
loaded_contacts.insert(*session_counter, contact);
|
||||
*session_counter += 1;
|
||||
})
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -844,7 +774,6 @@ impl SessionManager {
|
|||
loaded_contacts: RwLock::new(HashMap::new()),
|
||||
last_loaded_msg_offsets: RwLock::new(HashMap::new()),
|
||||
saved_msgs: RwLock::new(HashMap::new()),
|
||||
pending_msgs: Mutex::new(HashMap::new()),
|
||||
not_seen: RwLock::new(Vec::new()),
|
||||
mdns_service: Mutex::new(None),
|
||||
listener_stop_signal: Mutex::new(None),
|
||||
|
|
|
@ -1,7 +1,133 @@
|
|||
use std::{fmt::Display, net::{IpAddr, TcpStream}, str::from_utf8};
|
||||
use std::net::{IpAddr, TcpStream};
|
||||
use tungstenite::{WebSocket, protocol::Role, Message};
|
||||
use uuid::Uuid;
|
||||
use crate::{identity, print_error, protocol, session_manager::{LargeFileDownload, LargeFilesDownload}, utils::to_uuid_bytes};
|
||||
use crate::{protocol, session_manager::{LargeFileDownload, LargeFilesDownload}};
|
||||
|
||||
mod ui_messages {
|
||||
use std::{fmt::Display, iter::FromIterator, net::IpAddr, str::from_utf8};
|
||||
use tungstenite::Message;
|
||||
use uuid::Uuid;
|
||||
use crate::{print_error, session_manager::{LargeFileDownload, LargeFilesDownload}, protocol, utils::to_uuid_bytes};
|
||||
|
||||
fn simple_event(command: &str, session_id: &usize) -> Message {
|
||||
Message::from(format!("{} {}", command, session_id))
|
||||
}
|
||||
fn data_list<T: Display>(command: &str, data: Vec<T>) -> Message {
|
||||
Message::from(command.to_owned()+&String::from_iter(data.into_iter().map(|i| {
|
||||
format!(" {}", i)
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn on_disconnected(session_id: &usize) -> Message {
|
||||
simple_event("disconnected", session_id)
|
||||
}
|
||||
pub fn on_new_session(session_id: &usize, name: &str, outgoing: bool, fingerprint: &str, ip: IpAddr) -> Message {
|
||||
Message::from(format!("new_session {} {} {} {} {}", session_id, outgoing, fingerprint, ip, name))
|
||||
}
|
||||
pub fn on_file_received(session_id: &usize, buffer: &[u8]) -> Option<Message> {
|
||||
let uuid = Uuid::from_bytes(to_uuid_bytes(&buffer[1..17])?);
|
||||
match from_utf8(&buffer[17..]) {
|
||||
Ok(file_name) => Some(Message::from(format!("file {} {} {}", session_id, uuid.to_string(), file_name))),
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn new_files_transfer(session_id: &usize, files_transfer: &LargeFilesDownload) -> Message {
|
||||
if files_transfer.accepted {
|
||||
let mut s = format!(
|
||||
"files_transfer {} {}",
|
||||
session_id,
|
||||
files_transfer.index
|
||||
);
|
||||
files_transfer.files.iter().for_each(|file| {
|
||||
s.push_str(&format!(
|
||||
" {} {} {} {}",
|
||||
base64::encode(&file.file_name),
|
||||
file.file_size,
|
||||
file.transferred,
|
||||
file.last_chunk,
|
||||
));
|
||||
});
|
||||
Message::from(s)
|
||||
} else {
|
||||
on_ask_large_files(session_id, &files_transfer.files, files_transfer.download_location.to_str().unwrap())
|
||||
}
|
||||
}
|
||||
pub fn on_ask_large_files(session_id: &usize, files: &Vec<LargeFileDownload>, download_location: &str) -> Message {
|
||||
let mut s = format!("ask_large_files {} {}", session_id, base64::encode(download_location));
|
||||
files.into_iter().for_each(|file| {
|
||||
s.push_str(&format!(
|
||||
" {} {}",
|
||||
base64::encode(&file.file_name),
|
||||
file.file_size,
|
||||
));
|
||||
});
|
||||
Message::from(s)
|
||||
}
|
||||
pub fn on_large_files_accepted(session_id: &usize) -> Message {
|
||||
simple_event("files_accepted", session_id)
|
||||
}
|
||||
pub fn on_file_transfer_aborted(session_id: &usize) -> Message {
|
||||
simple_event("aborted", session_id)
|
||||
}
|
||||
pub fn on_new_message(session_id: &usize, outgoing: bool, buffer: &[u8]) -> Option<Message> {
|
||||
match from_utf8(&buffer[1..]) {
|
||||
Ok(msg) => Some(Message::from(format!("{} {} {} {}", "new_message", session_id, outgoing, msg))),
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn inc_files_transfer(session_id: &usize, chunk_size: u64) -> Message {
|
||||
Message::from(format!("inc_file_transfer {} {}", session_id, chunk_size))
|
||||
}
|
||||
pub fn load_msgs(session_id: &usize, msgs: &Vec<(bool, Vec<u8>)>) -> Message {
|
||||
let mut s = format!("load_msgs {}", session_id);
|
||||
msgs.into_iter().rev().for_each(|entry| {
|
||||
match entry.1[0] {
|
||||
protocol::Headers::MESSAGE => match from_utf8(&entry.1[1..]) {
|
||||
Ok(msg) => s.push_str(&format!(" m {} {}", entry.0, base64::encode(msg))),
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
protocol::Headers::FILE => {
|
||||
let uuid = Uuid::from_bytes(to_uuid_bytes(&entry.1[1..17]).unwrap());
|
||||
match from_utf8(&entry.1[17..]) {
|
||||
Ok(file_name) => s.push_str(&format!(" f {} {} {}", entry.0, uuid.to_string(), base64::encode(file_name))),
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
Message::from(s)
|
||||
}
|
||||
pub fn set_not_seen(session_ids: Vec<usize>) -> Message {
|
||||
data_list("not_seen", session_ids)
|
||||
}
|
||||
pub fn set_local_ips(ips: Vec<IpAddr>) -> Message {
|
||||
data_list("local_ips", ips)
|
||||
}
|
||||
pub fn on_name_told(session_id: &usize, name: &str) -> Message {
|
||||
Message::from(format!("name_told {} {}", session_id, name))
|
||||
}
|
||||
pub fn on_avatar_changed(session_id: Option<&usize>) -> Message {
|
||||
match session_id {
|
||||
Some(session_id) => simple_event("avatar_changed", session_id),
|
||||
None => Message::from("avatar_changed self")
|
||||
}
|
||||
}
|
||||
pub fn set_as_contact(session_id: usize, name: &str, verified: bool, fingerprint: &str) -> Message {
|
||||
Message::from(format!("is_contact {} {} {} {}", session_id, verified, fingerprint, name))
|
||||
}
|
||||
pub fn set_name(new_name: &str) -> Message {
|
||||
Message::from(format!("set_name {}", new_name))
|
||||
}
|
||||
pub fn password_changed(success: bool, is_protected: bool) -> Message {
|
||||
Message::from(format!("password_changed {} {}", success, is_protected))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UiConnection{
|
||||
pub websocket: WebSocket<TcpStream>,
|
||||
|
@ -11,138 +137,81 @@ pub struct UiConnection{
|
|||
impl UiConnection {
|
||||
pub fn new(websocket: WebSocket<TcpStream>) -> UiConnection {
|
||||
UiConnection {
|
||||
websocket,
|
||||
websocket: websocket,
|
||||
is_valid: true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_message<T: Into<Message>>(&mut self, message: T) {
|
||||
if self.websocket.write_message(message.into()).is_err() {
|
||||
pub fn write_message(&mut self, message: Message) {
|
||||
if self.websocket.write_message(message).is_err() {
|
||||
self.is_valid = false
|
||||
}
|
||||
}
|
||||
|
||||
fn simple_event(&mut self, command: &str, session_id: &usize) {
|
||||
self.write_message(format!("{} {}", command, session_id));
|
||||
}
|
||||
fn data_list<T: Display>(command: &str, data: &[T]) -> String {
|
||||
command.to_string()+&data.iter().map(|i| {
|
||||
format!(" {}", i)
|
||||
}).collect::<String>()
|
||||
}
|
||||
|
||||
pub fn on_ask_large_files(&mut self, session_id: &usize, files: &[LargeFileDownload], download_location: &str) {
|
||||
let mut s = format!("ask_large_files {} {}", session_id, base64::encode(download_location));
|
||||
files.iter().for_each(|file| {
|
||||
s.push_str(&format!(
|
||||
" {} {}",
|
||||
base64::encode(&file.file_name),
|
||||
file.file_size,
|
||||
));
|
||||
});
|
||||
self.write_message(s);
|
||||
}
|
||||
pub fn on_large_files_accepted(&mut self, session_id: &usize) {
|
||||
self.simple_event("files_accepted", session_id);
|
||||
}
|
||||
pub fn on_file_transfer_aborted(&mut self, session_id: &usize) {
|
||||
self.simple_event("aborted", session_id);
|
||||
}
|
||||
pub fn on_new_msg(&mut self, session_id: &usize, message: &identity::Message) {
|
||||
match from_utf8(&message.data[1..]) {
|
||||
Ok(msg) => self.write_message(format!("new_message {} {} {} {}", session_id, message.outgoing, message.timestamp, msg)),
|
||||
Err(e) => print_error!(e)
|
||||
pub fn on_received(&mut self, session_id: &usize, buffer: &[u8]) {
|
||||
let ui_message = match buffer[0] {
|
||||
protocol::Headers::MESSAGE => ui_messages::on_new_message(session_id, false, buffer),
|
||||
protocol::Headers::FILE => ui_messages::on_file_received(session_id, buffer),
|
||||
protocol::Headers::ACCEPT_LARGE_FILES => Some(ui_messages::on_large_files_accepted(session_id)),
|
||||
protocol::Headers::ABORT_FILES_TRANSFER => Some(ui_messages::on_file_transfer_aborted(session_id)),
|
||||
_ => None
|
||||
};
|
||||
if ui_message.is_some() {
|
||||
self.write_message(ui_message.unwrap())
|
||||
}
|
||||
}
|
||||
pub fn on_new_file(&mut self, session_id: &usize, outgoing: bool, timestamp: u64, filename: &str, uuid: Uuid) {
|
||||
self.write_message(format!("file {} {} {} {} {}", session_id, outgoing, timestamp, uuid.to_string(), filename));
|
||||
pub fn on_ask_large_files(&mut self, session_id: &usize, files: &Vec<LargeFileDownload>, download_location: &str) {
|
||||
self.write_message(ui_messages::on_ask_large_files(session_id, files, download_location))
|
||||
}
|
||||
pub fn on_msg_sent(&mut self, session_id: usize, buffer: &[u8]) {
|
||||
match buffer[0] {
|
||||
protocol::Headers::MESSAGE => match ui_messages::on_new_message(&session_id, true, buffer) {
|
||||
Some(msg) => self.write_message(msg),
|
||||
None => {}
|
||||
}
|
||||
protocol::Headers::ABORT_FILES_TRANSFER => self.write_message(ui_messages::on_file_transfer_aborted(&session_id)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
pub fn on_new_session(&mut self, session_id: &usize, name: &str, outgoing: bool, fingerprint: &str, ip: IpAddr, files_transfer: Option<&LargeFilesDownload>) {
|
||||
self.write_message(format!("new_session {} {} {} {} {}", session_id, outgoing, fingerprint, ip, name));
|
||||
self.write_message(ui_messages::on_new_session(session_id, name, outgoing, fingerprint, ip));
|
||||
if let Some(files_transfer) = files_transfer {
|
||||
if files_transfer.accepted {
|
||||
let mut s = format!(
|
||||
"files_transfer {} {}",
|
||||
session_id,
|
||||
files_transfer.index
|
||||
);
|
||||
files_transfer.files.iter().for_each(|file| {
|
||||
s.push_str(&format!(
|
||||
" {} {} {} {}",
|
||||
base64::encode(&file.file_name),
|
||||
file.file_size,
|
||||
file.transferred,
|
||||
file.last_chunk,
|
||||
));
|
||||
});
|
||||
self.write_message(s);
|
||||
} else {
|
||||
self.on_ask_large_files(session_id, &files_transfer.files, files_transfer.download_location.to_str().unwrap())
|
||||
}
|
||||
self.write_message(ui_messages::new_files_transfer(session_id, files_transfer));
|
||||
}
|
||||
}
|
||||
pub fn on_disconnected(&mut self, session_id: &usize) {
|
||||
self.simple_event("disconnected", session_id);
|
||||
self.write_message(ui_messages::on_disconnected(session_id));
|
||||
}
|
||||
pub fn on_name_told(&mut self, session_id: &usize, name: &str) {
|
||||
self.write_message(format!("name_told {} {}", session_id, name));
|
||||
self.write_message(ui_messages::on_name_told(session_id, name));
|
||||
}
|
||||
pub fn on_avatar_changed(&mut self, session_id: Option<&usize>) {
|
||||
match session_id {
|
||||
Some(session_id) => self.simple_event("avatar_changed", session_id),
|
||||
None => self.write_message("avatar_changed self")
|
||||
}
|
||||
self.write_message(ui_messages::on_avatar_changed(session_id));
|
||||
}
|
||||
|
||||
pub fn inc_files_transfer(&mut self, session_id: &usize, chunk_size: u64) {
|
||||
self.write_message(format!("inc_file_transfer {} {}", session_id, chunk_size));
|
||||
self.write_message(ui_messages::inc_files_transfer(session_id, chunk_size));
|
||||
}
|
||||
pub fn set_as_contact(&mut self, session_id: usize, name: &str, verified: bool, fingerprint: &str) {
|
||||
self.write_message(format!("is_contact {} {} {} {}", session_id, verified, fingerprint, name));
|
||||
self.write_message(ui_messages::set_as_contact(session_id, name, verified, fingerprint));
|
||||
}
|
||||
pub fn load_msgs(&mut self, session_id: &usize, msgs: &[identity::Message]) {
|
||||
let mut s = format!("load_msgs {}", session_id);
|
||||
msgs.iter().rev().for_each(|message| {
|
||||
match message.data[0] {
|
||||
protocol::Headers::MESSAGE => match from_utf8(&message.data[1..]) {
|
||||
Ok(msg) => s.push_str(&format!(" m {} {} {}", message.outgoing, message.timestamp, base64::encode(msg))),
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
protocol::Headers::FILE => {
|
||||
let uuid = Uuid::from_bytes(to_uuid_bytes(&message.data[1..17]).unwrap());
|
||||
match from_utf8(&message.data[17..]) {
|
||||
Ok(file_name) => s.push_str(&format!(" f {} {} {} {}", message.outgoing, message.timestamp, uuid.to_string(), base64::encode(file_name))),
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
self.write_message(s);
|
||||
pub fn load_msgs(&mut self, session_id: &usize, msgs: &Vec<(bool, Vec<u8>)>) {
|
||||
self.write_message(ui_messages::load_msgs(session_id, msgs));
|
||||
}
|
||||
pub fn set_not_seen(&mut self, session_ids: &[usize]) {
|
||||
self.write_message(Self::data_list("not_seen", session_ids));
|
||||
pub fn set_not_seen(&mut self, session_ids: Vec<usize>) {
|
||||
self.write_message(ui_messages::set_not_seen(session_ids));
|
||||
}
|
||||
pub fn new_pending_msg(&mut self, session_id: &usize, is_file: bool, data: &str) {
|
||||
self.write_message(format!("pending {} {} {}", session_id, is_file, data));
|
||||
}
|
||||
pub fn on_sending_pending_msgs(&mut self, session_id: &usize) {
|
||||
self.simple_event("sending_pending_msgs", session_id);
|
||||
}
|
||||
pub fn on_pending_msgs_sent(&mut self, session_id: &usize) {
|
||||
self.simple_event("pending_msgs_sent", session_id);
|
||||
}
|
||||
pub fn set_local_ips(&mut self, ips: &[IpAddr]) {
|
||||
self.write_message(Self::data_list("local_ips", ips));
|
||||
pub fn set_local_ips(&mut self, ips: Vec<IpAddr>) {
|
||||
self.write_message(ui_messages::set_local_ips(ips));
|
||||
}
|
||||
pub fn set_name(&mut self, new_name: &str) {
|
||||
self.write_message(format!("set_name {}", new_name));
|
||||
self.write_message(ui_messages::set_name(new_name));
|
||||
}
|
||||
pub fn password_changed(&mut self, success: bool, is_protected: bool) {
|
||||
self.write_message(format!("password_changed {} {}", success, is_protected));
|
||||
self.write_message(ui_messages::password_changed(success, is_protected));
|
||||
}
|
||||
pub fn logout(&mut self) {
|
||||
self.write_message("logout");
|
||||
self.write_message(Message::from("logout"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
10
src/utils.rs
10
src/utils.rs
|
@ -1,4 +1,4 @@
|
|||
use std::{convert::TryInto, time::{SystemTime, UNIX_EPOCH}, path::Path};
|
||||
use std::{convert::TryInto, time::{SystemTime, UNIX_EPOCH}, path::PathBuf};
|
||||
use uuid::Bytes;
|
||||
use crate::print_error;
|
||||
|
||||
|
@ -16,15 +16,11 @@ pub fn escape_double_quote(origin: String) -> String {
|
|||
origin.replace("\"", "\\\"")
|
||||
}
|
||||
|
||||
pub fn get_unix_timestamp_ms() -> u128 {
|
||||
pub fn get_unix_timestamp() -> u128 {
|
||||
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis()
|
||||
}
|
||||
|
||||
pub fn get_unix_timestamp_sec() -> u64 {
|
||||
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()
|
||||
}
|
||||
|
||||
pub fn get_not_used_path(file_name: &str, parent_directory: &Path) -> String {
|
||||
pub fn get_not_used_path(file_name: &str, parent_directory: &PathBuf) -> String {
|
||||
let has_extension = file_name.matches('.').count() > 0;
|
||||
let mut path = parent_directory.join(&file_name);
|
||||
let mut n = 1;
|
||||
|
|
Loading…
Reference in New Issue