v1.9.5
This commit is contained in:
parent
d603ee2700
commit
db32d2a071
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
15
src/error.rs
15
src/error.rs
|
@ -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)]
|
||||
|
|
|
@ -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)?
|
||||
};
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user