mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-25 23:43:18 +00:00
Forbid users to use empty titles for posts (#930)
- 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
This commit is contained in:
parent
7a9a973c89
commit
8d24659892
|
@ -158,12 +158,17 @@ pub fn is_valid_community_name(name: &str) -> bool {
|
||||||
VALID_COMMUNITY_NAME_REGEX.is_match(name)
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
is_email_regex,
|
is_email_regex,
|
||||||
is_valid_community_name,
|
is_valid_community_name,
|
||||||
is_valid_username,
|
is_valid_username,
|
||||||
|
is_valid_post_title,
|
||||||
remove_slurs,
|
remove_slurs,
|
||||||
scrape_text_for_mentions,
|
scrape_text_for_mentions,
|
||||||
slur_check,
|
slur_check,
|
||||||
|
@ -204,6 +209,15 @@ mod tests {
|
||||||
assert!(!is_valid_community_name(""));
|
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]
|
#[test]
|
||||||
fn test_slur_filter() {
|
fn test_slur_filter() {
|
||||||
let test =
|
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 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_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_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!(
|
pub static ref WEBFINGER_COMMUNITY_REGEX: Regex = Regex::new(&format!(
|
||||||
"^group:([a-z0-9_]{{3, 20}})@{}$",
|
"^group:([a-z0-9_]{{3, 20}})@{}$",
|
||||||
Settings::get().hostname
|
Settings::get().hostname
|
||||||
|
|
|
@ -28,7 +28,7 @@ use lemmy_db::{
|
||||||
Saveable,
|
Saveable,
|
||||||
SortType,
|
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 serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
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;
|
let user_id = claims.id;
|
||||||
|
|
||||||
// Check for a community ban
|
// 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;
|
fetch_iframely_and_pictrs_data(&self.client, data.url.to_owned()).await;
|
||||||
|
|
||||||
let post_form = PostForm {
|
let post_form = PostForm {
|
||||||
name: data.name.to_owned(),
|
name: data.name.trim().to_owned(),
|
||||||
url: data.url.to_owned(),
|
url: data.url.to_owned(),
|
||||||
body: data.body.to_owned(),
|
body: data.body.to_owned(),
|
||||||
community_id: data.community_id,
|
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) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
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 read_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??;
|
||||||
|
|
||||||
let post_form = PostForm {
|
let post_form = PostForm {
|
||||||
name: data.name.to_owned(),
|
name: data.name.trim().to_owned(),
|
||||||
url: data.url.to_owned(),
|
url: data.url.to_owned(),
|
||||||
body: data.body.to_owned(),
|
body: data.body.to_owned(),
|
||||||
creator_id: data.creator_id.to_owned(),
|
creator_id: data.creator_id.to_owned(),
|
||||||
|
|
10
ui/src/components/post-form.tsx
vendored
10
ui/src/components/post-form.tsx
vendored
|
@ -35,6 +35,7 @@ import {
|
||||||
setupTippy,
|
setupTippy,
|
||||||
hostname,
|
hostname,
|
||||||
pictrsDeleteToast,
|
pictrsDeleteToast,
|
||||||
|
validTitle,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import autosize from 'autosize';
|
import autosize from 'autosize';
|
||||||
import Tribute from 'tributejs/src/Tribute.js';
|
import Tribute from 'tributejs/src/Tribute.js';
|
||||||
|
@ -271,12 +272,19 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
value={this.state.postForm.name}
|
value={this.state.postForm.name}
|
||||||
id="post-title"
|
id="post-title"
|
||||||
onInput={linkEvent(this, this.handlePostNameChange)}
|
onInput={linkEvent(this, this.handlePostNameChange)}
|
||||||
class="form-control"
|
class={`form-control ${
|
||||||
|
!validTitle(this.state.postForm.name) && 'is-invalid'
|
||||||
|
}`}
|
||||||
required
|
required
|
||||||
rows={2}
|
rows={2}
|
||||||
minLength={3}
|
minLength={3}
|
||||||
maxLength={MAX_POST_TITLE_LENGTH}
|
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 && (
|
{this.state.suggestedPosts.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div class="my-1 text-muted small font-weight-bold">
|
<div class="my-1 text-muted small font-weight-bold">
|
||||||
|
|
9
ui/src/utils.ts
vendored
9
ui/src/utils.ts
vendored
|
@ -986,3 +986,12 @@ function canUseWebP() {
|
||||||
// // very old browser like IE 8, canvas not supported
|
// // very old browser like IE 8, canvas not supported
|
||||||
// return false;
|
// 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);
|
||||||
|
}
|
||||||
|
|
3
ui/translations/en.json
vendored
3
ui/translations/en.json
vendored
|
@ -268,5 +268,6 @@
|
||||||
"block_leaving": "Are you sure you want to leave?",
|
"block_leaving": "Are you sure you want to leave?",
|
||||||
"what_is": "What is",
|
"what_is": "What is",
|
||||||
"cake_day_title": "Cake day:",
|
"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"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue