new functions and fixes
This commit is contained in:
+123
-6
@@ -130,7 +130,7 @@ pub(super) fn content_references_file(content: &str, filename: &str, url: &str)
|
||||
if content.contains(url) {
|
||||
return true;
|
||||
}
|
||||
for marker in ["[file=", "[image=", "[img="] {
|
||||
for marker in ["[file=", "[image=", "[img=", "[video="] {
|
||||
let mut remaining = content;
|
||||
while let Some(index) = remaining.find(marker) {
|
||||
let after = &remaining[index + marker.len()..];
|
||||
@@ -513,17 +513,38 @@ async fn require_upload_permission(
|
||||
.ok_or_else(|| ApiError::forbidden("File uploads are disabled for guests."))
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct FileDownloadQuery {
|
||||
#[serde(default)]
|
||||
download: Option<String>,
|
||||
}
|
||||
|
||||
impl FileDownloadQuery {
|
||||
fn force_download(&self) -> bool {
|
||||
self.download.as_deref().is_some_and(|value| {
|
||||
matches!(
|
||||
value.trim().to_ascii_lowercase().as_str(),
|
||||
"1" | "true" | "yes" | "download"
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn download_file(
|
||||
State(state): State<SharedState>,
|
||||
headers: HeaderMap,
|
||||
Path((token, filename)): Path<(String, String)>,
|
||||
Query(query): Query<FileDownloadQuery>,
|
||||
) -> Result<Response, ApiError> {
|
||||
serve_token_file(&state, &token, &filename).await
|
||||
serve_token_file(&state, &headers, &token, &filename, query.force_download()).await
|
||||
}
|
||||
|
||||
async fn serve_token_file(
|
||||
state: &SharedState,
|
||||
headers: &HeaderMap,
|
||||
token: &str,
|
||||
filename: &str,
|
||||
force_download: bool,
|
||||
) -> Result<Response, ApiError> {
|
||||
let safe = sanitize_filename(filename);
|
||||
if safe != filename {
|
||||
@@ -543,15 +564,44 @@ async fn serve_token_file(
|
||||
.get_local_with_legacy(&key, &legacy_key)
|
||||
.await
|
||||
.map_err(|_| ApiError::not_found_file())?;
|
||||
let total_len = bytes.len();
|
||||
let guessed_mime = mime_guess::from_path(&safe).first_or_octet_stream();
|
||||
let inline_image = is_safe_inline_image_mime(guessed_mime.essence_str());
|
||||
let served_mime = if inline_image {
|
||||
let safe_inline = is_safe_inline_image_mime(guessed_mime.essence_str())
|
||||
|| is_safe_inline_video_mime(guessed_mime.essence_str());
|
||||
let served_mime = if safe_inline {
|
||||
guessed_mime.as_ref()
|
||||
} else {
|
||||
"application/octet-stream"
|
||||
};
|
||||
let disposition = if inline_image { "inline" } else { "attachment" };
|
||||
let mut response = bytes.into_response();
|
||||
let disposition = if safe_inline && !force_download {
|
||||
"inline"
|
||||
} else {
|
||||
"attachment"
|
||||
};
|
||||
|
||||
let requested_range = headers
|
||||
.get(header::RANGE)
|
||||
.and_then(|value| value.to_str().ok());
|
||||
let range = match requested_range {
|
||||
Some(value) => match parse_byte_range(value, total_len) {
|
||||
Ok(range) => range,
|
||||
Err(()) => return Ok(range_not_satisfiable(total_len)),
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
let (body, status, content_range) = if let Some((start, end)) = range {
|
||||
(
|
||||
bytes.slice(start..end),
|
||||
StatusCode::PARTIAL_CONTENT,
|
||||
Some(format!("bytes {start}-{}/{}", end - 1, total_len)),
|
||||
)
|
||||
} else {
|
||||
(bytes, StatusCode::OK, None)
|
||||
};
|
||||
let body_len = body.len();
|
||||
let mut response = body.into_response();
|
||||
*response.status_mut() = status;
|
||||
response.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_str(served_mime)
|
||||
@@ -562,6 +612,20 @@ async fn serve_token_file(
|
||||
HeaderValue::from_str(&format!("{disposition}; filename=\"{safe}\""))
|
||||
.expect("sanitized attachment filename"),
|
||||
);
|
||||
response.headers_mut().insert(
|
||||
header::ACCEPT_RANGES,
|
||||
HeaderValue::from_static("bytes"),
|
||||
);
|
||||
response.headers_mut().insert(
|
||||
header::CONTENT_LENGTH,
|
||||
HeaderValue::from_str(&body_len.to_string()).expect("valid content length"),
|
||||
);
|
||||
if let Some(content_range) = content_range {
|
||||
response.headers_mut().insert(
|
||||
header::CONTENT_RANGE,
|
||||
HeaderValue::from_str(&content_range).expect("valid content range"),
|
||||
);
|
||||
}
|
||||
response.headers_mut().insert(
|
||||
HeaderName::from_static("content-security-policy"),
|
||||
HeaderValue::from_static("default-src 'none'; sandbox"),
|
||||
@@ -584,6 +648,52 @@ async fn serve_token_file(
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn range_not_satisfiable(total_len: usize) -> Response {
|
||||
let mut response = StatusCode::RANGE_NOT_SATISFIABLE.into_response();
|
||||
response.headers_mut().insert(
|
||||
header::CONTENT_RANGE,
|
||||
HeaderValue::from_str(&format!("bytes */{total_len}")).expect("valid content range"),
|
||||
);
|
||||
response.headers_mut().insert(
|
||||
header::ACCEPT_RANGES,
|
||||
HeaderValue::from_static("bytes"),
|
||||
);
|
||||
response
|
||||
}
|
||||
|
||||
fn parse_byte_range(value: &str, total_len: usize) -> Result<Option<(usize, usize)>, ()> {
|
||||
if total_len == 0 {
|
||||
return Err(());
|
||||
}
|
||||
let range = value.trim().strip_prefix("bytes=").ok_or(())?;
|
||||
if range.contains(',') {
|
||||
return Err(());
|
||||
}
|
||||
let (start, end) = range.split_once('-').ok_or(())?;
|
||||
if start.is_empty() {
|
||||
let suffix_len = end.parse::<usize>().map_err(|_| ())?;
|
||||
if suffix_len == 0 {
|
||||
return Err(());
|
||||
}
|
||||
let start = total_len.saturating_sub(suffix_len);
|
||||
return Ok(Some((start, total_len)));
|
||||
}
|
||||
|
||||
let start = start.parse::<usize>().map_err(|_| ())?;
|
||||
if start >= total_len {
|
||||
return Err(());
|
||||
}
|
||||
let end_inclusive = if end.is_empty() {
|
||||
total_len - 1
|
||||
} else {
|
||||
end.parse::<usize>().map_err(|_| ())?.min(total_len - 1)
|
||||
};
|
||||
if end_inclusive < start {
|
||||
return Err(());
|
||||
}
|
||||
Ok(Some((start, end_inclusive + 1)))
|
||||
}
|
||||
|
||||
fn is_safe_inline_image_mime(value: &str) -> bool {
|
||||
matches!(
|
||||
value,
|
||||
@@ -597,6 +707,13 @@ fn is_safe_inline_image_mime(value: &str) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
fn is_safe_inline_video_mime(value: &str) -> bool {
|
||||
matches!(
|
||||
value,
|
||||
"video/mp4" | "video/webm" | "video/ogg" | "video/quicktime" | "video/x-m4v"
|
||||
)
|
||||
}
|
||||
|
||||
fn sanitize_filename(value: &str) -> String {
|
||||
let name = std::path::Path::new(value)
|
||||
.file_name()
|
||||
|
||||
@@ -432,6 +432,7 @@ pub struct NoteInfo {
|
||||
editor_line_numbers: bool,
|
||||
preview_line_numbers: bool,
|
||||
line_links: bool,
|
||||
toolbar_collapsed: bool,
|
||||
font_family: String,
|
||||
font_size: i64,
|
||||
personal_editor_settings: bool,
|
||||
@@ -461,6 +462,8 @@ pub struct EditorSettingsRequest {
|
||||
#[serde(default)]
|
||||
line_links: Option<bool>,
|
||||
#[serde(default)]
|
||||
toolbar_collapsed: Option<bool>,
|
||||
#[serde(default)]
|
||||
font_family: Option<String>,
|
||||
#[serde(default)]
|
||||
font_size: Option<i64>,
|
||||
@@ -502,6 +505,7 @@ async fn save_editor_settings(
|
||||
|| payload.editor_line_numbers.is_some()
|
||||
|| payload.preview_line_numbers.is_some()
|
||||
|| payload.line_links.is_some()
|
||||
|| payload.toolbar_collapsed.is_some()
|
||||
|| payload.font_family.is_some()
|
||||
|| payload.font_size.is_some();
|
||||
let wants_global_update = payload.authorship_mode.is_some() || payload.colors_enabled.is_some();
|
||||
@@ -553,6 +557,9 @@ async fn save_editor_settings(
|
||||
if let Some(value) = payload.line_links {
|
||||
preferences.line_links = value;
|
||||
}
|
||||
if let Some(value) = payload.toolbar_collapsed {
|
||||
preferences.toolbar_collapsed = value;
|
||||
}
|
||||
if let Some(value) = payload.font_family {
|
||||
preferences.font_family = match value.as_str() {
|
||||
"mono" | "system" | "serif" | "arial" | "georgia" => value,
|
||||
@@ -1084,6 +1091,7 @@ pub async fn note_info(
|
||||
editor_line_numbers: editor_preferences.editor_line_numbers,
|
||||
preview_line_numbers: editor_preferences.preview_line_numbers,
|
||||
line_links: editor_preferences.line_links,
|
||||
toolbar_collapsed: editor_preferences.toolbar_collapsed,
|
||||
font_family: editor_preferences.font_family,
|
||||
font_size: editor_preferences.font_size,
|
||||
personal_editor_settings,
|
||||
|
||||
@@ -54,6 +54,7 @@ pub struct PadInfo {
|
||||
editor_line_numbers: bool,
|
||||
preview_line_numbers: bool,
|
||||
line_links: bool,
|
||||
toolbar_collapsed: bool,
|
||||
font_family: String,
|
||||
font_size: i64,
|
||||
personal_editor_settings: bool,
|
||||
@@ -172,6 +173,7 @@ pub async fn pad_info(
|
||||
editor_line_numbers: editor_preferences.editor_line_numbers,
|
||||
preview_line_numbers: editor_preferences.preview_line_numbers,
|
||||
line_links: editor_preferences.line_links,
|
||||
toolbar_collapsed: editor_preferences.toolbar_collapsed,
|
||||
font_family: editor_preferences.font_family,
|
||||
font_size: editor_preferences.font_size,
|
||||
personal_editor_settings,
|
||||
|
||||
+21
-7
@@ -38,6 +38,7 @@ const MODULES: &[&str] = &[
|
||||
"toast",
|
||||
"theme",
|
||||
"url-state",
|
||||
"vendor-libs",
|
||||
"security",
|
||||
];
|
||||
|
||||
@@ -51,10 +52,20 @@ pub fn render_html(
|
||||
entrypoint: &str,
|
||||
) -> Response {
|
||||
let urls = AssetUrls::new(asset_version);
|
||||
let frontend_config = frontend_config(frontend_log_level, upload_max_size_bytes, external_auth);
|
||||
let frontend_config = frontend_config(
|
||||
frontend_log_level,
|
||||
upload_max_size_bytes,
|
||||
external_auth,
|
||||
asset_version,
|
||||
);
|
||||
let app_stylesheets = format!(
|
||||
"{}{}",
|
||||
urls.stylesheet("styles"),
|
||||
urls.stylesheet_path("libs/rustpad-player/player.css"),
|
||||
);
|
||||
let html = template
|
||||
.replace("__APP_THEME_BOOTSTRAP__", theme_bootstrap())
|
||||
.replace("__APP_STYLESHEET__", &urls.stylesheet("styles"))
|
||||
.replace("__APP_STYLESHEET__", &app_stylesheets)
|
||||
.replace("__APP_IMPORT_MAP__", &urls.import_map())
|
||||
.replace("__APP_ENTRYPOINT__", &urls.entrypoint(entrypoint))
|
||||
.replace(
|
||||
@@ -92,12 +103,14 @@ fn frontend_config(
|
||||
frontend_log_level: &str,
|
||||
upload_max_size_bytes: usize,
|
||||
external_auth: bool,
|
||||
asset_version: &str,
|
||||
) -> String {
|
||||
format!(
|
||||
r#"<script>window.__RUSTPAD_CONFIG__=Object.freeze({{frontendLogLevel:"{}",uploadMaxSizeBytes:{},externalAuth:{}}});</script>"#,
|
||||
r#"<script>window.__RUSTPAD_CONFIG__=Object.freeze({{frontendLogLevel:"{}",uploadMaxSizeBytes:{},externalAuth:{},assetVersion:"{}"}});</script>"#,
|
||||
escape_js_string(frontend_log_level),
|
||||
upload_max_size_bytes,
|
||||
external_auth,
|
||||
escape_js_string(asset_version),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -115,10 +128,11 @@ impl<'a> AssetUrls<'a> {
|
||||
}
|
||||
|
||||
fn stylesheet(&self, name: &str) -> String {
|
||||
format!(
|
||||
r#"<link rel="stylesheet" href="{}">"#,
|
||||
self.url(&format!("css/{name}.css"))
|
||||
)
|
||||
self.stylesheet_path(&format!("css/{name}.css"))
|
||||
}
|
||||
|
||||
fn stylesheet_path(&self, path: &str) -> String {
|
||||
format!(r#"<link rel="stylesheet" href="{}">"#, self.url(path))
|
||||
}
|
||||
|
||||
fn entrypoint(&self, name: &str) -> String {
|
||||
|
||||
@@ -15,6 +15,7 @@ pub struct EditorPreferences {
|
||||
pub editor_line_numbers: bool,
|
||||
pub preview_line_numbers: bool,
|
||||
pub line_links: bool,
|
||||
pub toolbar_collapsed: bool,
|
||||
pub font_family: String,
|
||||
pub font_size: i64,
|
||||
}
|
||||
@@ -26,6 +27,7 @@ impl Default for EditorPreferences {
|
||||
editor_line_numbers: true,
|
||||
preview_line_numbers: false,
|
||||
line_links: false,
|
||||
toolbar_collapsed: false,
|
||||
font_family: "mono".into(),
|
||||
font_size: 14,
|
||||
}
|
||||
@@ -92,8 +94,9 @@ pub async fn load_editor_preferences(
|
||||
editor_line_numbers: row.try_get::<i64, _>(1)? != 0,
|
||||
preview_line_numbers: row.try_get::<i64, _>(2)? != 0,
|
||||
line_links: row.try_get::<i64, _>(3)? != 0,
|
||||
font_family: crate::row_decode::text(&row, 4)?,
|
||||
font_size: row.try_get(5)?,
|
||||
toolbar_collapsed: row.try_get::<i64, _>(4)? != 0,
|
||||
font_family: crate::row_decode::text(&row, 5)?,
|
||||
font_size: row.try_get(6)?,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -117,6 +120,7 @@ pub async fn save_editor_configuration(
|
||||
.bind(preferences.editor_line_numbers)
|
||||
.bind(preferences.preview_line_numbers)
|
||||
.bind(preferences.line_links)
|
||||
.bind(preferences.toolbar_collapsed)
|
||||
.bind(&preferences.font_family)
|
||||
.bind(preferences.font_size)
|
||||
.execute(&mut *tx)
|
||||
|
||||
@@ -59,16 +59,16 @@ pub fn get(query: Query) -> &'static str {
|
||||
r#"DELETE FROM resource_editor_settings WHERE resource_kind = ? AND resource_slug = ?"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_SELECT_PAD => {
|
||||
r#"SELECT CAST(CASE WHEN compact_view THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN editor_line_numbers THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN preview_line_numbers THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN line_links THEN 1 ELSE 0 END AS SIGNED), CAST(font_family AS CHAR CHARACTER SET utf8mb4), font_size FROM user_editor_preferences WHERE user_id = ? AND pad_id = ?"#
|
||||
r#"SELECT CAST(CASE WHEN compact_view THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN editor_line_numbers THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN preview_line_numbers THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN line_links THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN toolbar_collapsed THEN 1 ELSE 0 END AS SIGNED), CAST(font_family AS CHAR CHARACTER SET utf8mb4), font_size FROM user_editor_preferences WHERE user_id = ? AND pad_id = ?"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_SELECT_NOTE => {
|
||||
r#"SELECT CAST(CASE WHEN compact_view THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN editor_line_numbers THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN preview_line_numbers THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN line_links THEN 1 ELSE 0 END AS SIGNED), CAST(font_family AS CHAR CHARACTER SET utf8mb4), font_size FROM user_editor_preferences WHERE user_id = ? AND note_id = ?"#
|
||||
r#"SELECT CAST(CASE WHEN compact_view THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN editor_line_numbers THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN preview_line_numbers THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN line_links THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN toolbar_collapsed THEN 1 ELSE 0 END AS SIGNED), CAST(font_family AS CHAR CHARACTER SET utf8mb4), font_size FROM user_editor_preferences WHERE user_id = ? AND note_id = ?"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_UPSERT_PAD => {
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, font_family, font_size, updated_at) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE compact_view = VALUES(compact_view), editor_line_numbers = VALUES(editor_line_numbers), preview_line_numbers = VALUES(preview_line_numbers), line_links = VALUES(line_links), font_family = VALUES(font_family), font_size = VALUES(font_size), updated_at = CURRENT_TIMESTAMP"#
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, toolbar_collapsed, font_family, font_size, updated_at) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE compact_view = VALUES(compact_view), editor_line_numbers = VALUES(editor_line_numbers), preview_line_numbers = VALUES(preview_line_numbers), line_links = VALUES(line_links), toolbar_collapsed = VALUES(toolbar_collapsed), font_family = VALUES(font_family), font_size = VALUES(font_size), updated_at = CURRENT_TIMESTAMP"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_UPSERT_NOTE => {
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, font_family, font_size, updated_at) VALUES (?, NULL, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE compact_view = VALUES(compact_view), editor_line_numbers = VALUES(editor_line_numbers), preview_line_numbers = VALUES(preview_line_numbers), line_links = VALUES(line_links), font_family = VALUES(font_family), font_size = VALUES(font_size), updated_at = CURRENT_TIMESTAMP"#
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, toolbar_collapsed, font_family, font_size, updated_at) VALUES (?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE compact_view = VALUES(compact_view), editor_line_numbers = VALUES(editor_line_numbers), preview_line_numbers = VALUES(preview_line_numbers), line_links = VALUES(line_links), toolbar_collapsed = VALUES(toolbar_collapsed), font_family = VALUES(font_family), font_size = VALUES(font_size), updated_at = CURRENT_TIMESTAMP"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_DELETE_BY_USER => {
|
||||
r#"DELETE FROM user_editor_preferences WHERE user_id = ?"#
|
||||
|
||||
@@ -59,16 +59,16 @@ pub fn get(query: Query) -> &'static str {
|
||||
r#"DELETE FROM resource_editor_settings WHERE resource_kind = $1 AND resource_slug = $2"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_SELECT_PAD => {
|
||||
r#"SELECT (CASE WHEN compact_view THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN editor_line_numbers THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN preview_line_numbers THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN line_links THEN 1 ELSE 0 END)::BIGINT, font_family, font_size FROM user_editor_preferences WHERE user_id = $1 AND pad_id = $2"#
|
||||
r#"SELECT (CASE WHEN compact_view THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN editor_line_numbers THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN preview_line_numbers THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN line_links THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN toolbar_collapsed THEN 1 ELSE 0 END)::BIGINT, font_family, font_size FROM user_editor_preferences WHERE user_id = $1 AND pad_id = $2"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_SELECT_NOTE => {
|
||||
r#"SELECT (CASE WHEN compact_view THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN editor_line_numbers THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN preview_line_numbers THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN line_links THEN 1 ELSE 0 END)::BIGINT, font_family, font_size FROM user_editor_preferences WHERE user_id = $1 AND note_id = $2"#
|
||||
r#"SELECT (CASE WHEN compact_view THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN editor_line_numbers THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN preview_line_numbers THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN line_links THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN toolbar_collapsed THEN 1 ELSE 0 END)::BIGINT, font_family, font_size FROM user_editor_preferences WHERE user_id = $1 AND note_id = $2"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_UPSERT_PAD => {
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, font_family, font_size, updated_at) VALUES ($1, $2, NULL, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP::text) ON CONFLICT (user_id, pad_id) DO UPDATE SET compact_view = EXCLUDED.compact_view, editor_line_numbers = EXCLUDED.editor_line_numbers, preview_line_numbers = EXCLUDED.preview_line_numbers, line_links = EXCLUDED.line_links, font_family = EXCLUDED.font_family, font_size = EXCLUDED.font_size, updated_at = CURRENT_TIMESTAMP::text"#
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, toolbar_collapsed, font_family, font_size, updated_at) VALUES ($1, $2, NULL, $3, $4, $5, $6, $7, $8, $9, CURRENT_TIMESTAMP::text) ON CONFLICT (user_id, pad_id) DO UPDATE SET compact_view = EXCLUDED.compact_view, editor_line_numbers = EXCLUDED.editor_line_numbers, preview_line_numbers = EXCLUDED.preview_line_numbers, line_links = EXCLUDED.line_links, toolbar_collapsed = EXCLUDED.toolbar_collapsed, font_family = EXCLUDED.font_family, font_size = EXCLUDED.font_size, updated_at = CURRENT_TIMESTAMP::text"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_UPSERT_NOTE => {
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, font_family, font_size, updated_at) VALUES ($1, NULL, $2, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP::text) ON CONFLICT (user_id, note_id) DO UPDATE SET compact_view = EXCLUDED.compact_view, editor_line_numbers = EXCLUDED.editor_line_numbers, preview_line_numbers = EXCLUDED.preview_line_numbers, line_links = EXCLUDED.line_links, font_family = EXCLUDED.font_family, font_size = EXCLUDED.font_size, updated_at = CURRENT_TIMESTAMP::text"#
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, toolbar_collapsed, font_family, font_size, updated_at) VALUES ($1, NULL, $2, $3, $4, $5, $6, $7, $8, $9, CURRENT_TIMESTAMP::text) ON CONFLICT (user_id, note_id) DO UPDATE SET compact_view = EXCLUDED.compact_view, editor_line_numbers = EXCLUDED.editor_line_numbers, preview_line_numbers = EXCLUDED.preview_line_numbers, line_links = EXCLUDED.line_links, toolbar_collapsed = EXCLUDED.toolbar_collapsed, font_family = EXCLUDED.font_family, font_size = EXCLUDED.font_size, updated_at = CURRENT_TIMESTAMP::text"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_DELETE_BY_USER => {
|
||||
r#"DELETE FROM user_editor_preferences WHERE user_id = $1"#
|
||||
|
||||
@@ -59,16 +59,16 @@ pub fn get(query: Query) -> &'static str {
|
||||
r#"DELETE FROM resource_editor_settings WHERE resource_kind = ? AND resource_slug = ?"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_SELECT_PAD => {
|
||||
r#"SELECT CASE WHEN compact_view THEN 1 ELSE 0 END, CASE WHEN editor_line_numbers THEN 1 ELSE 0 END, CASE WHEN preview_line_numbers THEN 1 ELSE 0 END, CASE WHEN line_links THEN 1 ELSE 0 END, font_family, font_size FROM user_editor_preferences WHERE user_id = ? AND pad_id = ?"#
|
||||
r#"SELECT CASE WHEN compact_view THEN 1 ELSE 0 END, CASE WHEN editor_line_numbers THEN 1 ELSE 0 END, CASE WHEN preview_line_numbers THEN 1 ELSE 0 END, CASE WHEN line_links THEN 1 ELSE 0 END, CASE WHEN toolbar_collapsed THEN 1 ELSE 0 END, font_family, font_size FROM user_editor_preferences WHERE user_id = ? AND pad_id = ?"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_SELECT_NOTE => {
|
||||
r#"SELECT CASE WHEN compact_view THEN 1 ELSE 0 END, CASE WHEN editor_line_numbers THEN 1 ELSE 0 END, CASE WHEN preview_line_numbers THEN 1 ELSE 0 END, CASE WHEN line_links THEN 1 ELSE 0 END, font_family, font_size FROM user_editor_preferences WHERE user_id = ? AND note_id = ?"#
|
||||
r#"SELECT CASE WHEN compact_view THEN 1 ELSE 0 END, CASE WHEN editor_line_numbers THEN 1 ELSE 0 END, CASE WHEN preview_line_numbers THEN 1 ELSE 0 END, CASE WHEN line_links THEN 1 ELSE 0 END, CASE WHEN toolbar_collapsed THEN 1 ELSE 0 END, font_family, font_size FROM user_editor_preferences WHERE user_id = ? AND note_id = ?"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_UPSERT_PAD => {
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, font_family, font_size, updated_at) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT (user_id, pad_id) DO UPDATE SET compact_view = excluded.compact_view, editor_line_numbers = excluded.editor_line_numbers, preview_line_numbers = excluded.preview_line_numbers, line_links = excluded.line_links, font_family = excluded.font_family, font_size = excluded.font_size, updated_at = CURRENT_TIMESTAMP"#
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, toolbar_collapsed, font_family, font_size, updated_at) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT (user_id, pad_id) DO UPDATE SET compact_view = excluded.compact_view, editor_line_numbers = excluded.editor_line_numbers, preview_line_numbers = excluded.preview_line_numbers, line_links = excluded.line_links, toolbar_collapsed = excluded.toolbar_collapsed, font_family = excluded.font_family, font_size = excluded.font_size, updated_at = CURRENT_TIMESTAMP"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_UPSERT_NOTE => {
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, font_family, font_size, updated_at) VALUES (?, NULL, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT (user_id, note_id) DO UPDATE SET compact_view = excluded.compact_view, editor_line_numbers = excluded.editor_line_numbers, preview_line_numbers = excluded.preview_line_numbers, line_links = excluded.line_links, font_family = excluded.font_family, font_size = excluded.font_size, updated_at = CURRENT_TIMESTAMP"#
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, toolbar_collapsed, font_family, font_size, updated_at) VALUES (?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT (user_id, note_id) DO UPDATE SET compact_view = excluded.compact_view, editor_line_numbers = excluded.editor_line_numbers, preview_line_numbers = excluded.preview_line_numbers, line_links = excluded.line_links, toolbar_collapsed = excluded.toolbar_collapsed, font_family = excluded.font_family, font_size = excluded.font_size, updated_at = CURRENT_TIMESTAMP"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_DELETE_BY_USER => {
|
||||
r#"DELETE FROM user_editor_preferences WHERE user_id = ?"#
|
||||
|
||||
+31
-1
@@ -7,7 +7,10 @@
|
||||
* See LICENSE file in repository root for details.
|
||||
*/
|
||||
|
||||
use super::{content_references_file, content_references_stored_file, is_safe_inline_image_mime};
|
||||
use super::{
|
||||
content_references_file, content_references_stored_file, is_safe_inline_image_mime,
|
||||
is_safe_inline_video_mime, parse_byte_range,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn only_raster_images_are_inline() {
|
||||
@@ -48,3 +51,30 @@ fn attachment_references_survive_origin_changes() {
|
||||
Some("https://new-files.example.com"),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_video_formats_are_inline() {
|
||||
assert!(is_safe_inline_video_mime("video/mp4"));
|
||||
assert!(is_safe_inline_video_mime("video/webm"));
|
||||
assert!(!is_safe_inline_video_mime("text/html"));
|
||||
assert!(!is_safe_inline_video_mime("application/javascript"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn video_alias_is_still_attached() {
|
||||
assert!(content_references_file(
|
||||
"[video=clip.mp4,Product demo]",
|
||||
"clip.mp4",
|
||||
"/f/token/clip.mp4",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_ranges_support_video_seeking() {
|
||||
assert_eq!(parse_byte_range("bytes=0-99", 1_000), Ok(Some((0, 100))));
|
||||
assert_eq!(parse_byte_range("bytes=500-", 1_000), Ok(Some((500, 1_000))));
|
||||
assert_eq!(parse_byte_range("bytes=-100", 1_000), Ok(Some((900, 1_000))));
|
||||
assert_eq!(parse_byte_range("bytes=900-2000", 1_000), Ok(Some((900, 1_000))));
|
||||
assert_eq!(parse_byte_range("bytes=1000-", 1_000), Err(()));
|
||||
assert_eq!(parse_byte_range("bytes=0-1,4-5", 1_000), Err(()));
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ fn boolean_projections_are_normalized_for_sqlx_any() {
|
||||
// MySQL BOOLEAN is TINYINT(1), which sqlx::Any 0.8 cannot map directly.
|
||||
for (query, expected_casts) in [
|
||||
(Query::RESOURCE_EDITOR_SETTINGS_SELECT, 1),
|
||||
(Query::EDITOR_PREFERENCES_SELECT_PAD, 4),
|
||||
(Query::EDITOR_PREFERENCES_SELECT_NOTE, 4),
|
||||
(Query::EDITOR_PREFERENCES_SELECT_PAD, 5),
|
||||
(Query::EDITOR_PREFERENCES_SELECT_NOTE, 5),
|
||||
(Query::AUTH_USER_BY_EXTERNAL_ID, 1),
|
||||
(Query::AUTH_USER_BY_SESSION, 1),
|
||||
(Query::AUTH_USER_BY_NICKNAME, 1),
|
||||
|
||||
Reference in New Issue
Block a user