]> Untitled Git - lemmy.git/commitdiff
Forbid users to use empty titles for posts (#930)
authorTony Antonov <MM263@users.noreply.github.com>
Sat, 11 Jul 2020 01:15:53 +0000 (19:15 -0600)
committerGitHub <noreply@github.com>
Sat, 11 Jul 2020 01:15:53 +0000 (21:15 -0400)
- Add a regex that checks if string contains anything but whitespace
- Check for whitespace-only titles on post creation and edit
- Trim whitespace from titles before saving
- Add frontend validation to title

server/lemmy_utils/src/lib.rs
server/src/api/post.rs
ui/src/components/post-form.tsx
ui/src/utils.ts
ui/translations/en.json

index f576ea00338c3ab5c74181a3437127c329274528..bbee8500a5c42aa4e0c79663cb69469f1e214f47 100644 (file)
@@ -158,12 +158,17 @@ pub fn is_valid_community_name(name: &str) -> bool {
   VALID_COMMUNITY_NAME_REGEX.is_match(name)
 }
 
+pub fn is_valid_post_title(title: &str) -> bool {
+  VALID_POST_TITLE_REGEX.is_match(title)
+}
+
 #[cfg(test)]
 mod tests {
   use crate::{
     is_email_regex,
     is_valid_community_name,
     is_valid_username,
+    is_valid_post_title,
     remove_slurs,
     scrape_text_for_mentions,
     slur_check,
@@ -204,6 +209,15 @@ mod tests {
     assert!(!is_valid_community_name(""));
   }
 
+  #[test]
+  fn test_valid_post_title() {
+    assert!(is_valid_post_title("Post Title"));
+    assert!(is_valid_post_title("   POST TITLE ðŸ˜ƒðŸ˜ƒðŸ˜ƒðŸ˜ƒðŸ˜ƒ"));
+    assert!(!is_valid_post_title("\n \n \n \n                  ")); // tabs/spaces/newlines
+  }
+
+
+
   #[test]
   fn test_slur_filter() {
     let test =
@@ -249,6 +263,7 @@ lazy_static! {
   static ref MENTIONS_REGEX: Regex = Regex::new(r"@(?P<name>[\w.]+)@(?P<domain>[a-zA-Z0-9._:-]+)").unwrap();
   static ref VALID_USERNAME_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9_]{3,20}$").unwrap();
   static ref VALID_COMMUNITY_NAME_REGEX: Regex = Regex::new(r"^[a-z0-9_]{3,20}$").unwrap();
+  static ref VALID_POST_TITLE_REGEX: Regex = Regex::new(r".*\S.*").unwrap();
   pub static ref WEBFINGER_COMMUNITY_REGEX: Regex = Regex::new(&format!(
     "^group:([a-z0-9_]{{3, 20}})@{}$",
     Settings::get().hostname
index c56a00dfe07b7371066e5d646beb7d5dbba9958d..cbdb976c6a794ddc1f31facaadec04560acd6f80 100644 (file)
@@ -28,7 +28,7 @@ use lemmy_db::{
   Saveable,
   SortType,
 };
-use lemmy_utils::{make_apub_endpoint, slur_check, slurs_vec_to_str, EndpointType};
+use lemmy_utils::{is_valid_post_title, make_apub_endpoint, slur_check, slurs_vec_to_str, EndpointType};
 use serde::{Deserialize, Serialize};
 use std::str::FromStr;
 
@@ -135,6 +135,10 @@ impl Perform for Oper<CreatePost> {
       }
     }
 
+    if !is_valid_post_title(&data.name) {
+      return Err(APIError::err("invalid_post_title").into());
+    }
+
     let user_id = claims.id;
 
     // Check for a community ban
@@ -156,7 +160,7 @@ impl Perform for Oper<CreatePost> {
       fetch_iframely_and_pictrs_data(&self.client, data.url.to_owned()).await;
 
     let post_form = PostForm {
-      name: data.name.to_owned(),
+      name: data.name.trim().to_owned(),
       url: data.url.to_owned(),
       body: data.body.to_owned(),
       community_id: data.community_id,
@@ -516,6 +520,10 @@ impl Perform for Oper<EditPost> {
       }
     }
 
+    if !is_valid_post_title(&data.name) {
+      return Err(APIError::err("invalid_post_title").into());
+    }
+
     let claims = match Claims::decode(&data.auth) {
       Ok(claims) => claims.claims,
       Err(_e) => return Err(APIError::err("not_logged_in").into()),
@@ -565,7 +573,7 @@ impl Perform for Oper<EditPost> {
     let read_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??;
 
     let post_form = PostForm {
-      name: data.name.to_owned(),
+      name: data.name.trim().to_owned(),
       url: data.url.to_owned(),
       body: data.body.to_owned(),
       creator_id: data.creator_id.to_owned(),
index 247c4cc639cfe602dd3777cb16f331ceea40b757..3052751051f1a4b50dae3385457954cc6a7ba77d 100644 (file)
@@ -35,6 +35,7 @@ import {
   setupTippy,
   hostname,
   pictrsDeleteToast,
+  validTitle,
 } from '../utils';
 import autosize from 'autosize';
 import Tribute from 'tributejs/src/Tribute.js';
@@ -271,12 +272,19 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
                 value={this.state.postForm.name}
                 id="post-title"
                 onInput={linkEvent(this, this.handlePostNameChange)}
-                class="form-control"
+                class={`form-control ${
+                  !validTitle(this.state.postForm.name) && 'is-invalid'
+                }`}
                 required
                 rows={2}
                 minLength={3}
                 maxLength={MAX_POST_TITLE_LENGTH}
               />
+              {!validTitle(this.state.postForm.name) && (
+                <div class="invalid-feedback">
+                  {i18n.t('invalid_post_title')}
+                </div>
+              )}
               {this.state.suggestedPosts.length > 0 && (
                 <>
                   <div class="my-1 text-muted small font-weight-bold">
index 2f06b70cd39999b23becf013ef50cd10c505773b..6276519ba2a382765b84ba6622f2877c72f3ec7f 100644 (file)
@@ -986,3 +986,12 @@ function canUseWebP() {
   // // very old browser like IE 8, canvas not supported
   // return false;
 }
+
+export function validTitle(title?: string): boolean {
+  // Initial title is null, minimum length is taken care of by textarea's minLength={3}
+  if (title === null || title.length < 3) return true;
+
+  const regex = new RegExp(/.*\S.*/, 'g');
+
+  return regex.test(title);
+}
index 89f69f69c492e767039952c0642d8a2f8f03b7f9..cb4347f1ceeacb1c408662e5d55a2356d62f0d53 100644 (file)
     "block_leaving": "Are you sure you want to leave?",
     "what_is": "What is",
     "cake_day_title": "Cake day:",
-    "cake_day_info": "It's {{ creator_name }}'s cake day today!"
+    "cake_day_info": "It's {{ creator_name }}'s cake day today!",
+    "invalid_post_title": "Invalid post title"
 }