mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
97 lines
2.9 KiB
Rust
97 lines
2.9 KiB
Rust
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use fluxer_markdown_parser::ParserFlags;
|
|
use fluxer_markdown_parser::plaintext::{PlaintextOptions, parse_and_render_plaintext};
|
|
use rustler::types::binary::{Binary, OwnedBinary};
|
|
use rustler::{Env, Error, NifResult, Term};
|
|
use serde::Deserialize;
|
|
|
|
const MAX_CONTENT_BYTES: usize = 16 * 1024;
|
|
const MAX_CONTEXT_JSON_BYTES: usize = 64 * 1024;
|
|
const MAX_CONTEXT_NAME_ENTRIES: usize = 512;
|
|
const MAX_RENDERED_BYTES: usize = 64 * 1024;
|
|
|
|
#[derive(Debug, Default, Deserialize)]
|
|
struct PushPlaintextContext {
|
|
#[serde(default)]
|
|
preserve_markdown: bool,
|
|
#[serde(default = "default_include_emoji_names")]
|
|
include_emoji_names: bool,
|
|
#[serde(default)]
|
|
include_link_urls: bool,
|
|
#[serde(default)]
|
|
users: HashMap<String, String>,
|
|
#[serde(default)]
|
|
roles: HashMap<String, String>,
|
|
#[serde(default)]
|
|
channels: HashMap<String, String>,
|
|
}
|
|
|
|
fn default_include_emoji_names() -> bool {
|
|
true
|
|
}
|
|
|
|
impl From<PushPlaintextContext> for PlaintextOptions {
|
|
fn from(value: PushPlaintextContext) -> Self {
|
|
Self {
|
|
preserve_markdown: value.preserve_markdown,
|
|
include_emoji_names: value.include_emoji_names,
|
|
include_link_urls: value.include_link_urls,
|
|
users: value.users,
|
|
roles: value.roles,
|
|
channels: value.channels,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PushPlaintextContext {
|
|
fn validate(&self) -> NifResult<()> {
|
|
if self.users.len() > MAX_CONTEXT_NAME_ENTRIES
|
|
|| self.roles.len() > MAX_CONTEXT_NAME_ENTRIES
|
|
|| self.channels.len() > MAX_CONTEXT_NAME_ENTRIES
|
|
{
|
|
return Err(Error::BadArg);
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[rustler::nif]
|
|
fn available() -> bool {
|
|
true
|
|
}
|
|
|
|
#[rustler::nif(schedule = "DirtyCpu")]
|
|
fn render_push_preview_nif<'a>(
|
|
env: Env<'a>,
|
|
content: Binary<'a>,
|
|
context_json: Binary<'a>,
|
|
) -> NifResult<Term<'a>> {
|
|
if content.as_slice().len() > MAX_CONTENT_BYTES
|
|
|| context_json.as_slice().len() > MAX_CONTEXT_JSON_BYTES
|
|
{
|
|
return Err(Error::BadArg);
|
|
}
|
|
let content = std::str::from_utf8(content.as_slice()).map_err(|_| Error::BadArg)?;
|
|
let context = serde_json::from_slice::<PushPlaintextContext>(context_json.as_slice())
|
|
.map_err(|_| Error::BadArg)?;
|
|
context.validate()?;
|
|
let options = PlaintextOptions::from(context);
|
|
let rendered = parse_and_render_plaintext(content, ParserFlags::ALL, "", &options)
|
|
.map_err(|_| Error::BadArg)?;
|
|
if rendered.len() > MAX_RENDERED_BYTES {
|
|
return Err(Error::BadArg);
|
|
}
|
|
binary_term(env, rendered.as_bytes())
|
|
}
|
|
|
|
fn binary_term<'a>(env: Env<'a>, bytes: &[u8]) -> NifResult<Term<'a>> {
|
|
let mut binary = OwnedBinary::new(bytes.len()).ok_or(Error::BadArg)?;
|
|
binary.as_mut_slice().copy_from_slice(bytes);
|
|
Ok(binary.release(env).to_term(env))
|
|
}
|
|
|
|
rustler::init!("push_markdown_plaintext_nif");
|