2019-03-19 14:37:56 +01:00
|
|
|
#![warn(clippy::too_many_arguments)]
|
2020-01-21 07:02:03 +01:00
|
|
|
use crate::template_utils::Ructe;
|
2020-05-04 15:28:52 +02:00
|
|
|
use atom_syndication::{
|
|
|
|
ContentBuilder, Entry, EntryBuilder, Feed, FeedBuilder, LinkBuilder, Person, PersonBuilder,
|
|
|
|
};
|
|
|
|
use chrono::naive::NaiveDateTime;
|
2019-10-30 11:22:28 +01:00
|
|
|
use plume_models::{posts::Post, Connection, CONFIG, ITEMS_PER_PAGE};
|
2018-07-20 18:42:35 +02:00
|
|
|
use rocket::{
|
2019-03-16 15:33:28 +01:00
|
|
|
http::{
|
2019-08-21 00:42:04 +02:00
|
|
|
hyper::header::{CacheControl, CacheDirective, ETag, EntityTag},
|
2019-03-20 17:56:17 +01:00
|
|
|
uri::{FromUriParam, Query},
|
|
|
|
RawStr, Status,
|
2019-03-16 15:33:28 +01:00
|
|
|
},
|
2019-01-27 10:55:22 +01:00
|
|
|
request::{self, FromFormValue, FromRequest, Request},
|
2019-08-21 00:42:04 +02:00
|
|
|
response::{self, Flash, NamedFile, Redirect, Responder, Response},
|
2019-03-20 17:56:17 +01:00
|
|
|
Outcome,
|
2018-07-20 18:42:35 +02:00
|
|
|
};
|
2019-08-21 00:42:04 +02:00
|
|
|
use std::{
|
|
|
|
collections::hash_map::DefaultHasher,
|
|
|
|
hash::Hasher,
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
};
|
2018-05-10 20:01:16 +02:00
|
|
|
|
2019-06-14 09:33:30 +02:00
|
|
|
/// Special return type used for routes that "cannot fail", and instead
|
|
|
|
/// `Redirect`, or `Flash<Redirect>`, when we cannot deliver a `Ructe` Response
|
|
|
|
#[allow(clippy::large_enum_variant)]
|
|
|
|
#[derive(Responder)]
|
|
|
|
pub enum RespondOrRedirect {
|
|
|
|
Response(Ructe),
|
|
|
|
FlashResponse(Flash<Ructe>),
|
|
|
|
Redirect(Redirect),
|
|
|
|
FlashRedirect(Flash<Redirect>),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Ructe> for RespondOrRedirect {
|
|
|
|
fn from(response: Ructe) -> Self {
|
|
|
|
RespondOrRedirect::Response(response)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Flash<Ructe>> for RespondOrRedirect {
|
|
|
|
fn from(response: Flash<Ructe>) -> Self {
|
|
|
|
RespondOrRedirect::FlashResponse(response)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Redirect> for RespondOrRedirect {
|
|
|
|
fn from(redirect: Redirect) -> Self {
|
|
|
|
RespondOrRedirect::Redirect(redirect)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Flash<Redirect>> for RespondOrRedirect {
|
|
|
|
fn from(redirect: Flash<Redirect>) -> Self {
|
|
|
|
RespondOrRedirect::FlashRedirect(redirect)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-25 20:23:45 +02:00
|
|
|
#[derive(Shrinkwrap, Copy, Clone, UriDisplayQuery)]
|
2018-12-06 18:54:16 +01:00
|
|
|
pub struct Page(i32);
|
2018-07-20 18:42:35 +02:00
|
|
|
|
2018-12-02 17:37:51 +01:00
|
|
|
impl<'v> FromFormValue<'v> for Page {
|
|
|
|
type Error = &'v RawStr;
|
|
|
|
fn from_form_value(form_value: &'v RawStr) -> Result<Page, &'v RawStr> {
|
|
|
|
match form_value.parse::<i32>() {
|
2018-12-06 18:54:16 +01:00
|
|
|
Ok(page) => Ok(Page(page)),
|
2018-12-02 17:37:51 +01:00
|
|
|
_ => Err(form_value),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-13 22:20:19 +01:00
|
|
|
impl FromUriParam<Query, Option<Page>> for Page {
|
|
|
|
type Target = Page;
|
|
|
|
|
|
|
|
fn from_uri_param(val: Option<Page>) -> Page {
|
|
|
|
val.unwrap_or_default()
|
2018-07-20 18:42:35 +02:00
|
|
|
}
|
2018-12-13 22:20:19 +01:00
|
|
|
}
|
2018-07-20 18:42:35 +02:00
|
|
|
|
2018-12-13 22:20:19 +01:00
|
|
|
impl Page {
|
2018-07-25 14:29:34 +02:00
|
|
|
/// Computes the total number of pages needed to display n_items
|
|
|
|
pub fn total(n_items: i32) -> i32 {
|
|
|
|
if n_items % ITEMS_PER_PAGE == 0 {
|
|
|
|
n_items / ITEMS_PER_PAGE
|
|
|
|
} else {
|
|
|
|
(n_items / ITEMS_PER_PAGE) + 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-19 14:37:56 +01:00
|
|
|
pub fn limits(self) -> (i32, i32) {
|
2018-12-06 18:54:16 +01:00
|
|
|
((self.0 - 1) * ITEMS_PER_PAGE, self.0 * ITEMS_PER_PAGE)
|
2018-07-20 18:42:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-25 20:23:45 +02:00
|
|
|
#[derive(Shrinkwrap)]
|
2019-01-27 10:55:22 +01:00
|
|
|
pub struct ContentLen(pub u64);
|
|
|
|
|
|
|
|
impl<'a, 'r> FromRequest<'a, 'r> for ContentLen {
|
|
|
|
type Error = ();
|
|
|
|
|
|
|
|
fn from_request(r: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
|
|
|
match r.limits().get("forms") {
|
|
|
|
Some(l) => Outcome::Success(ContentLen(l)),
|
|
|
|
None => Outcome::Failure((Status::InternalServerError, ())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-13 22:20:19 +01:00
|
|
|
impl Default for Page {
|
|
|
|
fn default() -> Self {
|
|
|
|
Page(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-17 22:09:07 +02:00
|
|
|
/// A form for remote interaction, used by multiple routes
|
2019-05-25 20:23:45 +02:00
|
|
|
#[derive(Shrinkwrap, Clone, Default, FromForm)]
|
2019-04-17 22:09:07 +02:00
|
|
|
pub struct RemoteForm {
|
|
|
|
pub remote: String,
|
|
|
|
}
|
|
|
|
|
2020-05-04 15:28:52 +02:00
|
|
|
pub fn build_atom_feed(
|
|
|
|
entries: Vec<Post>,
|
|
|
|
uri: &str,
|
|
|
|
title: &str,
|
|
|
|
default_updated: &NaiveDateTime,
|
|
|
|
conn: &Connection,
|
|
|
|
) -> Feed {
|
|
|
|
let updated = if entries.is_empty() {
|
|
|
|
default_updated
|
|
|
|
} else {
|
|
|
|
&entries[0].creation_date
|
|
|
|
};
|
|
|
|
|
|
|
|
FeedBuilder::default()
|
|
|
|
.title(title)
|
|
|
|
.id(uri)
|
|
|
|
.updated(updated.format("%Y-%m-%d %H:%M:%SZ").to_string())
|
|
|
|
.entries(
|
|
|
|
entries
|
|
|
|
.into_iter()
|
|
|
|
.map(|p| post_to_atom(p, conn))
|
|
|
|
.collect::<Vec<Entry>>(),
|
|
|
|
)
|
|
|
|
.links(vec![LinkBuilder::default()
|
|
|
|
.href(uri)
|
|
|
|
.rel("self")
|
|
|
|
.mime_type("application/atom+xml".to_string())
|
|
|
|
.build()
|
|
|
|
.expect("Atom feed: link error")])
|
|
|
|
.build()
|
|
|
|
.expect("user::atom_feed: Error building Atom feed")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn post_to_atom(post: Post, conn: &Connection) -> Entry {
|
|
|
|
let formatted_creation_date = post.creation_date.format("%Y-%m-%dT%H:%M:%SZ").to_string();
|
2018-09-01 22:08:26 +02:00
|
|
|
EntryBuilder::default()
|
2018-11-06 10:49:46 +01:00
|
|
|
.title(format!("<![CDATA[{}]]>", post.title))
|
2019-03-20 17:56:17 +01:00
|
|
|
.content(
|
|
|
|
ContentBuilder::default()
|
|
|
|
.value(format!("<![CDATA[{}]]>", *post.content.get()))
|
|
|
|
.content_type("html".to_string())
|
|
|
|
.build()
|
|
|
|
.expect("Atom feed: content error"),
|
|
|
|
)
|
|
|
|
.authors(
|
|
|
|
post.get_authors(&*conn)
|
|
|
|
.expect("Atom feed: author error")
|
|
|
|
.into_iter()
|
|
|
|
.map(|a| {
|
|
|
|
PersonBuilder::default()
|
|
|
|
.name(a.display_name)
|
|
|
|
.uri(a.ap_url)
|
|
|
|
.build()
|
|
|
|
.expect("Atom feed: author error")
|
|
|
|
})
|
|
|
|
.collect::<Vec<Person>>(),
|
|
|
|
)
|
2020-01-14 01:30:32 +01:00
|
|
|
// Using RFC 4287 format, see https://tools.ietf.org/html/rfc4287#section-3.3 for dates
|
|
|
|
// eg: 2003-12-13T18:30:02Z (Z is here because there is no timezone support with the NaiveDateTime crate)
|
2020-05-04 15:28:52 +02:00
|
|
|
.published(formatted_creation_date.clone())
|
|
|
|
.updated(formatted_creation_date)
|
|
|
|
.id(post.ap_url.clone())
|
2019-03-20 17:56:17 +01:00
|
|
|
.links(vec![LinkBuilder::default()
|
|
|
|
.href(post.ap_url)
|
|
|
|
.build()
|
|
|
|
.expect("Atom feed: link error")])
|
|
|
|
.build()
|
|
|
|
.expect("Atom feed: entry error")
|
2018-09-01 22:08:26 +02:00
|
|
|
}
|
|
|
|
|
2018-04-23 12:54:37 +02:00
|
|
|
pub mod blogs;
|
2018-05-10 11:44:57 +02:00
|
|
|
pub mod comments;
|
2018-06-18 17:59:49 +02:00
|
|
|
pub mod errors;
|
2018-04-22 15:35:37 +02:00
|
|
|
pub mod instance;
|
2018-05-10 18:38:03 +02:00
|
|
|
pub mod likes;
|
2018-09-02 22:55:42 +02:00
|
|
|
pub mod medias;
|
2018-05-13 15:35:55 +02:00
|
|
|
pub mod notifications;
|
2018-04-23 16:25:39 +02:00
|
|
|
pub mod posts;
|
2018-05-19 11:51:10 +02:00
|
|
|
pub mod reshares;
|
2019-03-20 17:56:17 +01:00
|
|
|
pub mod search;
|
2018-04-24 11:21:39 +02:00
|
|
|
pub mod session;
|
2018-09-06 14:06:04 +02:00
|
|
|
pub mod tags;
|
2019-10-07 19:08:20 +02:00
|
|
|
pub mod timelines;
|
2018-04-22 20:13:12 +02:00
|
|
|
pub mod user;
|
2018-04-24 10:35:45 +02:00
|
|
|
pub mod well_known;
|
2018-05-10 20:01:16 +02:00
|
|
|
|
2019-03-16 15:33:28 +01:00
|
|
|
#[derive(Responder)]
|
|
|
|
#[response()]
|
|
|
|
pub struct CachedFile {
|
|
|
|
inner: NamedFile,
|
2019-03-20 17:56:17 +01:00
|
|
|
cache_control: CacheControl,
|
2019-03-16 15:33:28 +01:00
|
|
|
}
|
|
|
|
|
2019-08-21 00:42:04 +02:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct ThemeFile(NamedFile);
|
|
|
|
|
|
|
|
impl<'r> Responder<'r> for ThemeFile {
|
2020-01-21 07:02:03 +01:00
|
|
|
fn respond_to(self, r: &Request<'_>) -> response::Result<'r> {
|
2019-08-21 21:41:11 +02:00
|
|
|
let contents = std::fs::read(self.0.path()).map_err(|_| Status::InternalServerError)?;
|
2019-08-21 00:42:04 +02:00
|
|
|
|
|
|
|
let mut hasher = DefaultHasher::new();
|
2019-08-21 21:41:11 +02:00
|
|
|
hasher.write(&contents);
|
2019-08-21 00:42:04 +02:00
|
|
|
let etag = format!("{:x}", hasher.finish());
|
|
|
|
|
|
|
|
if r.headers()
|
|
|
|
.get("If-None-Match")
|
|
|
|
.any(|s| s[1..s.len() - 1] == etag)
|
|
|
|
{
|
|
|
|
Response::build()
|
|
|
|
.status(Status::NotModified)
|
|
|
|
.header(ETag(EntityTag::strong(etag)))
|
|
|
|
.ok()
|
|
|
|
} else {
|
|
|
|
Response::build()
|
|
|
|
.merge(self.0.respond_to(r)?)
|
|
|
|
.header(ETag(EntityTag::strong(etag)))
|
|
|
|
.ok()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[get("/static/cached/<_build_id>/css/<file..>", rank = 1)]
|
|
|
|
pub fn theme_files(file: PathBuf, _build_id: &RawStr) -> Option<ThemeFile> {
|
2019-08-21 21:41:11 +02:00
|
|
|
NamedFile::open(Path::new("static/css/").join(file))
|
2019-08-21 00:42:04 +02:00
|
|
|
.ok()
|
|
|
|
.map(ThemeFile)
|
|
|
|
}
|
|
|
|
|
2019-03-16 15:33:28 +01:00
|
|
|
#[get("/static/cached/<_build_id>/<file..>", rank = 2)]
|
|
|
|
pub fn plume_static_files(file: PathBuf, _build_id: &RawStr) -> Option<CachedFile> {
|
|
|
|
static_files(file)
|
|
|
|
}
|
2019-10-28 22:28:28 +01:00
|
|
|
#[get("/static/media/<file..>")]
|
|
|
|
pub fn plume_media_files(file: PathBuf) -> Option<CachedFile> {
|
|
|
|
NamedFile::open(Path::new(&CONFIG.media_directory).join(file))
|
|
|
|
.ok()
|
|
|
|
.map(|f| CachedFile {
|
|
|
|
inner: f,
|
|
|
|
cache_control: CacheControl(vec![CacheDirective::MaxAge(60 * 60 * 24 * 30)]),
|
|
|
|
})
|
|
|
|
}
|
2019-03-16 15:33:28 +01:00
|
|
|
#[get("/static/<file..>", rank = 3)]
|
|
|
|
pub fn static_files(file: PathBuf) -> Option<CachedFile> {
|
2019-03-20 17:56:17 +01:00
|
|
|
NamedFile::open(Path::new("static/").join(file))
|
|
|
|
.ok()
|
|
|
|
.map(|f| CachedFile {
|
|
|
|
inner: f,
|
|
|
|
cache_control: CacheControl(vec![CacheDirective::MaxAge(60 * 60 * 24 * 30)]),
|
|
|
|
})
|
2018-05-10 20:01:16 +02:00
|
|
|
}
|