mirror of
https://github.com/chatmail/core.git
synced 2026-05-08 01:16:31 +03:00
refactor(oauth): safe oauth2 and remove custom json parser (#80)
* refactor(oauth): safe oauth2 and remove custom json parser Closes #46,#53
This commit is contained in:
committed by
GitHub
parent
94aa314f30
commit
a247e5b143
@@ -23,11 +23,14 @@ native-tls = "0.2.3"
|
|||||||
lettre = "0.9.0"
|
lettre = "0.9.0"
|
||||||
imap = "1.0.1"
|
imap = "1.0.1"
|
||||||
mmime = { git = "https://github.com/dignifiedquire/mmime" }
|
mmime = { git = "https://github.com/dignifiedquire/mmime" }
|
||||||
base64 = "0.10.1"
|
base64 = "0.10"
|
||||||
charset = "0.1.2"
|
charset = "0.1"
|
||||||
|
percent-encoding = "1.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.0.7"
|
tempfile = "3.0"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
|||||||
@@ -123,9 +123,14 @@ pub unsafe extern "C" fn dc_get_oauth2_url(
|
|||||||
redirect: *mut libc::c_char,
|
redirect: *mut libc::c_char,
|
||||||
) -> *mut libc::c_char {
|
) -> *mut libc::c_char {
|
||||||
assert!(!context.is_null());
|
assert!(!context.is_null());
|
||||||
let context = &*context;
|
|
||||||
|
|
||||||
dc_oauth2::dc_get_oauth2_url(context, addr, redirect)
|
let context = &*context;
|
||||||
|
let addr = dc_tools::to_string(addr);
|
||||||
|
let redirect = dc_tools::to_string(redirect);
|
||||||
|
match oauth2::dc_get_oauth2_url(context, addr, redirect) {
|
||||||
|
Some(res) => libc::strdup(dc_tools::to_cstring(res).as_ptr()),
|
||||||
|
None => std::ptr::null_mut(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ use deltachat::dc_imap::*;
|
|||||||
use deltachat::dc_imex::*;
|
use deltachat::dc_imex::*;
|
||||||
use deltachat::dc_job::*;
|
use deltachat::dc_job::*;
|
||||||
use deltachat::dc_jobthread::*;
|
use deltachat::dc_jobthread::*;
|
||||||
use deltachat::dc_jsmn::*;
|
|
||||||
use deltachat::dc_key::*;
|
use deltachat::dc_key::*;
|
||||||
use deltachat::dc_keyhistory::*;
|
use deltachat::dc_keyhistory::*;
|
||||||
use deltachat::dc_keyring::*;
|
use deltachat::dc_keyring::*;
|
||||||
@@ -28,7 +27,6 @@ use deltachat::dc_mimefactory::*;
|
|||||||
use deltachat::dc_mimeparser::*;
|
use deltachat::dc_mimeparser::*;
|
||||||
use deltachat::dc_move::*;
|
use deltachat::dc_move::*;
|
||||||
use deltachat::dc_msg::*;
|
use deltachat::dc_msg::*;
|
||||||
use deltachat::dc_oauth2::*;
|
|
||||||
use deltachat::dc_param::*;
|
use deltachat::dc_param::*;
|
||||||
use deltachat::dc_pgp::*;
|
use deltachat::dc_pgp::*;
|
||||||
use deltachat::dc_qr::*;
|
use deltachat::dc_qr::*;
|
||||||
@@ -43,6 +41,7 @@ use deltachat::dc_strbuilder::*;
|
|||||||
use deltachat::dc_strencode::*;
|
use deltachat::dc_strencode::*;
|
||||||
use deltachat::dc_token::*;
|
use deltachat::dc_token::*;
|
||||||
use deltachat::dc_tools::*;
|
use deltachat::dc_tools::*;
|
||||||
|
use deltachat::oauth2::*;
|
||||||
use deltachat::peerstate::*;
|
use deltachat::peerstate::*;
|
||||||
use deltachat::types::*;
|
use deltachat::types::*;
|
||||||
use deltachat::x::*;
|
use deltachat::x::*;
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ use deltachat::dc_imap::*;
|
|||||||
use deltachat::dc_imex::*;
|
use deltachat::dc_imex::*;
|
||||||
use deltachat::dc_job::*;
|
use deltachat::dc_job::*;
|
||||||
use deltachat::dc_jobthread::*;
|
use deltachat::dc_jobthread::*;
|
||||||
use deltachat::dc_jsmn::*;
|
|
||||||
use deltachat::dc_key::*;
|
use deltachat::dc_key::*;
|
||||||
use deltachat::dc_keyhistory::*;
|
use deltachat::dc_keyhistory::*;
|
||||||
use deltachat::dc_keyring::*;
|
use deltachat::dc_keyring::*;
|
||||||
@@ -47,7 +46,6 @@ use deltachat::dc_mimefactory::*;
|
|||||||
use deltachat::dc_mimeparser::*;
|
use deltachat::dc_mimeparser::*;
|
||||||
use deltachat::dc_move::*;
|
use deltachat::dc_move::*;
|
||||||
use deltachat::dc_msg::*;
|
use deltachat::dc_msg::*;
|
||||||
use deltachat::dc_oauth2::*;
|
|
||||||
use deltachat::dc_param::*;
|
use deltachat::dc_param::*;
|
||||||
use deltachat::dc_pgp::*;
|
use deltachat::dc_pgp::*;
|
||||||
use deltachat::dc_qr::*;
|
use deltachat::dc_qr::*;
|
||||||
@@ -62,6 +60,7 @@ use deltachat::dc_strbuilder::*;
|
|||||||
use deltachat::dc_strencode::*;
|
use deltachat::dc_strencode::*;
|
||||||
use deltachat::dc_token::*;
|
use deltachat::dc_token::*;
|
||||||
use deltachat::dc_tools::*;
|
use deltachat::dc_tools::*;
|
||||||
|
use deltachat::oauth2::*;
|
||||||
use deltachat::peerstate::*;
|
use deltachat::peerstate::*;
|
||||||
use deltachat::types::*;
|
use deltachat::types::*;
|
||||||
use deltachat::x::*;
|
use deltachat::x::*;
|
||||||
@@ -427,22 +426,19 @@ unsafe fn main_0(argc: libc::c_int, argv: *mut *mut libc::c_char) -> libc::c_int
|
|||||||
if addr.is_null() || *addr.offset(0isize) as libc::c_int == 0i32 {
|
if addr.is_null() || *addr.offset(0isize) as libc::c_int == 0i32 {
|
||||||
printf(b"oauth2: set addr first.\n\x00" as *const u8 as *const libc::c_char);
|
printf(b"oauth2: set addr first.\n\x00" as *const u8 as *const libc::c_char);
|
||||||
} else {
|
} else {
|
||||||
let oauth2_url: *mut libc::c_char = dc_get_oauth2_url(
|
let oauth2_url = dc_get_oauth2_url(
|
||||||
&ctx.read().unwrap(),
|
&ctx.read().unwrap(),
|
||||||
addr,
|
to_str(addr),
|
||||||
b"chat.delta:/com.b44t.messenger\x00" as *const u8 as *const libc::c_char,
|
"chat.delta:/com.b44t.messenger",
|
||||||
);
|
);
|
||||||
if oauth2_url.is_null() {
|
if oauth2_url.is_none() {
|
||||||
printf(
|
printf(
|
||||||
b"OAuth2 not available for %s.\n\x00" as *const u8 as *const libc::c_char,
|
b"OAuth2 not available for %s.\n\x00" as *const u8 as *const libc::c_char,
|
||||||
addr,
|
addr,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
printf(b"Open the following url, set mail_pw to the generated token and server_flags to 2:\n%s\n\x00"
|
println!("Open the following url, set mail_pw to the generated token and server_flags to 2:\n{}", oauth2_url.unwrap());
|
||||||
as *const u8 as *const libc::c_char,
|
|
||||||
oauth2_url);
|
|
||||||
}
|
}
|
||||||
free(oauth2_url as *mut libc::c_void);
|
|
||||||
}
|
}
|
||||||
free(addr as *mut libc::c_void);
|
free(addr as *mut libc::c_void);
|
||||||
} else if strcmp(cmd, b"clear\x00" as *const u8 as *const libc::c_char) == 0i32 {
|
} else if strcmp(cmd, b"clear\x00" as *const u8 as *const libc::c_char) == 0i32 {
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ use crate::dc_imap::*;
|
|||||||
use crate::dc_job::*;
|
use crate::dc_job::*;
|
||||||
use crate::dc_log::*;
|
use crate::dc_log::*;
|
||||||
use crate::dc_loginparam::*;
|
use crate::dc_loginparam::*;
|
||||||
use crate::dc_oauth2::*;
|
|
||||||
use crate::dc_saxparser::*;
|
use crate::dc_saxparser::*;
|
||||||
use crate::dc_sqlite3::*;
|
use crate::dc_sqlite3::*;
|
||||||
use crate::dc_strencode::*;
|
use crate::dc_strencode::*;
|
||||||
use crate::dc_tools::*;
|
use crate::dc_tools::*;
|
||||||
|
use crate::oauth2::*;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
use crate::x::*;
|
use crate::x::*;
|
||||||
|
|
||||||
@@ -205,11 +205,14 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &dc_context_t, _job: *mut
|
|||||||
}) as uintptr_t,
|
}) as uintptr_t,
|
||||||
0i32 as uintptr_t,
|
0i32 as uintptr_t,
|
||||||
);
|
);
|
||||||
let oauth2_addr: *mut libc::c_char =
|
let oauth2_addr = dc_get_oauth2_addr(
|
||||||
dc_get_oauth2_addr(context, (*param).addr, (*param).mail_pw);
|
context,
|
||||||
if !oauth2_addr.is_null() {
|
to_str((*param).addr),
|
||||||
|
to_str((*param).mail_pw),
|
||||||
|
);
|
||||||
|
if oauth2_addr.is_some() {
|
||||||
free((*param).addr as *mut libc::c_void);
|
free((*param).addr as *mut libc::c_void);
|
||||||
(*param).addr = oauth2_addr;
|
(*param).addr = strdup(to_cstring(oauth2_addr.unwrap()).as_ptr());
|
||||||
dc_sqlite3_set_config(
|
dc_sqlite3_set_config(
|
||||||
context,
|
context,
|
||||||
&context.sql.clone().read().unwrap(),
|
&context.sql.clone().read().unwrap(),
|
||||||
|
|||||||
@@ -839,7 +839,7 @@ pub unsafe fn dc_get_contact_encrinfo(
|
|||||||
.map(|k| k.formatted_fingerprint_c())
|
.map(|k| k.formatted_fingerprint_c())
|
||||||
.unwrap_or(std::ptr::null_mut());
|
.unwrap_or(std::ptr::null_mut());
|
||||||
if peerstate.addr.is_some()
|
if peerstate.addr.is_some()
|
||||||
&& to_str((*loginparam).addr) < peerstate.addr.as_ref().unwrap()
|
&& to_str((*loginparam).addr) < peerstate.addr.as_ref().unwrap().as_str()
|
||||||
{
|
{
|
||||||
cat_fingerprint(
|
cat_fingerprint(
|
||||||
&mut ret,
|
&mut ret,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::ffi::{CStr, CString};
|
use std::ffi::CString;
|
||||||
use std::net;
|
use std::net;
|
||||||
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
@@ -7,9 +7,9 @@ use crate::constants::*;
|
|||||||
use crate::dc_context::dc_context_t;
|
use crate::dc_context::dc_context_t;
|
||||||
use crate::dc_log::*;
|
use crate::dc_log::*;
|
||||||
use crate::dc_loginparam::*;
|
use crate::dc_loginparam::*;
|
||||||
use crate::dc_oauth2::dc_get_oauth2_access_token;
|
|
||||||
use crate::dc_sqlite3::*;
|
use crate::dc_sqlite3::*;
|
||||||
use crate::dc_tools::{to_str, to_string};
|
use crate::dc_tools::{to_str, to_string};
|
||||||
|
use crate::oauth2::dc_get_oauth2_access_token;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
|
|
||||||
pub const DC_IMAP_SEEN: usize = 0x0001;
|
pub const DC_IMAP_SEEN: usize = 0x0001;
|
||||||
@@ -413,22 +413,17 @@ impl Imap {
|
|||||||
if (server_flags & DC_LP_AUTH_OAUTH2) != 0 {
|
if (server_flags & DC_LP_AUTH_OAUTH2) != 0 {
|
||||||
let addr: &str = config.addr.as_ref();
|
let addr: &str = config.addr.as_ref();
|
||||||
|
|
||||||
let access_token = unsafe {
|
if let Some(token) =
|
||||||
CStr::from_ptr(dc_get_oauth2_access_token(
|
dc_get_oauth2_access_token(context, addr, imap_pw, DC_REGENERATE as usize)
|
||||||
context,
|
{
|
||||||
CString::new(addr).unwrap().as_ptr(),
|
let auth = OAuth2 {
|
||||||
CString::new(imap_pw).unwrap().as_ptr(),
|
user: imap_user.into(),
|
||||||
DC_REGENERATE as libc::c_int,
|
access_token: token,
|
||||||
))
|
};
|
||||||
.to_str()
|
client.authenticate("XOAUTH2", &auth)
|
||||||
.unwrap()
|
} else {
|
||||||
};
|
return 0;
|
||||||
|
}
|
||||||
let auth = OAuth2 {
|
|
||||||
user: imap_user.into(),
|
|
||||||
access_token: access_token.into(),
|
|
||||||
};
|
|
||||||
client.authenticate("XOAUTH2", &auth)
|
|
||||||
} else {
|
} else {
|
||||||
client.login(imap_user, imap_pw)
|
client.login(imap_user, imap_pw)
|
||||||
}
|
}
|
||||||
|
|||||||
405
src/dc_jsmn.rs
405
src/dc_jsmn.rs
@@ -1,405 +0,0 @@
|
|||||||
use crate::types::*;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright (c) 2010 Serge A. Zaitsev
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
/* *
|
|
||||||
* JSON type identifier. Basic types are:
|
|
||||||
* o Object
|
|
||||||
* o Array
|
|
||||||
* o String
|
|
||||||
* o Other primitive: number, boolean (true/false) or null
|
|
||||||
*/
|
|
||||||
pub type jsmntype_t = libc::c_uint;
|
|
||||||
pub const JSMN_PRIMITIVE: jsmntype_t = 4;
|
|
||||||
pub const JSMN_STRING: jsmntype_t = 3;
|
|
||||||
pub const JSMN_ARRAY: jsmntype_t = 2;
|
|
||||||
pub const JSMN_OBJECT: jsmntype_t = 1;
|
|
||||||
pub const JSMN_UNDEFINED: jsmntype_t = 0;
|
|
||||||
pub type jsmnerr = libc::c_int;
|
|
||||||
/* The string is not a full JSON packet, more bytes expected */
|
|
||||||
pub const JSMN_ERROR_PART: jsmnerr = -3;
|
|
||||||
/* Invalid character inside JSON string */
|
|
||||||
pub const JSMN_ERROR_INVAL: jsmnerr = -2;
|
|
||||||
/* Not enough tokens were provided */
|
|
||||||
pub const JSMN_ERROR_NOMEM: jsmnerr = -1;
|
|
||||||
/* *
|
|
||||||
* JSON token description.
|
|
||||||
* type type (object, array, string etc.)
|
|
||||||
* start start position in JSON data string
|
|
||||||
* end end position in JSON data string
|
|
||||||
*/
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct jsmntok_t {
|
|
||||||
pub type_0: jsmntype_t,
|
|
||||||
pub start: libc::c_int,
|
|
||||||
pub end: libc::c_int,
|
|
||||||
pub size: libc::c_int,
|
|
||||||
}
|
|
||||||
/* *
|
|
||||||
* JSON parser. Contains an array of token blocks available. Also stores
|
|
||||||
* the string being parsed now and current position in that string
|
|
||||||
*/
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct jsmn_parser {
|
|
||||||
pub pos: libc::c_uint,
|
|
||||||
pub toknext: libc::c_uint,
|
|
||||||
pub toksuper: libc::c_int,
|
|
||||||
}
|
|
||||||
|
|
||||||
/* *
|
|
||||||
* Create JSON parser over an array of tokens
|
|
||||||
*/
|
|
||||||
pub unsafe fn jsmn_init(mut parser: *mut jsmn_parser) {
|
|
||||||
(*parser).pos = 0i32 as libc::c_uint;
|
|
||||||
(*parser).toknext = 0i32 as libc::c_uint;
|
|
||||||
(*parser).toksuper = -1i32;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* *
|
|
||||||
* Run JSON parser. It parses a JSON data string into and array of tokens, each describing
|
|
||||||
* a single JSON object.
|
|
||||||
*/
|
|
||||||
pub unsafe fn jsmn_parse(
|
|
||||||
mut parser: *mut jsmn_parser,
|
|
||||||
js: *const libc::c_char,
|
|
||||||
len: size_t,
|
|
||||||
tokens: *mut jsmntok_t,
|
|
||||||
num_tokens: libc::c_uint,
|
|
||||||
) -> libc::c_int {
|
|
||||||
let mut r: libc::c_int;
|
|
||||||
let mut i: libc::c_int;
|
|
||||||
let mut token: *mut jsmntok_t;
|
|
||||||
let mut count: libc::c_int = (*parser).toknext as libc::c_int;
|
|
||||||
while (*parser).pos < len as libc::c_uint
|
|
||||||
&& *js.offset((*parser).pos as isize) as libc::c_int != '\u{0}' as i32
|
|
||||||
{
|
|
||||||
let c: libc::c_char;
|
|
||||||
let type_0: jsmntype_t;
|
|
||||||
c = *js.offset((*parser).pos as isize);
|
|
||||||
match c as libc::c_int {
|
|
||||||
123 | 91 => {
|
|
||||||
count += 1;
|
|
||||||
if !tokens.is_null() {
|
|
||||||
token = jsmn_alloc_token(parser, tokens, num_tokens as size_t);
|
|
||||||
if token.is_null() {
|
|
||||||
return JSMN_ERROR_NOMEM as libc::c_int;
|
|
||||||
}
|
|
||||||
if (*parser).toksuper != -1i32 {
|
|
||||||
let ref mut fresh0 = (*tokens.offset((*parser).toksuper as isize)).size;
|
|
||||||
*fresh0 += 1
|
|
||||||
}
|
|
||||||
(*token).type_0 = (if c as libc::c_int == '{' as i32 {
|
|
||||||
JSMN_OBJECT as libc::c_int
|
|
||||||
} else {
|
|
||||||
JSMN_ARRAY as libc::c_int
|
|
||||||
}) as jsmntype_t;
|
|
||||||
(*token).start = (*parser).pos as libc::c_int;
|
|
||||||
(*parser).toksuper =
|
|
||||||
(*parser).toknext.wrapping_sub(1i32 as libc::c_uint) as libc::c_int
|
|
||||||
}
|
|
||||||
}
|
|
||||||
125 | 93 => {
|
|
||||||
if !tokens.is_null() {
|
|
||||||
type_0 = (if c as libc::c_int == '}' as i32 {
|
|
||||||
JSMN_OBJECT as libc::c_int
|
|
||||||
} else {
|
|
||||||
JSMN_ARRAY as libc::c_int
|
|
||||||
}) as jsmntype_t;
|
|
||||||
i = (*parser).toknext.wrapping_sub(1i32 as libc::c_uint) as libc::c_int;
|
|
||||||
while i >= 0i32 {
|
|
||||||
token = &mut *tokens.offset(i as isize) as *mut jsmntok_t;
|
|
||||||
if (*token).start != -1i32 && (*token).end == -1i32 {
|
|
||||||
if (*token).type_0 as libc::c_uint != type_0 as libc::c_uint {
|
|
||||||
return JSMN_ERROR_INVAL as libc::c_int;
|
|
||||||
}
|
|
||||||
(*parser).toksuper = -1i32;
|
|
||||||
(*token).end =
|
|
||||||
(*parser).pos.wrapping_add(1i32 as libc::c_uint) as libc::c_int;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
i -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if i == -1i32 {
|
|
||||||
return JSMN_ERROR_INVAL as libc::c_int;
|
|
||||||
}
|
|
||||||
while i >= 0i32 {
|
|
||||||
token = &mut *tokens.offset(i as isize) as *mut jsmntok_t;
|
|
||||||
if (*token).start != -1i32 && (*token).end == -1i32 {
|
|
||||||
(*parser).toksuper = i;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
i -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
34 => {
|
|
||||||
r = jsmn_parse_string(parser, js, len, tokens, num_tokens as size_t);
|
|
||||||
if r < 0i32 {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
count += 1;
|
|
||||||
if (*parser).toksuper != -1i32 && !tokens.is_null() {
|
|
||||||
let ref mut fresh1 = (*tokens.offset((*parser).toksuper as isize)).size;
|
|
||||||
*fresh1 += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
9 | 13 | 10 | 32 => {}
|
|
||||||
58 => {
|
|
||||||
(*parser).toksuper =
|
|
||||||
(*parser).toknext.wrapping_sub(1i32 as libc::c_uint) as libc::c_int
|
|
||||||
}
|
|
||||||
44 => {
|
|
||||||
if !tokens.is_null()
|
|
||||||
&& (*parser).toksuper != -1i32
|
|
||||||
&& (*tokens.offset((*parser).toksuper as isize)).type_0 as libc::c_uint
|
|
||||||
!= JSMN_ARRAY as libc::c_int as libc::c_uint
|
|
||||||
&& (*tokens.offset((*parser).toksuper as isize)).type_0 as libc::c_uint
|
|
||||||
!= JSMN_OBJECT as libc::c_int as libc::c_uint
|
|
||||||
{
|
|
||||||
i = (*parser).toknext.wrapping_sub(1i32 as libc::c_uint) as libc::c_int;
|
|
||||||
while i >= 0i32 {
|
|
||||||
if (*tokens.offset(i as isize)).type_0 as libc::c_uint
|
|
||||||
== JSMN_ARRAY as libc::c_int as libc::c_uint
|
|
||||||
|| (*tokens.offset(i as isize)).type_0 as libc::c_uint
|
|
||||||
== JSMN_OBJECT as libc::c_int as libc::c_uint
|
|
||||||
{
|
|
||||||
if (*tokens.offset(i as isize)).start != -1i32
|
|
||||||
&& (*tokens.offset(i as isize)).end == -1i32
|
|
||||||
{
|
|
||||||
(*parser).toksuper = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens as size_t);
|
|
||||||
if r < 0i32 {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
count += 1;
|
|
||||||
if (*parser).toksuper != -1i32 && !tokens.is_null() {
|
|
||||||
let ref mut fresh2 = (*tokens.offset((*parser).toksuper as isize)).size;
|
|
||||||
*fresh2 += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(*parser).pos = (*parser).pos.wrapping_add(1)
|
|
||||||
}
|
|
||||||
if !tokens.is_null() {
|
|
||||||
i = (*parser).toknext.wrapping_sub(1i32 as libc::c_uint) as libc::c_int;
|
|
||||||
while i >= 0i32 {
|
|
||||||
if (*tokens.offset(i as isize)).start != -1i32
|
|
||||||
&& (*tokens.offset(i as isize)).end == -1i32
|
|
||||||
{
|
|
||||||
return JSMN_ERROR_PART as libc::c_int;
|
|
||||||
}
|
|
||||||
i -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
count
|
|
||||||
}
|
|
||||||
|
|
||||||
/* *
|
|
||||||
* Fills next available token with JSON primitive.
|
|
||||||
*/
|
|
||||||
unsafe fn jsmn_parse_primitive(
|
|
||||||
mut parser: *mut jsmn_parser,
|
|
||||||
js: *const libc::c_char,
|
|
||||||
len: size_t,
|
|
||||||
tokens: *mut jsmntok_t,
|
|
||||||
num_tokens: size_t,
|
|
||||||
) -> libc::c_int {
|
|
||||||
let token: *mut jsmntok_t;
|
|
||||||
let start: libc::c_int;
|
|
||||||
start = (*parser).pos as libc::c_int;
|
|
||||||
while (*parser).pos < len as libc::c_uint
|
|
||||||
&& *js.offset((*parser).pos as isize) as libc::c_int != '\u{0}' as i32
|
|
||||||
{
|
|
||||||
match *js.offset((*parser).pos as isize) as libc::c_int {
|
|
||||||
58 | 9 | 13 | 10 | 32 | 44 | 93 | 125 => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
if (*js.offset((*parser).pos as isize) as libc::c_int) < 32i32
|
|
||||||
|| *js.offset((*parser).pos as isize) as libc::c_int >= 127i32
|
|
||||||
{
|
|
||||||
(*parser).pos = start as libc::c_uint;
|
|
||||||
return JSMN_ERROR_INVAL as libc::c_int;
|
|
||||||
}
|
|
||||||
(*parser).pos = (*parser).pos.wrapping_add(1)
|
|
||||||
}
|
|
||||||
if tokens.is_null() {
|
|
||||||
(*parser).pos = (*parser).pos.wrapping_sub(1);
|
|
||||||
return 0i32;
|
|
||||||
}
|
|
||||||
token = jsmn_alloc_token(parser, tokens, num_tokens);
|
|
||||||
if token.is_null() {
|
|
||||||
(*parser).pos = start as libc::c_uint;
|
|
||||||
return JSMN_ERROR_NOMEM as libc::c_int;
|
|
||||||
}
|
|
||||||
jsmn_fill_token(token, JSMN_PRIMITIVE, start, (*parser).pos as libc::c_int);
|
|
||||||
(*parser).pos = (*parser).pos.wrapping_sub(1);
|
|
||||||
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
/* *
|
|
||||||
* Fills token type and boundaries.
|
|
||||||
*/
|
|
||||||
unsafe fn jsmn_fill_token(
|
|
||||||
mut token: *mut jsmntok_t,
|
|
||||||
type_0: jsmntype_t,
|
|
||||||
start: libc::c_int,
|
|
||||||
end: libc::c_int,
|
|
||||||
) {
|
|
||||||
(*token).type_0 = type_0;
|
|
||||||
(*token).start = start;
|
|
||||||
(*token).end = end;
|
|
||||||
(*token).size = 0i32;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright (c) 2010 Serge A. Zaitsev
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* *
|
|
||||||
* Allocates a fresh unused token from the token pool.
|
|
||||||
*/
|
|
||||||
unsafe fn jsmn_alloc_token(
|
|
||||||
mut parser: *mut jsmn_parser,
|
|
||||||
tokens: *mut jsmntok_t,
|
|
||||||
num_tokens: size_t,
|
|
||||||
) -> *mut jsmntok_t {
|
|
||||||
let mut tok: *mut jsmntok_t;
|
|
||||||
if (*parser).toknext as size_t >= num_tokens {
|
|
||||||
return 0 as *mut jsmntok_t;
|
|
||||||
}
|
|
||||||
let fresh3 = (*parser).toknext;
|
|
||||||
(*parser).toknext = (*parser).toknext.wrapping_add(1);
|
|
||||||
tok = &mut *tokens.offset(fresh3 as isize) as *mut jsmntok_t;
|
|
||||||
(*tok).end = -1i32;
|
|
||||||
(*tok).start = (*tok).end;
|
|
||||||
(*tok).size = 0i32;
|
|
||||||
|
|
||||||
tok
|
|
||||||
}
|
|
||||||
|
|
||||||
/* *
|
|
||||||
* Fills next token with JSON string.
|
|
||||||
*/
|
|
||||||
unsafe fn jsmn_parse_string(
|
|
||||||
mut parser: *mut jsmn_parser,
|
|
||||||
js: *const libc::c_char,
|
|
||||||
len: size_t,
|
|
||||||
tokens: *mut jsmntok_t,
|
|
||||||
num_tokens: size_t,
|
|
||||||
) -> libc::c_int {
|
|
||||||
let token: *mut jsmntok_t;
|
|
||||||
let start: libc::c_int = (*parser).pos as libc::c_int;
|
|
||||||
(*parser).pos = (*parser).pos.wrapping_add(1);
|
|
||||||
while ((*parser).pos as size_t) < len
|
|
||||||
&& *js.offset((*parser).pos as isize) as libc::c_int != '\u{0}' as i32
|
|
||||||
{
|
|
||||||
let c: libc::c_char = *js.offset((*parser).pos as isize);
|
|
||||||
if c as libc::c_int == '\"' as i32 {
|
|
||||||
if tokens.is_null() {
|
|
||||||
return 0i32;
|
|
||||||
}
|
|
||||||
token = jsmn_alloc_token(parser, tokens, num_tokens);
|
|
||||||
if token.is_null() {
|
|
||||||
(*parser).pos = start as libc::c_uint;
|
|
||||||
return JSMN_ERROR_NOMEM as libc::c_int;
|
|
||||||
}
|
|
||||||
jsmn_fill_token(
|
|
||||||
token,
|
|
||||||
JSMN_STRING,
|
|
||||||
start + 1i32,
|
|
||||||
(*parser).pos as libc::c_int,
|
|
||||||
);
|
|
||||||
return 0i32;
|
|
||||||
}
|
|
||||||
if c as libc::c_int == '\\' as i32 && ((*parser).pos.wrapping_add(1) as size_t) < len {
|
|
||||||
let mut i: libc::c_int;
|
|
||||||
(*parser).pos = (*parser).pos.wrapping_add(1);
|
|
||||||
match *js.offset((*parser).pos as isize) as libc::c_int {
|
|
||||||
34 | 47 | 92 | 98 | 102 | 114 | 110 | 116 => {}
|
|
||||||
117 => {
|
|
||||||
(*parser).pos = (*parser).pos.wrapping_add(1);
|
|
||||||
i = 0i32;
|
|
||||||
while i < 4i32
|
|
||||||
&& ((*parser).pos as size_t) < len
|
|
||||||
&& *js.offset((*parser).pos as isize) as libc::c_int != '\u{0}' as i32
|
|
||||||
{
|
|
||||||
if !(*js.offset((*parser).pos as isize) as libc::c_int >= 48i32
|
|
||||||
&& *js.offset((*parser).pos as isize) as libc::c_int <= 57i32
|
|
||||||
|| *js.offset((*parser).pos as isize) as libc::c_int >= 65i32
|
|
||||||
&& *js.offset((*parser).pos as isize) as libc::c_int <= 70i32
|
|
||||||
|| *js.offset((*parser).pos as isize) as libc::c_int >= 97i32
|
|
||||||
&& *js.offset((*parser).pos as isize) as libc::c_int <= 102i32)
|
|
||||||
{
|
|
||||||
(*parser).pos = start as libc::c_uint;
|
|
||||||
return JSMN_ERROR_INVAL as libc::c_int;
|
|
||||||
}
|
|
||||||
(*parser).pos = (*parser).pos.wrapping_add(1);
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
(*parser).pos = (*parser).pos.wrapping_sub(1)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
(*parser).pos = start as libc::c_uint;
|
|
||||||
return JSMN_ERROR_INVAL as libc::c_int;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(*parser).pos = (*parser).pos.wrapping_add(1)
|
|
||||||
}
|
|
||||||
(*parser).pos = start as libc::c_uint;
|
|
||||||
|
|
||||||
JSMN_ERROR_PART as libc::c_int
|
|
||||||
}
|
|
||||||
621
src/dc_oauth2.rs
621
src/dc_oauth2.rs
@@ -1,621 +0,0 @@
|
|||||||
use crate::constants::Event;
|
|
||||||
use crate::dc_contact::*;
|
|
||||||
use crate::dc_context::dc_context_t;
|
|
||||||
use crate::dc_jsmn::*;
|
|
||||||
use crate::dc_log::*;
|
|
||||||
use crate::dc_sqlite3::*;
|
|
||||||
use crate::dc_strencode::*;
|
|
||||||
use crate::dc_tools::*;
|
|
||||||
use crate::types::*;
|
|
||||||
use crate::x::*;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct oauth2_t {
|
|
||||||
pub client_id: *mut libc::c_char,
|
|
||||||
pub get_code: *mut libc::c_char,
|
|
||||||
pub init_token: *mut libc::c_char,
|
|
||||||
pub refresh_token: *mut libc::c_char,
|
|
||||||
pub get_userinfo: *mut libc::c_char,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn dc_get_oauth2_url(
|
|
||||||
context: &dc_context_t,
|
|
||||||
addr: *const libc::c_char,
|
|
||||||
redirect_uri: *const libc::c_char,
|
|
||||||
) -> *mut libc::c_char {
|
|
||||||
let mut oauth2: *mut oauth2_t = 0 as *mut oauth2_t;
|
|
||||||
let mut oauth2_url: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
||||||
if !(redirect_uri.is_null() || *redirect_uri.offset(0isize) as libc::c_int == 0i32) {
|
|
||||||
oauth2 = get_info(addr);
|
|
||||||
if !oauth2.is_null() {
|
|
||||||
dc_sqlite3_set_config(
|
|
||||||
context,
|
|
||||||
&context.sql.clone().read().unwrap(),
|
|
||||||
b"oauth2_pending_redirect_uri\x00" as *const u8 as *const libc::c_char,
|
|
||||||
redirect_uri,
|
|
||||||
);
|
|
||||||
oauth2_url = dc_strdup((*oauth2).get_code);
|
|
||||||
replace_in_uri(
|
|
||||||
&mut oauth2_url,
|
|
||||||
b"$CLIENT_ID\x00" as *const u8 as *const libc::c_char,
|
|
||||||
(*oauth2).client_id,
|
|
||||||
);
|
|
||||||
replace_in_uri(
|
|
||||||
&mut oauth2_url,
|
|
||||||
b"$REDIRECT_URI\x00" as *const u8 as *const libc::c_char,
|
|
||||||
redirect_uri,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(oauth2 as *mut libc::c_void);
|
|
||||||
|
|
||||||
oauth2_url
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn replace_in_uri(
|
|
||||||
uri: *mut *mut libc::c_char,
|
|
||||||
key: *const libc::c_char,
|
|
||||||
value: *const libc::c_char,
|
|
||||||
) {
|
|
||||||
if !uri.is_null() && !key.is_null() && !value.is_null() {
|
|
||||||
let value_urlencoded: *mut libc::c_char = dc_urlencode(value);
|
|
||||||
dc_str_replace(uri, key, value_urlencoded);
|
|
||||||
free(value_urlencoded as *mut libc::c_void);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn get_info(addr: *const libc::c_char) -> *mut oauth2_t {
|
|
||||||
let mut oauth2: *mut oauth2_t = 0 as *mut oauth2_t;
|
|
||||||
let addr_normalized: *mut libc::c_char;
|
|
||||||
let mut domain: *const libc::c_char;
|
|
||||||
addr_normalized = dc_addr_normalize(addr);
|
|
||||||
domain = strchr(addr_normalized, '@' as i32);
|
|
||||||
if !(domain.is_null() || *domain.offset(0isize) as libc::c_int == 0i32) {
|
|
||||||
domain = domain.offset(1isize);
|
|
||||||
if strcasecmp(domain, b"gmail.com\x00" as *const u8 as *const libc::c_char) == 0i32
|
|
||||||
|| strcasecmp(
|
|
||||||
domain,
|
|
||||||
b"googlemail.com\x00" as *const u8 as *const libc::c_char,
|
|
||||||
) == 0i32
|
|
||||||
{
|
|
||||||
oauth2 = calloc(1, ::std::mem::size_of::<oauth2_t>()) as *mut oauth2_t;
|
|
||||||
(*oauth2).client_id =
|
|
||||||
b"959970109878-4mvtgf6feshskf7695nfln6002mom908.apps.googleusercontent.com\x00"
|
|
||||||
as *const u8 as *const libc::c_char as *mut libc::c_char;
|
|
||||||
(*oauth2).get_code =
|
|
||||||
b"https://accounts.google.com/o/oauth2/auth?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline\x00"
|
|
||||||
as *const u8 as *const libc::c_char as *mut libc::c_char;
|
|
||||||
(*oauth2).init_token =
|
|
||||||
b"https://accounts.google.com/o/oauth2/token?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&code=$CODE&grant_type=authorization_code\x00"
|
|
||||||
as *const u8 as *const libc::c_char as *mut libc::c_char;
|
|
||||||
(*oauth2).refresh_token =
|
|
||||||
b"https://accounts.google.com/o/oauth2/token?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&refresh_token=$REFRESH_TOKEN&grant_type=refresh_token\x00"
|
|
||||||
as *const u8 as *const libc::c_char as *mut libc::c_char;
|
|
||||||
(*oauth2).get_userinfo =
|
|
||||||
b"https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=$ACCESS_TOKEN\x00"
|
|
||||||
as *const u8 as *const libc::c_char as *mut libc::c_char
|
|
||||||
} else if strcasecmp(
|
|
||||||
domain,
|
|
||||||
b"yandex.com\x00" as *const u8 as *const libc::c_char,
|
|
||||||
) == 0i32
|
|
||||||
|| strcasecmp(domain, b"yandex.ru\x00" as *const u8 as *const libc::c_char) == 0i32
|
|
||||||
|| strcasecmp(domain, b"yandex.ua\x00" as *const u8 as *const libc::c_char) == 0i32
|
|
||||||
{
|
|
||||||
oauth2 = calloc(1, ::std::mem::size_of::<oauth2_t>()) as *mut oauth2_t;
|
|
||||||
(*oauth2).client_id = b"c4d0b6735fc8420a816d7e1303469341\x00" as *const u8
|
|
||||||
as *const libc::c_char as *mut libc::c_char;
|
|
||||||
(*oauth2).get_code =
|
|
||||||
b"https://oauth.yandex.com/authorize?client_id=$CLIENT_ID&response_type=code&scope=mail%3Aimap_full%20mail%3Asmtp&force_confirm=true\x00"
|
|
||||||
as *const u8 as *const libc::c_char as *mut libc::c_char;
|
|
||||||
(*oauth2).init_token =
|
|
||||||
b"https://oauth.yandex.com/token?grant_type=authorization_code&code=$CODE&client_id=$CLIENT_ID&client_secret=58b8c6e94cf44fbe952da8511955dacf\x00"
|
|
||||||
as *const u8 as *const libc::c_char as *mut libc::c_char;
|
|
||||||
(*oauth2).refresh_token =
|
|
||||||
b"https://oauth.yandex.com/token?grant_type=refresh_token&refresh_token=$REFRESH_TOKEN&client_id=$CLIENT_ID&client_secret=58b8c6e94cf44fbe952da8511955dacf\x00"
|
|
||||||
as *const u8 as *const libc::c_char as *mut libc::c_char
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(addr_normalized as *mut libc::c_void);
|
|
||||||
|
|
||||||
oauth2
|
|
||||||
}
|
|
||||||
|
|
||||||
// the following function may block due http-requests;
|
|
||||||
// must not be called from the main thread or by the ui!
|
|
||||||
pub unsafe fn dc_get_oauth2_access_token(
|
|
||||||
context: &dc_context_t,
|
|
||||||
addr: *const libc::c_char,
|
|
||||||
code: *const libc::c_char,
|
|
||||||
flags: libc::c_int,
|
|
||||||
) -> *mut libc::c_char {
|
|
||||||
let current_block: u64;
|
|
||||||
let mut oauth2: *mut oauth2_t = 0 as *mut oauth2_t;
|
|
||||||
let mut access_token: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
||||||
let mut refresh_token: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
||||||
let mut refresh_token_for: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
||||||
let mut redirect_uri: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
||||||
let mut update_redirect_uri_on_success: libc::c_int = 0i32;
|
|
||||||
let mut token_url: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
||||||
let mut expires_in: time_t = 0i32 as time_t;
|
|
||||||
let mut error: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
||||||
let mut error_description: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
||||||
let mut json: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
||||||
let mut parser: jsmn_parser = jsmn_parser {
|
|
||||||
pos: 0,
|
|
||||||
toknext: 0,
|
|
||||||
toksuper: 0,
|
|
||||||
};
|
|
||||||
// we do not expect nor read more tokens
|
|
||||||
let mut tok: [jsmntok_t; 128] = [jsmntok_t {
|
|
||||||
type_0: JSMN_UNDEFINED,
|
|
||||||
start: 0,
|
|
||||||
end: 0,
|
|
||||||
size: 0,
|
|
||||||
}; 128];
|
|
||||||
let tok_cnt: libc::c_int;
|
|
||||||
if code.is_null() || *code.offset(0isize) as libc::c_int == 0i32 {
|
|
||||||
dc_log_warning(
|
|
||||||
context,
|
|
||||||
0i32,
|
|
||||||
b"Internal OAuth2 error\x00" as *const u8 as *const libc::c_char,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
oauth2 = get_info(addr);
|
|
||||||
if oauth2.is_null() {
|
|
||||||
dc_log_warning(
|
|
||||||
context,
|
|
||||||
0i32,
|
|
||||||
b"Internal OAuth2 error: 2\x00" as *const u8 as *const libc::c_char,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let lock = context.oauth2_critical.clone();
|
|
||||||
|
|
||||||
let l = lock.lock().unwrap();
|
|
||||||
// read generated token
|
|
||||||
if 0 == flags & 0x1i32 && !is_expired(context) {
|
|
||||||
access_token = dc_sqlite3_get_config(
|
|
||||||
context,
|
|
||||||
&context.sql.clone().read().unwrap(),
|
|
||||||
b"oauth2_access_token\x00" as *const u8 as *const libc::c_char,
|
|
||||||
0 as *const libc::c_char,
|
|
||||||
);
|
|
||||||
if !access_token.is_null() {
|
|
||||||
// success
|
|
||||||
current_block = 16914036240511706173;
|
|
||||||
} else {
|
|
||||||
current_block = 2838571290723028321;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
current_block = 2838571290723028321;
|
|
||||||
}
|
|
||||||
match current_block {
|
|
||||||
16914036240511706173 => {}
|
|
||||||
_ => {
|
|
||||||
refresh_token = dc_sqlite3_get_config(
|
|
||||||
context,
|
|
||||||
&context.sql.clone().read().unwrap(),
|
|
||||||
b"oauth2_refresh_token\x00" as *const u8 as *const libc::c_char,
|
|
||||||
0 as *const libc::c_char,
|
|
||||||
);
|
|
||||||
refresh_token_for = dc_sqlite3_get_config(
|
|
||||||
context,
|
|
||||||
&context.sql.clone().read().unwrap(),
|
|
||||||
b"oauth2_refresh_token_for\x00" as *const u8 as *const libc::c_char,
|
|
||||||
b"unset\x00" as *const u8 as *const libc::c_char,
|
|
||||||
);
|
|
||||||
if refresh_token.is_null() || strcmp(refresh_token_for, code) != 0i32 {
|
|
||||||
dc_log_info(
|
|
||||||
context,
|
|
||||||
0i32,
|
|
||||||
b"Generate OAuth2 refresh_token and access_token...\x00" as *const u8
|
|
||||||
as *const libc::c_char,
|
|
||||||
);
|
|
||||||
redirect_uri = dc_sqlite3_get_config(
|
|
||||||
context,
|
|
||||||
&context.sql.clone().read().unwrap(),
|
|
||||||
b"oauth2_pending_redirect_uri\x00" as *const u8 as *const libc::c_char,
|
|
||||||
b"unset\x00" as *const u8 as *const libc::c_char,
|
|
||||||
);
|
|
||||||
update_redirect_uri_on_success = 1i32;
|
|
||||||
token_url = dc_strdup((*oauth2).init_token)
|
|
||||||
} else {
|
|
||||||
dc_log_info(
|
|
||||||
context,
|
|
||||||
0i32,
|
|
||||||
b"Regenerate OAuth2 access_token by refresh_token...\x00" as *const u8
|
|
||||||
as *const libc::c_char,
|
|
||||||
);
|
|
||||||
redirect_uri = dc_sqlite3_get_config(
|
|
||||||
context,
|
|
||||||
&context.sql.clone().read().unwrap(),
|
|
||||||
b"oauth2_redirect_uri\x00" as *const u8 as *const libc::c_char,
|
|
||||||
b"unset\x00" as *const u8 as *const libc::c_char,
|
|
||||||
);
|
|
||||||
token_url = dc_strdup((*oauth2).refresh_token)
|
|
||||||
}
|
|
||||||
replace_in_uri(
|
|
||||||
&mut token_url,
|
|
||||||
b"$CLIENT_ID\x00" as *const u8 as *const libc::c_char,
|
|
||||||
(*oauth2).client_id,
|
|
||||||
);
|
|
||||||
replace_in_uri(
|
|
||||||
&mut token_url,
|
|
||||||
b"$REDIRECT_URI\x00" as *const u8 as *const libc::c_char,
|
|
||||||
redirect_uri,
|
|
||||||
);
|
|
||||||
replace_in_uri(
|
|
||||||
&mut token_url,
|
|
||||||
b"$CODE\x00" as *const u8 as *const libc::c_char,
|
|
||||||
code,
|
|
||||||
);
|
|
||||||
replace_in_uri(
|
|
||||||
&mut token_url,
|
|
||||||
b"$REFRESH_TOKEN\x00" as *const u8 as *const libc::c_char,
|
|
||||||
refresh_token,
|
|
||||||
);
|
|
||||||
json = (context.cb)(
|
|
||||||
context,
|
|
||||||
Event::HTTP_POST,
|
|
||||||
token_url as uintptr_t,
|
|
||||||
0i32 as uintptr_t,
|
|
||||||
) as *mut libc::c_char;
|
|
||||||
if json.is_null() {
|
|
||||||
dc_log_warning(
|
|
||||||
context,
|
|
||||||
0i32,
|
|
||||||
b"Error calling OAuth2 at %s\x00" as *const u8 as *const libc::c_char,
|
|
||||||
token_url,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
jsmn_init(&mut parser);
|
|
||||||
tok_cnt = jsmn_parse(
|
|
||||||
&mut parser,
|
|
||||||
json,
|
|
||||||
strlen(json),
|
|
||||||
tok.as_mut_ptr(),
|
|
||||||
(::std::mem::size_of::<[jsmntok_t; 128]>() as libc::c_ulong)
|
|
||||||
.wrapping_div(::std::mem::size_of::<jsmntok_t>() as libc::c_ulong)
|
|
||||||
as libc::c_uint,
|
|
||||||
);
|
|
||||||
if tok_cnt < 2i32
|
|
||||||
|| tok[0usize].type_0 as libc::c_uint
|
|
||||||
!= JSMN_OBJECT as libc::c_int as libc::c_uint
|
|
||||||
{
|
|
||||||
dc_log_warning(
|
|
||||||
context,
|
|
||||||
0i32,
|
|
||||||
b"Failed to parse OAuth2 json from %s\x00" as *const u8
|
|
||||||
as *const libc::c_char,
|
|
||||||
token_url,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let mut i: libc::c_int = 1i32;
|
|
||||||
while i < tok_cnt {
|
|
||||||
if access_token.is_null()
|
|
||||||
&& jsoneq(
|
|
||||||
json,
|
|
||||||
&mut *tok.as_mut_ptr().offset(i as isize),
|
|
||||||
b"access_token\x00" as *const u8 as *const libc::c_char,
|
|
||||||
) == 0i32
|
|
||||||
{
|
|
||||||
access_token = jsondup(
|
|
||||||
json,
|
|
||||||
&mut *tok.as_mut_ptr().offset((i + 1i32) as isize),
|
|
||||||
)
|
|
||||||
} else if refresh_token.is_null()
|
|
||||||
&& jsoneq(
|
|
||||||
json,
|
|
||||||
&mut *tok.as_mut_ptr().offset(i as isize),
|
|
||||||
b"refresh_token\x00" as *const u8 as *const libc::c_char,
|
|
||||||
) == 0i32
|
|
||||||
{
|
|
||||||
refresh_token = jsondup(
|
|
||||||
json,
|
|
||||||
&mut *tok.as_mut_ptr().offset((i + 1i32) as isize),
|
|
||||||
)
|
|
||||||
} else if jsoneq(
|
|
||||||
json,
|
|
||||||
&mut *tok.as_mut_ptr().offset(i as isize),
|
|
||||||
b"expires_in\x00" as *const u8 as *const libc::c_char,
|
|
||||||
) == 0i32
|
|
||||||
{
|
|
||||||
let expires_in_str: *mut libc::c_char = jsondup(
|
|
||||||
json,
|
|
||||||
&mut *tok.as_mut_ptr().offset((i + 1i32) as isize),
|
|
||||||
);
|
|
||||||
if !expires_in_str.is_null() {
|
|
||||||
let val: time_t = atol(expires_in_str);
|
|
||||||
if val > 20 && val < (60 * 60 * 24 * 365 * 5) {
|
|
||||||
expires_in = val
|
|
||||||
}
|
|
||||||
free(expires_in_str as *mut libc::c_void);
|
|
||||||
}
|
|
||||||
} else if error.is_null()
|
|
||||||
&& jsoneq(
|
|
||||||
json,
|
|
||||||
&mut *tok.as_mut_ptr().offset(i as isize),
|
|
||||||
b"error\x00" as *const u8 as *const libc::c_char,
|
|
||||||
) == 0i32
|
|
||||||
{
|
|
||||||
error = jsondup(
|
|
||||||
json,
|
|
||||||
&mut *tok.as_mut_ptr().offset((i + 1i32) as isize),
|
|
||||||
)
|
|
||||||
} else if error_description.is_null()
|
|
||||||
&& jsoneq(
|
|
||||||
json,
|
|
||||||
&mut *tok.as_mut_ptr().offset(i as isize),
|
|
||||||
b"error_description\x00" as *const u8
|
|
||||||
as *const libc::c_char,
|
|
||||||
) == 0i32
|
|
||||||
{
|
|
||||||
error_description = jsondup(
|
|
||||||
json,
|
|
||||||
&mut *tok.as_mut_ptr().offset((i + 1i32) as isize),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
if !error.is_null() || !error_description.is_null() {
|
|
||||||
dc_log_warning(
|
|
||||||
context,
|
|
||||||
0i32,
|
|
||||||
b"OAuth error: %s: %s\x00" as *const u8 as *const libc::c_char,
|
|
||||||
if !error.is_null() {
|
|
||||||
error
|
|
||||||
} else {
|
|
||||||
b"unknown\x00" as *const u8 as *const libc::c_char
|
|
||||||
},
|
|
||||||
if !error_description.is_null() {
|
|
||||||
error_description
|
|
||||||
} else {
|
|
||||||
b"no details\x00" as *const u8 as *const libc::c_char
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if !refresh_token.is_null()
|
|
||||||
&& 0 != *refresh_token.offset(0isize) as libc::c_int
|
|
||||||
{
|
|
||||||
dc_sqlite3_set_config(
|
|
||||||
context,
|
|
||||||
&context.sql.clone().read().unwrap(),
|
|
||||||
b"oauth2_refresh_token\x00" as *const u8 as *const libc::c_char,
|
|
||||||
refresh_token,
|
|
||||||
);
|
|
||||||
dc_sqlite3_set_config(
|
|
||||||
context,
|
|
||||||
&context.sql.clone().read().unwrap(),
|
|
||||||
b"oauth2_refresh_token_for\x00" as *const u8
|
|
||||||
as *const libc::c_char,
|
|
||||||
code,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// after that, save the access token.
|
|
||||||
// if it's unset, we may get it in the next round as we have the refresh_token now.
|
|
||||||
if access_token.is_null()
|
|
||||||
|| *access_token.offset(0isize) as libc::c_int == 0i32
|
|
||||||
{
|
|
||||||
dc_log_warning(
|
|
||||||
context,
|
|
||||||
0i32,
|
|
||||||
b"Failed to find OAuth2 access token\x00" as *const u8
|
|
||||||
as *const libc::c_char,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
dc_sqlite3_set_config(
|
|
||||||
context,
|
|
||||||
&context.sql.clone().read().unwrap(),
|
|
||||||
b"oauth2_access_token\x00" as *const u8 as *const libc::c_char,
|
|
||||||
access_token,
|
|
||||||
);
|
|
||||||
dc_sqlite3_set_config_int64(
|
|
||||||
context,
|
|
||||||
&context.sql.clone().read().unwrap(),
|
|
||||||
b"oauth2_timestamp_expires\x00" as *const u8
|
|
||||||
as *const libc::c_char,
|
|
||||||
(if 0 != expires_in {
|
|
||||||
time(0 as *mut time_t) + expires_in - 5
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}) as int64_t,
|
|
||||||
);
|
|
||||||
if 0 != update_redirect_uri_on_success {
|
|
||||||
dc_sqlite3_set_config(
|
|
||||||
context,
|
|
||||||
&context.sql.clone().read().unwrap(),
|
|
||||||
b"oauth2_redirect_uri\x00" as *const u8
|
|
||||||
as *const libc::c_char,
|
|
||||||
redirect_uri,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
drop(l);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(refresh_token as *mut libc::c_void);
|
|
||||||
free(refresh_token_for as *mut libc::c_void);
|
|
||||||
free(redirect_uri as *mut libc::c_void);
|
|
||||||
free(token_url as *mut libc::c_void);
|
|
||||||
free(json as *mut libc::c_void);
|
|
||||||
free(error as *mut libc::c_void);
|
|
||||||
free(error_description as *mut libc::c_void);
|
|
||||||
free(oauth2 as *mut libc::c_void);
|
|
||||||
return if !access_token.is_null() {
|
|
||||||
access_token
|
|
||||||
} else {
|
|
||||||
dc_strdup(0 as *const libc::c_char)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn jsondup(json: *const libc::c_char, tok: *mut jsmntok_t) -> *mut libc::c_char {
|
|
||||||
if (*tok).type_0 as libc::c_uint == JSMN_STRING as libc::c_int as libc::c_uint
|
|
||||||
|| (*tok).type_0 as libc::c_uint == JSMN_PRIMITIVE as libc::c_int as libc::c_uint
|
|
||||||
{
|
|
||||||
return strndup(
|
|
||||||
json.offset((*tok).start as isize),
|
|
||||||
((*tok).end - (*tok).start) as libc::c_ulong,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
strdup(b"\x00" as *const u8 as *const libc::c_char)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn jsoneq(
|
|
||||||
json: *const libc::c_char,
|
|
||||||
tok: *mut jsmntok_t,
|
|
||||||
s: *const libc::c_char,
|
|
||||||
) -> libc::c_int {
|
|
||||||
if (*tok).type_0 as libc::c_uint == JSMN_STRING as libc::c_int as libc::c_uint
|
|
||||||
&& strlen(s) as libc::c_int == (*tok).end - (*tok).start
|
|
||||||
&& strncmp(
|
|
||||||
json.offset((*tok).start as isize),
|
|
||||||
s,
|
|
||||||
((*tok).end - (*tok).start) as usize,
|
|
||||||
) == 0i32
|
|
||||||
{
|
|
||||||
return 0i32;
|
|
||||||
}
|
|
||||||
|
|
||||||
-1
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn is_expired(context: &dc_context_t) -> bool {
|
|
||||||
let expire_timestamp: time_t = dc_sqlite3_get_config_int64(
|
|
||||||
context,
|
|
||||||
&context.sql.clone().read().unwrap(),
|
|
||||||
b"oauth2_timestamp_expires\x00" as *const u8 as *const libc::c_char,
|
|
||||||
0i32 as int64_t,
|
|
||||||
) as time_t;
|
|
||||||
if expire_timestamp <= 0 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if expire_timestamp > time(0 as *mut time_t) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn dc_get_oauth2_addr(
|
|
||||||
context: &dc_context_t,
|
|
||||||
addr: *const libc::c_char,
|
|
||||||
code: *const libc::c_char,
|
|
||||||
) -> *mut libc::c_char {
|
|
||||||
let mut access_token: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
||||||
let mut addr_out: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
||||||
let oauth2: *mut oauth2_t;
|
|
||||||
if !({
|
|
||||||
oauth2 = get_info(addr);
|
|
||||||
oauth2.is_null()
|
|
||||||
} || (*oauth2).get_userinfo.is_null())
|
|
||||||
{
|
|
||||||
access_token = dc_get_oauth2_access_token(context, addr, code, 0i32);
|
|
||||||
addr_out = get_oauth2_addr(context, oauth2, access_token);
|
|
||||||
if addr_out.is_null() {
|
|
||||||
free(access_token as *mut libc::c_void);
|
|
||||||
access_token = dc_get_oauth2_access_token(context, addr, code, 0x1i32);
|
|
||||||
addr_out = get_oauth2_addr(context, oauth2, access_token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(access_token as *mut libc::c_void);
|
|
||||||
free(oauth2 as *mut libc::c_void);
|
|
||||||
|
|
||||||
addr_out
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn get_oauth2_addr(
|
|
||||||
context: &dc_context_t,
|
|
||||||
oauth2: *const oauth2_t,
|
|
||||||
access_token: *const libc::c_char,
|
|
||||||
) -> *mut libc::c_char {
|
|
||||||
let mut addr_out: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
||||||
let mut userinfo_url: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
||||||
let mut json: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
||||||
let mut parser: jsmn_parser = jsmn_parser {
|
|
||||||
pos: 0,
|
|
||||||
toknext: 0,
|
|
||||||
toksuper: 0,
|
|
||||||
};
|
|
||||||
// we do not expect nor read more tokens
|
|
||||||
let mut tok: [jsmntok_t; 128] = [jsmntok_t {
|
|
||||||
type_0: JSMN_UNDEFINED,
|
|
||||||
start: 0,
|
|
||||||
end: 0,
|
|
||||||
size: 0,
|
|
||||||
}; 128];
|
|
||||||
let tok_cnt: libc::c_int;
|
|
||||||
if !(access_token.is_null()
|
|
||||||
|| *access_token.offset(0isize) as libc::c_int == 0i32
|
|
||||||
|| oauth2.is_null())
|
|
||||||
{
|
|
||||||
userinfo_url = dc_strdup((*oauth2).get_userinfo);
|
|
||||||
replace_in_uri(
|
|
||||||
&mut userinfo_url,
|
|
||||||
b"$ACCESS_TOKEN\x00" as *const u8 as *const libc::c_char,
|
|
||||||
access_token,
|
|
||||||
);
|
|
||||||
json = (context.cb)(
|
|
||||||
context,
|
|
||||||
Event::HTTP_GET,
|
|
||||||
userinfo_url as uintptr_t,
|
|
||||||
0i32 as uintptr_t,
|
|
||||||
) as *mut libc::c_char;
|
|
||||||
if json.is_null() {
|
|
||||||
dc_log_warning(
|
|
||||||
context,
|
|
||||||
0i32,
|
|
||||||
b"Error getting userinfo.\x00" as *const u8 as *const libc::c_char,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
jsmn_init(&mut parser);
|
|
||||||
tok_cnt = jsmn_parse(
|
|
||||||
&mut parser,
|
|
||||||
json,
|
|
||||||
strlen(json),
|
|
||||||
tok.as_mut_ptr(),
|
|
||||||
(::std::mem::size_of::<[jsmntok_t; 128]>() as libc::c_ulong)
|
|
||||||
.wrapping_div(::std::mem::size_of::<jsmntok_t>() as libc::c_ulong)
|
|
||||||
as libc::c_uint,
|
|
||||||
);
|
|
||||||
if tok_cnt < 2i32
|
|
||||||
|| tok[0usize].type_0 as libc::c_uint != JSMN_OBJECT as libc::c_int as libc::c_uint
|
|
||||||
{
|
|
||||||
dc_log_warning(
|
|
||||||
context,
|
|
||||||
0i32,
|
|
||||||
b"Failed to parse userinfo.\x00" as *const u8 as *const libc::c_char,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let mut i: libc::c_int = 1i32;
|
|
||||||
while i < tok_cnt {
|
|
||||||
if addr_out.is_null()
|
|
||||||
&& jsoneq(
|
|
||||||
json,
|
|
||||||
&mut *tok.as_mut_ptr().offset(i as isize),
|
|
||||||
b"email\x00" as *const u8 as *const libc::c_char,
|
|
||||||
) == 0i32
|
|
||||||
{
|
|
||||||
addr_out = jsondup(json, &mut *tok.as_mut_ptr().offset((i + 1i32) as isize))
|
|
||||||
}
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
if addr_out.is_null() {
|
|
||||||
dc_log_warning(
|
|
||||||
context,
|
|
||||||
0i32,
|
|
||||||
b"E-mail missing in userinfo.\x00" as *const u8 as *const libc::c_char,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(userinfo_url as *mut libc::c_void);
|
|
||||||
free(json as *mut libc::c_void);
|
|
||||||
|
|
||||||
addr_out
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,8 @@ use crate::constants::*;
|
|||||||
use crate::dc_context::dc_context_t;
|
use crate::dc_context::dc_context_t;
|
||||||
use crate::dc_log::*;
|
use crate::dc_log::*;
|
||||||
use crate::dc_loginparam::*;
|
use crate::dc_loginparam::*;
|
||||||
use crate::dc_oauth2::*;
|
use crate::dc_tools::*;
|
||||||
|
use crate::oauth2::*;
|
||||||
use crate::x::*;
|
use crate::x::*;
|
||||||
|
|
||||||
pub struct Smtp {
|
pub struct Smtp {
|
||||||
@@ -106,18 +107,15 @@ impl Smtp {
|
|||||||
|
|
||||||
let creds = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
|
let creds = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
|
||||||
// oauth2
|
// oauth2
|
||||||
|
let addr = to_str(lp.addr);
|
||||||
let access_token =
|
let send_pw = to_str(lp.send_pw);
|
||||||
unsafe { dc_get_oauth2_access_token(context, lp.addr, lp.send_pw, 0i32) };
|
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, 0);
|
||||||
if access_token.is_null() {
|
if access_token.is_none() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
let user = to_str(lp.send_user);
|
||||||
|
|
||||||
let user = unsafe { CStr::from_ptr(lp.send_user).to_str().unwrap().to_string() };
|
lettre::smtp::authentication::Credentials::new(user.into(), access_token.unwrap())
|
||||||
let token = unsafe { CStr::from_ptr(access_token).to_str().unwrap().to_string() };
|
|
||||||
unsafe { free(access_token as *mut libc::c_void) };
|
|
||||||
|
|
||||||
lettre::smtp::authentication::Credentials::new(user, token)
|
|
||||||
} else {
|
} else {
|
||||||
// plain
|
// plain
|
||||||
let user = unsafe { CStr::from_ptr(lp.send_user).to_str().unwrap().to_string() };
|
let user = unsafe { CStr::from_ptr(lp.send_user).to_str().unwrap().to_string() };
|
||||||
|
|||||||
@@ -1645,6 +1645,22 @@ pub unsafe fn dc_make_rel_and_copy(
|
|||||||
success
|
success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_cstring<S: AsRef<str>>(s: S) -> std::ffi::CString {
|
||||||
|
std::ffi::CString::new(s.as_ref()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_string(s: *const libc::c_char) -> String {
|
||||||
|
if s.is_null() {
|
||||||
|
return "".into();
|
||||||
|
}
|
||||||
|
unsafe { std::ffi::CStr::from_ptr(s).to_str().unwrap().to_string() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_str<'a>(s: *const libc::c_char) -> &'a str {
|
||||||
|
assert!(!s.is_null(), "cannot be used on null pointers");
|
||||||
|
unsafe { std::ffi::CStr::from_ptr(s).to_str().unwrap() }
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -1952,19 +1968,3 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_cstring<S: AsRef<str>>(s: S) -> std::ffi::CString {
|
|
||||||
std::ffi::CString::new(s.as_ref()).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_string(s: *const libc::c_char) -> String {
|
|
||||||
if s.is_null() {
|
|
||||||
return "".into();
|
|
||||||
}
|
|
||||||
unsafe { std::ffi::CStr::from_ptr(s).to_str().unwrap().to_string() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_str<'a>(s: *const libc::c_char) -> &'a str {
|
|
||||||
assert!(!s.is_null(), "cannot be used on null pointers");
|
|
||||||
unsafe { std::ffi::CStr::from_ptr(s).to_str().unwrap() }
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ extern crate smallvec;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod dc_log;
|
pub mod dc_log;
|
||||||
|
|
||||||
|
pub mod oauth2;
|
||||||
pub mod peerstate;
|
pub mod peerstate;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod x;
|
pub mod x;
|
||||||
@@ -42,7 +43,6 @@ pub mod dc_imap;
|
|||||||
pub mod dc_imex;
|
pub mod dc_imex;
|
||||||
pub mod dc_job;
|
pub mod dc_job;
|
||||||
pub mod dc_jobthread;
|
pub mod dc_jobthread;
|
||||||
pub mod dc_jsmn;
|
|
||||||
pub mod dc_key;
|
pub mod dc_key;
|
||||||
pub mod dc_keyhistory;
|
pub mod dc_keyhistory;
|
||||||
pub mod dc_keyring;
|
pub mod dc_keyring;
|
||||||
@@ -53,7 +53,6 @@ pub mod dc_mimefactory;
|
|||||||
pub mod dc_mimeparser;
|
pub mod dc_mimeparser;
|
||||||
pub mod dc_move;
|
pub mod dc_move;
|
||||||
pub mod dc_msg;
|
pub mod dc_msg;
|
||||||
pub mod dc_oauth2;
|
|
||||||
pub mod dc_param;
|
pub mod dc_param;
|
||||||
pub mod dc_pgp;
|
pub mod dc_pgp;
|
||||||
pub mod dc_qr;
|
pub mod dc_qr;
|
||||||
|
|||||||
393
src/oauth2.rs
Normal file
393
src/oauth2.rs
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::dc_context::dc_context_t;
|
||||||
|
use crate::dc_log::*;
|
||||||
|
use crate::dc_sqlite3::*;
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::types::*;
|
||||||
|
use crate::x::*;
|
||||||
|
|
||||||
|
const OAUTH2_GMAIL: Oauth2 = Oauth2 {
|
||||||
|
client_id: "959970109878-4mvtgf6feshskf7695nfln6002mom908.apps.googleusercontent.com",
|
||||||
|
get_code: "https://accounts.google.com/o/oauth2/auth?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline",
|
||||||
|
init_token: "https://accounts.google.com/o/oauth2/token?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&code=$CODE&grant_type=authorization_code",
|
||||||
|
refresh_token: "https://accounts.google.com/o/oauth2/token?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&refresh_token=$REFRESH_TOKEN&grant_type=refresh_token",
|
||||||
|
get_userinfo: Some("https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=$ACCESS_TOKEN"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const OAUTH2_YANDEX: Oauth2 = Oauth2 {
|
||||||
|
client_id: "c4d0b6735fc8420a816d7e1303469341",
|
||||||
|
get_code: "https://oauth.yandex.com/authorize?client_id=$CLIENT_ID&response_type=code&scope=mail%3Aimap_full%20mail%3Asmtp&force_confirm=true",
|
||||||
|
init_token: "https://oauth.yandex.com/token?grant_type=authorization_code&code=$CODE&client_id=$CLIENT_ID&client_secret=58b8c6e94cf44fbe952da8511955dacf",
|
||||||
|
refresh_token: "https://oauth.yandex.com/token?grant_type=refresh_token&refresh_token=$REFRESH_TOKEN&client_id=$CLIENT_ID&client_secret=58b8c6e94cf44fbe952da8511955dacf",
|
||||||
|
get_userinfo: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Oauth2 {
|
||||||
|
client_id: &'static str,
|
||||||
|
get_code: &'static str,
|
||||||
|
init_token: &'static str,
|
||||||
|
refresh_token: &'static str,
|
||||||
|
get_userinfo: Option<&'static str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Response {
|
||||||
|
// Should always be there according to: https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/
|
||||||
|
// but previous code handled its abscense.
|
||||||
|
access_token: Option<String>,
|
||||||
|
token_type: String,
|
||||||
|
expires_in: Option<u64>,
|
||||||
|
refresh_token: Option<String>,
|
||||||
|
scope: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dc_get_oauth2_url(
|
||||||
|
context: &dc_context_t,
|
||||||
|
addr: impl AsRef<str>,
|
||||||
|
redirect_uri: impl AsRef<str>,
|
||||||
|
) -> Option<String> {
|
||||||
|
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||||
|
set_config(
|
||||||
|
context,
|
||||||
|
"oauth2_pending_redirect_uri",
|
||||||
|
redirect_uri.as_ref(),
|
||||||
|
);
|
||||||
|
let oauth2_url = replace_in_uri(&oauth2.get_code, "$CLIENT_ID", &oauth2.client_id);
|
||||||
|
let oauth2_url = replace_in_uri(&oauth2_url, "$REDIRECT_URI", redirect_uri.as_ref());
|
||||||
|
|
||||||
|
Some(oauth2_url)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following function may block due http-requests;
|
||||||
|
// must not be called from the main thread or by the ui!
|
||||||
|
pub fn dc_get_oauth2_access_token(
|
||||||
|
context: &dc_context_t,
|
||||||
|
addr: impl AsRef<str>,
|
||||||
|
code: impl AsRef<str>,
|
||||||
|
flags: usize,
|
||||||
|
) -> Option<String> {
|
||||||
|
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||||
|
let lock = context.oauth2_critical.clone();
|
||||||
|
let _l = lock.lock().unwrap();
|
||||||
|
|
||||||
|
// read generated token
|
||||||
|
if 0 == flags & 0x1 && !is_expired(context) {
|
||||||
|
let access_token = get_config(context, "oauth2_access_token");
|
||||||
|
if access_token.is_some() {
|
||||||
|
// success
|
||||||
|
return access_token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let refresh_token = get_config(context, "oauth2_refresh_token");
|
||||||
|
let refresh_token_for =
|
||||||
|
get_config(context, "oauth2_refresh_token_for").unwrap_or_else(|| "unset".into());
|
||||||
|
|
||||||
|
let (redirect_uri, token_url, update_redirect_uri_on_success) =
|
||||||
|
if refresh_token.is_none() || refresh_token_for != code.as_ref() {
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
0, "Generate OAuth2 refresh_token and access_token...",
|
||||||
|
);
|
||||||
|
(
|
||||||
|
get_config(context, "oauth2_pending_redirect_uri")
|
||||||
|
.unwrap_or_else(|| "unset".into()),
|
||||||
|
oauth2.init_token,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
0, "Regenerate OAuth2 access_token by refresh_token...",
|
||||||
|
);
|
||||||
|
(
|
||||||
|
get_config(context, "oauth2_redirect_uri").unwrap_or_else(|| "unset".into()),
|
||||||
|
oauth2.refresh_token,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let mut token_url = replace_in_uri(&token_url, "$CLIENT_ID", oauth2.client_id);
|
||||||
|
token_url = replace_in_uri(&token_url, "$REDIRECT_URI", &redirect_uri);
|
||||||
|
token_url = replace_in_uri(&token_url, "$CODE", code.as_ref());
|
||||||
|
if let Some(ref token) = refresh_token {
|
||||||
|
token_url = replace_in_uri(&token_url, "$REFRESH_TOKEN", token);
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = reqwest::Client::new().post(&token_url).send();
|
||||||
|
if response.is_err() {
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
format!("Error calling OAuth2 at {}: {:?}", token_url, response)
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut response = response.unwrap();
|
||||||
|
if !response.status().is_success() {
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
format!(
|
||||||
|
"Error calling OAuth2 at {}: {:?}",
|
||||||
|
token_url,
|
||||||
|
response.status()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed: reqwest::Result<Response> = response.json();
|
||||||
|
if parsed.is_err() {
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
format!(
|
||||||
|
"Failed to parse OAuth2 JSON response from {}: error: {:?}",
|
||||||
|
token_url, parsed
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
println!("response: {:?}", &parsed);
|
||||||
|
let response = parsed.unwrap();
|
||||||
|
if let Some(ref token) = response.refresh_token {
|
||||||
|
set_config(context, "oauth2_refresh_token", token);
|
||||||
|
set_config(context, "oauth2_refresh_token_for", code.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
// after that, save the access token.
|
||||||
|
// if it's unset, we may get it in the next round as we have the refresh_token now.
|
||||||
|
if let Some(ref token) = response.access_token {
|
||||||
|
set_config(context, "oauth2_access_token", token);
|
||||||
|
let expires_in = response
|
||||||
|
.expires_in
|
||||||
|
// refresh a bet before
|
||||||
|
.map(|t| unsafe { time(0 as *mut time_t) as u64 } + t - 5)
|
||||||
|
.unwrap_or_else(|| 0);
|
||||||
|
set_config_int64(context, "oauth2_timestamp_expires", expires_in as i64);
|
||||||
|
|
||||||
|
if update_redirect_uri_on_success {
|
||||||
|
set_config(context, "oauth2_redirect_uri", redirect_uri.as_ref());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!(context, 0, "Failed to find OAuth2 access token");
|
||||||
|
}
|
||||||
|
|
||||||
|
response.access_token
|
||||||
|
} else {
|
||||||
|
warn!(context, 0, "Internal OAuth2 error: 2");
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dc_get_oauth2_addr(
|
||||||
|
context: &dc_context_t,
|
||||||
|
addr: impl AsRef<str>,
|
||||||
|
code: impl AsRef<str>,
|
||||||
|
) -> Option<String> {
|
||||||
|
let oauth2 = Oauth2::from_address(addr.as_ref());
|
||||||
|
if oauth2.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let oauth2 = oauth2.unwrap();
|
||||||
|
if oauth2.get_userinfo.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(access_token) = dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), 0)
|
||||||
|
{
|
||||||
|
let addr_out = oauth2.get_addr(context, access_token);
|
||||||
|
if addr_out.is_none() {
|
||||||
|
// regenerate
|
||||||
|
if let Some(access_token) = dc_get_oauth2_access_token(context, addr, code, 0x1) {
|
||||||
|
oauth2.get_addr(context, access_token)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addr_out
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Oauth2 {
|
||||||
|
fn from_address(addr: impl AsRef<str>) -> Option<Self> {
|
||||||
|
let addr_normalized = normalize_addr(addr.as_ref());
|
||||||
|
if let Some(domain) = addr_normalized
|
||||||
|
.find('@')
|
||||||
|
.map(|index| addr_normalized.split_at(index + 1).1)
|
||||||
|
{
|
||||||
|
match domain {
|
||||||
|
"gmail.com" | "googlemail.com" => Some(OAUTH2_GMAIL),
|
||||||
|
"yandex.com" | "yandex.ru" | "yandex.ua" => Some(OAUTH2_YANDEX),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_addr(&self, context: &dc_context_t, access_token: impl AsRef<str>) -> Option<String> {
|
||||||
|
let userinfo_url = self.get_userinfo.unwrap_or_else(|| "");
|
||||||
|
let userinfo_url = replace_in_uri(&userinfo_url, "$ACCESS_TOKEN", access_token);
|
||||||
|
|
||||||
|
let response = reqwest::Client::new().post(&userinfo_url).send();
|
||||||
|
if response.is_err() {
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
format!("Error getting userinfo: {:?}", response)
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut response = response.unwrap();
|
||||||
|
if !response.status().is_success() {
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
format!("Error getting userinfo: {:?}", response.status())
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed: reqwest::Result<HashMap<String, String>> = response.json();
|
||||||
|
if parsed.is_err() {
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
format!("Failed to parse userinfo JSON response: {:?}", parsed)
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if let Ok(response) = parsed {
|
||||||
|
let addr = response.get("email");
|
||||||
|
if addr.is_none() {
|
||||||
|
warn!(context, 0, "E-mail missing in userinfo.");
|
||||||
|
}
|
||||||
|
|
||||||
|
addr.map(|addr| addr.to_string())
|
||||||
|
} else {
|
||||||
|
warn!(context, 0, "Failed to parse userinfo.");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config(context: &dc_context_t, key: &str) -> Option<String> {
|
||||||
|
let key_c = CString::new(key).unwrap();
|
||||||
|
let res = unsafe {
|
||||||
|
dc_sqlite3_get_config(
|
||||||
|
context,
|
||||||
|
&context.sql.clone().read().unwrap(),
|
||||||
|
key_c.as_ptr(),
|
||||||
|
std::ptr::null(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if res.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(to_string(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_config(context: &dc_context_t, key: &str, value: &str) {
|
||||||
|
let key_c = CString::new(key).unwrap();
|
||||||
|
let value_c = CString::new(value).unwrap();
|
||||||
|
unsafe {
|
||||||
|
dc_sqlite3_set_config(
|
||||||
|
context,
|
||||||
|
&context.sql.clone().read().unwrap(),
|
||||||
|
key_c.as_ptr(),
|
||||||
|
value_c.as_ptr(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_config_int64(context: &dc_context_t, key: &str, value: i64) {
|
||||||
|
let key_c = CString::new(key).unwrap();
|
||||||
|
unsafe {
|
||||||
|
dc_sqlite3_set_config_int64(
|
||||||
|
context,
|
||||||
|
&context.sql.clone().read().unwrap(),
|
||||||
|
key_c.as_ptr(),
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_expired(context: &dc_context_t) -> bool {
|
||||||
|
let expire_timestamp = unsafe {
|
||||||
|
dc_sqlite3_get_config_int64(
|
||||||
|
context,
|
||||||
|
&context.sql.clone().read().unwrap(),
|
||||||
|
b"oauth2_timestamp_expires\x00" as *const u8 as *const libc::c_char,
|
||||||
|
0i32 as int64_t,
|
||||||
|
)
|
||||||
|
} as time_t;
|
||||||
|
|
||||||
|
if expire_timestamp <= 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if expire_timestamp > unsafe { time(0 as *mut time_t) } {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_in_uri(uri: impl AsRef<str>, key: impl AsRef<str>, value: impl AsRef<str>) -> String {
|
||||||
|
let value_urlencoded = utf8_percent_encode(value.as_ref(), DEFAULT_ENCODE_SET).to_string();
|
||||||
|
uri.as_ref().replace(key.as_ref(), &value_urlencoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_addr(addr: &str) -> &str {
|
||||||
|
let normalized = addr.trim();
|
||||||
|
normalized.trim_start_matches("mailto:")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_normalize_addr() {
|
||||||
|
assert_eq!(normalize_addr(" hello@mail.de "), "hello@mail.de");
|
||||||
|
assert_eq!(normalize_addr("mailto:hello@mail.de "), "hello@mail.de");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_replace_in_uri() {
|
||||||
|
assert_eq!(
|
||||||
|
replace_in_uri("helloworld", "world", "a-b c"),
|
||||||
|
"helloa-b%20c"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_oauth_from_address() {
|
||||||
|
assert_eq!(Oauth2::from_address("hello@gmail.com"), Some(OAUTH2_GMAIL));
|
||||||
|
assert_eq!(
|
||||||
|
Oauth2::from_address("hello@googlemail.com"),
|
||||||
|
Some(OAUTH2_GMAIL)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Oauth2::from_address("hello@yandex.com"),
|
||||||
|
Some(OAUTH2_YANDEX)
|
||||||
|
);
|
||||||
|
assert_eq!(Oauth2::from_address("hello@yandex.ru"), Some(OAUTH2_YANDEX));
|
||||||
|
|
||||||
|
assert_eq!(Oauth2::from_address("hello@web.de"), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ use deltachat::dc_securejoin::*;
|
|||||||
use deltachat::dc_strbuilder::*;
|
use deltachat::dc_strbuilder::*;
|
||||||
use deltachat::dc_strencode::*;
|
use deltachat::dc_strencode::*;
|
||||||
use deltachat::dc_tools::*;
|
use deltachat::dc_tools::*;
|
||||||
|
use deltachat::oauth2::*;
|
||||||
use deltachat::types::*;
|
use deltachat::types::*;
|
||||||
use deltachat::x::*;
|
use deltachat::x::*;
|
||||||
use libc;
|
use libc;
|
||||||
@@ -2519,6 +2520,36 @@ fn test_dc_mimeparser_with_context() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dc_get_oauth2_url() {
|
||||||
|
let ctx = unsafe { create_test_context() };
|
||||||
|
let addr = "dignifiedquire@gmail.com";
|
||||||
|
let redirect_uri = "chat.delta:/com.b44t.messenger";
|
||||||
|
let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri);
|
||||||
|
|
||||||
|
assert_eq!(res, Some("https://accounts.google.com/o/oauth2/auth?client_id=959970109878-4mvtgf6feshskf7695nfln6002mom908.apps.googleusercontent.com&redirect_uri=chat.delta:/com.b44t.messenger&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dc_get_oauth2_addr() {
|
||||||
|
let ctx = unsafe { create_test_context() };
|
||||||
|
let addr = "dignifiedquire@gmail.com";
|
||||||
|
let code = "fail";
|
||||||
|
let res = dc_get_oauth2_addr(&ctx.ctx, addr, code);
|
||||||
|
// this should fail as it is an invalid password
|
||||||
|
assert_eq!(res, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dc_get_oauth2_token() {
|
||||||
|
let ctx = unsafe { create_test_context() };
|
||||||
|
let addr = "dignifiedquire@gmail.com";
|
||||||
|
let code = "fail";
|
||||||
|
let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, 0);
|
||||||
|
// this should fail as it is an invalid password
|
||||||
|
assert_eq!(res, None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stress_tests() {
|
fn test_stress_tests() {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|||||||
Reference in New Issue
Block a user