]> Untitled Git - lemmy.git/blob - server/src/lib.rs
Initial post-listing community non-local.
[lemmy.git] / server / src / lib.rs
1 #![recursion_limit = "512"]
2 #[macro_use]
3 pub extern crate strum_macros;
4 #[macro_use]
5 pub extern crate lazy_static;
6 #[macro_use]
7 pub extern crate failure;
8 #[macro_use]
9 pub extern crate diesel;
10 pub extern crate actix;
11 pub extern crate actix_web;
12 pub extern crate bcrypt;
13 pub extern crate chrono;
14 pub extern crate comrak;
15 pub extern crate dotenv;
16 pub extern crate jsonwebtoken;
17 pub extern crate lettre;
18 pub extern crate lettre_email;
19 extern crate log;
20 pub extern crate openssl;
21 pub extern crate rand;
22 pub extern crate regex;
23 pub extern crate rss;
24 pub extern crate serde;
25 pub extern crate serde_json;
26 pub extern crate sha2;
27 pub extern crate strum;
28
29 pub mod api;
30 pub mod apub;
31 pub mod db;
32 pub mod routes;
33 pub mod schema;
34 pub mod settings;
35 pub mod version;
36 pub mod websocket;
37
38 use crate::settings::Settings;
39 use chrono::{DateTime, FixedOffset, Local, NaiveDateTime};
40 use isahc::prelude::*;
41 use lettre::smtp::authentication::{Credentials, Mechanism};
42 use lettre::smtp::extension::ClientId;
43 use lettre::smtp::ConnectionReuseParameters;
44 use lettre::{ClientSecurity, SmtpClient, Transport};
45 use lettre_email::Email;
46 use log::error;
47 use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
48 use rand::distributions::Alphanumeric;
49 use rand::{thread_rng, Rng};
50 use regex::{Regex, RegexBuilder};
51 use serde::Deserialize;
52
53 pub fn naive_now() -> NaiveDateTime {
54   chrono::prelude::Utc::now().naive_utc()
55 }
56
57 pub fn naive_from_unix(time: i64) -> NaiveDateTime {
58   NaiveDateTime::from_timestamp(time, 0)
59 }
60
61 pub fn convert_datetime(datetime: NaiveDateTime) -> DateTime<FixedOffset> {
62   let now = Local::now();
63   DateTime::<FixedOffset>::from_utc(datetime, *now.offset())
64 }
65
66 pub fn is_email_regex(test: &str) -> bool {
67   EMAIL_REGEX.is_match(test)
68 }
69
70 pub fn remove_slurs(test: &str) -> String {
71   SLUR_REGEX.replace_all(test, "*removed*").to_string()
72 }
73
74 pub fn slur_check(test: &str) -> Result<(), Vec<&str>> {
75   let mut matches: Vec<&str> = SLUR_REGEX.find_iter(test).map(|mat| mat.as_str()).collect();
76
77   // Unique
78   matches.sort_unstable();
79   matches.dedup();
80
81   if matches.is_empty() {
82     Ok(())
83   } else {
84     Err(matches)
85   }
86 }
87
88 pub fn slurs_vec_to_str(slurs: Vec<&str>) -> String {
89   let start = "No slurs - ";
90   let combined = &slurs.join(", ");
91   [start, combined].concat()
92 }
93
94 pub fn extract_usernames(test: &str) -> Vec<&str> {
95   let mut matches: Vec<&str> = USERNAME_MATCHES_REGEX
96     .find_iter(test)
97     .map(|mat| mat.as_str())
98     .collect();
99
100   // Unique
101   matches.sort_unstable();
102   matches.dedup();
103
104   // Remove /u/
105   matches.iter().map(|t| &t[3..]).collect()
106 }
107
108 pub fn generate_random_string() -> String {
109   thread_rng().sample_iter(&Alphanumeric).take(30).collect()
110 }
111
112 pub fn send_email(
113   subject: &str,
114   to_email: &str,
115   to_username: &str,
116   html: &str,
117 ) -> Result<(), String> {
118   let email_config = Settings::get().email.as_ref().ok_or("no_email_setup")?;
119
120   let email = Email::builder()
121     .to((to_email, to_username))
122     .from(email_config.smtp_from_address.to_owned())
123     .subject(subject)
124     .html(html)
125     .build()
126     .unwrap();
127
128   let mailer = if email_config.use_tls {
129     SmtpClient::new_simple(&email_config.smtp_server).unwrap()
130   } else {
131     SmtpClient::new(&email_config.smtp_server, ClientSecurity::None).unwrap()
132   }
133   .hello_name(ClientId::Domain(Settings::get().hostname.to_owned()))
134   .smtp_utf8(true)
135   .authentication_mechanism(Mechanism::Plain)
136   .connection_reuse(ConnectionReuseParameters::ReuseUnlimited);
137   let mailer = if let (Some(login), Some(password)) =
138     (&email_config.smtp_login, &email_config.smtp_password)
139   {
140     mailer.credentials(Credentials::new(login.to_owned(), password.to_owned()))
141   } else {
142     mailer
143   };
144
145   let mut transport = mailer.transport();
146   let result = transport.send(email.into());
147   transport.close();
148
149   match result {
150     Ok(_) => Ok(()),
151     Err(e) => Err(e.to_string()),
152   }
153 }
154
155 #[derive(Deserialize, Debug)]
156 pub struct IframelyResponse {
157   title: Option<String>,
158   description: Option<String>,
159   thumbnail_url: Option<String>,
160   html: Option<String>,
161 }
162
163 pub fn fetch_iframely(url: &str) -> Result<IframelyResponse, failure::Error> {
164   let fetch_url = format!("http://iframely/oembed?url={}", url);
165   let text = isahc::get(&fetch_url)?.text()?;
166   let res: IframelyResponse = serde_json::from_str(&text)?;
167   Ok(res)
168 }
169
170 #[derive(Deserialize, Debug)]
171 pub struct PictshareResponse {
172   status: String,
173   url: String,
174 }
175
176 pub fn fetch_pictshare(image_url: &str) -> Result<PictshareResponse, failure::Error> {
177   let fetch_url = format!(
178     "http://pictshare/api/geturl.php?url={}",
179     utf8_percent_encode(image_url, NON_ALPHANUMERIC)
180   );
181   let text = isahc::get(&fetch_url)?.text()?;
182   let res: PictshareResponse = serde_json::from_str(&text)?;
183   Ok(res)
184 }
185
186 fn fetch_iframely_and_pictshare_data(
187   url: Option<String>,
188 ) -> (
189   Option<String>,
190   Option<String>,
191   Option<String>,
192   Option<String>,
193 ) {
194   // Fetch iframely data
195   let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) = match url {
196     Some(url) => match fetch_iframely(&url) {
197       Ok(res) => (res.title, res.description, res.thumbnail_url, res.html),
198       Err(e) => {
199         error!("iframely err: {}", e);
200         (None, None, None, None)
201       }
202     },
203     None => (None, None, None, None),
204   };
205
206   // Fetch pictshare thumbnail
207   let pictshare_thumbnail = match iframely_thumbnail_url {
208     Some(iframely_thumbnail_url) => match fetch_pictshare(&iframely_thumbnail_url) {
209       Ok(res) => Some(res.url),
210       Err(e) => {
211         error!("pictshare err: {}", e);
212         None
213       }
214     },
215     None => None,
216   };
217
218   (
219     iframely_title,
220     iframely_description,
221     iframely_html,
222     pictshare_thumbnail,
223   )
224 }
225
226 pub fn markdown_to_html(text: &str) -> String {
227   comrak::markdown_to_html(text, &comrak::ComrakOptions::default())
228 }
229
230 #[cfg(test)]
231 mod tests {
232   use crate::{extract_usernames, is_email_regex, remove_slurs, slur_check, slurs_vec_to_str};
233
234   #[test]
235   fn test_email() {
236     assert!(is_email_regex("gush@gmail.com"));
237     assert!(!is_email_regex("nada_neutho"));
238   }
239
240   #[test]
241   fn test_slur_filter() {
242     let test =
243       "coons test dindu ladyboy tranny retardeds. Capitalized Niggerz. This is a bunch of other safe text.";
244     let slur_free = "No slurs here";
245     assert_eq!(
246       remove_slurs(&test),
247       "*removed* test *removed* *removed* *removed* *removed*. Capitalized *removed*. This is a bunch of other safe text."
248         .to_string()
249     );
250
251     let has_slurs_vec = vec![
252       "Niggerz",
253       "coons",
254       "dindu",
255       "ladyboy",
256       "retardeds",
257       "tranny",
258     ];
259     let has_slurs_err_str = "No slurs - Niggerz, coons, dindu, ladyboy, retardeds, tranny";
260
261     assert_eq!(slur_check(test), Err(has_slurs_vec));
262     assert_eq!(slur_check(slur_free), Ok(()));
263     if let Err(slur_vec) = slur_check(test) {
264       assert_eq!(&slurs_vec_to_str(slur_vec), has_slurs_err_str);
265     }
266   }
267
268   #[test]
269   fn test_extract_usernames() {
270     let usernames = extract_usernames("this is a user mention for [/u/testme](/u/testme) and thats all. Oh [/u/another](/u/another) user. And the first again [/u/testme](/u/testme) okay");
271     let expected = vec!["another", "testme"];
272     assert_eq!(usernames, expected);
273   }
274
275   // These helped with testing
276   // #[test]
277   // fn test_iframely() {
278   //   let res = fetch_iframely("https://www.redspark.nu/?p=15341");
279   //   assert!(res.is_ok());
280   // }
281
282   // #[test]
283   // fn test_pictshare() {
284   //   let res = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpg");
285   //   assert!(res.is_ok());
286   //   let res_other = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpgaoeu");
287   //   assert!(res_other.is_err());
288   // }
289
290   // #[test]
291   // fn test_send_email() {
292   //  let result =  send_email("not a subject", "test_email@gmail.com", "ur user", "<h1>HI there</h1>");
293   //   assert!(result.is_ok());
294   // }
295 }
296
297 lazy_static! {
298   static ref EMAIL_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
299   static ref SLUR_REGEX: Regex = RegexBuilder::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|nig(\b|g?(a|er)?(s|z)?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btrann?(y|ies?)|ladyboy(s?)|\b(b|re|r)tard(ed)?s?)").case_insensitive(true).build().unwrap();
300   static ref USERNAME_MATCHES_REGEX: Regex = Regex::new(r"/u/[a-zA-Z][0-9a-zA-Z_]*").unwrap();
301 }