From 5acfffb352a47e052673f7ecc0c87653bc6c8ec4 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Tue, 19 Sep 2023 16:00:21 +0200 Subject: [PATCH] httpd: Improve data-uri support We were just using `strip_prefix` with a hardcoded value, which wasn't able to adjust to URIs that start with `data:image/png` or other content and types. Signed-off-by: Sebastian Martinez --- radicle-httpd/src/api.rs | 29 +++++++++++++++++++- radicle-httpd/src/api/v1/projects.rs | 41 +++++++++------------------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/radicle-httpd/src/api.rs b/radicle-httpd/src/api.rs index 037b14ecb..9ec4e331e 100644 --- a/radicle-httpd/src/api.rs +++ b/radicle-httpd/src/api.rs @@ -10,13 +10,14 @@ use axum::http::Method; use axum::response::{IntoResponse, Json}; use axum::routing::get; use axum::Router; +use base64::prelude::{Engine, BASE64_STANDARD}; use serde::{Deserialize, Serialize}; use serde_json::json; use tokio::sync::RwLock; use tower_http::cors::{self, CorsLayer}; -use radicle::cob::issue; use radicle::cob::patch; +use radicle::cob::{issue, Uri}; use radicle::identity::Id; use radicle::node::routing::Store; use radicle::storage::{ReadRepository, ReadStorage}; @@ -204,3 +205,29 @@ mod project { pub trackings: usize, } } + +/// A `data:` URI. +#[derive(Debug, Clone)] +pub struct DataUri(Vec); + +impl From for Vec { + fn from(value: DataUri) -> Self { + value.0 + } +} + +impl TryFrom<&Uri> for DataUri { + type Error = Uri; + + fn try_from(value: &Uri) -> Result { + if let Some(data_uri) = value.as_str().strip_prefix("data:") { + let (_, uri_data) = data_uri.split_once(',').ok_or(value.clone())?; + let uri_data = BASE64_STANDARD + .decode(uri_data) + .map_err(|_| value.clone())?; + + return Ok(DataUri(uri_data)); + } + Err(value.clone()) + } +} diff --git a/radicle-httpd/src/api/v1/projects.rs b/radicle-httpd/src/api/v1/projects.rs index f9eb37d65..b632075a4 100644 --- a/radicle-httpd/src/api/v1/projects.rs +++ b/radicle-httpd/src/api/v1/projects.rs @@ -7,7 +7,6 @@ use axum::response::IntoResponse; use axum::routing::{get, patch, post}; use axum::{Json, Router}; use axum_auth::AuthBearer; -use base64::prelude::{Engine, BASE64_STANDARD}; use hyper::StatusCode; use radicle_surf::blob::{Blob, BlobRef}; use serde::{Deserialize, Serialize}; @@ -25,7 +24,7 @@ use radicle_surf::{diff, Glob, Oid, Repository}; use crate::api::error::Error; use crate::api::project::Info; -use crate::api::{self, CobsQuery, Context, PaginationQuery}; +use crate::api::{self, CobsQuery, Context, DataUri, PaginationQuery}; use crate::axum_extra::{Path, Query}; const CACHE_1_HOUR: &str = "public, max-age=3600, must-revalidate"; @@ -534,18 +533,11 @@ async fn issue_create_handler( .embeds .into_iter() .filter_map(|embed| { - if let Some(content) = embed - .content - .as_str() - .strip_prefix("data:content/type;base64,") - { - return BASE64_STANDARD.decode(content).ok().map(|content| Embed { - name: embed.name, - content, - }); - } - - None + let content = TryInto::::try_into(&embed.content).ok()?; + Some(Embed { + name: embed.name, + content: content.into(), + }) }) .collect(); @@ -604,18 +596,11 @@ async fn issue_update_handler( let embeds: Vec = embeds .into_iter() .filter_map(|embed| { - if let Some(content) = embed - .content - .as_str() - .strip_prefix("data:content/type;base64,") - { - return BASE64_STANDARD.decode(content).ok().map(|content| Embed { - name: embed.name, - content, - }); - } - - None + let content = TryInto::::try_into(&embed.content).ok()?; + Some(Embed { + name: embed.name, + content: content.into(), + }) }) .collect(); if let Some(to) = reply_to { @@ -1848,7 +1833,7 @@ mod routes { "embeds": [ { "name": "example.html", - "content": "data:content/type;base64,PGh0bWw+SGVsbG8gV29ybGQhPC9odG1sPg==" + "content": "data:image/png;base64,PGh0bWw+SGVsbG8gV29ybGQhPC9odG1sPg==" } ], "assignees": [], @@ -1924,7 +1909,7 @@ mod routes { "embeds": [ { "name": "image.jpg", - "content": "data:content/type;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4//8/AAX+Av4N70a4AAAAAElFTkSuQmCC" + "content": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4//8/AAX+Av4N70a4AAAAAElFTkSuQmCC" } ], "replyTo": CONTRIBUTOR_ISSUE_ID,