Skip to content

Commit 7bb5337

Browse files
fix(types): Serialize attachment headers as JSON
Use serde_json to write attachment item headers instead of formatting the JSON string manually. This keeps string fields encoded consistently with the rest of envelope serialization.
1 parent 881f66f commit 7bb5337

1 file changed

Lines changed: 71 additions & 18 deletions

File tree

sentry-types/src/protocol/attachment.rs

Lines changed: 71 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use std::fmt;
22

3-
use serde::Deserialize;
3+
use serde::{Deserialize, Serialize};
44

55
/// The different types an attachment can have.
6-
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Default)]
6+
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Default)]
77
pub enum AttachmentType {
88
#[serde(rename = "event.attachment")]
99
/// (default) A standard attachment without special meaning.
@@ -56,27 +56,51 @@ pub struct Attachment {
5656
pub ty: Option<AttachmentType>,
5757
}
5858

59+
struct AttachmentHeaderType;
60+
61+
impl Serialize for AttachmentHeaderType {
62+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
63+
where
64+
S: serde::Serializer,
65+
{
66+
"attachment".serialize(serializer)
67+
}
68+
}
69+
70+
#[derive(Serialize)]
71+
struct AttachmentHeader<'a> {
72+
r#type: AttachmentHeaderType,
73+
length: usize,
74+
filename: &'a str,
75+
attachment_type: &'a AttachmentType,
76+
content_type: &'a str,
77+
}
78+
5979
impl Attachment {
6080
/// Writes the attachment and its headers to the provided `Writer`.
6181
pub fn to_writer<W>(&self, writer: &mut W) -> std::io::Result<()>
6282
where
6383
W: std::io::Write,
6484
{
65-
writeln!(
66-
writer,
67-
r#"{{"type":"attachment","length":{length},"filename":"{filename}","attachment_type":"{at}","content_type":"{ct}"}}"#,
68-
filename = self.filename,
69-
length = self.buffer.len(),
70-
at = self
71-
.ty
72-
.as_ref()
73-
.unwrap_or(&AttachmentType::default())
74-
.as_str(),
75-
ct = self
76-
.content_type
77-
.as_ref()
78-
.unwrap_or(&"application/octet-stream".to_string())
79-
)?;
85+
let attachment_type = match self.ty.as_ref() {
86+
Some(ty) => ty,
87+
None => &Default::default(),
88+
};
89+
90+
let content_type = self
91+
.content_type
92+
.as_deref()
93+
.unwrap_or("application/octet-stream");
94+
let header = AttachmentHeader {
95+
r#type: AttachmentHeaderType,
96+
length: self.buffer.len(),
97+
filename: &self.filename,
98+
attachment_type,
99+
content_type,
100+
};
101+
102+
serde_json::to_writer(&mut *writer, &header)?;
103+
writeln!(writer)?;
80104

81105
writer.write_all(&self.buffer)?;
82106
Ok(())
@@ -99,7 +123,7 @@ impl fmt::Debug for Attachment {
99123
#[cfg(test)]
100124
mod tests {
101125
use super::*;
102-
use serde_json;
126+
use serde_json::{self, json};
103127

104128
#[test]
105129
fn test_attachment_type_deserialize() {
@@ -109,4 +133,33 @@ mod tests {
109133
let result: AttachmentType = serde_json::from_str(r#""my.custom.type""#).unwrap();
110134
assert_eq!(result, AttachmentType::Custom("my.custom.type".to_string()));
111135
}
136+
137+
#[test]
138+
fn test_attachment_header_escapes_json_strings() {
139+
let attachment = Attachment {
140+
buffer: b"payload".to_vec(),
141+
filename: "file \"name\"\npart.txt".to_string(),
142+
content_type: Some("text/\"plain\nnext".to_string()),
143+
ty: Some(AttachmentType::Custom("custom/\"type\nnext".to_string())),
144+
};
145+
146+
let mut buf = Vec::new();
147+
attachment.to_writer(&mut buf).unwrap();
148+
149+
let mut parts = buf.splitn(2, |&b| b == b'\n');
150+
let header: serde_json::Value = serde_json::from_slice(parts.next().unwrap()).unwrap();
151+
let payload = parts.next().unwrap();
152+
153+
assert_eq!(
154+
header,
155+
json!({
156+
"type": "attachment",
157+
"length": 7,
158+
"filename": "file \"name\"\npart.txt",
159+
"content_type": "text/\"plain\nnext",
160+
"attachment_type": "custom/\"type\nnext",
161+
})
162+
);
163+
assert_eq!(payload, b"payload");
164+
}
112165
}

0 commit comments

Comments
 (0)