This commit is contained in:
sunli 2020-04-21 12:13:14 +08:00
parent d603ee2700
commit db32d2a071
8 changed files with 134 additions and 37 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql"
version = "1.9.4"
version = "1.9.5"
authors = ["sunli <scott_s829@163.com>"]
edition = "2018"
description = "The GraphQL server library implemented by rust"
@ -18,7 +18,7 @@ default = ["bson", "uuid", "url", "chrono-tz", "validators"]
validators = ["regex"]
[dependencies]
async-graphql-derive = { path = "async-graphql-derive", version = "1.9.4" }
async-graphql-derive = { path = "async-graphql-derive", version = "1.9.5" }
graphql-parser = "=0.2.3"
anyhow = "1.0.26"
thiserror = "1.0.11"

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql-actix-web"
version = "1.0.4"
version = "1.0.5"
authors = ["sunli <scott_s829@163.com>"]
edition = "2018"
description = "async-graphql for actix-web"
@ -13,7 +13,7 @@ keywords = ["futures", "async", "graphql"]
categories = ["network-programming", "asynchronous"]
[dependencies]
async-graphql = { path = "..", version = "1.9.4" }
async-graphql = { path = "..", version = "1.9.5" }
actix-web = "2.0.0"
actix-web-actors = "2.0.0"
actix = "0.9.0"

View File

@ -7,7 +7,7 @@ mod subscription;
use actix_web::dev::{Payload, PayloadStream};
use actix_web::{http, web, Error, FromRequest, HttpRequest};
use async_graphql::http::StreamBody;
use async_graphql::{IntoQueryBuilder, IntoQueryBuilderOpts, QueryBuilder};
use async_graphql::{IntoQueryBuilder, IntoQueryBuilderOpts, ParseRequestError, QueryBuilder};
use futures::channel::mpsc;
use futures::{Future, SinkExt, StreamExt, TryFutureExt};
use std::pin::Pin;
@ -56,7 +56,12 @@ impl FromRequest for GQLRequest {
(content_type, StreamBody::new(rx))
.into_query_builder_opts(&config)
.map_ok(GQLRequest)
.map_err(actix_web::error::ErrorBadRequest)
.map_err(|err| match err {
ParseRequestError::TooManyFiles | ParseRequestError::TooLarge => {
actix_web::error::ErrorPayloadTooLarge(err)
}
_ => actix_web::error::ErrorBadRequest(err),
})
.await
})
}

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql-derive"
version = "1.9.4"
version = "1.9.5"
authors = ["sunli <scott_s829@163.com>"]
edition = "2018"
description = "Macros for async-graphql"

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql-warp"
version = "1.0.5"
version = "1.0.6"
authors = ["sunli <scott_s829@163.com>"]
edition = "2018"
description = "async-graphql for warp"
@ -13,7 +13,7 @@ keywords = ["futures", "async", "graphql"]
categories = ["network-programming", "asynchronous"]
[dependencies]
async-graphql = { path = "..", version = "1.9.4" }
async-graphql = { path = "..", version = "1.9.5" }
warp = "0.2.2"
futures = "0.3.0"
bytes = "0.5.4"

View File

