Normalize panic message and return 400 or 404 when suitable

This commit is contained in:
Trinity Pointard 2018-10-20 11:04:20 +02:00
parent 4e6f3209d5
commit fd92383f87
14 changed files with 162 additions and 161 deletions

View File

@ -19,7 +19,7 @@ fn get(id: i32, conn: DbConn) -> Json<serde_json::Value> {
#[get("/posts")]
fn list(conn: DbConn, uri: &Origin) -> Json<serde_json::Value> {
let query: PostEndpoint = serde_qs::from_str(uri.query().unwrap_or("")).expect("Invalid query string");
let query: PostEndpoint = serde_qs::from_str(uri.query().unwrap_or("")).expect("api::list: invalid query error");
let post = <Post as Provider<Connection>>::list(&*conn, query);
Json(json!(post))
}

View File

@ -16,7 +16,7 @@ use plume_models::{
pub trait Inbox {
fn received(&self, conn: &Connection, act: serde_json::Value) -> Result<(), Error> {
let actor_id = Id::new(act["actor"].as_str().unwrap_or_else(|| act["actor"]["id"].as_str().expect("No actor ID for incoming activity")));
let actor_id = Id::new(act["actor"].as_str().unwrap_or_else(|| act["actor"]["id"].as_str().expect("Inbox::received: actor_id missing error")));
match act["type"].as_str() {
Some(t) => {
match t {
@ -47,7 +47,7 @@ pub trait Inbox {
},
"Undo" => {
let act: Undo = serde_json::from_value(act.clone())?;
match act.undo_props.object["type"].as_str().unwrap() {
match act.undo_props.object["type"].as_str().expect("Inbox::received: undo without original type error") {
"Like" => {
likes::Like::delete_id(act.undo_props.object_object::<Like>()?.object_props.id_string()?, conn);
Ok(())

View File

@ -56,7 +56,7 @@ fn init_pool() -> Option<DbPool> {
}
fn main() {
let pool = init_pool().expect("Couldn't intialize database pool");
let pool = init_pool().expect("main: database pool initialization error");
rocket::ignite()
.mount("/", routes![
routes::blogs::paginated_details,
@ -176,6 +176,6 @@ fn main() {
("/login".to_owned(), "/login".to_owned(), rocket::http::Method::Post),
("/users/new".to_owned(), "/users/new".to_owned(), rocket::http::Method::Post),
])
.finalize().unwrap())
.finalize().expect("main: csrf fairing creation error"))
.launch();
}

View File

@ -1,9 +1,9 @@
use activitypub::collection::OrderedCollection;
use atom_syndication::{Entry, FeedBuilder};
use rocket::{
http::ContentType,
request::LenientForm,
response::{Redirect, Flash, content::Content},
http::ContentType
response::{Redirect, Flash, content::Content}
};
use rocket_contrib::Template;
use serde_json;
@ -49,9 +49,9 @@ fn details(name: String, conn: DbConn, user: Option<User>) -> Template {
}
#[get("/~/<name>", rank = 1)]
fn activity_details(name: String, conn: DbConn, _ap: ApRequest) -> ActivityStream<CustomGroup> {
let blog = Blog::find_local(&*conn, name).unwrap();
ActivityStream::new(blog.into_activity(&*conn))
fn activity_details(name: String, conn: DbConn, _ap: ApRequest) -> Option<ActivityStream<CustomGroup>> {
let blog = Blog::find_local(&*conn, name)?;
Some(ActivityStream::new(blog.into_activity(&*conn)))
}
#[get("/blogs/new")]
@ -130,22 +130,23 @@ fn create(conn: DbConn, data: LenientForm<NewBlogForm>, user: User) -> Result<Re
}
#[get("/~/<name>/outbox")]
fn outbox(name: String, conn: DbConn) -> ActivityStream<OrderedCollection> {
let blog = Blog::find_local(&*conn, name).unwrap();
blog.outbox(&*conn)
fn outbox(name: String, conn: DbConn) -> Option<ActivityStream<OrderedCollection>> {
let blog = Blog::find_local(&*conn, name)?;
Some(blog.outbox(&*conn))
}
#[get("/~/<name>/atom.xml")]
fn atom_feed(name: String, conn: DbConn) -> Content<String> {
let blog = Blog::find_by_fqn(&*conn, name.clone()).expect("Unable to find blog");
fn atom_feed(name: String, conn: DbConn) -> Option<Content<String>> {
let blog = Blog::find_by_fqn(&*conn, name.clone())?;
let feed = FeedBuilder::default()
.title(blog.title.clone())
.id(Instance::get_local(&*conn).unwrap().compute_box("~", name, "atom.xml"))
.id(Instance::get_local(&*conn).expect("blogs::atom_feed: local instance not found error")
.compute_box("~", name, "atom.xml"))
.entries(Post::get_recents_for_blog(&*conn, &blog, 15)
.into_iter()
.map(|p| super::post_to_atom(p, &*conn))
.collect::<Vec<Entry>>())
.build()
.expect("Error building Atom feed");
Content(ContentType::new("application", "atom+xml"), feed.to_string())
.expect("blogs::atom_feed: feed creation error");
Some(Content(ContentType::new("application", "atom+xml"), feed.to_string()))
}

View File

@ -28,9 +28,10 @@ struct NewCommentForm {
}
#[post("/~/<blog_name>/<slug>/comment", data = "<data>")]
fn create(blog_name: String, slug: String, data: LenientForm<NewCommentForm>, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Result<Redirect, Template> {
let blog = Blog::find_by_fqn(&*conn, blog_name.clone()).unwrap();
let post = Post::find_by_slug(&*conn, slug.clone(), blog.id).unwrap();
fn create(blog_name: String, slug: String, data: LenientForm<NewCommentForm>, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>)
-> Result<Redirect, Option<Template>> {
let blog = Blog::find_by_fqn(&*conn, blog_name.clone()).ok_or(None)?;
let post = Post::find_by_slug(&*conn, slug.clone(), blog.id).ok_or(None)?;
let form = data.get();
form.validate()
.map(|_| {
@ -63,7 +64,7 @@ fn create(blog_name: String, slug: String, data: LenientForm<NewCommentForm>, us
let comments = Comment::list_by_post(&*conn, post.id);
let comms = comments.clone();
Template::render("posts/details", json!({
Some(Template::render("posts/details", json!({
"author": post.get_authors(&*conn)[0].to_json(&*conn),
"post": post,
"blog": blog,
@ -74,10 +75,10 @@ fn create(blog_name: String, slug: String, data: LenientForm<NewCommentForm>, us
"has_reshared": user.has_reshared(&*conn, &post),
"account": user.to_json(&*conn),
"date": &post.creation_date.timestamp(),
"previous": form.responding_to.map(|r| Comment::get(&*conn, r).expect("Error retrieving previous comment").to_json(&*conn, &vec![])),
"previous": form.responding_to.and_then(|r| Comment::get(&*conn, r)).map(|r| r.to_json(&*conn, &vec![])),
"user_fqn": user.get_fqn(&*conn),
"errors": errors
}))
})))
})
}

View File

@ -6,21 +6,21 @@ use plume_models::users::User;
#[catch(404)]
fn not_found(req: &Request) -> Template {
let conn = req.guard::<DbConn>().expect("404: DbConn error");
let conn = req.guard::<DbConn>().succeeded();
let user = User::from_request(req).succeeded();
Template::render("errors/404", json!({
"error_message": "Page not found",
"account": user.map(|u| u.to_json(&*conn))
"account": user.and_then(|u| conn.map(|conn| u.to_json(&*conn)))
}))
}
#[catch(500)]
fn server_error(req: &Request) -> Template {
let conn = req.guard::<DbConn>().expect("500: DbConn error");
let conn = req.guard::<DbConn>().succeeded();
let user = User::from_request(req).succeeded();
Template::render("errors/500", json!({
"error_message": "Server error",
"account": user.map(|u| u.to_json(&*conn))
"account": user.and_then(|u| conn.map(|conn| u.to_json(&*conn)))
}))
}

View File

@ -1,5 +1,5 @@
use gettextrs::gettext;
use rocket::{request::LenientForm, response::Redirect};
use rocket::{request::LenientForm, response::{status, Redirect}};
use rocket_contrib::{Json, Template};
use serde_json;
use validator::{Validate};
@ -52,7 +52,7 @@ fn index(conn: DbConn, user: Option<User>) -> Template {
#[get("/local?<page>")]
fn paginated_local(conn: DbConn, user: Option<User>, page: Page) -> Template {
let instance = Instance::get_local(&*conn).unwrap();
let instance = Instance::get_local(&*conn).expect("instance::paginated_local: local instance not found error");
let articles = Post::get_instance_page(&*conn, instance.id, page.limits());
Template::render("instance/local", json!({
"account": user.map(|u| u.to_json(&*conn)),
@ -129,7 +129,7 @@ fn update_settings(conn: DbConn, admin: Admin, form: LenientForm<InstanceSetting
let form = form.get();
form.validate()
.map(|_| {
let instance = Instance::get_local(&*conn).unwrap();
let instance = Instance::get_local(&*conn).expect("instance::update_settings: local instance not found error");
instance.update(&*conn,
form.name.clone(),
form.open_registrations,
@ -196,31 +196,31 @@ fn ban(_admin: Admin, conn: DbConn, id: i32) -> Redirect {
}
#[post("/inbox", data = "<data>")]
fn shared_inbox(conn: DbConn, data: String, headers: Headers) -> String {
let act: serde_json::Value = serde_json::from_str(&data[..]).unwrap();
fn shared_inbox(conn: DbConn, data: String, headers: Headers) -> Result<String, status::BadRequest<&'static str>> {
let act: serde_json::Value = serde_json::from_str(&data[..]).expect("instance::shared_inbox: deserialization error");
let activity = act.clone();
let actor_id = activity["actor"].as_str()
.unwrap_or_else(|| activity["actor"]["id"].as_str().expect("No actor ID for incoming activity, blocks by panicking"));
.or_else(|| activity["actor"]["id"].as_str()).ok_or(status::BadRequest(Some("Missing actor id for activity")))?;
let actor = User::from_url(&conn, actor_id.to_owned()).unwrap();
let actor = User::from_url(&conn, actor_id.to_owned()).expect("instance::shared_inbox: user error");
if !verify_http_headers(&actor, headers.0.clone(), data).is_secure() &&
!act.clone().verify(&actor) {
println!("Rejected invalid activity supposedly from {}, with headers {:?}", actor.username, headers.0);
return "invalid signature".to_owned();
return Err(status::BadRequest(Some("Invalid signature")));
}
if Instance::is_blocked(&*conn, actor_id.to_string()) {
return String::new();
return Ok(String::new());
}
let instance = Instance::get_local(&*conn).unwrap();
match instance.received(&*conn, act) {
let instance = Instance::get_local(&*conn).expect("instance::shared_inbox: local instance not found error");
Ok(match instance.received(&*conn, act) {
Ok(_) => String::new(),
Err(e) => {
println!("Shared inbox error: {}\n{}", e.as_fail(), e.backtrace());
format!("Error: {}", e.as_fail())
}
}
})
}
#[get("/nodeinfo")]
@ -263,7 +263,7 @@ fn about(user: Option<User>, conn: DbConn) -> Template {
#[get("/manifest.json")]
fn web_manifest(conn: DbConn) -> Json<serde_json::Value> {
let instance = Instance::get_local(&*conn).unwrap();
let instance = Instance::get_local(&*conn).expect("instance::web_manifest: local instance not found error");
Json(json!({
"name": &instance.name,
"description": &instance.short_description,

View File

@ -12,9 +12,9 @@ use plume_models::{
};
#[post("/~/<blog>/<slug>/like")]
fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Redirect {
let b = Blog::find_by_fqn(&*conn, blog.clone()).unwrap();
let post = Post::find_by_slug(&*conn, slug.clone(), b.id).unwrap();
fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Option<Redirect> {
let b = Blog::find_by_fqn(&*conn, blog.clone())?;
let post = Post::find_by_slug(&*conn, slug.clone(), b.id)?;
if !user.has_liked(&*conn, &post) {
let like = likes::Like::insert(&*conn, likes::NewLike {
@ -29,13 +29,13 @@ fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Po
let act = like.into_activity(&*conn);
worker.execute(Thunk::of(move || broadcast(&user, act, dest)));
} else {
let like = likes::Like::find_by_user_on_post(&*conn, user.id, post.id).unwrap();
let like = likes::Like::find_by_user_on_post(&*conn, user.id, post.id).expect("likes::create: like exist but not found error");
let delete_act = like.delete(&*conn);
let dest = User::one_by_instance(&*conn);
worker.execute(Thunk::of(move || broadcast(&user, delete_act, dest)));
}
Redirect::to(uri!(super::posts::details: blog = blog, slug = slug))
Some(Redirect::to(uri!(super::posts::details: blog = blog, slug = slug)))
}
#[post("/~/<blog>/<slug>/like", rank = 2)]

View File

@ -1,6 +1,6 @@
use guid_create::GUID;
use multipart::server::{Multipart, save::{SavedData, SaveResult}};
use rocket::{Data, http::ContentType, response::Redirect};
use rocket::{Data, http::ContentType, response::{Redirect, status}};
use rocket_contrib::Template;
use serde_json;
use std::fs;
@ -25,30 +25,27 @@ fn new(user: User, conn: DbConn) -> Template {
}
#[post("/medias/new", data = "<data>")]
fn upload(user: User, data: Data, ct: &ContentType, conn: DbConn) -> Redirect {
fn upload(user: User, data: Data, ct: &ContentType, conn: DbConn) -> Result<Redirect, status::BadRequest<&'static str>> {
if ct.is_form_data() {
let (_, boundary) = ct.params().find(|&(k, _)| k == "boundary").expect("No boundary");
let (_, boundary) = ct.params().find(|&(k, _)| k == "boundary").ok_or_else(|| status::BadRequest(Some("No boundary")))?;
match Multipart::with_body(data.open(), boundary).save().temp() {
SaveResult::Full(entries) => {
let fields = entries.fields;
let filename = fields.get(&"file".to_string()).unwrap().into_iter().next().unwrap().headers
.filename.clone()
.unwrap_or("x.png".to_string()); // PNG by default
let ext = filename.rsplitn(2, ".")
.next()
.unwrap();
let filename = fields.get(&"file".to_string()).and_then(|v| v.into_iter().next())
.ok_or_else(|| status::BadRequest(Some("No file uploaded")))?.headers
.filename.clone();
let ext = filename.and_then(|f| f.rsplit('.').next().map(|ext| ext.to_owned()))
.unwrap_or("png".to_owned());
let dest = format!("static/media/{}.{}", GUID::rand().to_string(), ext);
if let SavedData::Bytes(ref bytes) = fields[&"file".to_string()][0].data {
fs::write(&dest, bytes).expect("Couldn't save upload");
} else {
if let SavedData::File(ref path, _) = fields[&"file".to_string()][0].data {
fs::copy(path, &dest).expect("Couldn't copy temp upload");
} else {
println!("not file");
return Redirect::to(uri!(new));
match fields[&"file".to_string()][0].data {
SavedData::Bytes(ref bytes) => fs::write(&dest, bytes).expect("media::upload: Couldn't save upload"),
SavedData::File(ref path, _) => {fs::copy(path, &dest).expect("media::upload: Couldn't copy upload");},
_ => {
println!("not a file");
return Ok(Redirect::to(uri!(new)));
}
}
@ -67,16 +64,16 @@ fn upload(user: User, data: Data, ct: &ContentType, conn: DbConn) -> Redirect {
owner_id: user.id
});
println!("ok");
Redirect::to(uri!(details: id = media.id))
Ok(Redirect::to(uri!(details: id = media.id)))
},
SaveResult::Partial(_, _) | SaveResult::Error(_) => {
println!("partial err");
Redirect::to(uri!(new))
Ok(Redirect::to(uri!(new)))
}
}
} else {
println!("not form data");
Redirect::to(uri!(new))
Ok(Redirect::to(uri!(new)))
}
}
@ -98,15 +95,15 @@ fn details(id: i32, user: User, conn: DbConn) -> Template {
}
#[post("/medias/<id>/delete")]
fn delete(id: i32, _user: User, conn: DbConn) -> Redirect {
let media = Media::get(&*conn, id).expect("Media to delete not found");
fn delete(id: i32, _user: User, conn: DbConn) -> Option<Redirect> {
let media = Media::get(&*conn, id)?;
media.delete(&*conn);
Redirect::to(uri!(list))
Some(Redirect::to(uri!(list)))
}
#[post("/medias/<id>/avatar")]
fn set_avatar(id: i32, user: User, conn: DbConn) -> Redirect {
let media = Media::get(&*conn, id).expect("Media to delete not found");
fn set_avatar(id: i32, user: User, conn: DbConn) -> Option<Redirect> {
let media = Media::get(&*conn, id)?;
user.set_avatar(&*conn, media.id);
Redirect::to(uri!(details: id = id))
Some(Redirect::to(uri!(details: id = id)))
}

View File

@ -58,7 +58,8 @@ fn details_response(blog: String, slug: String, conn: DbConn, user: Option<User>
"has_reshared": user.clone().map(|u| u.has_reshared(&*conn, &post)).unwrap_or(false),
"account": &user.clone().map(|u| u.to_json(&*conn)),
"date": &post.creation_date.timestamp(),
"previous": query.and_then(|q| q.responding_to.map(|r| Comment::get(&*conn, r).expect("Error retrieving previous comment").to_json(&*conn, &vec![]))),
"previous": query.and_then(|q| q.responding_to.map(|r| Comment::get(&*conn, r)
.expect("posts::details_reponse: Error retrieving previous comment").to_json(&*conn, &vec![]))),
"user_fqn": user.clone().map(|u| u.get_fqn(&*conn)).unwrap_or(String::new()),
"is_author": user.clone().map(|u| post.get_authors(&*conn).into_iter().any(|a| u.id == a.id)).unwrap_or(false),
"is_following": user.map(|u| u.is_following(&*conn, post.get_authors(&*conn)[0].id)).unwrap_or(false)
@ -73,13 +74,13 @@ fn details_response(blog: String, slug: String, conn: DbConn, user: Option<User>
}
#[get("/~/<blog>/<slug>", rank = 3)]
fn activity_details(blog: String, slug: String, conn: DbConn, _ap: ApRequest) -> Result<ActivityStream<Article>, String> {
let blog = Blog::find_by_fqn(&*conn, blog).unwrap();
let post = Post::find_by_slug(&*conn, slug, blog.id).unwrap();
fn activity_details(blog: String, slug: String, conn: DbConn, _ap: ApRequest) -> Result<ActivityStream<Article>, Option<String>> {
let blog = Blog::find_by_fqn(&*conn, blog).ok_or(None)?;
let post = Post::find_by_slug(&*conn, slug, blog.id).ok_or(None)?;
if post.published {
Ok(ActivityStream::new(post.into_activity(&*conn)))
} else {
Err(String::from("Not published yet."))
Err(Some(String::from("Not published yet.")))
}
}
@ -92,42 +93,42 @@ fn new_auth(blog: String) -> Flash<Redirect> {
}
#[get("/~/<blog>/new", rank = 1)]
fn new(blog: String, user: User, conn: DbConn) -> Template {
let b = Blog::find_by_fqn(&*conn, blog.to_string()).unwrap();
fn new(blog: String, user: User, conn: DbConn) -> Option<Template> {
let b = Blog::find_by_fqn(&*conn, blog.to_string())?;
if !user.is_author_in(&*conn, b.clone()) {
Template::render("errors/403", json!({
Some(Template::render("errors/403", json!({// TODO actually return 403 error code
"error_message": "You are not author in this blog."
}))
})))
} else {
Template::render("posts/new", json!({
Some(Template::render("posts/new", json!({
"account": user.to_json(&*conn),
"instance": Instance::get_local(&*conn),
"editing": false,
"errors": null,
"form": null,
"is_draft": true,
}))
})))
}
}
#[get("/~/<blog>/<slug>/edit")]
fn edit(blog: String, slug: String, user: User, conn: DbConn) -> Template {
let b = Blog::find_by_fqn(&*conn, blog.to_string());
let post = b.clone().and_then(|blog| Post::find_by_slug(&*conn, slug, blog.id)).expect("Post to edit not found");
fn edit(blog: String, slug: String, user: User, conn: DbConn) -> Option<Template> {
let b = Blog::find_by_fqn(&*conn, blog.to_string())?;
let post = Post::find_by_slug(&*conn, slug, b.id)?;
if !user.is_author_in(&*conn, b.clone().unwrap()) {
Template::render("errors/403", json!({
if !user.is_author_in(&*conn, b) {
Some(Template::render("errors/403", json!({// TODO actually return 403 error code
"error_message": "You are not author in this blog."
}))
})))
} else {
let source = if post.source.clone().len() > 0 {
post.source.clone()
let source = if post.source.len() > 0 {
post.source
} else {
post.content.clone().get().clone() // fallback to HTML if the markdown was not stored
post.content.get().clone() // fallback to HTML if the markdown was not stored
};
Template::render("posts/new", json!({
Some(Template::render("posts/new", json!({
"account": user.to_json(&*conn),
"instance": Instance::get_local(&*conn),
"editing": true,
@ -145,14 +146,15 @@ fn edit(blog: String, slug: String, user: User, conn: DbConn) -> Template {
draft: true,
},
"is_draft": !post.published
}))
})))
}
}
#[post("/~/<blog>/<slug>/edit", data = "<data>")]
fn update(blog: String, slug: String, user: User, conn: DbConn, data: LenientForm<NewPostForm>, worker: State<Pool<ThunkWorker<()>>>) -> Result<Redirect, Template> {
let b = Blog::find_by_fqn(&*conn, blog.to_string());
let mut post = b.clone().and_then(|blog| Post::find_by_slug(&*conn, slug.clone(), blog.id)).expect("Post to update not found");
fn update(blog: String, slug: String, user: User, conn: DbConn, data: LenientForm<NewPostForm>, worker: State<Pool<ThunkWorker<()>>>)
-> Result<Redirect, Option<Template>> {
let b = Blog::find_by_fqn(&*conn, blog.to_string()).ok_or(None)?;
let mut post = Post::find_by_slug(&*conn, slug.clone(), b.id).ok_or(None)?;
let form = data.get();
let new_slug = if !post.published {
@ -167,7 +169,7 @@ fn update(blog: String, slug: String, user: User, conn: DbConn, data: LenientFor
};
if new_slug != slug {
if let Some(_) = Post::find_by_slug(&*conn, new_slug.clone(), b.clone().unwrap().id) {
if let Some(_) = Post::find_by_slug(&*conn, new_slug.clone(), b.id) {
errors.add("title", ValidationError {
code: Cow::from("existing_slug"),
message: Some(Cow::from("A post with the same title already exists.")),
@ -177,7 +179,7 @@ fn update(blog: String, slug: String, user: User, conn: DbConn, data: LenientFor
}
if errors.is_empty() {
if !user.is_author_in(&*conn, b.clone().unwrap()) {
if !user.is_author_in(&*conn, b) {
// actually it's not "Ok"…
Ok(Redirect::to(uri!(super::blogs::details: name = blog)))
} else {
@ -229,14 +231,14 @@ fn update(blog: String, slug: String, user: User, conn: DbConn, data: LenientFor
Ok(Redirect::to(uri!(details: blog = blog, slug = new_slug)))
}
} else {
Err(Template::render("posts/new", json!({
Err(Some(Template::render("posts/new", json!({
"account": user.to_json(&*conn),
"instance": Instance::get_local(&*conn),
"editing": true,
"errors": errors.inner(),
"form": form,
"is_draft": form.draft,
})))
}))))
}
}
@ -263,8 +265,8 @@ fn valid_slug(title: &str) -> Result<(), ValidationError> {
}
#[post("/~/<blog_name>/new", data = "<data>")]
fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Result<Redirect, Template> {
let blog = Blog::find_by_fqn(&*conn, blog_name.to_string()).unwrap();
fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Result<Redirect, Option<Template>> {
let blog = Blog::find_by_fqn(&*conn, blog_name.to_string()).ok_or(None)?;
let form = data.get();
let slug = form.title.to_string().to_kebab_case();
@ -331,14 +333,14 @@ fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: D
Ok(Redirect::to(uri!(details: blog = blog_name, slug = slug)))
}
} else {
Err(Template::render("posts/new", json!({
Err(Some(Template::render("posts/new", json!({
"account": user.to_json(&*conn),
"instance": Instance::get_local(&*conn),
"editing": false,
"errors": errors.inner(),
"form": form,
"is_draft": form.draft
})))
}))))
}
}

View File

@ -12,9 +12,9 @@ use plume_models::{
};
#[post("/~/<blog>/<slug>/reshare")]
fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Redirect {
let b = Blog::find_by_fqn(&*conn, blog.clone()).unwrap();
let post = Post::find_by_slug(&*conn, slug.clone(), b.id).unwrap();
fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Option<Redirect> {
let b = Blog::find_by_fqn(&*conn, blog.clone())?;
let post = Post::find_by_slug(&*conn, slug.clone(), b.id)?;
if !user.has_reshared(&*conn, &post) {
let reshare = Reshare::insert(&*conn, NewReshare {
@ -29,13 +29,14 @@ fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Po
let act = reshare.into_activity(&*conn);
worker.execute(Thunk::of(move || broadcast(&user, act, dest)));
} else {
let reshare = Reshare::find_by_user_on_post(&*conn, user.id, post.id).unwrap();
let reshare = Reshare::find_by_user_on_post(&*conn, user.id, post.id)
.expect("reshares::create: reshare exist but not found error");
let delete_act = reshare.delete(&*conn);
let dest = User::one_by_instance(&*conn);
worker.execute(Thunk::of(move || broadcast(&user, delete_act, dest)));
}
Redirect::to(uri!(super::posts::details: blog = blog, slug = slug))
Some(Redirect::to(uri!(super::posts::details: blog = blog, slug = slug)))
}
#[post("/~/<blog>/<slug>/reshare", rank=1)]

View File

@ -50,22 +50,23 @@ struct LoginForm {
fn create(conn: DbConn, data: LenientForm<LoginForm>, flash: Option<FlashMessage>, mut cookies: Cookies) -> Result<Redirect, Template> {
let form = data.get();
let user = User::find_by_email(&*conn, form.email_or_name.to_string())
.map(|u| Ok(u))
.unwrap_or_else(|| User::find_local(&*conn, form.email_or_name.to_string()).map(|u| Ok(u)).unwrap_or(Err(())));
.or_else(|| User::find_local(&*conn, form.email_or_name.to_string()));
let mut errors = match form.validate() {
Ok(_) => ValidationErrors::new(),
Err(e) => e
};
if let Err(_) = user.clone() {
if let Some(user) = user.clone() {
if !user.auth(form.password.clone()) {
let mut err = ValidationError::new("invalid_login");
err.message = Some(Cow::from("Invalid username or password"));
errors.add("email_or_name", err)
}
} else {
// Fake password verification, only to avoid different login times
// that could be used to see if an email adress is registered or not
User::get(&*conn, 1).map(|u| u.auth(form.password.clone()));
let mut err = ValidationError::new("invalid_login");
err.message = Some(Cow::from("Invalid username or password"));
errors.add("email_or_name", err)
} else if !user.clone().expect("User not found").auth(form.password.clone()) {
let mut err = ValidationError::new("invalid_login");
err.message = Some(Cow::from("Invalid username or password"));
errors.add("email_or_name", err)
@ -107,7 +108,6 @@ fn create(conn: DbConn, data: LenientForm<LoginForm>, flash: Option<FlashMessage
#[get("/logout")]
fn delete(mut cookies: Cookies) -> Redirect {
let cookie = cookies.get_private(AUTH_COOKIE).unwrap();
cookies.remove_private(cookie);
cookies.get_private(AUTH_COOKIE).map(|cookie| cookies.remove_private(cookie));
Redirect::to("/")
}

View File

@ -10,19 +10,19 @@ use plume_models::{
use routes::Page;
#[get("/tag/<name>")]
fn tag(user: Option<User>, conn: DbConn, name: String) -> Template {
fn tag(user: Option<User>, conn: DbConn, name: String) -> Option<Template> {
paginated_tag(user, conn, name, Page::first())
}
#[get("/tag/<name>?<page>")]
fn paginated_tag(user: Option<User>, conn: DbConn, name: String, page: Page) -> Template {
let tag = Tag::find_by_name(&*conn, name).expect("Rendering tags::tag: tag not found");
fn paginated_tag(user: Option<User>, conn: DbConn, name: String, page: Page) -> Option<Template> {
let tag = Tag::find_by_name(&*conn, name)?;
let posts = Post::list_by_tag(&*conn, tag.tag.clone(), page.limits());
Template::render("tags/index", json!({
Some(Template::render("tags/index", json!({
"tag": tag.clone(),
"account": user.map(|u| u.to_json(&*conn)),
"articles": posts.into_iter().map(|p| p.to_json(&*conn)).collect::<Vec<serde_json::Value>>(),
"page": page.page,
"n_pages": Page::total(Post::count_for_tag(&*conn, tag.tag) as i32)
}))
})))
}

View File

@ -6,7 +6,7 @@ use activitypub::{
use atom_syndication::{Entry, FeedBuilder};
use rocket::{
request::LenientForm,
response::{Redirect, Flash, Content},
response::{Content, Flash, Redirect, status},
http::{ContentType, Cookies}
};
use rocket_contrib::Template;
@ -70,7 +70,7 @@ fn details(name: String, conn: DbConn, account: Option<User>, worker: Worker, fe
worker.execute(Thunk::of(move || {
for user_id in user_clone.fetch_followers_ids() {
let follower = User::find_by_ap_url(&*fecth_followers_conn, user_id.clone())
.unwrap_or_else(|| User::fetch_from_url(&*fecth_followers_conn, user_id).expect("Couldn't fetch follower"));
.unwrap_or_else(|| User::fetch_from_url(&*fecth_followers_conn, user_id).expect("user::details: Couldn't fetch follower"));
follows::Follow::insert(&*fecth_followers_conn, follows::NewFollow {
follower_id: follower.id,
following_id: user_clone.id,
@ -121,8 +121,8 @@ fn dashboard_auth() -> Flash<Redirect> {
}
#[post("/@/<name>/follow")]
fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Redirect {
let target = User::find_by_fqn(&*conn, name.clone()).unwrap();
fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Option<Redirect> {
let target = User::find_by_fqn(&*conn, name.clone())?;
if let Some(follow) = follows::Follow::find(&*conn, user.id, target.id) {
let delete_act = follow.delete(&*conn);
worker.execute(Thunk::of(move || broadcast(&user, delete_act, vec![target])));
@ -137,7 +137,7 @@ fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Redirect {
let act = f.into_activity(&*conn);
worker.execute(Thunk::of(move || broadcast(&user, act, vec![target])));
}
Redirect::to(uri!(details: name = name))
Some(Redirect::to(uri!(details: name = name)))
}
#[post("/@/<name>/follow", rank = 2)]
@ -176,9 +176,9 @@ fn followers(name: String, conn: DbConn, account: Option<User>) -> Template {
#[get("/@/<name>", rank = 1)]
fn activity_details(name: String, conn: DbConn, _ap: ApRequest) -> ActivityStream<CustomPerson> {
let user = User::find_local(&*conn, name).unwrap();
ActivityStream::new(user.into_activity(&*conn))
fn activity_details(name: String, conn: DbConn, _ap: ApRequest) -> Option<ActivityStream<CustomPerson>> {
let user = User::find_local(&*conn, name)?;
Some(ActivityStream::new(user.into_activity(&*conn)))
}
#[get("/users/new")]
@ -228,17 +228,16 @@ fn update(_name: String, conn: DbConn, user: User, data: LenientForm<UpdateUserF
}
#[post("/@/<name>/delete")]
fn delete(name: String, conn: DbConn, user: User, mut cookies: Cookies) -> Redirect {
let account = User::find_by_fqn(&*conn, name.clone()).unwrap();
fn delete(name: String, conn: DbConn, user: User, mut cookies: Cookies) -> Option<Redirect> {
let account = User::find_by_fqn(&*conn, name.clone())?;
if user.id == account.id {
account.delete(&*conn);
let cookie = cookies.get_private(AUTH_COOKIE).unwrap();
cookies.remove_private(cookie);
cookies.get_private(AUTH_COOKIE).map(|cookie| cookies.remove_private(cookie));
Redirect::to(uri!(super::instance::index))
Some(Redirect::to(uri!(super::instance::index)))
} else {
Redirect::to(uri!(edit: name = name))
Some(Redirect::to(uri!(edit: name = name)))
}
}
@ -291,54 +290,54 @@ fn create(conn: DbConn, data: LenientForm<NewUserForm>) -> Result<Redirect, Temp
}
#[get("/@/<name>/outbox")]
fn outbox(name: String, conn: DbConn) -> ActivityStream<OrderedCollection> {
let user = User::find_local(&*conn, name).unwrap();
user.outbox(&*conn)
fn outbox(name: String, conn: DbConn) -> Option<ActivityStream<OrderedCollection>> {
let user = User::find_local(&*conn, name)?;
Some(user.outbox(&*conn))
}
#[post("/@/<name>/inbox", data = "<data>")]
fn inbox(name: String, conn: DbConn, data: String, headers: Headers) -> String {
let user = User::find_local(&*conn, name).unwrap();
let act: serde_json::Value = serde_json::from_str(&data[..]).unwrap();
fn inbox(name: String, conn: DbConn, data: String, headers: Headers) -> Result<String, Option<status::BadRequest<&'static str>>> {
let user = User::find_local(&*conn, name).ok_or(None)?;
let act: serde_json::Value = serde_json::from_str(&data[..]).expect("user::inbox: deserialization error");
let activity = act.clone();
let actor_id = activity["actor"].as_str()
.unwrap_or_else(|| activity["actor"]["id"].as_str().expect("User: No actor ID for incoming activity, blocks by panicking"));
.or_else(|| activity["actor"]["id"].as_str()).ok_or(Some(status::BadRequest(Some("Missing actor id for activity"))))?;
let actor = User::from_url(&conn, actor_id.to_owned()).unwrap();
let actor = User::from_url(&conn, actor_id.to_owned()).expect("user::inbox: user error");
if !verify_http_headers(&actor, headers.0.clone(), data).is_secure() &&
!act.clone().verify(&actor) {
println!("Rejected invalid activity supposedly from {}, with headers {:?}", actor.username, headers.0);
return "invalid signature".to_owned();
return Err(Some(status::BadRequest(Some("Invalid signature"))));
}
if Instance::is_blocked(&*conn, actor_id.to_string()) {
return String::new();
return Ok(String::new());
}
match user.received(&*conn, act) {
Ok(match user.received(&*conn, act) {
Ok(_) => String::new(),
Err(e) => {
println!("User inbox error: {}\n{}", e.as_fail(), e.backtrace());
format!("Error: {}", e.as_fail())
}
}
})
}
#[get("/@/<name>/followers")]
fn ap_followers(name: String, conn: DbConn, _ap: ApRequest) -> ActivityStream<OrderedCollection> {
let user = User::find_local(&*conn, name).unwrap();
fn ap_followers(name: String, conn: DbConn, _ap: ApRequest) -> Option<ActivityStream<OrderedCollection>> {
let user = User::find_local(&*conn, name)?;
let followers = user.get_followers(&*conn).into_iter().map(|f| Id::new(f.ap_url)).collect::<Vec<Id>>();
let mut coll = OrderedCollection::default();
coll.object_props.set_id_string(user.followers_endpoint).expect("Follower collection: id error");
coll.collection_props.set_total_items_u64(followers.len() as u64).expect("Follower collection: totalItems error");
coll.collection_props.set_items_link_vec(followers).expect("Follower collection: items error");
ActivityStream::new(coll)
coll.object_props.set_id_string(user.followers_endpoint).expect("user::ap_followers: id error");
coll.collection_props.set_total_items_u64(followers.len() as u64).expect("user::ap_followers: totalItems error");
coll.collection_props.set_items_link_vec(followers).expect("user::ap_followers items error");
Some(ActivityStream::new(coll))
}
#[get("/@/<name>/atom.xml")]
fn atom_feed(name: String, conn: DbConn) -> Content<String> {
let author = User::find_by_fqn(&*conn, name.clone()).expect("Unable to find author");
fn atom_feed(name: String, conn: DbConn) -> Option<Content<String>> {
let author = User::find_by_fqn(&*conn, name.clone())?;
let feed = FeedBuilder::default()
.title(author.display_name.clone())
.id(Instance::get_local(&*conn).unwrap().compute_box("~", name, "atom.xml"))
@ -347,6 +346,6 @@ fn atom_feed(name: String, conn: DbConn) -> Content<String> {
.map(|p| super::post_to_atom(p, &*conn))
.collect::<Vec<Entry>>())
.build()
.expect("Error building Atom feed");
Content(ContentType::new("application", "atom+xml"), feed.to_string())
.expect("user::atom_feed: Error building Atom feed");
Some(Content(ContentType::new("application", "atom+xml"), feed.to_string()))
}