use bytes::{Buf, BufMut, BytesMut};
use memchr::memmem;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::fmt::{self, Display, Formatter};
use std::num::ParseIntError;
use std::str::Utf8Error;
pub const MAX_CONTENT_LENGTH: usize = 50 * 1024 * 1024;
pub const MAX_HEADERS_SIZE: usize = 8192;
#[derive(Debug)]
pub enum ParseError {
Body(serde_json::Error),
Encode(std::io::Error),
Headers(httparse::Error),
InvalidContentType,
InvalidContentLength(String),
MissingContentLength,
Utf8(Utf8Error),
ContentLengthTooLarge(usize),
HeadersTooLarge,
EmptyMessage,
EnvelopeViolation(String),
}
impl Display for ParseError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
ParseError::Body(e) => write!(f, "unable to parse JSON body: {e}"),
ParseError::Encode(e) => write!(f, "failed to encode response: {e}"),
ParseError::Headers(e) => write!(f, "failed to parse headers: {e}"),
ParseError::InvalidContentType => write!(f, "unable to parse content type"),
ParseError::InvalidContentLength(e) => {
write!(f, "unable to parse content length: {e}")
}
ParseError::MissingContentLength => {
write!(f, "missing required `Content-Length` header")
}
ParseError::Utf8(e) => write!(f, "request contains invalid UTF8: {e}"),
ParseError::ContentLengthTooLarge(len) => {
write!(
f,
"content length {len} exceeds maximum allowed size ({MAX_CONTENT_LENGTH} bytes)"
)
}
ParseError::HeadersTooLarge => {
write!(
f,
"headers size exceeds maximum allowed size ({MAX_HEADERS_SIZE} bytes)"
)
}
ParseError::EmptyMessage => write!(f, "empty / zero-length message received"),
ParseError::EnvelopeViolation(e) => {
write!(f, "JSON-RPC envelope violation: {e}")
}
}
}
}
impl std::error::Error for ParseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ParseError::Body(e) => Some(e),
ParseError::Encode(e) => Some(e),
ParseError::Utf8(e) => Some(e),
_ => None,
}
}
}
impl From<serde_json::Error> for ParseError {
fn from(error: serde_json::Error) -> Self {
ParseError::Body(error)
}
}
impl From<std::io::Error> for ParseError {
fn from(error: std::io::Error) -> Self {
ParseError::Encode(error)
}
}
impl From<httparse::Error> for ParseError {
fn from(error: httparse::Error) -> Self {
ParseError::Headers(error)
}
}
impl From<Utf8Error> for ParseError {
fn from(error: Utf8Error) -> Self {
ParseError::Utf8(error)
}
}
pub fn decode_message<T: DeserializeOwned>(
src: &mut BytesMut,
state_content_len: &mut Option<usize>,
) -> Result<Option<T>, ParseError> {
if let Some(content_len) = *state_content_len {
if src.len() < content_len {
return Ok(None);
}
let bytes = &src[..content_len];
let message = std::str::from_utf8(bytes)?;
let result = if message.is_empty() {
Err(ParseError::EmptyMessage)
} else {
match serde_json::from_str::<serde_json::Value>(message) {
Ok(val) => match validate_envelope(&val) {
Ok(()) => match serde_json::from_value::<T>(val) {
Ok(parsed) => Ok(Some(parsed)),
Err(err) => Err(err.into()),
},
Err(err_msg) => Err(ParseError::EnvelopeViolation(err_msg.to_string())),
},
Err(err) => Err(err.into()),
}
};
src.advance(content_len);
*state_content_len = None;
result
} else {
if let Some(pos) = memmem::find(src, b"\r\n\r\n") {
let headers_len = pos + 4;
if headers_len > MAX_HEADERS_SIZE {
src.clear();
return Err(ParseError::HeadersTooLarge);
}
let mut dst = [httparse::EMPTY_HEADER; 16];
let parsed = httparse::parse_headers(&src[..headers_len], &mut dst);
match parsed {
Ok(httparse::Status::Complete((len, headers))) => {
debug_assert_eq!(len, headers_len);
match decode_headers(headers) {
Ok(content_len) => {
src.advance(headers_len);
*state_content_len = Some(content_len);
decode_message(src, state_content_len)
}
Err(err) => {
if !src.is_empty() {
src.advance(1);
}
recover_garbage(src);
Err(err)
}
}
}
Ok(httparse::Status::Partial) => {
if src.len() > MAX_HEADERS_SIZE {
src.clear();
return Err(ParseError::HeadersTooLarge);
}
Ok(None)
}
Err(err) => {
if !src.is_empty() {
src.advance(1);
}
recover_garbage(src);
Err(err.into())
}
}
} else {
if src.len() > MAX_HEADERS_SIZE {
src.clear();
return Err(ParseError::HeadersTooLarge);
}
Ok(None)
}
}
}
pub fn encode_message<T: Serialize>(item: T, dst: &mut BytesMut) -> Result<(), ParseError> {
let msg = serde_json::to_string(&item)?;
let digits = number_of_digits(msg.len());
dst.reserve(msg.len() + digits + 20);
let mut writer = dst.writer();
std::io::Write::write_fmt(
&mut writer,
format_args!("Content-Length: {}\r\n\r\n{}", msg.len(), msg),
)?;
std::io::Write::flush(&mut writer)?;
Ok(())
}
fn number_of_digits(mut n: usize) -> usize {
if n == 0 {
return 1;
}
let mut num_digits = 0;
while n > 0 {
n /= 10;
num_digits += 1;
}
num_digits
}
fn decode_headers(headers: &[httparse::Header<'_>]) -> Result<usize, ParseError> {
let mut content_len = None;
for header in headers {
if header.name.is_empty() {
continue;
}
if header.name.eq_ignore_ascii_case("Content-Length") {
let string = std::str::from_utf8(header.value)?;
let parsed_len: usize = string
.trim()
.parse()
.map_err(|e: ParseIntError| ParseError::InvalidContentLength(e.to_string()))?;
if parsed_len > MAX_CONTENT_LENGTH {
return Err(ParseError::ContentLengthTooLarge(parsed_len));
}
content_len = Some(parsed_len);
} else if header.name.eq_ignore_ascii_case("Content-Type") {
let string = std::str::from_utf8(header.value)?;
let mut parts = string.split(';');
let _media_type = parts.next().unwrap_or("").trim();
let mut charset_ok = true;
for part in parts {
let part = part.trim();
if part.to_ascii_lowercase().starts_with("charset=") {
let charset_val = &part["charset=".len()..];
if !charset_val.eq_ignore_ascii_case("utf-8")
&& !charset_val.eq_ignore_ascii_case("utf8")
{
charset_ok = false;
}
}
}
if !charset_ok {
return Err(ParseError::InvalidContentType);
}
}
}
if let Some(content_len) = content_len {
Ok(content_len)
} else {
Err(ParseError::MissingContentLength)
}
}
fn recover_garbage(src: &mut BytesMut) {
if let Some(pos) = find_case_insensitive(src, b"content-length") {
src.advance(pos);
} else {
let name_len = b"content-length".len();
if src.len() > name_len {
src.advance(src.len() - name_len);
}
}
}
fn find_case_insensitive(src: &[u8], target: &[u8]) -> Option<usize> {
if src.len() < target.len() {
return None;
}
for i in 0..=(src.len() - target.len()) {
let mut matches = true;
for (j, &byte) in target.iter().enumerate() {
if src[i + j].to_ascii_lowercase() != byte {
matches = false;
break;
}
}
if matches {
return Some(i);
}
}
None
}
pub fn validate_envelope(value: &serde_json::Value) -> Result<(), &'static str> {
let obj = match value.as_object() {
Some(o) => o,
None => return Err("JSON-RPC message must be a JSON object"),
};
match obj.get("jsonrpc") {
Some(v) => {
if v.as_str() != Some("2.0") {
return Err("jsonrpc version must be \"2.0\"");
}
}
None => return Err("missing jsonrpc version field"),
}
let has_method = obj.contains_key("method");
let has_result = obj.contains_key("result");
let has_error = obj.contains_key("error");
if has_method {
let method_val = obj.get("method").unwrap();
if !method_val.is_string() {
return Err("method must be a string");
}
if let Some(_id_val) = obj
.get("id")
.filter(|v| !v.is_number() && !v.is_string() && !v.is_null())
{
return Err("id must be a number, string, or null");
}
} else if has_result || has_error {
if has_result && has_error {
return Err("response cannot contain both result and error fields");
}
match obj.get("id") {
Some(id_val) => {
if !id_val.is_number() && !id_val.is_string() && !id_val.is_null() {
return Err("id must be a number, string, or null");
}
}
None => return Err("response must have an id field"),
}
if has_error {
let error_val = obj.get("error").unwrap();
let error_obj = match error_val.as_object() {
Some(eo) => eo,
None => return Err("error field must be an object"),
};
match error_obj.get("code") {
Some(code_val) => {
if !code_val.is_i64() {
return Err("error code must be an integer");
}
}
None => return Err("error object must contain a code field"),
}
match error_obj.get("message") {
Some(msg_val) => {
if !msg_val.is_string() {
return Err("error message must be a string");
}
}
None => return Err("error object must contain a message field"),
}
}
} else {
return Err(
"message must be either a request (with method) or a response (with result or error)",
);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn make_message(headers: &[(&str, &str)], body: &str) -> BytesMut {
let mut msg = String::new();
for (name, val) in headers {
msg.push_str(&format!("{}: {}\r\n", name, val));
}
msg.push_str("\r\n");
msg.push_str(body);
BytesMut::from(msg.as_str())
}
#[test]
fn test_case_insensitive_headers() {
let mut buf = make_message(
&[
("content-length", "39"),
("content-type", "application/vscode-jsonrpc; charset=utf8"),
],
r#"{"jsonrpc":"2.0","method":"foo","id":1}"#,
);
let mut state = None;
let decoded: Result<Option<serde_json::Value>, _> = decode_message(&mut buf, &mut state);
assert!(decoded.is_ok());
let val = decoded.unwrap().unwrap();
assert_eq!(val["method"], "foo");
}
#[test]
fn test_optional_charset() {
let mut buf = make_message(
&[
("Content-Length", "39"),
("Content-Type", "application/vscode-jsonrpc"),
],
r#"{"jsonrpc":"2.0","method":"foo","id":1}"#,
);
let mut state = None;
let decoded: Result<Option<serde_json::Value>, _> = decode_message(&mut buf, &mut state);
assert!(decoded.is_ok());
let val = decoded.unwrap().unwrap();
assert_eq!(val["method"], "foo");
}
#[test]
fn test_huge_content_length_rejected() {
let mut buf = make_message(
&[
("Content-Length", "100000000"), ("Content-Type", "application/vscode-jsonrpc"),
],
"",
);
let mut state = None;
let decoded: Result<Option<serde_json::Value>, _> = decode_message(&mut buf, &mut state);
assert!(decoded.is_err());
assert!(matches!(
decoded.err().unwrap(),
ParseError::ContentLengthTooLarge(_)
));
}
#[test]
fn test_headers_too_large_rejected() {
let mut buf = BytesMut::from(vec![b'a'; 9000].as_slice());
let mut state = None;
let decoded: Result<Option<serde_json::Value>, _> = decode_message(&mut buf, &mut state);
assert!(decoded.is_err());
assert!(matches!(
decoded.err().unwrap(),
ParseError::HeadersTooLarge
));
}
#[test]
fn test_zero_length_message_rejected() {
let mut buf = make_message(
&[
("Content-Length", "0"),
("Content-Type", "application/vscode-jsonrpc"),
],
"",
);
let mut state = None;
let decoded: Result<Option<serde_json::Value>, _> = decode_message(&mut buf, &mut state);
assert!(decoded.is_err());
assert!(matches!(decoded.err().unwrap(), ParseError::EmptyMessage));
}
#[test]
fn test_jsonrpc_envelope_validation() {
let val1 = serde_json::json!({
"method": "foo",
"id": 1
});
assert!(validate_envelope(&val1).is_err());
let val2 = serde_json::json!({
"jsonrpc": "1.0",
"method": "foo",
"id": 1
});
assert!(validate_envelope(&val2).is_err());
let val3 = serde_json::json!({
"jsonrpc": "2.0",
"method": 123,
"id": 1
});
assert!(validate_envelope(&val3).is_err());
let val4 = serde_json::json!({
"jsonrpc": "2.0",
"method": "foo",
"id": {}
});
assert!(validate_envelope(&val4).is_err());
let val5 = serde_json::json!({
"jsonrpc": "2.0",
"method": "foo"
});
assert!(validate_envelope(&val5).is_ok());
let val6 = serde_json::json!({
"jsonrpc": "2.0",
"result": "ok",
"id": 1
});
assert!(validate_envelope(&val6).is_ok());
}
#[test]
fn test_garbage_recovery() {
let mut buf = BytesMut::from(
"some garbage content-length: 39\r\n\r\n{\"jsonrpc\":\"2.0\",\"method\":\"foo\",\"id\":1}",
);
let mut state = None;
let decoded: Result<Option<serde_json::Value>, _> = decode_message(&mut buf, &mut state);
assert!(decoded.is_err());
let decoded2: Result<Option<serde_json::Value>, _> = decode_message(&mut buf, &mut state);
assert!(decoded2.is_ok());
assert_eq!(decoded2.unwrap().unwrap()["method"], "foo");
}
}