Compare commits
17 Commits
Author | SHA1 | Date |
---|---|---|
Matéo Duparc | 0bd49c092d | |
Matéo Duparc | ede41e5715 | |
Matéo Duparc | 6c5bbc3f64 | |
Matéo Duparc | 5ae61222f4 | |
Matéo Duparc | dce526922e | |
Matéo Duparc | 8c8ede431c | |
Matéo Duparc | c4417cf802 | |
Matéo Duparc | 0e1c17973b | |
Matéo Duparc | f2cfb6bda0 | |
Matéo Duparc | dda30bf5af | |
Matéo Duparc | 5081f9dbf4 | |
Matéo Duparc | 6b81cec83a | |
Matéo Duparc | 40cab75f96 | |
Matéo Duparc | 03a9df81ec | |
Matéo Duparc | 3543ef2824 | |
Matéo Duparc | 496c115fa9 | |
Matéo Duparc | dc9fde39be |
File diff suppressed because it is too large
Load Diff
40
Cargo.toml
40
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "aira"
|
name = "aira"
|
||||||
version = "0.0.3"
|
version = "0.1.1"
|
||||||
authors = ["Hardcore Sushi <hardcore.sushi@disroot.org>"]
|
authors = ["Hardcore Sushi <hardcore.sushi@disroot.org>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
exclude = ["src/frontend"]
|
exclude = ["src/frontend"]
|
||||||
|
@ -9,37 +9,41 @@ exclude = ["src/frontend"]
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rand-7 = { package = "rand", version = "0.7.3" }
|
rand-7 = { package = "rand", version = "0.7.3" }
|
||||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "net", "io-util"] }
|
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "net", "io-util"] }
|
||||||
async-psec = { version = "0.3", features = ["split"] }
|
async-psec = { version = "0.4", features = ["split"] }
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
socket2 = "0.4"
|
socket2 = "0.4"
|
||||||
rusqlite = { version = "0.25.1", features = ["bundled"] }
|
rusqlite = { version = "0.27", features = ["bundled"] }
|
||||||
ed25519-dalek = "1" #for singing
|
ed25519-dalek = "1" #for singatures
|
||||||
sha2 = "0.9"
|
sha2 = "0.10"
|
||||||
aes-gcm = "0.9"
|
aes-gcm = "0.9"
|
||||||
aes-gcm-siv = "0.10" #Database
|
aes-gcm-siv = "0.10" #database encryption
|
||||||
hkdf = "0.11"
|
hkdf = "0.12"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
actix-web = "3"
|
actix-web = "4"
|
||||||
actix-multipart = "0.3"
|
env_logger = "0.9"
|
||||||
time = "0.2" #needed for actix cookies
|
actix-multipart = "0.4"
|
||||||
|
time = "0.3" #needed for actix cookies
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
tungstenite = "0.13" #websocket
|
tungstenite = "0.17" #websocket
|
||||||
serde = "1.0" #serialization
|
serde = { version = "1.0", features = ["derive"] } #serialization
|
||||||
html-escape = "0.2"
|
html-escape = "0.2"
|
||||||
sanitize-filename = "0.3"
|
sanitize-filename = "0.3"
|
||||||
platform-dirs = "0.3"
|
platform-dirs = "0.3"
|
||||||
uuid = { version = "0.8", features = ["v4"] }
|
uuid = { version = "1.0", features = ["v4"] }
|
||||||
webbrowser = "0.5"
|
webbrowser = "0.7"
|
||||||
libmdns = "0.6" #mDNS advertiser
|
libmdns = "0.6" #mDNS advertiser
|
||||||
multicast_dns = "0.5" #mDNS browser
|
multicast_dns = "0.5" #mDNS browser
|
||||||
if-addrs = "0.6"
|
if-addrs = "0.7"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
scrypt = "0.7"
|
scrypt = "0.10"
|
||||||
zeroize = "1.2"
|
zeroize = "1.5"
|
||||||
image = "0.23"
|
image = "0.24"
|
||||||
yaml-rust = "0.4" #only in debug mode
|
yaml-rust = "0.4" #only in debug mode
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
html-minifier = "3.0"
|
html-minifier = "3.0"
|
||||||
yaml-rust = "0.4"
|
yaml-rust = "0.4"
|
||||||
linked-hash-map = "0.5"
|
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: 60 KiB After Width: | Height: | Size: 61 KiB |
|
@ -4,4 +4,5 @@ pub const APPLICATION_FOLDER: &str = "AIRA";
|
||||||
pub const DB_NAME: &str = "AIRA.db";
|
pub const DB_NAME: &str = "AIRA.db";
|
||||||
pub const HTTP_COOKIE_NAME: &str = "aira_auth";
|
pub const HTTP_COOKIE_NAME: &str = "aira_auth";
|
||||||
pub const MSG_LOADING_COUNT: usize = 20;
|
pub const MSG_LOADING_COUNT: usize = 20;
|
||||||
pub const FILE_CHUNK_SIZE: usize = 1023996;
|
pub const FILE_CHUNK_SIZE: usize = 1_023_996;
|
||||||
|
pub const MAX_RECV_SIZE: usize = 16_383_996;
|
|
@ -34,8 +34,8 @@ label {
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
margin-right: .5em;
|
margin-right: .5em;
|
||||||
width: 1.5em;
|
width: 2.5em;
|
||||||
height: 1.5em;
|
height: 2.5em;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
<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>
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -120,7 +120,7 @@ button:hover::after {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
#avatarContainer .avatar {
|
#avatarContainer .avatar {
|
||||||
font-size: 4em;
|
font-size: 2.5em;
|
||||||
}
|
}
|
||||||
#removeAvatar {
|
#removeAvatar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -149,8 +149,7 @@ button:hover::after {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
#session_info .avatar {
|
#session_info .avatar {
|
||||||
width: 6em;
|
font-size: 2.5em;
|
||||||
height: 6em;
|
|
||||||
display: block;
|
display: block;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
@ -198,7 +197,6 @@ button:hover::after {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 1.7em;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
#me>div {
|
#me>div {
|
||||||
|
@ -207,6 +205,7 @@ button:hover::after {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
#me p {
|
#me p {
|
||||||
|
font-size: 1.7em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
display: inline;
|
display: inline;
|
||||||
|
@ -214,6 +213,9 @@ button:hover::after {
|
||||||
#me>div:hover p {
|
#me>div:hover p {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
#me .avatar {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
#left_panel ul:last-of-type, #msg_log {
|
#left_panel ul:last-of-type, #msg_log {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
@ -235,8 +237,7 @@ button:hover::after {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
#left_panel ul li .avatar {
|
#left_panel ul li .avatar {
|
||||||
width: 2em;
|
font-size: .9em;
|
||||||
height: 2em;
|
|
||||||
}
|
}
|
||||||
#left_panel ul li:hover, #left_panel ul li.current {
|
#left_panel ul li:hover, #left_panel ul li.current {
|
||||||
background-color: #333940;
|
background-color: #333940;
|
||||||
|
@ -254,7 +255,8 @@ button:hover::after {
|
||||||
#left_panel ul li.is_verified p::after {
|
#left_panel ul li.is_verified p::after {
|
||||||
content: url("/static/imgs/icons/verified/ACCENT_COLOR");
|
content: url("/static/imgs/icons/verified/ACCENT_COLOR");
|
||||||
}
|
}
|
||||||
#left_panel ul li .not_seen_marker {
|
#left_panel ul li.not_seen::after {
|
||||||
|
content: "";
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
background-color: var(--accent);
|
background-color: var(--accent);
|
||||||
|
@ -295,7 +297,6 @@ button:hover::after {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px 20px;
|
padding: 20px 20px;
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
}
|
||||||
#chat_header>div {
|
#chat_header>div {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -307,6 +308,7 @@ button:hover::after {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
#chat_header>div>p { /*name*/
|
#chat_header>div>p { /*name*/
|
||||||
|
font-size: 1.5em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
@ -354,7 +356,6 @@ button:hover::after {
|
||||||
}
|
}
|
||||||
#file_transfer {
|
#file_transfer {
|
||||||
border-top: 2px solid var(--accent);
|
border-top: 2px solid var(--accent);
|
||||||
display: none;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
#file_transfer.active {
|
#file_transfer.active {
|
||||||
|
@ -401,31 +402,46 @@ button:hover::after {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: var(--accent);
|
background-color: var(--accent);
|
||||||
}
|
}
|
||||||
#message_box {
|
|
||||||
border-top: 2px solid var(--accent);
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
#msg_log {
|
#msg_log {
|
||||||
font-size: 1.1em;
|
overflow-y: auto;
|
||||||
overflow-y: scroll;
|
white-space: pre-wrap;
|
||||||
white-space: pre;
|
}
|
||||||
|
#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;
|
||||||
}
|
}
|
||||||
#msg_log p {
|
#msg_log p {
|
||||||
|
font-size: 1.1em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
#msg_log .avatar {
|
||||||
|
font-size: .8em;
|
||||||
|
}
|
||||||
#msg_log li .header {
|
#msg_log li .header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 15px;
|
|
||||||
}
|
}
|
||||||
#msg_log li .header p {
|
#msg_log li .header p {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
margin-left: .5em;
|
||||||
}
|
}
|
||||||
#msg_log li .content {
|
#msg_log li .content {
|
||||||
margin-left: 2em;
|
margin-left: 3em;
|
||||||
margin-top: 5px;
|
}
|
||||||
margin-bottom: 10px;
|
#msg_log li .content p {
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
#msg_log a {
|
#msg_log a {
|
||||||
color: #238cf5;
|
color: #238cf5;
|
||||||
|
@ -452,6 +468,149 @@ button:hover::after {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
}
|
}
|
||||||
#message_box, #chat_header, #msg_log {
|
#message_box, #message_box.online #offline_warning, #chat_header, #msg_log, #file_transfer {
|
||||||
display: none;
|
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,6 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>AIRA</title>
|
<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/commons/style.css">
|
||||||
<link rel="stylesheet" href="/static/index.css">
|
<link rel="stylesheet" href="/static/index.css">
|
||||||
</head>
|
</head>
|
||||||
|
@ -41,6 +42,10 @@
|
||||||
</div>
|
</div>
|
||||||
<ul id="msg_log">
|
<ul id="msg_log">
|
||||||
</ul>
|
</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_transfer">
|
||||||
<div id="file_control">
|
<div id="file_control">
|
||||||
<button id="file_cancel" title="Cancel"></button>
|
<button id="file_cancel" title="Cancel"></button>
|
||||||
|
@ -56,10 +61,22 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="message_box">
|
<div id="message_box">
|
||||||
<input type="text" id="message_input" placeholder="Send a message...">
|
<div id="offline_warning">
|
||||||
<label title="Send file" class="file_picker">
|
<div>
|
||||||
<input type="file" id="attach_file" multiple>
|
<h3>Your contact seems to be offline.</h3>
|
||||||
</label>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
@ -76,4 +93,3 @@
|
||||||
<script src="/static/index.js"></script>
|
<script src="/static/index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let identityName = undefined;
|
let identityName = undefined;
|
||||||
let socket = null;
|
let socket = new WebSocket("ws://"+location.hostname+":"+websocketPort+"/ws");;
|
||||||
let notificationAllowed = false;
|
let notificationAllowed = false;
|
||||||
let localIps = [];
|
let localIps = [];
|
||||||
let currentSessionId = -1;
|
let currentSessionId = -1;
|
||||||
let sessionsData = new Map();
|
let sessionsData = new Map();
|
||||||
let msgHistory = new Map();
|
let msgHistory = new Map();
|
||||||
|
let pendingMsgs = new Map();
|
||||||
let pendingFilesTransfers = new Map();
|
let pendingFilesTransfers = new Map();
|
||||||
let avatarTimestamps = new Map([
|
let avatarTimestamps = new Map([
|
||||||
["self", Date.now()]
|
["self", Date.now()]
|
||||||
|
@ -14,7 +15,7 @@ let avatarTimestamps = new Map([
|
||||||
|
|
||||||
function onClickSession(event) {
|
function onClickSession(event) {
|
||||||
let sessionId = event.currentTarget.getAttribute("data-sessionId");
|
let sessionId = event.currentTarget.getAttribute("data-sessionId");
|
||||||
if (sessionId != null) {
|
if (sessionId != null && socket.readyState === WebSocket.OPEN) {
|
||||||
currentSessionId = sessionId;
|
currentSessionId = sessionId;
|
||||||
let session = sessionsData.get(sessionId);
|
let session = sessionsData.get(sessionId);
|
||||||
if (!session.seen) {
|
if (!session.seen) {
|
||||||
|
@ -80,6 +81,7 @@ document.getElementById("delete_conversation").onclick = function() {
|
||||||
document.getElementById("add_contact").onclick = function() {
|
document.getElementById("add_contact").onclick = function() {
|
||||||
socket.send("contact "+currentSessionId);
|
socket.send("contact "+currentSessionId);
|
||||||
sessionsData.get(currentSessionId).isContact = true;
|
sessionsData.get(currentSessionId).isContact = true;
|
||||||
|
pendingMsgs.set(currentSessionId, []);
|
||||||
displayHeader();
|
displayHeader();
|
||||||
displaySessions();
|
displaySessions();
|
||||||
};
|
};
|
||||||
|
@ -103,7 +105,9 @@ document.getElementById("remove_contact").onclick = function() {
|
||||||
if (!session.isOnline) {
|
if (!session.isOnline) {
|
||||||
sessionsData.delete(currentSessionId);
|
sessionsData.delete(currentSessionId);
|
||||||
msgHistory.get(currentSessionId).length = 0;
|
msgHistory.get(currentSessionId).length = 0;
|
||||||
|
displayChatBottom();
|
||||||
}
|
}
|
||||||
|
pendingMsgs.delete(currentSessionId);
|
||||||
displayHeader();
|
displayHeader();
|
||||||
displaySessions();
|
displaySessions();
|
||||||
displayHistory();
|
displayHistory();
|
||||||
|
@ -174,7 +178,7 @@ document.getElementById("attach_file").onchange = function(event) {
|
||||||
let files = event.target.files;
|
let files = event.target.files;
|
||||||
let useLargeFileTransfer = false;
|
let useLargeFileTransfer = false;
|
||||||
for (let i=0; i<files.length; ++i) {
|
for (let i=0; i<files.length; ++i) {
|
||||||
if (files[i].size > 32760000) {
|
if (files[i].size > 16380000) {
|
||||||
useLargeFileTransfer = true;
|
useLargeFileTransfer = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -215,7 +219,11 @@ document.getElementById("attach_file").onchange = function(event) {
|
||||||
formData.append("", files[i]);
|
formData.append("", files[i]);
|
||||||
fetch("/send_file", {method: "POST", body: formData}).then(response => {
|
fetch("/send_file", {method: "POST", body: formData}).then(response => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
response.text().then(uuid => onFileSent(currentSessionId, uuid, files[i].name));
|
response.text().then(text => {
|
||||||
|
if (text === "pending") {
|
||||||
|
newPendingMsg(currentSessionId, true, files[i].name);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log(response);
|
console.log(response);
|
||||||
}
|
}
|
||||||
|
@ -226,16 +234,19 @@ document.getElementById("attach_file").onchange = function(event) {
|
||||||
document.getElementById("file_cancel").onclick = function() {
|
document.getElementById("file_cancel").onclick = function() {
|
||||||
socket.send("abort "+currentSessionId);
|
socket.send("abort "+currentSessionId);
|
||||||
};
|
};
|
||||||
let msg_log = document.getElementById("msg_log");
|
let msgLog = document.getElementById("msg_log");
|
||||||
msg_log.onscroll = function() {
|
msgLog.onscroll = function() {
|
||||||
if (sessionsData.get(currentSessionId).isContact) {
|
let session = sessionsData.get(currentSessionId);
|
||||||
if (msg_log.scrollTop < 30) {
|
if (typeof session !== "undefined") {
|
||||||
socket.send("load_msgs "+currentSessionId);
|
if (session.isContact) {
|
||||||
|
if (msgLog.scrollTop < 30) {
|
||||||
|
socket.send("load_msgs "+currentSessionId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let profile_div = document.querySelector("#me>div");
|
let profileDiv = document.querySelector("#me>div");
|
||||||
profile_div.onclick = function() {
|
profileDiv.onclick = function() {
|
||||||
let mainDiv = document.createElement("div");
|
let mainDiv = document.createElement("div");
|
||||||
mainDiv.id = "profile_info";
|
mainDiv.id = "profile_info";
|
||||||
let avatarContainer = document.createElement("div");
|
let avatarContainer = document.createElement("div");
|
||||||
|
@ -324,32 +335,35 @@ profile_div.onclick = function() {
|
||||||
changePasswordButton.textContent = "Change password";
|
changePasswordButton.textContent = "Change password";
|
||||||
changePasswordButton.onclick = function() {
|
changePasswordButton.onclick = function() {
|
||||||
let inputs = document.querySelectorAll("input[type=\"password\"]");
|
let inputs = document.querySelectorAll("input[type=\"password\"]");
|
||||||
let newPassword, newPasswordConfirm;
|
let newPassword, newPasswordConfirm, oldPassword;
|
||||||
if (isIdentityProtected) {
|
if (isIdentityProtected) {
|
||||||
newPassword = inputs[1];
|
oldPassword = inputs[0].value;
|
||||||
newPasswordConfirm = inputs[2];
|
newPassword = inputs[1].value;
|
||||||
|
newPasswordConfirm = inputs[2].value;
|
||||||
} else {
|
} else {
|
||||||
newPassword = inputs[0];
|
newPassword = inputs[0].value;
|
||||||
newPasswordConfirm = inputs[1];
|
newPasswordConfirm = inputs[1].value;
|
||||||
}
|
}
|
||||||
if (newPassword.value == newPasswordConfirm.value) {
|
if (newPassword == newPasswordConfirm) {
|
||||||
let newPassword_set = newPassword.value.length > 0;
|
let newPasswordSet = newPassword.length > 0;
|
||||||
if (isIdentityProtected || newPassword_set) { //don't change password if identity is not protected and new password is blank
|
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
|
||||||
let msg = "change_password";
|
let msg = "change_password";
|
||||||
if (isIdentityProtected) {
|
if (isIdentityProtected) {
|
||||||
msg += " "+b64EncodeUnicode(inputs[0].value);
|
msg += " "+b64EncodeUnicode(inputs[0].value);
|
||||||
}
|
}
|
||||||
if (newPassword_set) {
|
if (newPasswordSet) {
|
||||||
msg += " "+b64EncodeUnicode(newPassword.value);
|
msg += " "+b64EncodeUnicode(newPassword);
|
||||||
}
|
}
|
||||||
socket.send(msg);
|
socket.send(msg);
|
||||||
} else {
|
} else {
|
||||||
removePopup();
|
removePopup();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newPassword.value = "";
|
newPassword = "";
|
||||||
newPasswordConfirm.value = "";
|
newPasswordConfirm = "";
|
||||||
errorMsg.textContent = "Passwords don't match";
|
errorMsg.textContent = "Passwords don't match.";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
sectionPassword.appendChild(changePasswordButton);
|
sectionPassword.appendChild(changePasswordButton);
|
||||||
|
@ -436,8 +450,17 @@ function getCookie(cname) {
|
||||||
}
|
}
|
||||||
return "";
|
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() {
|
socket.onopen = function() {
|
||||||
console.log("Connected");
|
console.log("Connected");
|
||||||
socket.send(getCookie("aira_auth")); //authenticating websocket connection
|
socket.send(getCookie("aira_auth")); //authenticating websocket connection
|
||||||
|
@ -468,10 +491,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));
|
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;
|
break;
|
||||||
case "new_message":
|
case "new_message":
|
||||||
onNewMessage(args[1], args[2] === "true", msg.data.slice(args[0].length+args[1].length+args[2].length+3));
|
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));
|
||||||
break;
|
break;
|
||||||
case "file":
|
case "file":
|
||||||
onFileReceived(args[1], args[2], msg.data.slice(args[0].length+args[1].length+args[2].length+3));
|
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));
|
||||||
break;
|
break;
|
||||||
case "files_transfer":
|
case "files_transfer":
|
||||||
onNewFilesTransfer(args[1], args[2], msg.data.slice(args[0].length+args[1].length+args[2].length+3));
|
onNewFilesTransfer(args[1], args[2], msg.data.slice(args[0].length+args[1].length+args[2].length+3));
|
||||||
|
@ -500,6 +523,15 @@ socket.onmessage = function(msg) {
|
||||||
case "not_seen":
|
case "not_seen":
|
||||||
setNotSeen(msg.data.slice(args[0].length+1));
|
setNotSeen(msg.data.slice(args[0].length+1));
|
||||||
break;
|
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":
|
case "local_ips":
|
||||||
setLocalIps(msg.data.slice(args[0].length+1));
|
setLocalIps(msg.data.slice(args[0].length+1));
|
||||||
break;
|
break;
|
||||||
|
@ -519,6 +551,12 @@ socket.onmessage = function(msg) {
|
||||||
};
|
};
|
||||||
socket.onclose = function() {
|
socket.onclose = function() {
|
||||||
console.log("Disconnected");
|
console.log("Disconnected");
|
||||||
|
currentSessionId = -1;
|
||||||
|
displayHistory();
|
||||||
|
displayHeader();
|
||||||
|
displayChatBottom();
|
||||||
|
displaySessions();
|
||||||
|
document.getElementById("disconnected").classList.add("disconnected");
|
||||||
};
|
};
|
||||||
|
|
||||||
function onNewSession(sessionId, outgoing, fingerprint, ip, name) {
|
function onNewSession(sessionId, outgoing, fingerprint, ip, name) {
|
||||||
|
@ -564,6 +602,22 @@ function setNotSeen(strSessionIds) {
|
||||||
}
|
}
|
||||||
displaySessions();
|
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) {
|
function setLocalIps(strIPs) {
|
||||||
localIps = strIPs.split(' ');
|
localIps = strIPs.split(' ');
|
||||||
}
|
}
|
||||||
|
@ -576,6 +630,7 @@ function onIsContact(sessionId, verified, fingerprint, name) {
|
||||||
} else {
|
} else {
|
||||||
addSession(sessionId, name, undefined, fingerprint, undefined, true, verified, false);
|
addSession(sessionId, name, undefined, fingerprint, undefined, true, verified, false);
|
||||||
}
|
}
|
||||||
|
pendingMsgs.set(sessionId, []);
|
||||||
}
|
}
|
||||||
function onMsgOrFileReceived(sessionId, outgoing, body) {
|
function onMsgOrFileReceived(sessionId, outgoing, body) {
|
||||||
if (currentSessionId == sessionId) {
|
if (currentSessionId == sessionId) {
|
||||||
|
@ -589,16 +644,22 @@ function onMsgOrFileReceived(sessionId, outgoing, body) {
|
||||||
}
|
}
|
||||||
if (document.hidden && !outgoing) {
|
if (document.hidden && !outgoing) {
|
||||||
if (notificationAllowed) {
|
if (notificationAllowed) {
|
||||||
new Notification(sessionsData.get(sessionId).name, {
|
let sessionName = sessionsData.get(sessionId).name;
|
||||||
"body": body
|
new Notification(sessionName, {
|
||||||
|
"body": body,
|
||||||
|
"icon": "/avatar/"+sessionId+"/"+sessionName+"?"+avatarTimestamps.get(sessionId)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function onNewMessage(sessionId, outgoing, msg) {
|
function onNewMessage(sessionId, outgoing, timestamp, msg) {
|
||||||
msgHistory.get(sessionId).push([outgoing, false, msg]);
|
msgHistory.get(sessionId).push([outgoing, timestamp, false, msg]);
|
||||||
onMsgOrFileReceived(sessionId, outgoing, 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) {
|
function onNewFilesTransfer(sessionId, index, filesInfo) {
|
||||||
let split = filesInfo.split(' ');
|
let split = filesInfo.split(' ');
|
||||||
let files = [];
|
let files = [];
|
||||||
|
@ -683,7 +744,8 @@ function onAskLargeFiles(sessionId, encodedDownloadLocation, filesInfo) {
|
||||||
showPopup(mainDiv, false);
|
showPopup(mainDiv, false);
|
||||||
if (document.hidden && notificationAllowed) {
|
if (document.hidden && notificationAllowed) {
|
||||||
new Notification(sessionName, {
|
new Notification(sessionName, {
|
||||||
"body": "Files download request"
|
"body": "Files download request",
|
||||||
|
"icon": "/avatar/"+sessionId+"/"+sessionName+"?"+avatarTimestamps.get(sessionId)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -726,29 +788,32 @@ function onIncFilesTransfer(sessionId, chunkSize) {
|
||||||
}
|
}
|
||||||
function onMsgsLoad(sessionId, strMsgs) {
|
function onMsgsLoad(sessionId, strMsgs) {
|
||||||
let msgs = strMsgs.split(' ');
|
let msgs = strMsgs.split(' ');
|
||||||
let n = 0;
|
if (msgs.length > 3) {
|
||||||
while (n < msgs.length) {
|
let n = 0;
|
||||||
let outgoing = msgs[n+1] === "true";
|
while (n < msgs.length) {
|
||||||
switch (msgs[n]) {
|
let outgoing = msgs[n+1] === "true";
|
||||||
case 'm':
|
let timestamp = parseTimestamp(msgs[n+2]);
|
||||||
let msg = b64DecodeUnicode(msgs[n+2]);
|
switch (msgs[n]) {
|
||||||
msgHistory.get(sessionId).unshift([outgoing, false, msg]);
|
case 'm':
|
||||||
n += 3;
|
let msg = b64DecodeUnicode(msgs[n+3]);
|
||||||
break;
|
msgHistory.get(sessionId).unshift([outgoing, timestamp, false, msg]);
|
||||||
case 'f':
|
n += 4;
|
||||||
let uuid = msgs[n+2];
|
break;
|
||||||
let fileName = b64DecodeUnicode(msgs[n+3]);
|
case 'f':
|
||||||
msgHistory.get(sessionId).unshift([outgoing, true, [uuid, fileName]]);
|
let uuid = msgs[n+3];
|
||||||
n += 4;
|
let fileName = b64DecodeUnicode(msgs[n+4]);
|
||||||
|
msgHistory.get(sessionId).unshift([outgoing, timestamp, true, [uuid, fileName]]);
|
||||||
|
n += 5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if (currentSessionId == sessionId) {
|
||||||
if (currentSessionId == sessionId) {
|
if (msgLog.scrollHeight - msgLog.scrollTop === msgLog.clientHeight) {
|
||||||
if (msg_log.scrollHeight - msg_log.scrollTop === msg_log.clientHeight) {
|
displayHistory();
|
||||||
displayHistory();
|
} else {
|
||||||
} else {
|
let backupHeight = msgLog.scrollHeight;
|
||||||
let backupHeight = msg_log.scrollHeight;
|
displayHistory(false);
|
||||||
displayHistory(false);
|
msgLog.scrollTop = msgLog.scrollHeight-backupHeight;
|
||||||
msg_log.scrollTop = msg_log.scrollHeight-backupHeight;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -762,6 +827,7 @@ function onDisconnected(sessionId) {
|
||||||
}
|
}
|
||||||
if (currentSessionId == sessionId) {
|
if (currentSessionId == sessionId) {
|
||||||
displayChatBottom();
|
displayChatBottom();
|
||||||
|
scrollHistoryToBottom();
|
||||||
}
|
}
|
||||||
if (currentSessionId == sessionId && !session.isContact) {
|
if (currentSessionId == sessionId && !session.isContact) {
|
||||||
currentSessionId = -1;
|
currentSessionId = -1;
|
||||||
|
@ -769,16 +835,6 @@ function onDisconnected(sessionId) {
|
||||||
}
|
}
|
||||||
displaySessions();
|
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) {
|
function onNameSet(newName) {
|
||||||
removePopup();
|
removePopup();
|
||||||
identityName = newName;
|
identityName = newName;
|
||||||
|
@ -925,11 +981,11 @@ function logout() {
|
||||||
window.location = "/logout";
|
window.location = "/logout";
|
||||||
}
|
}
|
||||||
function displayProfile() {
|
function displayProfile() {
|
||||||
profile_div.innerHTML = "";
|
profileDiv.innerHTML = "";
|
||||||
profile_div.appendChild(generateSelfAvatar(avatarTimestamps.get("self")));
|
profileDiv.appendChild(generateSelfAvatar(avatarTimestamps.get("self")));
|
||||||
let p = document.createElement("p");
|
let p = document.createElement("p");
|
||||||
p.textContent = identityName;
|
p.textContent = identityName;
|
||||||
profile_div.appendChild(p);
|
profileDiv.appendChild(p);
|
||||||
}
|
}
|
||||||
function displayHeader() {
|
function displayHeader() {
|
||||||
chatHeader.children[0].innerHTML = "";
|
chatHeader.children[0].innerHTML = "";
|
||||||
|
@ -1002,9 +1058,7 @@ function generateSession(sessionId, session) {
|
||||||
li.classList.add("is_verified");
|
li.classList.add("is_verified");
|
||||||
}
|
}
|
||||||
if (!session.seen) {
|
if (!session.seen) {
|
||||||
let marker = document.createElement("div");
|
li.classList.add("not_seen");
|
||||||
marker.classList.add("not_seen_marker");
|
|
||||||
li.appendChild(marker);
|
|
||||||
}
|
}
|
||||||
if (sessionId == currentSessionId) {
|
if (sessionId == currentSessionId) {
|
||||||
li.classList.add("current");
|
li.classList.add("current");
|
||||||
|
@ -1027,20 +1081,27 @@ function generateMsgHeader(name, sessionId) {
|
||||||
div.appendChild(p);
|
div.appendChild(p);
|
||||||
return div;
|
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) {
|
function generateMessage(name, sessionId, msg) {
|
||||||
let p = document.createElement("p");
|
let p = document.createElement("p");
|
||||||
p.appendChild(document.createTextNode(msg));
|
p.appendChild(document.createTextNode(msg));
|
||||||
let div = document.createElement("div");
|
let div = document.createElement("div");
|
||||||
div.classList.add("content");
|
div.classList.add("content");
|
||||||
div.appendChild(linkifyElement(p));
|
div.appendChild(linkifyElement(p));
|
||||||
let li = document.createElement("li");
|
let divContainer = document.createElement("div");
|
||||||
if (typeof name !== "undefined") {
|
if (typeof name !== "undefined") {
|
||||||
li.appendChild(generateMsgHeader(name, sessionId));
|
divContainer.appendChild(generateMsgHeader(name, sessionId));
|
||||||
}
|
}
|
||||||
li.appendChild(div);
|
divContainer.appendChild(div);
|
||||||
return li;
|
return divContainer;
|
||||||
}
|
}
|
||||||
function generateFile(name, sessionId, outgoing, file_info) {
|
function generateFile(name, sessionId, outgoing, fileInfo) {
|
||||||
let div1 = document.createElement("div");
|
let div1 = document.createElement("div");
|
||||||
div1.classList.add("file");
|
div1.classList.add("file");
|
||||||
div1.classList.add("content");
|
div1.classList.add("content");
|
||||||
|
@ -1052,20 +1113,24 @@ function generateFile(name, sessionId, outgoing, file_info) {
|
||||||
h4.textContent = "File received:";
|
h4.textContent = "File received:";
|
||||||
}
|
}
|
||||||
div2.appendChild(h4);
|
div2.appendChild(h4);
|
||||||
let p = document.createElement("p");
|
|
||||||
p.textContent = file_info[1];
|
|
||||||
div2.appendChild(p);
|
|
||||||
div1.appendChild(div2);
|
div1.appendChild(div2);
|
||||||
let a = document.createElement("a");
|
let p = document.createElement("p");
|
||||||
a.href = "/load_file?uuid="+file_info[0]+"&file_name="+encodeURIComponent(file_info[1]);
|
if (typeof fileInfo === "string") { //pending
|
||||||
a.target = "_blank";
|
p.textContent = fileInfo;
|
||||||
div1.appendChild(a);
|
} else {
|
||||||
let li = document.createElement("li");
|
p.textContent = fileInfo[1];
|
||||||
if (typeof name !== "undefined") {
|
let a = document.createElement("a");
|
||||||
li.appendChild(generateMsgHeader(name, sessionId));
|
a.href = "/load_file?uuid="+fileInfo[0]+"&file_name="+encodeURIComponent(fileInfo[1]);
|
||||||
|
a.target = "_blank";
|
||||||
|
div1.appendChild(a);
|
||||||
}
|
}
|
||||||
li.appendChild(div1);
|
div2.appendChild(p);
|
||||||
return li;
|
let divContainer = document.createElement("div");
|
||||||
|
if (typeof name !== "undefined") {
|
||||||
|
divContainer.appendChild(generateMsgHeader(name, sessionId));
|
||||||
|
}
|
||||||
|
divContainer.appendChild(div1);
|
||||||
|
return divContainer;
|
||||||
}
|
}
|
||||||
function generateFileInfo(fileName, fileSize, p) {
|
function generateFileInfo(fileName, fileSize, p) {
|
||||||
let span = document.createElement("span");
|
let span = document.createElement("span");
|
||||||
|
@ -1078,13 +1143,16 @@ function displayChatBottom(speed = undefined) {
|
||||||
let fileTransfer = document.getElementById("file_transfer");
|
let fileTransfer = document.getElementById("file_transfer");
|
||||||
let session = sessionsData.get(currentSessionId);
|
let session = sessionsData.get(currentSessionId);
|
||||||
if (typeof session === "undefined") {
|
if (typeof session === "undefined") {
|
||||||
msgBox.removeAttribute("style");
|
msgBox.classList.remove("active");
|
||||||
fileTransfer.classList.remove("active");
|
fileTransfer.classList.remove("active");
|
||||||
} else {
|
} else {
|
||||||
|
if (session.isContact || session.isOnline) {
|
||||||
|
msgBox.classList.add("active");
|
||||||
|
}
|
||||||
if (session.isOnline) {
|
if (session.isOnline) {
|
||||||
msgBox.style.display = "flex";
|
msgBox.classList.add("online");
|
||||||
} else {
|
} else {
|
||||||
msgBox.removeAttribute("style");
|
msgBox.classList.remove("online");
|
||||||
}
|
}
|
||||||
if (pendingFilesTransfers.has(currentSessionId)) {
|
if (pendingFilesTransfers.has(currentSessionId)) {
|
||||||
let fileInfo = document.getElementById("file_info");
|
let fileInfo = document.getElementById("file_info");
|
||||||
|
@ -1129,35 +1197,72 @@ function displayChatBottom(speed = undefined) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function scrollHistoryToBottom() {
|
||||||
|
msgLog.scrollTop = msgLog.scrollHeight;
|
||||||
|
}
|
||||||
function displayHistory(scrollToBottom = true) {
|
function displayHistory(scrollToBottom = true) {
|
||||||
msg_log.style.display = "block";
|
msgLog.innerHTML = "";
|
||||||
msg_log.innerHTML = "";
|
|
||||||
let session = sessionsData.get(currentSessionId);
|
let session = sessionsData.get(currentSessionId);
|
||||||
let previousOutgoing = undefined;
|
if (typeof session === "undefined") {
|
||||||
msgHistory.get(currentSessionId).forEach(entry => {
|
msgLog.style.display = "none";
|
||||||
let name = undefined;
|
} else {
|
||||||
let sessionId = undefined;
|
msgLog.style.display = "block";
|
||||||
if (previousOutgoing != entry[0]) {
|
let previousOutgoing = undefined;
|
||||||
previousOutgoing = entry[0];
|
msgHistory.get(currentSessionId).forEach(entry => {
|
||||||
if (entry[0]) { //outgoing msg
|
let name = undefined;
|
||||||
name = identityName;
|
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]);
|
||||||
} else {
|
} else {
|
||||||
name = session.name;
|
div = generateMessage(name, sessionId, entry[3]);
|
||||||
sessionId = currentSessionId;
|
}
|
||||||
|
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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (entry[1]) { //is file
|
if (scrollToBottom) {
|
||||||
msg_log.appendChild(generateFile(name, sessionId, entry[0], entry[2]));
|
scrollHistoryToBottom();
|
||||||
} 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);
|
socket.send("load_msgs "+currentSessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>AIRA - Login</title>
|
<title>AIRA - Login</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/commons/style.css">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
@ -58,8 +59,7 @@
|
||||||
padding: 10px 50px;
|
padding: 10px 50px;
|
||||||
}
|
}
|
||||||
.avatar {
|
.avatar {
|
||||||
width: 7em;
|
font-size: 3em;
|
||||||
height: 7em;
|
|
||||||
}
|
}
|
||||||
#identity h2 {
|
#identity h2 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>AIRA - Logged out</title>
|
<title>AIRA - Logged out</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/commons/style.css">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
|
124
src/identity.rs
124
src/identity.rs
|
@ -43,12 +43,11 @@ fn get_database_path() -> String {
|
||||||
AppDirs::new(Some(constants::APPLICATION_FOLDER), false).unwrap().data_dir.join(constants::DB_NAME).to_str().unwrap().to_owned()
|
AppDirs::new(Some(constants::APPLICATION_FOLDER), false).unwrap().data_dir.join(constants::DB_NAME).to_str().unwrap().to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EncryptedIdentity {
|
#[derive(Debug, Clone)]
|
||||||
name: String,
|
pub struct Message {
|
||||||
encrypted_keypair: Vec<u8>,
|
pub outgoing: bool,
|
||||||
salt: Vec<u8>,
|
pub timestamp: u64,
|
||||||
encrypted_master_key: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
encrypted_use_padding: Vec<u8>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Contact {
|
pub struct Contact {
|
||||||
|
@ -60,6 +59,14 @@ pub struct Contact {
|
||||||
pub seen: bool,
|
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 struct Identity {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub keypair: Keypair,
|
pub keypair: Keypair,
|
||||||
|
@ -87,8 +94,8 @@ impl Identity {
|
||||||
};
|
};
|
||||||
Ok(Contact {
|
Ok(Contact {
|
||||||
uuid: contact_uuid,
|
uuid: contact_uuid,
|
||||||
public_key: public_key,
|
public_key,
|
||||||
name: name,
|
name,
|
||||||
avatar: avatar_uuid,
|
avatar: avatar_uuid,
|
||||||
verified: false,
|
verified: false,
|
||||||
seen: true,
|
seen: true,
|
||||||
|
@ -242,16 +249,17 @@ impl Identity {
|
||||||
Ok(file_uuid)
|
Ok(file_uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store_msg(&self, contact_uuid: &Uuid, outgoing: bool, data: &[u8]) -> Result<usize, rusqlite::Error> {
|
pub fn store_msg(&self, contact_uuid: &Uuid, message: &Message) -> Result<usize, rusqlite::Error> {
|
||||||
let db = Connection::open(get_database_path())?;
|
let db = Connection::open(get_database_path())?;
|
||||||
db.execute(&format!("CREATE TABLE IF NOT EXISTS \"{}\" (outgoing BLOB, data BLOB)", contact_uuid), [])?;
|
db.execute(&format!("CREATE TABLE IF NOT EXISTS \"{}\" (outgoing BLOB, timestamp BLOB, data BLOB)", contact_uuid), [])?;
|
||||||
let outgoing_byte: u8 = bool_to_byte(outgoing);
|
let outgoing_byte: u8 = bool_to_byte(message.outgoing);
|
||||||
let encrypted_outgoing = crypto::encrypt_data(&[outgoing_byte], &self.master_key).unwrap();
|
let encrypted_outgoing = crypto::encrypt_data(&[outgoing_byte], &self.master_key).unwrap();
|
||||||
let encrypted_data = crypto::encrypt_data(data, &self.master_key).unwrap();
|
let encrypted_timestamp = crypto::encrypt_data(&message.timestamp.to_be_bytes(), &self.master_key).unwrap();
|
||||||
db.execute(&format!("INSERT INTO \"{}\" (outgoing, data) VALUES (?1, ?2)", contact_uuid), params![encrypted_outgoing, encrypted_data])
|
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])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_msgs(&self, contact_uuid: &Uuid, offset: usize, mut count: usize) -> Option<Vec<(bool, Vec<u8>)>> {
|
pub fn load_msgs(&self, contact_uuid: &Uuid, offset: usize, mut count: usize) -> Option<Vec<Message>> {
|
||||||
match Connection::open(get_database_path()) {
|
match Connection::open(get_database_path()) {
|
||||||
Ok(db) => {
|
Ok(db) => {
|
||||||
if let Ok(mut stmt) = db.prepare(&format!("SELECT count(*) FROM \"{}\"", contact_uuid)) {
|
if let Ok(mut stmt) = db.prepare(&format!("SELECT count(*) FROM \"{}\"", contact_uuid)) {
|
||||||
|
@ -262,24 +270,30 @@ impl Identity {
|
||||||
if offset+count >= total {
|
if offset+count >= total {
|
||||||
count = total-offset;
|
count = total-offset;
|
||||||
}
|
}
|
||||||
let mut stmt = db.prepare(&format!("SELECT outgoing, data FROM \"{}\" LIMIT {} OFFSET {}", contact_uuid, count, total-offset-count)).unwrap();
|
let mut stmt = db.prepare(&format!("SELECT outgoing, timestamp, data FROM \"{}\" LIMIT {} OFFSET {}", contact_uuid, count, total-offset-count)).unwrap();
|
||||||
let mut rows = stmt.query([]).unwrap();
|
let mut rows = stmt.query([]).unwrap();
|
||||||
let mut msgs = Vec::new();
|
let mut msgs = Vec::new();
|
||||||
while let Ok(Some(row)) = rows.next() {
|
while let Ok(Some(row)) = rows.next() {
|
||||||
let encrypted_outgoing: Vec<u8> = row.get(0).unwrap();
|
let encrypted_outgoing: Vec<u8> = row.get(0).unwrap();
|
||||||
match crypto::decrypt_data(encrypted_outgoing.as_slice(), &self.master_key){
|
match crypto::decrypt_data(encrypted_outgoing.as_slice(), &self.master_key){
|
||||||
Ok(outgoing) => {
|
Ok(outgoing) => {
|
||||||
match byte_to_bool(outgoing[0]) {
|
if let Ok(outgoing) = byte_to_bool(outgoing[0]) {
|
||||||
Ok(outgoing) => {
|
let encrypted_timestamp: Vec<u8> = row.get(1).unwrap();
|
||||||
let encrypted_data: Vec<u8> = row.get(1).unwrap();
|
match crypto::decrypt_data(&encrypted_timestamp, &self.master_key) {
|
||||||
match crypto::decrypt_data(encrypted_data.as_slice(), &self.master_key) {
|
Ok(timestamp) => {
|
||||||
Ok(data) => msgs.push((outgoing, data)),
|
let encrypted_data: Vec<u8> = row.get(2).unwrap();
|
||||||
Err(e) => print_error!(e)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Err(e) => print_error!(e)
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Err(e) => print_error!(e)
|
Err(e) => print_error!(e)
|
||||||
}
|
}
|
||||||
|
@ -364,14 +378,8 @@ impl Identity {
|
||||||
pub fn load_identity(password: Option<&[u8]>) -> Result<Identity, String> {
|
pub fn load_identity(password: Option<&[u8]>) -> Result<Identity, String> {
|
||||||
match Identity::load_encrypted_identity() {
|
match Identity::load_encrypted_identity() {
|
||||||
Ok(encrypted_identity) => {
|
Ok(encrypted_identity) => {
|
||||||
let master_key: [u8; crypto::MASTER_KEY_LEN] = if password.is_none() {
|
let master_key: [u8; crypto::MASTER_KEY_LEN] = match password {
|
||||||
if encrypted_identity.encrypted_master_key.len() == crypto::MASTER_KEY_LEN {
|
Some(password) => match crypto::decrypt_master_key(&encrypted_identity.encrypted_master_key, password, &encrypted_identity.salt) {
|
||||||
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,
|
Ok(master_key) => master_key,
|
||||||
Err(e) => return Err(
|
Err(e) => return Err(
|
||||||
match e {
|
match e {
|
||||||
|
@ -380,6 +388,11 @@ 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) {
|
match crypto::decrypt_data(&encrypted_identity.encrypted_keypair, &master_key) {
|
||||||
Ok(keypair) => {
|
Ok(keypair) => {
|
||||||
|
@ -425,13 +438,16 @@ impl Identity {
|
||||||
let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
|
let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
|
||||||
db.set(DBKeys::NAME, name.as_bytes())?;
|
db.set(DBKeys::NAME, name.as_bytes())?;
|
||||||
db.set(DBKeys::KEYPAIR, &encrypted_keypair)?;
|
db.set(DBKeys::KEYPAIR, &encrypted_keypair)?;
|
||||||
let salt = if password.is_none() { //no password
|
let salt = match password {
|
||||||
db.set(DBKeys::MASTER_KEY, &master_key)?; //storing master_key in plaintext
|
Some(password) => {
|
||||||
[0; crypto::SALT_LEN]
|
let (salt, encrypted_master_key) = crypto::encrypt_master_key(master_key, password);
|
||||||
} else {
|
db.set(DBKeys::MASTER_KEY, &encrypted_master_key)?;
|
||||||
let (salt, encrypted_master_key) = crypto::encrypt_master_key(master_key, password.unwrap());
|
salt
|
||||||
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]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
db.set(DBKeys::SALT, &salt)?;
|
db.set(DBKeys::SALT, &salt)?;
|
||||||
let encrypted_use_padding = crypto::encrypt_data(&[bool_to_byte(true)], &master_key).unwrap();
|
let encrypted_use_padding = crypto::encrypt_data(&[bool_to_byte(true)], &master_key).unwrap();
|
||||||
|
@ -446,13 +462,16 @@ impl Identity {
|
||||||
|
|
||||||
fn update_master_key(master_key: [u8; crypto::MASTER_KEY_LEN], new_password: Option<&[u8]>) -> Result<usize, rusqlite::Error> {
|
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 db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
|
||||||
let salt = if new_password.is_none() { //no password
|
let salt = match new_password {
|
||||||
db.update(DBKeys::MASTER_KEY, &master_key)?;
|
Some(new_password) => {
|
||||||
[0; crypto::SALT_LEN]
|
let (salt, encrypted_master_key) = crypto::encrypt_master_key(master_key, new_password);
|
||||||
} else {
|
db.update(DBKeys::MASTER_KEY, &encrypted_master_key)?;
|
||||||
let (salt, encrypted_master_key) = crypto::encrypt_master_key(master_key, new_password.unwrap());
|
salt
|
||||||
db.update(DBKeys::MASTER_KEY, &encrypted_master_key)?;
|
}
|
||||||
salt
|
None => {
|
||||||
|
db.update(DBKeys::MASTER_KEY, &master_key)?;
|
||||||
|
[0; crypto::SALT_LEN]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
db.update(DBKeys::SALT, &salt)
|
db.update(DBKeys::SALT, &salt)
|
||||||
}
|
}
|
||||||
|
@ -460,20 +479,19 @@ impl Identity {
|
||||||
pub fn change_password(old_password: Option<&[u8]>, new_password: Option<&[u8]>) -> Result<bool, String> {
|
pub fn change_password(old_password: Option<&[u8]>, new_password: Option<&[u8]>) -> Result<bool, String> {
|
||||||
match Identity::load_encrypted_identity() {
|
match Identity::load_encrypted_identity() {
|
||||||
Ok(encrypted_identity) => {
|
Ok(encrypted_identity) => {
|
||||||
let master_key: [u8; crypto::MASTER_KEY_LEN] = if old_password.is_none() {
|
let master_key: [u8; crypto::MASTER_KEY_LEN] = match old_password {
|
||||||
if encrypted_identity.encrypted_master_key.len() == crypto::MASTER_KEY_LEN {
|
Some(old_password) => match crypto::decrypt_master_key(&encrypted_identity.encrypted_master_key, old_password, &encrypted_identity.salt) {
|
||||||
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,
|
Ok(master_key) => master_key,
|
||||||
Err(e) => return match e {
|
Err(e) => return match e {
|
||||||
CryptoError::DecryptionFailed => Ok(false),
|
CryptoError::DecryptionFailed => Ok(false),
|
||||||
CryptoError::InvalidLength => Err(String::from(DATABASE_CORRUPED_ERROR))
|
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) {
|
match Identity::update_master_key(master_key, new_password) {
|
||||||
Ok(_) => Ok(true),
|
Ok(_) => Ok(true),
|
||||||
|
|
|
@ -12,7 +12,7 @@ impl<'a> KeyValueTable<'a> {
|
||||||
Ok(KeyValueTable {db, table_name})
|
Ok(KeyValueTable {db, table_name})
|
||||||
}
|
}
|
||||||
pub fn set(&self, key: &str, value: &[u8]) -> Result<usize, Error> {
|
pub fn set(&self, key: &str, value: &[u8]) -> Result<usize, Error> {
|
||||||
Ok(self.db.execute(&format!("INSERT INTO {} (key, value) VALUES (?1, ?2)", self.table_name), params![key, value])?)
|
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> {
|
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))?;
|
let mut stmt = self.db.prepare(&format!("SELECT value FROM {} WHERE key=\"{}\"", self.table_name, key))?;
|
||||||
|
|
307
src/main.rs
307
src/main.rs
|
@ -8,12 +8,12 @@ mod ui_interface;
|
||||||
mod constants;
|
mod constants;
|
||||||
mod discovery;
|
mod discovery;
|
||||||
|
|
||||||
use std::{env, fs, io, net::SocketAddr, str::{FromStr, from_utf8}, sync::{Arc, RwLock}};
|
use std::{env, fs, io::{self, Cursor}, net::SocketAddr, str::{FromStr, from_utf8}, sync::{Arc, RwLock}, cmp::Ordering};
|
||||||
use image::GenericImageView;
|
use image::GenericImageView;
|
||||||
use tokio::{net::TcpListener, runtime::Handle, sync::mpsc};
|
use tokio::{net::TcpListener, runtime::Handle, sync::mpsc, task::JoinError};
|
||||||
use actix_web::{App, HttpMessage, HttpRequest, HttpResponse, HttpServer, http::{header, CookieBuilder}, web, web::Data};
|
|
||||||
use actix_multipart::Multipart;
|
|
||||||
use tungstenite::Message;
|
use tungstenite::Message;
|
||||||
|
use actix_web::{App, HttpRequest, HttpResponse, HttpServer, http::header, cookie::CookieBuilder, web, web::Data};
|
||||||
|
use actix_multipart::Multipart;
|
||||||
use futures::{StreamExt, TryStreamExt};
|
use futures::{StreamExt, TryStreamExt};
|
||||||
use rand::{RngCore, rngs::OsRng};
|
use rand::{RngCore, rngs::OsRng};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -25,41 +25,36 @@ use identity::Identity;
|
||||||
use session_manager::{SessionManager, SessionCommand};
|
use session_manager::{SessionManager, SessionCommand};
|
||||||
use ui_interface::UiConnection;
|
use ui_interface::UiConnection;
|
||||||
|
|
||||||
async fn start_websocket_server(global_vars: Arc<RwLock<GlobalVars>>) -> u16 {
|
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("127.0.0.1".to_owned());
|
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("0".to_owned());
|
let websocket_port = env::var("AIRA_WEBSOCKET_PORT").unwrap_or_else(|_| "0".to_owned());
|
||||||
let server = TcpListener::bind(websocket_bind_addr+":"+&websocket_port).await.unwrap();
|
let server = TcpListener::bind(websocket_bind_addr+":"+&websocket_port).await.unwrap();
|
||||||
let websocket_port = server.local_addr().unwrap().port();
|
let websocket_port = server.local_addr().unwrap().port();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let worker_done = Arc::new(RwLock::new(true));
|
|
||||||
loop {
|
loop {
|
||||||
let (stream, _addr) = server.accept().await.unwrap();
|
let (stream, _addr) = server.accept().await.unwrap();
|
||||||
if *worker_done.read().unwrap() {
|
let ui_auth_token = {
|
||||||
let ui_auth_token = {
|
ui_auth_token.read().unwrap().clone()
|
||||||
global_vars.clone().read().unwrap().ui_auth_token.clone()
|
};
|
||||||
};
|
if let Some(ui_auth_token) = ui_auth_token {
|
||||||
if let Some(ui_auth_token) = ui_auth_token {
|
let stream = stream.into_std().unwrap();
|
||||||
let stream = stream.into_std().unwrap();
|
stream.set_nonblocking(false).unwrap();
|
||||||
stream.set_nonblocking(false).unwrap();
|
match tungstenite::accept(stream) {
|
||||||
match tungstenite::accept(stream.try_clone().unwrap()) {
|
Ok(mut websocket) => {
|
||||||
Ok(mut websocket) => {
|
if let Ok(message) = websocket.read_message() { //waiting for auth token
|
||||||
if let Ok(message) = websocket.read_message() { //waiting for auth token
|
match message.into_text() {
|
||||||
match message.into_text() {
|
Ok(token) => {
|
||||||
Ok(token) => {
|
if token == ui_auth_token {
|
||||||
if token == ui_auth_token {
|
let ui_connection = UiConnection::new(websocket);
|
||||||
let ui_connection = UiConnection::new(websocket);
|
session_manager.set_ui_connection(ui_connection.clone());
|
||||||
let global_vars = global_vars.clone();
|
websocket_worker(ui_connection, session_manager.clone()).await.unwrap();
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,7 +65,6 @@ async fn start_websocket_server(global_vars: Arc<RwLock<GlobalVars>>) -> u16 {
|
||||||
fn discover_peers(session_manager: Arc<SessionManager>) {
|
fn discover_peers(session_manager: Arc<SessionManager>) {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
discovery::discover_peers(move |discovery_manager, ip| {
|
discovery::discover_peers(move |discovery_manager, ip| {
|
||||||
println!("New peer discovered: {}", ip);
|
|
||||||
let session_manager = session_manager.clone();
|
let session_manager = session_manager.clone();
|
||||||
if session_manager.is_identity_loaded() {
|
if session_manager.is_identity_loaded() {
|
||||||
tokio::spawn( async move {
|
tokio::spawn( async move {
|
||||||
|
@ -85,19 +79,18 @@ fn discover_peers(session_manager: Arc<SessionManager>) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_msgs(session_manager: Arc<SessionManager>, ui_connection: &mut UiConnection, session_id: &usize) {
|
fn load_msgs(session_manager: &SessionManager, ui_connection: &mut UiConnection, session_id: &usize) {
|
||||||
if let Some(msgs) = session_manager.load_msgs(session_id, constants::MSG_LOADING_COUNT) {
|
if let Some(msgs) = session_manager.load_msgs(session_id, constants::MSG_LOADING_COUNT) {
|
||||||
ui_connection.load_msgs(session_id, &msgs);
|
ui_connection.load_msgs(session_id, &msgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLock<GlobalVars>>, worker_done: Arc<RwLock<bool>>) {
|
async fn websocket_worker(mut ui_connection: UiConnection, session_manager: Arc<SessionManager>) -> Result<(), JoinError> {
|
||||||
let session_manager = global_vars.read().unwrap().session_manager.clone();
|
|
||||||
ui_connection.set_name(&session_manager.identity.read().unwrap().as_ref().unwrap().name);
|
ui_connection.set_name(&session_manager.identity.read().unwrap().as_ref().unwrap().name);
|
||||||
session_manager.list_contacts().into_iter().for_each(|contact|{
|
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));
|
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);
|
session_manager.last_loaded_msg_offsets.write().unwrap().insert(contact.0, 0);
|
||||||
load_msgs(session_manager.clone(), &mut ui_connection, &contact.0);
|
load_msgs(&session_manager, &mut ui_connection, &contact.0);
|
||||||
});
|
});
|
||||||
session_manager.sessions.read().unwrap().iter().for_each(|session| {
|
session_manager.sessions.read().unwrap().iter().for_each(|session| {
|
||||||
ui_connection.on_new_session(
|
ui_connection.on_new_session(
|
||||||
|
@ -112,14 +105,28 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||||
{
|
{
|
||||||
let not_seen = session_manager.not_seen.read().unwrap();
|
let not_seen = session_manager.not_seen.read().unwrap();
|
||||||
if not_seen.len() > 0 {
|
if not_seen.len() > 0 {
|
||||||
ui_connection.set_not_seen(not_seen.clone());
|
ui_connection.set_not_seen(¬_seen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
session_manager.get_saved_msgs().into_iter().for_each(|msgs| {
|
session_manager.get_saved_msgs().into_iter().for_each(|msgs| {
|
||||||
if msgs.1.len() > 0 {
|
if !msgs.1.is_empty() {
|
||||||
ui_connection.load_msgs(&msgs.0, &msgs.1);
|
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();
|
let mut ips = Vec::new();
|
||||||
match if_addrs::get_if_addrs() {
|
match if_addrs::get_if_addrs() {
|
||||||
Ok(ifaces) => for iface in ifaces {
|
Ok(ifaces) => for iface in ifaces {
|
||||||
|
@ -129,10 +136,10 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||||
}
|
}
|
||||||
Err(e) => print_error!(e)
|
Err(e) => print_error!(e)
|
||||||
}
|
}
|
||||||
ui_connection.set_local_ips(ips);
|
ui_connection.set_local_ips(&ips);
|
||||||
discover_peers(session_manager.clone());
|
discover_peers(session_manager.clone());
|
||||||
let handle = Handle::current();
|
let handle = Handle::current();
|
||||||
std::thread::spawn(move || { //new thread needed to block on read_message() without blocking tokio tasks
|
tokio::task::spawn_blocking(move || {
|
||||||
loop {
|
loop {
|
||||||
match ui_connection.websocket.read_message() {
|
match ui_connection.websocket.read_message() {
|
||||||
Ok(msg) => {
|
Ok(msg) => {
|
||||||
|
@ -140,10 +147,12 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||||
ui_connection.write_message(Message::Pong(Vec::new())); //not sure if I'm doing this right
|
ui_connection.write_message(Message::Pong(Vec::new())); //not sure if I'm doing this right
|
||||||
} else if msg.is_text() {
|
} else if msg.is_text() {
|
||||||
let msg = msg.into_text().unwrap();
|
let msg = msg.into_text().unwrap();
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
println!("Message: {}", msg);
|
||||||
let mut ui_connection = ui_connection.clone();
|
let mut ui_connection = ui_connection.clone();
|
||||||
let session_manager = session_manager.clone();
|
let session_manager = session_manager.clone();
|
||||||
handle.spawn(async move {
|
handle.spawn(async move {
|
||||||
let args: Vec<&str> = msg.split(" ").collect();
|
let args: Vec<&str> = msg.split_whitespace().collect();
|
||||||
match args[0] {
|
match args[0] {
|
||||||
"set_seen" => {
|
"set_seen" => {
|
||||||
let session_id: usize = args[1].parse().unwrap();
|
let session_id: usize = args[1].parse().unwrap();
|
||||||
|
@ -160,11 +169,14 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||||
"refresh" => discover_peers(session_manager.clone()),
|
"refresh" => discover_peers(session_manager.clone()),
|
||||||
"send" => {
|
"send" => {
|
||||||
let session_id: usize = args[1].parse().unwrap();
|
let session_id: usize = args[1].parse().unwrap();
|
||||||
let buffer = protocol::new_message(msg[args[0].len()+args[1].len()+2..].to_string());
|
let msg_content = &msg[args[0].len()+args[1].len()+2..];
|
||||||
if session_manager.send_command(&session_id, SessionCommand::Send {
|
let buffer = protocol::new_message(msg_content);
|
||||||
buff: buffer.clone()
|
#[allow(unused_must_use)] {
|
||||||
}).await {
|
if let Ok(sent) = session_manager.send_or_add_to_pending(&session_id, buffer).await {
|
||||||
session_manager.store_msg(&session_id, true, buffer);
|
if !sent {
|
||||||
|
ui_connection.new_pending_msg(&session_id, false, msg_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"large_files" => {
|
"large_files" => {
|
||||||
|
@ -173,9 +185,9 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||||
for n in (2..args.len()).step_by(2) {
|
for n in (2..args.len()).step_by(2) {
|
||||||
file_info.push((args[n].parse::<u64>().unwrap(), base64::decode(args[n+1]).unwrap()));
|
file_info.push((args[n].parse::<u64>().unwrap(), base64::decode(args[n+1]).unwrap()));
|
||||||
}
|
}
|
||||||
session_manager.send_command(&session_id, SessionCommand::Send {
|
#[allow(unused_must_use)] {
|
||||||
buff: protocol::ask_large_files(file_info)
|
session_manager.send_or_add_to_pending(&session_id, protocol::ask_large_files(file_info)).await;
|
||||||
}).await;
|
}
|
||||||
}
|
}
|
||||||
"download" => {
|
"download" => {
|
||||||
let session_id: usize = args[1].parse().unwrap();
|
let session_id: usize = args[1].parse().unwrap();
|
||||||
|
@ -195,7 +207,7 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||||
}
|
}
|
||||||
"load_msgs" => {
|
"load_msgs" => {
|
||||||
let session_id: usize = args[1].parse().unwrap();
|
let session_id: usize = args[1].parse().unwrap();
|
||||||
load_msgs(session_manager.clone(), &mut ui_connection, &session_id);
|
load_msgs(&session_manager, &mut ui_connection, &session_id);
|
||||||
}
|
}
|
||||||
"contact" => {
|
"contact" => {
|
||||||
let session_id: usize = args[1].parse().unwrap();
|
let session_id: usize = args[1].parse().unwrap();
|
||||||
|
@ -206,7 +218,7 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||||
}
|
}
|
||||||
"uncontact" => {
|
"uncontact" => {
|
||||||
let session_id: usize = args[1].parse().unwrap();
|
let session_id: usize = args[1].parse().unwrap();
|
||||||
match session_manager.remove_contact(session_id) {
|
match session_manager.remove_contact(&session_id) {
|
||||||
Ok(_) => {},
|
Ok(_) => {},
|
||||||
Err(e) => print_error!(e)
|
Err(e) => print_error!(e)
|
||||||
}
|
}
|
||||||
|
@ -259,11 +271,11 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||||
(None, Some(base64::decode(args[1]).unwrap()))
|
(None, Some(base64::decode(args[1]).unwrap()))
|
||||||
};
|
};
|
||||||
let result = Identity::change_password(old_password.as_deref(), new_password.as_deref());
|
let result = Identity::change_password(old_password.as_deref(), new_password.as_deref());
|
||||||
if old_password.is_some() {
|
if let Some(mut old_password) = old_password {
|
||||||
old_password.unwrap().zeroize();
|
old_password.zeroize();
|
||||||
}
|
}
|
||||||
let is_identity_protected = if new_password.is_some() {
|
let is_identity_protected = if let Some(mut new_password) = new_password {
|
||||||
new_password.unwrap().zeroize();
|
new_password.zeroize();
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -287,7 +299,6 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
match e {
|
match e {
|
||||||
tungstenite::Error::ConnectionClosed => {
|
tungstenite::Error::ConnectionClosed => {
|
||||||
*worker_done.write().unwrap() = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ => print_error!(e)
|
_ => print_error!(e)
|
||||||
|
@ -295,13 +306,13 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_authenticated(req: &HttpRequest) -> bool {
|
fn is_authenticated(req: &HttpRequest) -> bool {
|
||||||
if let Some(cookie) = req.cookie(constants::HTTP_COOKIE_NAME) {
|
if let Some(cookie) = req.cookie(constants::HTTP_COOKIE_NAME) {
|
||||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||||
if let Some(token) = &global_vars.read().unwrap().ui_auth_token {
|
if let Some(token) = global_vars.ui_auth_token.read().unwrap().as_ref() {
|
||||||
return token == cookie.value();
|
return token == cookie.value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -310,7 +321,7 @@ fn is_authenticated(req: &HttpRequest) -> bool {
|
||||||
|
|
||||||
async fn handle_set_avatar(req: HttpRequest, mut payload: Multipart) -> HttpResponse {
|
async fn handle_set_avatar(req: HttpRequest, mut payload: Multipart) -> HttpResponse {
|
||||||
if let Ok(Some(mut field)) = payload.try_next().await {
|
if let Ok(Some(mut field)) = payload.try_next().await {
|
||||||
let content_disposition = field.content_disposition().unwrap();
|
let content_disposition = field.content_disposition();
|
||||||
if let Some(name) = content_disposition.get_name() {
|
if let Some(name) = content_disposition.get_name() {
|
||||||
if name == "avatar" {
|
if name == "avatar" {
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
|
@ -322,17 +333,15 @@ async fn handle_set_avatar(req: HttpRequest, mut payload: Multipart) -> HttpResp
|
||||||
match image::load_from_memory_with_format(&buffer, format) {
|
match image::load_from_memory_with_format(&buffer, format) {
|
||||||
Ok(image) => {
|
Ok(image) => {
|
||||||
let (width, height) = image.dimensions();
|
let (width, height) = image.dimensions();
|
||||||
let image = if width < height {
|
let image = match width.cmp(&height) {
|
||||||
image.crop_imm(0, (height-width)/2, width, width)
|
Ordering::Greater => image.crop_imm((width-height)/2, 0, height, height),
|
||||||
} else if width > height {
|
Ordering::Less => image.crop_imm(0, (height-width)/2, width, width),
|
||||||
image.crop_imm((width-height)/2, 0, height, height)
|
Ordering::Equal => image,
|
||||||
} else {
|
|
||||||
image
|
|
||||||
};
|
};
|
||||||
let mut avatar = Vec::new();
|
let mut avatar = Vec::new();
|
||||||
image.write_to(&mut avatar, format).unwrap();
|
image.write_to(&mut Cursor::new(&mut avatar), format).unwrap();
|
||||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||||
let session_manager = &global_vars.read().unwrap().session_manager;
|
let session_manager = &global_vars.session_manager;
|
||||||
let is_authenticated = is_authenticated(&req);
|
let is_authenticated = is_authenticated(&req);
|
||||||
let is_running = session_manager.is_identity_loaded();
|
let is_running = session_manager.is_identity_loaded();
|
||||||
if is_authenticated || !is_running {
|
if is_authenticated || !is_running {
|
||||||
|
@ -370,23 +379,23 @@ fn reply_with_avatar(avatar: Option<Vec<u8>>, name: Option<&str>) -> HttpRespons
|
||||||
let svg = include_str!(concat!(env!("OUT_DIR"), "/text_avatar.svg"));
|
let svg = include_str!(concat!(env!("OUT_DIR"), "/text_avatar.svg"));
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
let svg = replace_fields("src/frontend/imgs/text_avatar.svg");
|
let svg = replace_fields("src/frontend/imgs/text_avatar.svg");
|
||||||
HttpResponse::Ok().content_type("image/svg+xml").body(svg.replace("LETTER", &name.chars().nth(0).unwrap_or('?').to_string()))
|
HttpResponse::Ok().content_type("image/svg+xml").body(svg.replace("LETTER", &name.chars().next().unwrap_or('?').to_string()))
|
||||||
}
|
}
|
||||||
None => HttpResponse::InternalServerError().finish()
|
None => HttpResponse::InternalServerError().finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_avatar(req: HttpRequest) -> HttpResponse {
|
async fn handle_avatar(req: HttpRequest) -> HttpResponse {
|
||||||
let splits: Vec<&str> = req.path()[1..].split("/").collect();
|
let splits: Vec<&str> = req.path()[1..].split('/').collect();
|
||||||
if splits.len() == 2 {
|
if splits.len() == 2 {
|
||||||
if splits[1] == "self" {
|
if splits[1] == "self" {
|
||||||
return reply_with_avatar(Identity::get_identity_avatar().ok(), Identity::get_identity_name().ok().as_deref());
|
return reply_with_avatar(Identity::get_identity_avatar().ok(), Identity::get_identity_name().ok().as_deref());
|
||||||
}
|
}
|
||||||
} else if splits.len() == 3 && is_authenticated(&req) {
|
} else if splits.len() == 3 && is_authenticated(&req) {
|
||||||
if let Ok(session_id) = splits[1].parse() {
|
if let Ok(session_id) = splits[1].parse() {
|
||||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||||
return reply_with_avatar(global_vars.read().unwrap().session_manager.get_avatar(&session_id), Some(splits[2]));
|
return reply_with_avatar(global_vars.session_manager.get_avatar(&session_id), Some(splits[2]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HttpResponse::BadRequest().finish()
|
HttpResponse::BadRequest().finish()
|
||||||
|
@ -397,16 +406,13 @@ struct FileInfo {
|
||||||
uuid: String,
|
uuid: String,
|
||||||
file_name: String,
|
file_name: String,
|
||||||
}
|
}
|
||||||
fn handle_load_file(req: HttpRequest, file_info: web::Query<FileInfo>) -> HttpResponse {
|
async fn handle_load_file(req: HttpRequest, file_info: web::Query<FileInfo>) -> HttpResponse {
|
||||||
if is_authenticated(&req) {
|
if is_authenticated(&req) {
|
||||||
match Uuid::from_str(&file_info.uuid) {
|
match Uuid::from_str(&file_info.uuid) {
|
||||||
Ok(uuid) => {
|
Ok(uuid) => {
|
||||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||||
match global_vars.read().unwrap().session_manager.identity.read().unwrap().as_ref().unwrap().load_file(uuid) {
|
if let Some(buffer) = global_vars.session_manager.identity.read().unwrap().as_ref().unwrap().load_file(uuid) {
|
||||||
Some(buffer) => {
|
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);
|
||||||
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)
|
Err(e) => print_error!(e)
|
||||||
|
@ -419,33 +425,27 @@ async fn handle_send_file(req: HttpRequest, mut payload: Multipart) -> HttpRespo
|
||||||
if is_authenticated(&req) {
|
if is_authenticated(&req) {
|
||||||
let mut session_id: Option<usize> = None;
|
let mut session_id: Option<usize> = None;
|
||||||
while let Ok(Some(mut field)) = payload.try_next().await {
|
while let Ok(Some(mut field)) = payload.try_next().await {
|
||||||
let content_disposition = field.content_disposition().unwrap();
|
let content_disposition = field.content_disposition();
|
||||||
if let Some(name) = content_disposition.get_name() {
|
if let Some(name) = content_disposition.get_name() {
|
||||||
if name == "session_id" {
|
if name == "session_id" {
|
||||||
if let Some(Ok(raw_id)) = field.next().await {
|
if let Some(Ok(raw_id)) = field.next().await {
|
||||||
session_id = Some(from_utf8(&raw_id).unwrap().parse().unwrap());
|
session_id = Some(from_utf8(&raw_id).unwrap().parse().unwrap());
|
||||||
}
|
}
|
||||||
} else if session_id.is_some() {
|
} else if session_id.is_some() {
|
||||||
let filename = content_disposition.get_filename().unwrap();
|
let filename = content_disposition.get_filename().unwrap().to_owned();
|
||||||
let session_id = session_id.unwrap();
|
let session_id = session_id.unwrap();
|
||||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||||
let global_vars_read = global_vars.read().unwrap();
|
|
||||||
if req.path() == "/send_file" {
|
if req.path() == "/send_file" {
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
while let Some(Ok(chunk)) = field.next().await {
|
while let Some(Ok(chunk)) = field.next().await {
|
||||||
buffer.extend(chunk);
|
buffer.extend(chunk);
|
||||||
}
|
}
|
||||||
if global_vars_read.session_manager.send_command(&session_id, SessionCommand::Send {
|
if let Ok(sent) = global_vars.session_manager.send_or_add_to_pending(&session_id, protocol::file(&filename, &buffer)).await {
|
||||||
buff: protocol::file(filename, &buffer)
|
return if sent {
|
||||||
}).await {
|
HttpResponse::Ok().finish()
|
||||||
match global_vars_read.session_manager.store_file(&session_id, &buffer) {
|
} else {
|
||||||
Ok(file_uuid) => {
|
HttpResponse::Ok().body("pending")
|
||||||
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 {
|
} else {
|
||||||
let (ack_sender, mut ack_receiver) = mpsc::channel(1);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !global_vars_read.session_manager.send_command(&session_id, SessionCommand::EncryptFileChunk{
|
if !global_vars.session_manager.send_command(&session_id, SessionCommand::EncryptFileChunk{
|
||||||
plain_text: chunk_buffer.clone()
|
plain_text: chunk_buffer.clone()
|
||||||
}).await {
|
}).await {
|
||||||
return HttpResponse::InternalServerError().finish();
|
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 {
|
if !match ack_receiver.recv().await {
|
||||||
Some(should_continue) => {
|
Some(should_continue) => {
|
||||||
//send previous encrypted chunk even if transfert is aborted to keep PSEC nonces syncrhonized
|
//send previous encrypted chunk even if transfert is aborted to keep PSEC nonces syncrhonized
|
||||||
if global_vars_read.session_manager.send_command(&session_id, SessionCommand::SendEncryptedFileChunk {
|
if global_vars.session_manager.send_command(&session_id, SessionCommand::SendEncryptedFileChunk {
|
||||||
ack_sender: ack_sender.clone()
|
ack_sender: ack_sender.clone()
|
||||||
}).await {
|
}).await {
|
||||||
should_continue
|
should_continue
|
||||||
|
@ -506,29 +506,31 @@ async fn handle_send_file(req: HttpRequest, mut payload: Multipart) -> HttpRespo
|
||||||
|
|
||||||
async fn handle_logout(req: HttpRequest) -> HttpResponse {
|
async fn handle_logout(req: HttpRequest) -> HttpResponse {
|
||||||
if is_authenticated(&req) {
|
if is_authenticated(&req) {
|
||||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||||
let mut global_vars_write = global_vars.write().unwrap();
|
if global_vars.session_manager.is_identity_loaded() {
|
||||||
if global_vars_write.session_manager.is_identity_loaded() {
|
*global_vars.ui_auth_token.write().unwrap() = None;
|
||||||
global_vars_write.ui_auth_token = None;
|
global_vars.session_manager.stop().await;
|
||||||
global_vars_write.session_manager.stop().await;
|
|
||||||
}
|
}
|
||||||
if Identity::is_protected().unwrap_or(true) {
|
if Identity::is_protected().unwrap_or(true) {
|
||||||
HttpResponse::Found().header(header::LOCATION, "/").finish()
|
HttpResponse::Found().append_header((header::LOCATION, "/")).finish()
|
||||||
} else {
|
} else {
|
||||||
HttpResponse::Ok().body(include_str!("frontend/logout.html"))
|
#[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)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
HttpResponse::Unauthorized().finish()
|
HttpResponse::Unauthorized().finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn login(identity: Identity, global_vars: &Arc<RwLock<GlobalVars>>) -> HttpResponse {
|
fn login(identity: Identity, global_vars: &GlobalVars) -> HttpResponse {
|
||||||
let mut global_vars_write = global_vars.write().unwrap();
|
let session_manager = global_vars.session_manager.clone();
|
||||||
let session_manager = global_vars_write.session_manager.clone();
|
|
||||||
if !session_manager.is_identity_loaded() {
|
if !session_manager.is_identity_loaded() {
|
||||||
global_vars_write.session_manager.set_identity(Some(identity));
|
session_manager.set_identity(Some(identity));
|
||||||
global_vars_write.tokio_handle.clone().spawn(async move {
|
global_vars.tokio_handle.spawn(async move {
|
||||||
if SessionManager::start_listener(session_manager.clone()).await.is_err() {
|
if SessionManager::start_listener(session_manager).await.is_err() {
|
||||||
print_error!("You won't be able to receive incomming connections from other peers.");
|
print_error!("You won't be able to receive incomming connections from other peers.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -536,15 +538,15 @@ fn login(identity: Identity, global_vars: &Arc<RwLock<GlobalVars>>) -> HttpRespo
|
||||||
let mut raw_cookie = [0; 32];
|
let mut raw_cookie = [0; 32];
|
||||||
OsRng.fill_bytes(&mut raw_cookie);
|
OsRng.fill_bytes(&mut raw_cookie);
|
||||||
let cookie_value = base64::encode(raw_cookie);
|
let cookie_value = base64::encode(raw_cookie);
|
||||||
global_vars_write.ui_auth_token = Some(cookie_value.clone());
|
*global_vars.ui_auth_token.write().unwrap() = Some(cookie_value.clone());
|
||||||
let cookie = CookieBuilder::new(constants::HTTP_COOKIE_NAME, cookie_value).max_age(time::Duration::hours(4)).finish();
|
let cookie = CookieBuilder::new(constants::HTTP_COOKIE_NAME, cookie_value).max_age(time::Duration::hours(4)).finish();
|
||||||
HttpResponse::Found()
|
HttpResponse::Found()
|
||||||
.header(header::LOCATION, "/")
|
.append_header((header::LOCATION, "/"))
|
||||||
.set_header(header::SET_COOKIE, cookie.to_string())
|
.insert_header((header::SET_COOKIE, cookie.to_string()))
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_identity_loaded(identity: Identity, global_vars: &Arc<RwLock<GlobalVars>>) -> HttpResponse {
|
fn on_identity_loaded(identity: Identity, global_vars: &Arc<GlobalVars>) -> HttpResponse {
|
||||||
match Identity::clear_cache() {
|
match Identity::clear_cache() {
|
||||||
Ok(_) => {},
|
Ok(_) => {},
|
||||||
Err(e) => print_error!(e)
|
Err(e) => print_error!(e)
|
||||||
|
@ -556,13 +558,13 @@ fn on_identity_loaded(identity: Identity, global_vars: &Arc<RwLock<GlobalVars>>)
|
||||||
struct LoginParams {
|
struct LoginParams {
|
||||||
password: String,
|
password: String,
|
||||||
}
|
}
|
||||||
fn handle_login(req: HttpRequest, mut params: web::Form<LoginParams>) -> HttpResponse {
|
async fn handle_login(req: HttpRequest, mut params: web::Form<LoginParams>) -> HttpResponse {
|
||||||
let response = match Identity::load_identity(Some(params.password.as_bytes())) {
|
let response = match Identity::load_identity(Some(params.password.as_bytes())) {
|
||||||
Ok(identity) => {
|
Ok(identity) => {
|
||||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||||
on_identity_loaded(identity, global_vars)
|
on_identity_loaded(identity, global_vars)
|
||||||
}
|
}
|
||||||
Err(e) => generate_login_response(Some(&e.to_string()))
|
Err(e) => generate_login_response(Some(&e))
|
||||||
};
|
};
|
||||||
params.password.zeroize();
|
params.password.zeroize();
|
||||||
response
|
response
|
||||||
|
@ -611,15 +613,15 @@ async fn handle_create(req: HttpRequest, mut params: web::Form<CreateParams>) ->
|
||||||
let response = if params.password == params.password_confirm {
|
let response = if params.password == params.password_confirm {
|
||||||
match Identity::create_identidy(
|
match Identity::create_identidy(
|
||||||
¶ms.name,
|
¶ms.name,
|
||||||
if params.password.len() == 0 { //no password
|
if params.password.is_empty() { //no password
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(params.password.as_bytes())
|
Some(params.password.as_bytes())
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Ok(identity) => {
|
Ok(identity) => {
|
||||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||||
login(identity, global_vars.get_ref())
|
login(identity, global_vars)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error!(e);
|
print_error!(e);
|
||||||
|
@ -634,7 +636,7 @@ async fn handle_create(req: HttpRequest, mut params: web::Form<CreateParams>) ->
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
fn index_not_logged_in(global_vars: &Arc<RwLock<GlobalVars>>) -> HttpResponse {
|
fn index_not_logged_in(global_vars: &Arc<GlobalVars>) -> HttpResponse {
|
||||||
if Identity::is_protected().unwrap_or(true) {
|
if Identity::is_protected().unwrap_or(true) {
|
||||||
generate_login_response(None)
|
generate_login_response(None)
|
||||||
} else {
|
} else {
|
||||||
|
@ -646,22 +648,21 @@ fn index_not_logged_in(global_vars: &Arc<RwLock<GlobalVars>>) -> HttpResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_index(req: HttpRequest) -> HttpResponse {
|
async fn handle_index(req: HttpRequest) -> HttpResponse {
|
||||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
let global_vars = req.app_data::<Data<GlobalVars>>().unwrap();
|
||||||
if is_authenticated(&req) {
|
if is_authenticated(&req) {
|
||||||
let global_vars_read = global_vars.read().unwrap();
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
let html = fs::read_to_string("src/frontend/index.html").unwrap()
|
let html = fs::read_to_string("src/frontend/index.html").unwrap()
|
||||||
.replace("AIRA_VERSION", env!("CARGO_PKG_VERSION"));
|
.replace("AIRA_VERSION", env!("CARGO_PKG_VERSION"));
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
let html = include_str!(concat!(env!("OUT_DIR"), "/index.html"));
|
let html = include_str!(concat!(env!("OUT_DIR"), "/index.html"));
|
||||||
let public_key = global_vars_read.session_manager.identity.read().unwrap().as_ref().unwrap().get_public_key();
|
let identity = global_vars.session_manager.identity.read().unwrap();
|
||||||
let use_padding = global_vars_read.session_manager.identity.read().unwrap().as_ref().unwrap().use_padding.to_string();
|
let identity = identity.as_ref().unwrap();
|
||||||
HttpResponse::Ok().body(
|
HttpResponse::Ok().body(
|
||||||
html
|
html
|
||||||
.replace("IDENTITY_FINGERPRINT", &crypto::generate_fingerprint(&public_key))
|
.replace("IDENTITY_FINGERPRINT", &crypto::generate_fingerprint(&identity.get_public_key()))
|
||||||
.replace("WEBSOCKET_PORT", &global_vars_read.websocket_port.to_string())
|
.replace("WEBSOCKET_PORT", &global_vars.websocket_port.to_string())
|
||||||
.replace("IS_IDENTITY_PROTECTED", &Identity::is_protected().unwrap().to_string())
|
.replace("IS_IDENTITY_PROTECTED", &Identity::is_protected().unwrap().to_string())
|
||||||
.replace("PSEC_PADDING", &use_padding)
|
.replace("PSEC_PADDING", &identity.use_padding.to_string())
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
index_not_logged_in(global_vars)
|
index_not_logged_in(global_vars)
|
||||||
|
@ -682,8 +683,8 @@ fn replace_fields(file_path: &str) -> String {
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_static(req: HttpRequest) -> HttpResponse {
|
async fn handle_static(req: HttpRequest) -> HttpResponse {
|
||||||
let splits: Vec<&str> = req.path()[1..].split("/").collect();
|
let splits: Vec<&str> = req.path()[1..].split('/').collect();
|
||||||
if splits[0] == "static" {
|
if splits[0] == "static" {
|
||||||
let mut response_builder = HttpResponse::Ok();
|
let mut response_builder = HttpResponse::Ok();
|
||||||
match splits[1] {
|
match splits[1] {
|
||||||
|
@ -707,7 +708,8 @@ fn handle_static(req: HttpRequest) -> HttpResponse {
|
||||||
} else {
|
} else {
|
||||||
"none"
|
"none"
|
||||||
};
|
};
|
||||||
match match splits[3] {
|
if let Some(body) = match splits[3] {
|
||||||
|
"logo" => Some(include_str!("frontend/imgs/icons/logo.svg")),
|
||||||
"verified" => Some(include_str!("frontend/imgs/icons/verified.svg")),
|
"verified" => Some(include_str!("frontend/imgs/icons/verified.svg")),
|
||||||
"add_contact" => Some(include_str!("frontend/imgs/icons/add_contact.svg")),
|
"add_contact" => Some(include_str!("frontend/imgs/icons/add_contact.svg")),
|
||||||
"remove_contact" => Some(include_str!("frontend/imgs/icons/remove_contact.svg")),
|
"remove_contact" => Some(include_str!("frontend/imgs/icons/remove_contact.svg")),
|
||||||
|
@ -722,11 +724,8 @@ fn handle_static(req: HttpRequest) -> HttpResponse {
|
||||||
"profile" => Some(include_str!("frontend/imgs/icons/profile.svg")),
|
"profile" => Some(include_str!("frontend/imgs/icons/profile.svg")),
|
||||||
_ => None
|
_ => None
|
||||||
} {
|
} {
|
||||||
Some(body) => {
|
response_builder.content_type("image/svg+xml");
|
||||||
response_builder.content_type("image/svg+xml");
|
return response_builder.body(body.replace("FILL_COLOR", color))
|
||||||
return response_builder.body(body.replace("FILL_COLOR", color))
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
} else if splits.len() == 3 {
|
} else if splits.len() == 3 {
|
||||||
match splits[2] {
|
match splits[2] {
|
||||||
|
@ -766,13 +765,12 @@ fn handle_static(req: HttpRequest) -> HttpResponse {
|
||||||
}
|
}
|
||||||
"libs" => {
|
"libs" => {
|
||||||
if splits.len() == 3 {
|
if splits.len() == 3 {
|
||||||
match match splits[2] {
|
if let Some(body) = match splits[2] {
|
||||||
"linkify.min.js" => Some(include_str!("frontend/libs/linkify.min.js")),
|
"linkify.min.js" => Some(include_str!("frontend/libs/linkify.min.js")),
|
||||||
"linkify-element.min.js" => Some(include_str!("frontend/libs/linkify-element.min.js")),
|
"linkify-element.min.js" => Some(include_str!("frontend/libs/linkify-element.min.js")),
|
||||||
_ => None
|
_ => None
|
||||||
} {
|
} {
|
||||||
Some(body) => return response_builder.content_type(JS_CONTENT_TYPE).body(body),
|
return response_builder.content_type(JS_CONTENT_TYPE).body(body);
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -782,17 +780,15 @@ fn handle_static(req: HttpRequest) -> HttpResponse {
|
||||||
HttpResponse::NotFound().finish()
|
HttpResponse::NotFound().finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::main]
|
async fn start_http_server(global_vars: GlobalVars) -> io::Result<()> {
|
||||||
async fn start_http_server(global_vars: Arc<RwLock<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");
|
||||||
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") {
|
let http_port = match env::var("AIRA_HTTP_PORT") {
|
||||||
Ok(port) => port.parse().expect("AIRA_HTTP_PORT invalid"),
|
Ok(port) => port.parse().expect("AIRA_HTTP_PORT invalid"),
|
||||||
Err(_) => constants::UI_PORT
|
Err(_) => constants::UI_PORT
|
||||||
};
|
};
|
||||||
let server = HttpServer::new(move || {
|
let server = HttpServer::new(move || {
|
||||||
let global_vars_clone = global_vars.clone();
|
|
||||||
App::new()
|
App::new()
|
||||||
.data(global_vars_clone)
|
.app_data(Data::new(global_vars.clone()))
|
||||||
.service(web::resource("/")
|
.service(web::resource("/")
|
||||||
.route(web::get().to(handle_index))
|
.route(web::get().to(handle_index))
|
||||||
.route(web::post().to(handle_create))
|
.route(web::post().to(handle_create))
|
||||||
|
@ -802,8 +798,8 @@ async fn start_http_server(global_vars: Arc<RwLock<GlobalVars>>) -> io::Result<(
|
||||||
.route("/send_large_file", web::post().to(handle_send_file))
|
.route("/send_large_file", web::post().to(handle_send_file))
|
||||||
.route("/load_file", web::get().to(handle_load_file))
|
.route("/load_file", web::get().to(handle_load_file))
|
||||||
.route("/set_avatar", web::post().to(handle_set_avatar))
|
.route("/set_avatar", web::post().to(handle_set_avatar))
|
||||||
.route("/avatar/*", web::get().to(handle_avatar))
|
.route("/avatar/{_}*", web::get().to(handle_avatar))
|
||||||
.route("/static/.*", web::get().to(handle_static))
|
.route("/static/{_}*", web::get().to(handle_static))
|
||||||
.route("/logout", web::get().to(handle_logout))
|
.route("/logout", web::get().to(handle_logout))
|
||||||
}
|
}
|
||||||
).bind(SocketAddr::new(http_addr, http_port))?;
|
).bind(SocketAddr::new(http_addr, http_port))?;
|
||||||
|
@ -815,10 +811,11 @@ async fn start_http_server(global_vars: Arc<RwLock<GlobalVars>>) -> io::Result<(
|
||||||
server.run().await
|
server.run().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
struct GlobalVars {
|
struct GlobalVars {
|
||||||
session_manager: Arc<SessionManager>,
|
session_manager: Arc<SessionManager>,
|
||||||
websocket_port: u16,
|
websocket_port: u16,
|
||||||
ui_auth_token: Option<String>,
|
ui_auth_token: Arc<RwLock<Option<String>>>,
|
||||||
tokio_handle: Handle,
|
tokio_handle: Handle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -829,13 +826,13 @@ async fn main() {
|
||||||
print_error!(e);
|
print_error!(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let global_vars = Arc::new(RwLock::new(GlobalVars {
|
let ui_auth_token = Arc::new(RwLock::new(None));
|
||||||
session_manager: Arc::new(SessionManager::new()),
|
let session_manager = Arc::new(SessionManager::new());
|
||||||
websocket_port: 0,
|
let websocket_port = start_websocket_server(ui_auth_token.clone(), session_manager.clone()).await;
|
||||||
ui_auth_token: None,
|
start_http_server(GlobalVars {
|
||||||
|
session_manager,
|
||||||
|
websocket_port,
|
||||||
|
ui_auth_token,
|
||||||
tokio_handle: Handle::current(),
|
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 const ABORT_FILES_TRANSFER: u8 = 0x0a;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_message(message: String) -> Vec<u8> {
|
pub fn new_message(message: &str) -> Vec<u8> {
|
||||||
[&[Headers::MESSAGE], message.as_bytes()].concat()
|
[&[Headers::MESSAGE], message.as_bytes()].concat()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,17 +33,21 @@ 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()
|
[&[Headers::FILE], &(file_name.len() as u16).to_be_bytes()[..], file_name.as_bytes(), buffer].concat()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_file<'a>(buffer: &'a [u8]) -> Option<(&'a [u8], &'a [u8])> {
|
pub fn get_file_name(buffer: &[u8]) -> Option<&str> {
|
||||||
if buffer.len() > 3 {
|
if buffer.len() > 3 {
|
||||||
let file_name_len = u16::from_be_bytes([buffer[1], buffer[2]]) as usize;
|
let file_name_len = u16::from_be_bytes([buffer[1], buffer[2]]) as usize;
|
||||||
if buffer.len() > 3+file_name_len {
|
if buffer.len() > 3+file_name_len {
|
||||||
let file_name = &buffer[3..3+file_name_len];
|
return from_utf8(&buffer[3..3+file_name_len]).ok();
|
||||||
return Some((file_name, &buffer[3+file_name_len..]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
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> {
|
pub fn ask_large_files(file_info: Vec<(u64, Vec<u8>)>) -> Vec<u8> {
|
||||||
let mut buff = vec![Headers::ASK_LARGE_FILES];
|
let mut buff = vec![Headers::ASK_LARGE_FILES];
|
||||||
file_info.into_iter().for_each(|info| {
|
file_info.into_iter().for_each(|info| {
|
||||||
|
|
|
@ -4,8 +4,7 @@ use libmdns::Service;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use platform_dirs::UserDirs;
|
use platform_dirs::UserDirs;
|
||||||
use async_psec::{PUBLIC_KEY_LENGTH, Session, SessionWriteHalf, PsecWriter, PsecReader, PsecError};
|
use async_psec::{PUBLIC_KEY_LENGTH, Session, SessionWriteHalf, PsecWriter, PsecReader, PsecError};
|
||||||
use crate::{constants, protocol, crypto, discovery, identity::{Contact, Identity}, print_error, utils::{get_unix_timestamp, get_not_used_path}};
|
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::ui_interface::UiConnection;
|
|
||||||
|
|
||||||
pub enum SessionCommand {
|
pub enum SessionCommand {
|
||||||
Send {
|
Send {
|
||||||
|
@ -55,7 +54,8 @@ pub struct SessionManager {
|
||||||
ui_connection: Mutex<Option<UiConnection>>,
|
ui_connection: Mutex<Option<UiConnection>>,
|
||||||
loaded_contacts: RwLock<HashMap<usize, Contact>>,
|
loaded_contacts: RwLock<HashMap<usize, Contact>>,
|
||||||
pub last_loaded_msg_offsets: RwLock<HashMap<usize, usize>>,
|
pub last_loaded_msg_offsets: RwLock<HashMap<usize, usize>>,
|
||||||
pub saved_msgs: RwLock<HashMap<usize, Vec<(bool, Vec<u8>)>>>,
|
saved_msgs: RwLock<HashMap<usize, Vec<Message>>>,
|
||||||
|
pub pending_msgs: Mutex<HashMap<usize, Vec<Vec<u8>>>>,
|
||||||
pub not_seen: RwLock<Vec<usize>>,
|
pub not_seen: RwLock<Vec<usize>>,
|
||||||
mdns_service: Mutex<Option<Service>>,
|
mdns_service: Mutex<Option<Service>>,
|
||||||
listener_stop_signal: Mutex<Option<Sender<()>>>,
|
listener_stop_signal: Mutex<Option<Sender<()>>>,
|
||||||
|
@ -65,11 +65,10 @@ impl SessionManager {
|
||||||
|
|
||||||
fn with_ui_connection<F>(&self, f: F) where F: FnOnce(&mut UiConnection) {
|
fn with_ui_connection<F>(&self, f: F) where F: FnOnce(&mut UiConnection) {
|
||||||
let mut ui_connection_opt = self.ui_connection.lock().unwrap();
|
let mut ui_connection_opt = self.ui_connection.lock().unwrap();
|
||||||
match ui_connection_opt.as_mut() {
|
if let Some(ui_connection) = ui_connection_opt.as_mut() {
|
||||||
Some(ui_connection) => if ui_connection.is_valid {
|
if ui_connection.is_valid {
|
||||||
f(ui_connection);
|
f(ui_connection);
|
||||||
}
|
}
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,11 +87,11 @@ impl SessionManager {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store_msg(&self, session_id: &usize, outgoing: bool, buffer: Vec<u8>) {
|
pub fn store_msg(&self, session_id: &usize, message: Message) {
|
||||||
let mut msg_saved = false;
|
let mut msg_saved = false;
|
||||||
if let Some(contact) = self.loaded_contacts.read().unwrap().get(session_id) {
|
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()
|
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, outgoing, &buffer) {
|
match self.identity.read().unwrap().as_ref().unwrap().store_msg(&contact.uuid, &message) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
*offsets.get_mut(session_id).unwrap() += 1;
|
*offsets.get_mut(session_id).unwrap() += 1;
|
||||||
msg_saved = true;
|
msg_saved = true;
|
||||||
|
@ -101,16 +100,16 @@ impl SessionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !msg_saved {
|
if !msg_saved {
|
||||||
self.saved_msgs.write().unwrap().get_mut(&session_id).unwrap().push((outgoing, buffer));
|
//can be None if session disconnected
|
||||||
|
if let Some(saved_msgs) = self.saved_msgs.write().unwrap().get_mut(&session_id) {
|
||||||
|
saved_msgs.push(message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_session_sender(&self, session_id: &usize) -> Option<Sender<SessionCommand>> {
|
fn get_session_sender(&self, session_id: &usize) -> Option<Sender<SessionCommand>> {
|
||||||
let mut sessions = self.sessions.write().unwrap();
|
let sessions = self.sessions.read().unwrap();
|
||||||
match sessions.get_mut(session_id) {
|
sessions.get(session_id).map(|session_data| session_data.sender.clone())
|
||||||
Some(session_data) => Some(session_data.sender.clone()),
|
|
||||||
None => None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_command(&self, session_id: &usize, session_command: SessionCommand) -> bool {
|
pub async fn send_command(&self, session_id: &usize, session_command: SessionCommand) -> bool {
|
||||||
|
@ -127,6 +126,21 @@ 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) {
|
fn remove_session(&self, session_id: &usize) {
|
||||||
self.with_ui_connection(|ui_connection| {
|
self.with_ui_connection(|ui_connection| {
|
||||||
ui_connection.on_disconnected(&session_id);
|
ui_connection.on_disconnected(&session_id);
|
||||||
|
@ -151,23 +165,66 @@ impl SessionManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
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_write, &buff).await?;
|
self.encrypt_and_send(session_writer, &buff).await?;
|
||||||
if buff[0] == protocol::Headers::ACCEPT_LARGE_FILES {
|
let timestamp = get_unix_timestamp_sec();
|
||||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download.as_mut().unwrap().accepted = true;
|
Ok(match buff[0] {
|
||||||
} else if buff[0] == protocol::Headers::ABORT_FILES_TRANSFER {
|
protocol::Headers::MESSAGE => {
|
||||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download = None;
|
let msg = Message {
|
||||||
*is_sending = false;
|
outgoing: true,
|
||||||
if let Some(ack_sender) = file_ack_sender {
|
timestamp,
|
||||||
if let Err(e) = ack_sender.send(false).await {
|
data: buff,
|
||||||
print_error!(e);
|
};
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
*file_ack_sender = None;
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.with_ui_connection(|ui_connection| {
|
|
||||||
ui_connection.on_msg_sent(session_id, &buff);
|
|
||||||
});
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +280,7 @@ impl SessionManager {
|
||||||
let mut sessions = self.sessions.write().unwrap();
|
let mut sessions = self.sessions.write().unwrap();
|
||||||
let files_transfer = sessions.get_mut(&session_id).unwrap().files_download.as_mut().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];
|
let file_transfer = &mut files_transfer.files[files_transfer.index];
|
||||||
file_transfer.last_chunk = get_unix_timestamp();
|
file_transfer.last_chunk = get_unix_timestamp_ms();
|
||||||
file_transfer.transferred += chunk_size;
|
file_transfer.transferred += chunk_size;
|
||||||
if file_transfer.transferred >= file_transfer.file_size { //we downloaded all the file
|
if file_transfer.transferred >= file_transfer.file_size { //we downloaded all the file
|
||||||
if files_transfer.index+1 == files_transfer.files.len() {
|
if files_transfer.index+1 == files_transfer.files.len() {
|
||||||
|
@ -281,7 +338,7 @@ impl SessionManager {
|
||||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download = None;
|
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download = None;
|
||||||
local_file_handle = None;
|
local_file_handle = None;
|
||||||
self.with_ui_connection(|ui_connection| {
|
self.with_ui_connection(|ui_connection| {
|
||||||
ui_connection.on_received(&session_id, &buffer);
|
ui_connection.on_file_transfer_aborted(&session_id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
protocol::Headers::ASK_LARGE_FILES => {
|
protocol::Headers::ASK_LARGE_FILES => {
|
||||||
|
@ -293,7 +350,7 @@ impl SessionManager {
|
||||||
file_name: info.1,
|
file_name: info.1,
|
||||||
file_size: info.0,
|
file_size: info.0,
|
||||||
transferred: 0,
|
transferred: 0,
|
||||||
last_chunk: get_unix_timestamp(),
|
last_chunk: get_unix_timestamp_ms(),
|
||||||
}
|
}
|
||||||
}).collect();
|
}).collect();
|
||||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download = Some(LargeFilesDownload {
|
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download = Some(LargeFilesDownload {
|
||||||
|
@ -369,46 +426,52 @@ impl SessionManager {
|
||||||
protocol::Headers::REMOVE_AVATAR => self.set_avatar_uuid(&session_id, None),
|
protocol::Headers::REMOVE_AVATAR => self.set_avatar_uuid(&session_id, None),
|
||||||
_ => {
|
_ => {
|
||||||
let header = buffer[0];
|
let header = buffer[0];
|
||||||
let buffer = match header {
|
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);
|
||||||
|
}
|
||||||
protocol::Headers::FILE => {
|
protocol::Headers::FILE => {
|
||||||
if let Some((file_name, content)) = protocol::parse_file(&buffer) {
|
if let Some((filename, content)) = protocol::parse_file(&buffer) {
|
||||||
match self.store_file(&session_id, content) {
|
match self.store_file(&session_id, content) {
|
||||||
Ok(file_uuid) => {
|
Ok(file_uuid) => {
|
||||||
Some([&[protocol::Headers::FILE][..], file_uuid.as_bytes(), file_name].concat())
|
self.set_seen(session_id, false);
|
||||||
}
|
self.with_ui_connection(|ui_connection| {
|
||||||
Err(e) => {
|
ui_connection.on_new_file(&session_id, false, timestamp, filename, file_uuid);
|
||||||
print_error!(e);
|
});
|
||||||
None
|
self.store_msg(&session_id, Message {
|
||||||
|
outgoing: false,
|
||||||
|
timestamp,
|
||||||
|
data: [&[protocol::Headers::FILE][..], file_uuid.as_bytes(), filename.as_bytes()].concat(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
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;
|
is_sending = true;
|
||||||
last_chunks_sizes = Some(Vec::new());
|
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) => {
|
Err(e) => {
|
||||||
if e != PsecError::BrokenPipe && e != PsecError::ConnectionReset && e != PsecError::BufferTooLarge {
|
if e != PsecError::BrokenPipe && e != PsecError::ConnectionReset {
|
||||||
print_error!(e);
|
print_error!(e);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -421,11 +484,9 @@ impl SessionManager {
|
||||||
//don't send msg if we already encrypted a file chunk (keep PSEC nonces synchronized)
|
//don't send msg if we already encrypted a file chunk (keep PSEC nonces synchronized)
|
||||||
if is_sending {
|
if is_sending {
|
||||||
msg_queue.push(buff);
|
msg_queue.push(buff);
|
||||||
} else {
|
} else if let Err(e) = self.send_msg(session_id, &mut session_write, buff, &mut is_sending, &mut file_ack_sender).await {
|
||||||
if let Err(e) = self.send_msg(session_id, &mut session_write, &buff, &mut is_sending, &mut file_ack_sender).await {
|
print_error!(e);
|
||||||
print_error!(e);
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SessionCommand::EncryptFileChunk { plain_text } => {
|
SessionCommand::EncryptFileChunk { plain_text } => {
|
||||||
|
@ -438,9 +499,9 @@ impl SessionManager {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
file_ack_sender = Some(ack_sender);
|
file_ack_sender = Some(ack_sender);
|
||||||
//once the pre-encrypted chunk is sent, we can send the pending messages
|
//once the pre-encrypted chunk is sent, we can send the pending messages
|
||||||
while msg_queue.len() > 0 {
|
while !msg_queue.is_empty() {
|
||||||
let msg = msg_queue.remove(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);
|
print_error!(e);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -461,9 +522,28 @@ 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) {
|
fn handle_new_session(session_manager: Arc<SessionManager>, mut session: Session, outgoing: bool) {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut peer_public_key = [0; PUBLIC_KEY_LENGTH];
|
let mut peer_public_key = [0; PUBLIC_KEY_LENGTH];
|
||||||
|
session.set_max_recv_size(constants::MAX_RECV_SIZE, false);
|
||||||
let session = {
|
let session = {
|
||||||
let identity = {
|
let identity = {
|
||||||
session_manager.identity.read().unwrap().clone()
|
session_manager.identity.read().unwrap().clone()
|
||||||
|
@ -508,7 +588,7 @@ impl SessionManager {
|
||||||
outgoing,
|
outgoing,
|
||||||
peer_public_key,
|
peer_public_key,
|
||||||
ip,
|
ip,
|
||||||
sender: sender,
|
sender,
|
||||||
files_download: None,
|
files_download: None,
|
||||||
};
|
};
|
||||||
let mut session_id = None;
|
let mut session_id = None;
|
||||||
|
@ -538,17 +618,10 @@ impl SessionManager {
|
||||||
session_manager.with_ui_connection(|ui_connection| {
|
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);
|
ui_connection.on_new_session(&session_id, &ip.to_string(), outgoing, &crypto::generate_fingerprint(&peer_public_key), ip, None);
|
||||||
});
|
});
|
||||||
if !is_contact {
|
match session_manager.on_session_initialized(&mut session, session_id, is_contact).await {
|
||||||
match session_manager.encrypt_and_send(&mut session, &protocol::ask_profile_info()).await {
|
Ok(_) => session_manager.session_worker(session_id, receiver, session).await,
|
||||||
Ok(_) => {}
|
Err(e) => print_error!(e)
|
||||||
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);
|
session_manager.remove_session(&session_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -583,7 +656,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()
|
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<(bool, Vec<u8>)>> {
|
pub fn get_saved_msgs(&self) -> HashMap<usize, Vec<Message>> {
|
||||||
self.saved_msgs.read().unwrap().clone()
|
self.saved_msgs.read().unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,16 +669,13 @@ impl SessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut loaded_contacts = self.loaded_contacts.write().unwrap();
|
let mut loaded_contacts = self.loaded_contacts.write().unwrap();
|
||||||
match loaded_contacts.get_mut(&session_id) {
|
if let Some(contact) = loaded_contacts.get_mut(&session_id) {
|
||||||
Some(contact) => {
|
if contact.seen != seen {
|
||||||
if contact.seen != seen {
|
match self.identity.read().unwrap().as_ref().unwrap().set_contact_seen(&contact.uuid, seen) {
|
||||||
match self.identity.read().unwrap().as_ref().unwrap().set_contact_seen(&contact.uuid, seen) {
|
Ok(_) => contact.seen = seen,
|
||||||
Ok(_) => contact.seen = seen,
|
Err(e) => print_error!(e)
|
||||||
Err(e) => print_error!(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,20 +685,22 @@ impl SessionManager {
|
||||||
let contact = self.identity.read().unwrap().as_ref().unwrap().add_contact(session.name.clone(), session.avatar, session.peer_public_key)?;
|
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.loaded_contacts.write().unwrap().insert(session_id, contact);
|
||||||
self.last_loaded_msg_offsets.write().unwrap().insert(session_id, 0);
|
self.last_loaded_msg_offsets.write().unwrap().insert(session_id, 0);
|
||||||
|
self.pending_msgs.lock().unwrap().insert(session_id, Vec::new());
|
||||||
Ok(())
|
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 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 result.is_ok() {
|
||||||
if let Some(contact) = loaded_contacts.remove(&session_id) {
|
if let Some(contact) = loaded_contacts.remove(session_id) {
|
||||||
if let Some(session) = self.sessions.write().unwrap().get_mut(&session_id) {
|
if let Some(session) = self.sessions.write().unwrap().get_mut(session_id) {
|
||||||
session.name = contact.name;
|
session.name = contact.name;
|
||||||
session.avatar = contact.avatar;
|
session.avatar = contact.avatar;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.last_loaded_msg_offsets.write().unwrap().remove(&session_id);
|
self.last_loaded_msg_offsets.write().unwrap().remove(session_id);
|
||||||
|
self.pending_msgs.lock().unwrap().remove(session_id);
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@ -653,13 +725,13 @@ impl SessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store_file(&self, session_id: &usize, data: &[u8]) -> Result<Uuid, rusqlite::Error> {
|
pub fn store_file(&self, session_id: &usize, data: &[u8]) -> Result<Uuid, rusqlite::Error> {
|
||||||
self.identity.read().unwrap().as_ref().unwrap().store_file(match self.loaded_contacts.read().unwrap().get(session_id) {
|
self.identity.read().unwrap().as_ref().unwrap().store_file(
|
||||||
Some(contact) => Some(contact.uuid),
|
self.loaded_contacts.read().unwrap().get(session_id).map(|contact| contact.uuid),
|
||||||
None => None
|
data
|
||||||
}, data)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_msgs(&self, session_id: &usize, count: usize) -> Option<Vec<(bool, Vec<u8>)>> {
|
pub fn load_msgs(&self, session_id: &usize, count: usize) -> Option<Vec<Message>> {
|
||||||
let mut offsets = self.last_loaded_msg_offsets.write().unwrap();
|
let mut offsets = self.last_loaded_msg_offsets.write().unwrap();
|
||||||
let msgs = self.identity.read().unwrap().as_ref().unwrap().load_msgs(
|
let msgs = self.identity.read().unwrap().as_ref().unwrap().load_msgs(
|
||||||
&self.loaded_contacts.read().unwrap().get(session_id)?.uuid,
|
&self.loaded_contacts.read().unwrap().get(session_id)?.uuid,
|
||||||
|
@ -743,20 +815,18 @@ impl SessionManager {
|
||||||
}
|
}
|
||||||
*identity_guard = identity;
|
*identity_guard = identity;
|
||||||
if identity_guard.is_some() { //login
|
if identity_guard.is_some() { //login
|
||||||
match identity_guard.as_ref().unwrap().load_contacts() {
|
if let Some(contacts) = identity_guard.as_ref().unwrap().load_contacts() {
|
||||||
Some(contacts) => {
|
let mut loaded_contacts = self.loaded_contacts.write().unwrap();
|
||||||
let mut loaded_contacts = self.loaded_contacts.write().unwrap();
|
let mut session_counter = self.session_counter.write().unwrap();
|
||||||
let mut session_counter = self.session_counter.write().unwrap();
|
let mut not_seen = self.not_seen.write().unwrap();
|
||||||
let mut not_seen = self.not_seen.write().unwrap();
|
contacts.into_iter().for_each(|contact| {
|
||||||
contacts.into_iter().for_each(|contact| {
|
if !contact.seen {
|
||||||
if !contact.seen {
|
not_seen.push(*session_counter);
|
||||||
not_seen.push(*session_counter);
|
}
|
||||||
}
|
loaded_contacts.insert(*session_counter, contact);
|
||||||
loaded_contacts.insert(*session_counter, contact);
|
self.pending_msgs.lock().unwrap().insert(*session_counter, Vec::new());
|
||||||
*session_counter += 1;
|
*session_counter += 1;
|
||||||
})
|
});
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -774,6 +844,7 @@ impl SessionManager {
|
||||||
loaded_contacts: RwLock::new(HashMap::new()),
|
loaded_contacts: RwLock::new(HashMap::new()),
|
||||||
last_loaded_msg_offsets: RwLock::new(HashMap::new()),
|
last_loaded_msg_offsets: RwLock::new(HashMap::new()),
|
||||||
saved_msgs: RwLock::new(HashMap::new()),
|
saved_msgs: RwLock::new(HashMap::new()),
|
||||||
|
pending_msgs: Mutex::new(HashMap::new()),
|
||||||
not_seen: RwLock::new(Vec::new()),
|
not_seen: RwLock::new(Vec::new()),
|
||||||
mdns_service: Mutex::new(None),
|
mdns_service: Mutex::new(None),
|
||||||
listener_stop_signal: Mutex::new(None),
|
listener_stop_signal: Mutex::new(None),
|
||||||
|
|
|
@ -1,133 +1,7 @@
|
||||||
use std::net::{IpAddr, TcpStream};
|
use std::{fmt::Display, net::{IpAddr, TcpStream}, str::from_utf8};
|
||||||
use tungstenite::{WebSocket, protocol::Role, Message};
|
use tungstenite::{WebSocket, protocol::Role, Message};
|
||||||
use crate::{protocol, session_manager::{LargeFileDownload, LargeFilesDownload}};
|
use uuid::Uuid;
|
||||||
|
use crate::{identity, print_error, protocol, session_manager::{LargeFileDownload, LargeFilesDownload}, utils::to_uuid_bytes};
|
||||||
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 struct UiConnection{
|
||||||
pub websocket: WebSocket<TcpStream>,
|
pub websocket: WebSocket<TcpStream>,
|
||||||
|
@ -137,81 +11,138 @@ pub struct UiConnection{
|
||||||
impl UiConnection {
|
impl UiConnection {
|
||||||
pub fn new(websocket: WebSocket<TcpStream>) -> UiConnection {
|
pub fn new(websocket: WebSocket<TcpStream>) -> UiConnection {
|
||||||
UiConnection {
|
UiConnection {
|
||||||
websocket: websocket,
|
websocket,
|
||||||
is_valid: true
|
is_valid: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_message(&mut self, message: Message) {
|
pub fn write_message<T: Into<Message>>(&mut self, message: T) {
|
||||||
if self.websocket.write_message(message).is_err() {
|
if self.websocket.write_message(message.into()).is_err() {
|
||||||
self.is_valid = false
|
self.is_valid = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_received(&mut self, session_id: &usize, buffer: &[u8]) {
|
fn simple_event(&mut self, command: &str, session_id: &usize) {
|
||||||
let ui_message = match buffer[0] {
|
self.write_message(format!("{} {}", command, session_id));
|
||||||
protocol::Headers::MESSAGE => ui_messages::on_new_message(session_id, false, buffer),
|
}
|
||||||
protocol::Headers::FILE => ui_messages::on_file_received(session_id, buffer),
|
fn data_list<T: Display>(command: &str, data: &[T]) -> String {
|
||||||
protocol::Headers::ACCEPT_LARGE_FILES => Some(ui_messages::on_large_files_accepted(session_id)),
|
command.to_string()+&data.iter().map(|i| {
|
||||||
protocol::Headers::ABORT_FILES_TRANSFER => Some(ui_messages::on_file_transfer_aborted(session_id)),
|
format!(" {}", i)
|
||||||
_ => None
|
}).collect::<String>()
|
||||||
};
|
}
|
||||||
if ui_message.is_some() {
|
|
||||||
self.write_message(ui_message.unwrap())
|
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_ask_large_files(&mut self, session_id: &usize, files: &Vec<LargeFileDownload>, download_location: &str) {
|
pub fn on_new_file(&mut self, session_id: &usize, outgoing: bool, timestamp: u64, filename: &str, uuid: Uuid) {
|
||||||
self.write_message(ui_messages::on_ask_large_files(session_id, files, download_location))
|
self.write_message(format!("file {} {} {} {} {}", session_id, outgoing, timestamp, uuid.to_string(), filename));
|
||||||
}
|
|
||||||
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>) {
|
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(ui_messages::on_new_session(session_id, name, outgoing, fingerprint, ip));
|
self.write_message(format!("new_session {} {} {} {} {}", session_id, outgoing, fingerprint, ip, name));
|
||||||
if let Some(files_transfer) = files_transfer {
|
if let Some(files_transfer) = files_transfer {
|
||||||
self.write_message(ui_messages::new_files_transfer(session_id, 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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn on_disconnected(&mut self, session_id: &usize) {
|
pub fn on_disconnected(&mut self, session_id: &usize) {
|
||||||
self.write_message(ui_messages::on_disconnected(session_id));
|
self.simple_event("disconnected", session_id);
|
||||||
}
|
}
|
||||||
pub fn on_name_told(&mut self, session_id: &usize, name: &str) {
|
pub fn on_name_told(&mut self, session_id: &usize, name: &str) {
|
||||||
self.write_message(ui_messages::on_name_told(session_id, name));
|
self.write_message(format!("name_told {} {}", session_id, name));
|
||||||
}
|
}
|
||||||
pub fn on_avatar_changed(&mut self, session_id: Option<&usize>) {
|
pub fn on_avatar_changed(&mut self, session_id: Option<&usize>) {
|
||||||
self.write_message(ui_messages::on_avatar_changed(session_id));
|
match session_id {
|
||||||
|
Some(session_id) => self.simple_event("avatar_changed", session_id),
|
||||||
|
None => self.write_message("avatar_changed self")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inc_files_transfer(&mut self, session_id: &usize, chunk_size: u64) {
|
pub fn inc_files_transfer(&mut self, session_id: &usize, chunk_size: u64) {
|
||||||
self.write_message(ui_messages::inc_files_transfer(session_id, chunk_size));
|
self.write_message(format!("inc_file_transfer {} {}", session_id, chunk_size));
|
||||||
}
|
}
|
||||||
pub fn set_as_contact(&mut self, session_id: usize, name: &str, verified: bool, fingerprint: &str) {
|
pub fn set_as_contact(&mut self, session_id: usize, name: &str, verified: bool, fingerprint: &str) {
|
||||||
self.write_message(ui_messages::set_as_contact(session_id, name, verified, fingerprint));
|
self.write_message(format!("is_contact {} {} {} {}", session_id, verified, fingerprint, name));
|
||||||
}
|
}
|
||||||
pub fn load_msgs(&mut self, session_id: &usize, msgs: &Vec<(bool, Vec<u8>)>) {
|
pub fn load_msgs(&mut self, session_id: &usize, msgs: &[identity::Message]) {
|
||||||
self.write_message(ui_messages::load_msgs(session_id, msgs));
|
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 set_not_seen(&mut self, session_ids: Vec<usize>) {
|
pub fn set_not_seen(&mut self, session_ids: &[usize]) {
|
||||||
self.write_message(ui_messages::set_not_seen(session_ids));
|
self.write_message(Self::data_list("not_seen", session_ids));
|
||||||
}
|
}
|
||||||
pub fn set_local_ips(&mut self, ips: Vec<IpAddr>) {
|
pub fn new_pending_msg(&mut self, session_id: &usize, is_file: bool, data: &str) {
|
||||||
self.write_message(ui_messages::set_local_ips(ips));
|
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_name(&mut self, new_name: &str) {
|
pub fn set_name(&mut self, new_name: &str) {
|
||||||
self.write_message(ui_messages::set_name(new_name));
|
self.write_message(format!("set_name {}", new_name));
|
||||||
}
|
}
|
||||||
pub fn password_changed(&mut self, success: bool, is_protected: bool) {
|
pub fn password_changed(&mut self, success: bool, is_protected: bool) {
|
||||||
self.write_message(ui_messages::password_changed(success, is_protected));
|
self.write_message(format!("password_changed {} {}", success, is_protected));
|
||||||
}
|
}
|
||||||
pub fn logout(&mut self) {
|
pub fn logout(&mut self) {
|
||||||
self.write_message(Message::from("logout"));
|
self.write_message("logout");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
src/utils.rs
10
src/utils.rs
|
@ -1,4 +1,4 @@
|
||||||
use std::{convert::TryInto, time::{SystemTime, UNIX_EPOCH}, path::PathBuf};
|
use std::{convert::TryInto, time::{SystemTime, UNIX_EPOCH}, path::Path};
|
||||||
use uuid::Bytes;
|
use uuid::Bytes;
|
||||||
use crate::print_error;
|
use crate::print_error;
|
||||||
|
|
||||||
|
@ -16,11 +16,15 @@ pub fn escape_double_quote(origin: String) -> String {
|
||||||
origin.replace("\"", "\\\"")
|
origin.replace("\"", "\\\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_unix_timestamp() -> u128 {
|
pub fn get_unix_timestamp_ms() -> u128 {
|
||||||
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis()
|
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_not_used_path(file_name: &str, parent_directory: &PathBuf) -> String {
|
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 {
|
||||||
let has_extension = file_name.matches('.').count() > 0;
|
let has_extension = file_name.matches('.').count() > 0;
|
||||||
let mut path = parent_directory.join(&file_name);
|
let mut path = parent_directory.join(&file_name);
|
||||||
let mut n = 1;
|
let mut n = 1;
|
||||||
|
|
Loading…
Reference in New Issue