@ -307,7 +307,7 @@ impl From<ParseError> for Error {
#[derive(Debug, Error)]
pub enum ParseRequestError {
#[error("{0}")]
Io(std::io::Error),
Io(#[from] std::io::Error),
#[error("Invalid request: {0}")]
InvalidRequest(serde_json::Error),
@ -315,8 +315,8 @@ pub enum ParseRequestError {
#[error("Invalid files map: {0}")]
InvalidFilesMap(serde_json::Error),
#[error("Invalid multipart data: {0}")]
InvalidMultipart(std::io::Error),
#[error("Invalid multipart data")]
InvalidMultipart,
#[error("Missing \"operators\" part")]
MissingOperatorsPart,
@ -324,14 +324,17 @@ pub enum ParseRequestError {
#[error("Missing \"map\" part")]
MissingMapPart,
#[error("Failed to read part data: {0}")]
PartData(#[from] std::io::Error),
#[error("It's not an upload operation")]
NotUpload,
#[error("Missing files")]
MissingFiles,
#[error("Too many files")]
TooManyFiles,
#[error("The file size is too large")]
TooLarge,
}
#[allow(missing_docs)]

View File

@ -36,20 +36,19 @@ where
opts.max_file_size,
opts.max_num_files,
)
.await
.map_err(ParseRequestError::InvalidMultipart)?;
.await?;
let gql_request: GQLRequest = {
let part = multipart
.remove("operations")
.ok_or_else(|| ParseRequestError::MissingOperatorsPart)?;
let reader = part.create_reader().map_err(ParseRequestError::PartData)?;
let reader = part.create_reader()?;
serde_json::from_reader(reader).map_err(ParseRequestError::InvalidRequest)?
};
let mut map: HashMap<String, Vec<String>> = {
let part = multipart
.remove("map")
.ok_or_else(|| ParseRequestError::MissingMapPart)?;
let reader = part.create_reader().map_err(ParseRequestError::PartData)?;
let reader = part.create_reader()?;
serde_json::from_reader(reader).map_err(ParseRequestError::InvalidFilesMap)?
};

View File

@ -1,10 +1,11 @@
use super::token_reader::*;
use futures::io::{BufReader, ErrorKind};
use crate::ParseRequestError;
use futures::io::BufReader;
use futures::{AsyncBufRead, AsyncRead};
use http::{header::HeaderName, HeaderMap, HeaderValue};
use itertools::Itertools;
use std::fs::File;
use std::io::{Cursor, Error, Read, Result, Write};
use std::io::{Cursor, Read, Write};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use tempdir::TempDir;
@ -25,7 +26,7 @@ pub struct Part {
}
impl Part {
pub fn create_reader<'a>(&'a self) -> Result<Box<dyn Read + 'a>> {
pub fn create_reader<'a>(&'a self) -> Result<Box<dyn Read + 'a>, std::io::Error> {
let reader: Box<dyn Read> = match &self.data {
PartData::Bytes(bytes) => Box::new(Cursor::new(bytes)),
PartData::File(path) => Box::new(File::open(path)?),
@ -40,7 +41,7 @@ struct ContentDisposition {
}
impl ContentDisposition {
fn parse(value: &str) -> Result<ContentDisposition> {
fn parse(value: &str) -> Result<ContentDisposition, ParseRequestError> {
let name = regex::Regex::new("name=\"(?P<name>.*?)\"")
.unwrap()
.captures(value)
@ -65,7 +66,7 @@ impl Multipart {
temp_dir_in: Option<&Path>,
max_file_size: Option<usize>,
max_num_files: Option<usize>,
) -> Result<Multipart> {
) -> Result<Multipart, ParseRequestError> {
let mut reader = BufReader::new(reader);
let mut temp_dir = None;
let mut parts = Vec::new();
@ -120,17 +121,19 @@ impl Multipart {
parts: &mut Vec<Part>,
max_num_files: usize,
current_num_files: &mut usize,
) -> Result<()> {
) -> Result<(), ParseRequestError> {
if parts.last().unwrap().filename.is_some() {
*current_num_files += 1;
if *current_num_files > max_num_files {
return Err(Error::from(ErrorKind::InvalidData));
return Err(ParseRequestError::TooManyFiles);
}
}
Ok(())
}
async fn parse_headers<R: AsyncBufRead + Unpin>(mut reader: R) -> Result<HeaderMap> {
async fn parse_headers<R: AsyncBufRead + Unpin>(
mut reader: R,
) -> Result<HeaderMap, ParseRequestError> {
let mut buf = [0; 256];
let mut header_data = Vec::new();
let mut state = ReadUntilState::default();
@ -148,17 +151,17 @@ impl Multipart {
let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS];
header_data.extend_from_slice(b"\r\n\r\n");
let headers = match httparse::parse_headers(&header_data, &mut headers)
.map_err(|_| Error::from(ErrorKind::InvalidData))?
.map_err(|_| ParseRequestError::InvalidMultipart)?
{
httparse::Status::Complete((_, headers)) => headers,
_ => return Err(Error::from(ErrorKind::InvalidData)),
_ => return Err(ParseRequestError::InvalidMultipart),
};
let mut headers_map = HeaderMap::new();
for httparse::Header { name, value } in headers {
headers_map.insert(
HeaderName::from_str(name).map_err(|_| Error::from(ErrorKind::InvalidData))?,
HeaderValue::from_bytes(value).map_err(|_| Error::from(ErrorKind::InvalidData))?,
HeaderName::from_str(name).map_err(|_| ParseRequestError::InvalidMultipart)?,
HeaderValue::from_bytes(value).map_err(|_| ParseRequestError::InvalidMultipart)?,
);
}
@ -172,7 +175,7 @@ impl Multipart {
temp_dir_in: Option<&Path>,
max_file_size: usize,
boundary: &str,
) -> Result<Part> {
) -> Result<Part, ParseRequestError> {
let content_disposition = headers
.get(http::header::CONTENT_DISPOSITION)
.and_then(|value| value.to_str().ok())
@ -208,7 +211,7 @@ impl Multipart {
.await?;
total_size += size;
if total_size > max_file_size {
return Err(Error::from(ErrorKind::InvalidData));
return Err(ParseRequestError::TooLarge);
}
file.write_all(&buf[..size])?;
if found {
@ -264,11 +267,11 @@ mod tests {
async fn test_parse() {
let data: &[u8] = b"--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
test\r\n\
Content-Type: text/plain; charset=utf-8\r\n\r\n\
test\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
data\r\n\
Content-Type: text/plain; charset=utf-8\r\n\r\n\
data\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n";
let multipart =
Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", None, None, None)
@ -292,4 +295,91 @@ mod tests {
Some("text/plain; charset=utf-8")
);
}
#[async_std::test]
async fn test_parse_limit_file_size() {
let data: &[u8] = b"--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\n\r\n\
12345\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Type: text/plain; charset=utf-8\r\n\r\n\
data\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n";
assert!(Multipart::parse(
data,
"abbc761f78ff4d7cb7573b5a23f96ef0",
None,
Some(5),
None,
)
.await
.is_ok());
assert!(Multipart::parse(
data,
"abbc761f78ff4d7cb7573b5a23f96ef0",
None,
Some(6),
None,
)
.await
.is_ok());
assert!(Multipart::parse(
data,
"abbc761f78ff4d7cb7573b5a23f96ef0",
None,
Some(4),
None,
)
.await
.is_err());
}
#[async_std::test]
async fn test_parse_limit_num_files() {
let data: &[u8] = b"--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\n\r\n\
12345\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file1\"; filename=\"fn1.txt\"\r\n\r\n\
data\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file2\"\r\n\r\n\
data\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n";
assert!(Multipart::parse(
data,
"abbc761f78ff4d7cb7573b5a23f96ef0",
None,
None,
Some(1)
)
.await
.is_err());
assert!(Multipart::parse(
data,
"abbc761f78ff4d7cb7573b5a23f96ef0",
None,
None,
Some(2)
)
.await
.is_ok());
assert!(Multipart::parse(
data,
"abbc761f78ff4d7cb7573b5a23f96ef0",
None,
None,
Some(3)
)
.await
.is_ok());
}
}