Compare commits
No commits in common. "ee7fe2963fa7a1ccbe1a4d7ce0b173d2c9181020" and "ea15e25fe2e59f06ff4523362cb4a9e152ef0267" have entirely different histories.
ee7fe2963f
...
ea15e25fe2
|
@ -74,7 +74,7 @@ paths:
|
||||||
example: {}
|
example: {}
|
||||||
401:
|
401:
|
||||||
description: |-
|
description: |-
|
||||||
This error code is returned if one of these conditions are met:
|
This error code is returned if one of the two conditions are met:
|
||||||
|
|
||||||
1. The instance does not allow deleting files via API.
|
1. The instance does not allow deleting files via API.
|
||||||
2. The file has been uploaded from another IP, which is not this one, and the API was not authorized via an API key.
|
2. The file has been uploaded from another IP, which is not this one, and the API was not authorized via an API key.
|
||||||
|
@ -90,9 +90,6 @@ paths:
|
||||||
fid:
|
fid:
|
||||||
type: string
|
type: string
|
||||||
example: ID or name of the file. It is the NAME in file.blek.codes/uploads/NAME
|
example: ID or name of the file. It is the NAME in file.blek.codes/uploads/NAME
|
||||||
api_key:
|
|
||||||
type: string
|
|
||||||
example: '123'
|
|
||||||
/api/files/upload:
|
/api/files/upload:
|
||||||
post:
|
post:
|
||||||
summary: Upload a file
|
summary: Upload a file
|
||||||
|
@ -105,8 +102,6 @@ paths:
|
||||||
security:
|
security:
|
||||||
- apikey: [ key ]
|
- apikey: [ key ]
|
||||||
requestBody:
|
requestBody:
|
||||||
description: |-
|
|
||||||
A multipart form
|
|
||||||
content:
|
content:
|
||||||
multipart/form-data:
|
multipart/form-data:
|
||||||
schema:
|
schema:
|
||||||
|
@ -122,18 +117,7 @@ paths:
|
||||||
description: Instance-specific password needed to upload files
|
description: Instance-specific password needed to upload files
|
||||||
metadata:
|
metadata:
|
||||||
type: object
|
type: object
|
||||||
description: |-
|
description: file info
|
||||||
JSON object with file info:
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
sha512: string,
|
|
||||||
name?: string,
|
|
||||||
pass?: string
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the content type does not matter on this one.
|
|
||||||
properties:
|
properties:
|
||||||
sha512:
|
sha512:
|
||||||
type: string
|
type: string
|
||||||
|
@ -141,48 +125,17 @@ paths:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
description: Optional name of the file so it would be accessible like file.blek.codes/uploads/{name}
|
description: Optional name of the file so it would be accessible like file.blek.codes/uploads/{name}
|
||||||
pass:
|
|
||||||
type: string
|
|
||||||
description: Optional password protection for the file
|
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: File uploaded successfully
|
description: File uploaded successfully
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
status:
|
|
||||||
example: 'OK'
|
|
||||||
401:
|
401:
|
||||||
description: |-
|
description: |-
|
||||||
This error code is returned if one of the 4 conditions are met:
|
This error code is returned if one of the two conditions are met:
|
||||||
|
|
||||||
1. The instance does not allow API file uploads.
|
1. The instance does not allow API file uploads.
|
||||||
2. The instance requires API key for all API manipulations.
|
2. The instance requires API key for all API manipulations.
|
||||||
3. The provided API key is invalid.
|
3. The provided API key is invalid.
|
||||||
4. API authorization is not enabled, but a key is provided
|
4. API authorization is not enabled, but a key is provided
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
error:
|
|
||||||
example: 'APIPasswordDenied'
|
|
||||||
details:
|
|
||||||
example: 'API password authorization has been denied.'
|
|
||||||
403:
|
|
||||||
description: |-
|
|
||||||
This error code is returned if your request payload is malformed or the hash doesn't match the file
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
error:
|
|
||||||
example: 'APIError'
|
|
||||||
details:
|
|
||||||
example: 'Request payload invalid'
|
|
||||||
|
|
||||||
components:
|
components:
|
||||||
|
|
||||||
|
|
|
@ -25,18 +25,23 @@ impl FileManager {
|
||||||
|
|
||||||
async fn find_all(self: &Self, predicate: String) -> Result<Vec<File>, Box<dyn Error>> {
|
async fn find_all(self: &Self, predicate: String) -> Result<Vec<File>, Box<dyn Error>> {
|
||||||
let mut conn = self.conn.get_async_connection().await?;
|
let mut conn = self.conn.get_async_connection().await?;
|
||||||
let keys: Vec<String> = conn.keys(predicate).await?;
|
let found: Vec<String> = conn.keys(predicate).await?;
|
||||||
|
let serialized: Vec<File> =
|
||||||
let mut data: Vec<File> = vec![];
|
found.iter()
|
||||||
|
.map(|x| {
|
||||||
for key in keys.iter() {
|
let result = serde_json::from_str(&x);
|
||||||
let raw: String = conn.get(key.clone()).await?;
|
match result {
|
||||||
|
Ok(x) => Some(x),
|
||||||
let mut parsed: File = serde_json::from_str(raw.as_str())?;
|
Err(err) => {
|
||||||
data.push(parsed);
|
log::error!("Error while serializing {x}: {:?}", err);
|
||||||
}
|
None
|
||||||
|
}
|
||||||
Ok(data)
|
}
|
||||||
|
})
|
||||||
|
.filter(|x| x.is_some())
|
||||||
|
.map(|x| x.unwrap())
|
||||||
|
.collect();
|
||||||
|
Ok(serialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Filter options
|
/// Filter options
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use warp::{reply::Reply, reject::Rejection, Filter};
|
use warp::{reply::Reply, reject::Rejection, Filter};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
use self::files::{get_all::get_all_f, delete::delete_f, upload::upload_f};
|
use self::files::get_all::get_all_f;
|
||||||
|
|
||||||
use super::state::SharedState;
|
use super::state::SharedState;
|
||||||
|
|
||||||
|
@ -24,7 +24,5 @@ pub fn get_routes(state: SharedState) -> impl Filter<Extract = impl Reply, Error
|
||||||
warp::path!("api")
|
warp::path!("api")
|
||||||
.and(warp::path::end())
|
.and(warp::path::end())
|
||||||
.map(api_root)
|
.map(api_root)
|
||||||
.or(get_all_f(state.clone()))
|
.or(get_all_f(state))
|
||||||
.or(delete_f(state.clone()))
|
|
||||||
.or(upload_f(state))
|
|
||||||
}
|
}
|
|
@ -1,61 +1 @@
|
||||||
use warp::{reply::{WithStatus, Json, json}, http::StatusCode};
|
|
||||||
|
|
||||||
use crate::web::state::SharedState;
|
|
||||||
|
|
||||||
use super::types::{ErrorMessage, Error};
|
|
||||||
|
|
||||||
fn check_api_enabled(state: &SharedState) -> Result<(), WithStatus<Json>> {
|
|
||||||
if ! state.config.api.enabled {
|
|
||||||
return Err(
|
|
||||||
warp::reply::with_status(
|
|
||||||
json(&ErrorMessage::new(Error::APIDisabled)),
|
|
||||||
StatusCode::SERVICE_UNAVAILABLE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_api_pass(state: &SharedState) -> bool {
|
|
||||||
if let Some(keys) = state.config.api.apikeys.clone() {
|
|
||||||
keys.len() != 0
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_api_pass(state: &SharedState, key: String) -> Result<(), WithStatus<Json>> {
|
|
||||||
let mut valid = {
|
|
||||||
if let Some(keys) = state.config.api.apikeys.clone() {
|
|
||||||
keys.iter().find(|x| (**x) == key).is_some()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if key.len() == 0 {
|
|
||||||
valid = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if valid {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(
|
|
||||||
warp::reply::with_status(
|
|
||||||
json(&ErrorMessage::new(Error::APIPasswordDenied)),
|
|
||||||
StatusCode::FORBIDDEN
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn function_disabled_err(status: StatusCode) -> WithStatus<Json> {
|
|
||||||
warp::reply::with_status(
|
|
||||||
json(&ErrorMessage::new(Error::APIFunctionDisabled)),
|
|
||||||
status
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod get_all;
|
pub mod get_all;
|
||||||
pub mod delete;
|
|
||||||
pub mod upload;
|
|
|
@ -1,137 +0,0 @@
|
||||||
use std::net::IpAddr;
|
|
||||||
|
|
||||||
use serde_json::json;
|
|
||||||
use warp::{reply::{Reply, json, with_status}, reject::Rejection, Filter, http::StatusCode};
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use warp_real_ip::real_ip;
|
|
||||||
|
|
||||||
use crate::{web::{state::SharedState, rejection::HttpReject, api::types::{ErrorMessage, Error}}, files::File};
|
|
||||||
|
|
||||||
use super::{function_disabled_err, check_api_enabled};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
||||||
pub struct DeleteFunctionPayload {
|
|
||||||
pub fid: String,
|
|
||||||
pub api_key: Option<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn delete(state: SharedState, body: DeleteFunctionPayload, ip: Option<IpAddr>) -> Result<Box<dyn Reply>, Rejection> {
|
|
||||||
if let Err(res) = check_api_enabled(&state) {
|
|
||||||
return Ok(Box::new(res));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.config.api.delete) || (!state.config.api.enabled) {
|
|
||||||
return Ok(Box::new(function_disabled_err(StatusCode::UNAUTHORIZED)))
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut sudo_authorized = false;
|
|
||||||
let mut blocked = false;
|
|
||||||
|
|
||||||
if let Some(keys) = state.config.api.apikeys.clone() {
|
|
||||||
if let Some(key) = body.api_key {
|
|
||||||
|
|
||||||
if keys.contains(&key) {
|
|
||||||
sudo_authorized = true;
|
|
||||||
blocked = false;
|
|
||||||
} else {
|
|
||||||
sudo_authorized = false;
|
|
||||||
blocked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
sudo_authorized = false;
|
|
||||||
blocked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ! sudo_authorized {
|
|
||||||
if ip.is_none() { // need the ip if sudo is not authorized
|
|
||||||
blocked = true // to check if the file is the own file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let ip = ip.unwrap();
|
|
||||||
|
|
||||||
let id = body.fid;
|
|
||||||
let mut file = state.file_mgr.find_by_hash(id.clone())
|
|
||||||
.map_err(|x| HttpReject::StringError(x.to_string()))?;
|
|
||||||
|
|
||||||
if let None = file {
|
|
||||||
file = state.file_mgr.find_by_name(id)
|
|
||||||
.map_err(|x| HttpReject::StringError(x.to_string()))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let None = file.clone() {
|
|
||||||
return Ok(
|
|
||||||
Box::new(
|
|
||||||
warp::reply::with_status(
|
|
||||||
json(
|
|
||||||
&ErrorMessage {
|
|
||||||
error: Error::APIError,
|
|
||||||
details: Some("No file with that ID was found.".into())
|
|
||||||
}
|
|
||||||
),
|
|
||||||
StatusCode::NOT_FOUND
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let file: File = file.unwrap();
|
|
||||||
|
|
||||||
if let Some(uploader) = file.uploader_ip {
|
|
||||||
if uploader != ip && (!sudo_authorized) {
|
|
||||||
blocked = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
blocked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if blocked {
|
|
||||||
return Ok(
|
|
||||||
Box::new(
|
|
||||||
with_status(
|
|
||||||
json(
|
|
||||||
&ErrorMessage {
|
|
||||||
error: Error::APIPasswordDenied,
|
|
||||||
details: Some(
|
|
||||||
"Request has been denied for one of the following reasons: password auth did not pass, file was uploaded by someone else, the instance does not allow deleting files via the API".into()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
StatusCode::UNAUTHORIZED
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = file.delete(state).await;
|
|
||||||
if let Err(err) = res {
|
|
||||||
return Ok(
|
|
||||||
Box::new(
|
|
||||||
with_status(
|
|
||||||
json(
|
|
||||||
&ErrorMessage {
|
|
||||||
error: Error::APIError,
|
|
||||||
details: Some(format!("Couldn't delete file: {}", err))
|
|
||||||
}
|
|
||||||
),
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Box::new(json(&json!({}))))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_f(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
|
||||||
|
|
||||||
let proxy_ip = state.env.proxy_addr;
|
|
||||||
|
|
||||||
warp::path!("api" / "files" / "delete")
|
|
||||||
.map(move || state.clone())
|
|
||||||
.and(warp::body::json())
|
|
||||||
.and(real_ip(vec![proxy_ip]))
|
|
||||||
.and_then(delete)
|
|
||||||
}
|
|
|
@ -1,77 +1,35 @@
|
||||||
use std::net::IpAddr;
|
use warp::{reply::{Reply, json}, reject::Rejection, Filter, http::StatusCode};
|
||||||
|
|
||||||
use warp::{reply::{Reply, json, with_status}, reject::Rejection, Filter, http::StatusCode};
|
use crate::web::{state::SharedState, rejection::HttpReject};
|
||||||
use warp_real_ip::real_ip;
|
|
||||||
|
|
||||||
use crate::web::{state::SharedState, api::types::{ErrorMessage, Error}};
|
use super::super::types::{ErrorMessage, Error};
|
||||||
|
|
||||||
use super::{check_api_enabled, function_disabled_err};
|
pub async fn get_all(state: SharedState) -> Result<Box<dyn Reply>, Rejection> {
|
||||||
|
if ! state.config.api.enabled {
|
||||||
pub async fn get_all(state: SharedState, ip: Option<IpAddr>) -> Result<Box<dyn Reply>, Rejection> {
|
|
||||||
if let Err(res) = check_api_enabled(&state) {
|
|
||||||
return Ok(Box::new(res))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.config.api.get_all) || (!state.config.api.enabled) {
|
|
||||||
return Ok(Box::new(function_disabled_err(StatusCode::UNAUTHORIZED)))
|
|
||||||
}
|
|
||||||
|
|
||||||
let found =
|
|
||||||
state.file_mgr.get_all(true, true)
|
|
||||||
.await
|
|
||||||
.map_err(|x| x.to_string());
|
|
||||||
|
|
||||||
if let Err(err) = found {
|
|
||||||
return Ok(
|
return Ok(
|
||||||
Box::new(
|
Box::new(
|
||||||
with_status(
|
warp::reply::with_status(
|
||||||
json(
|
json(&ErrorMessage::new(Error::APIDisabled)),
|
||||||
&ErrorMessage {
|
StatusCode::SERVICE_UNAVAILABLE
|
||||||
error: Error::APIError,
|
|
||||||
details: Some(
|
|
||||||
format!("Error while getting all files: {err}")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
let mut found = found.unwrap();
|
|
||||||
|
|
||||||
if state.config.api.get_all_own_only {
|
|
||||||
found = found
|
|
||||||
.iter()
|
|
||||||
.filter(
|
|
||||||
|x| {
|
|
||||||
if let Some(owner) = x.uploader_ip {
|
|
||||||
if let Some(caller) = ip {
|
|
||||||
return owner == caller
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
).map(|x| x.clone()).collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(
|
|
||||||
Box::new(
|
|
||||||
json(
|
|
||||||
&found
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
Box::new(
|
||||||
|
json(
|
||||||
|
&state.file_mgr.get_all(true, true)
|
||||||
|
.await
|
||||||
|
.map_err(|x| x.to_string())
|
||||||
|
.map_err(|x| HttpReject::StringError(x))?
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_all_f(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
pub fn get_all_f(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||||
|
|
||||||
let proxy = state.env.proxy_addr;
|
|
||||||
|
|
||||||
warp::path!("api" / "files" / "get_all")
|
warp::path!("api" / "files" / "get_all")
|
||||||
.map(move || state.clone())
|
.map(move || state.clone())
|
||||||
.and(real_ip(vec![proxy]))
|
|
||||||
.and_then(get_all)
|
.and_then(get_all)
|
||||||
}
|
}
|
|
@ -1,266 +0,0 @@
|
||||||
use std::{collections::HashMap, net::IpAddr};
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
use serde_json::json;
|
|
||||||
use sha2::{Sha512, Digest, digest::FixedOutput};
|
|
||||||
use warp::{reply::{Reply, with_status, json}, http::StatusCode, reject::Rejection, Filter, filters::multipart::FormData};
|
|
||||||
use warp_real_ip::real_ip;
|
|
||||||
|
|
||||||
use crate::{web::{state::SharedState, forms::FormElement, api::types::{ErrorMessage, Error}}, files::{File, lookup::LookupKind}};
|
|
||||||
|
|
||||||
use super::{is_api_pass, check_api_pass};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct UploadAPIMetadata {
|
|
||||||
sha512: String,
|
|
||||||
name: Option<String>,
|
|
||||||
pass: Option<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UploadAPIPayload {
|
|
||||||
file: Vec<u8>,
|
|
||||||
file_type: String,
|
|
||||||
instance_pass: Option<String>,
|
|
||||||
metadata: UploadAPIMetadata
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for UploadAPIPayload {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
file: vec![],
|
|
||||||
file_type: "application/octet-stream".into(),
|
|
||||||
instance_pass: None,
|
|
||||||
metadata: UploadAPIMetadata {
|
|
||||||
sha512: "".into(),
|
|
||||||
name: None,
|
|
||||||
pass: None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UploadAPIPayload {
|
|
||||||
pub fn from_form(data: HashMap<String, FormElement>) -> Option<UploadAPIPayload> {
|
|
||||||
|
|
||||||
let mut out = Self::default();
|
|
||||||
|
|
||||||
let file = data.get("file");
|
|
||||||
let instance_pass = data.get("instance_pass");
|
|
||||||
let metadata = data.get("metadata");
|
|
||||||
|
|
||||||
let mut fields_set = false;
|
|
||||||
|
|
||||||
// required fields
|
|
||||||
if let Some(file) = file {
|
|
||||||
if let Some(metadata) = metadata {
|
|
||||||
if let Some(metadata) = metadata.as_atr_or_none() {
|
|
||||||
out.file = file.data.clone();
|
|
||||||
if let Ok(metadata) = serde_json::from_str(&metadata) {
|
|
||||||
out.metadata = metadata;
|
|
||||||
}
|
|
||||||
fields_set = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out.file_type = file.mime.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
// optional ones
|
|
||||||
if let Some(pass) = instance_pass {
|
|
||||||
if let Some(pass) = pass.as_atr_or_none() {
|
|
||||||
out.instance_pass = Some(pass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ! fields_set {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn upload(state: SharedState, data: FormData, ip: Option<IpAddr>) -> Result<Box<dyn Reply>, Rejection> {
|
|
||||||
|
|
||||||
if (!state.config.api.enabled) || (!state.config.api.upload) {
|
|
||||||
return Ok(
|
|
||||||
Box::new(
|
|
||||||
with_status(
|
|
||||||
json(&ErrorMessage::new(Error::APIDisabled)),
|
|
||||||
StatusCode::UNAUTHORIZED
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ! state.config.files.allow_uploads {
|
|
||||||
return Ok(
|
|
||||||
Box::new(
|
|
||||||
with_status(
|
|
||||||
json(
|
|
||||||
&ErrorMessage {
|
|
||||||
error: Error::APIDisabled,
|
|
||||||
details: Some(
|
|
||||||
match state.config.files.upload_disable_reason {
|
|
||||||
Some(reason) => format!("Uploads were disabled for the following reason: {reason}"),
|
|
||||||
None => format!("Uploads were disabled by the administrator")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
StatusCode::UNAUTHORIZED
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = FormElement::from_formdata(data)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Err(err) = data {
|
|
||||||
return Ok(
|
|
||||||
Box::new(
|
|
||||||
with_status(
|
|
||||||
json(
|
|
||||||
&ErrorMessage {
|
|
||||||
error: Error::APIError,
|
|
||||||
details: Some(
|
|
||||||
format!("Error while parsing payload: {err}")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
StatusCode::BAD_REQUEST
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = data.unwrap(); // this is guaranteed to be `Ok` at this point
|
|
||||||
|
|
||||||
let payload = UploadAPIPayload::from_form(data);
|
|
||||||
if let Some(payload) = payload {
|
|
||||||
if is_api_pass(&state) {
|
|
||||||
if let Err(res) = check_api_pass(
|
|
||||||
&state,
|
|
||||||
match payload.instance_pass {
|
|
||||||
Some(x) => x,
|
|
||||||
None => "".into()
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
return Ok(Box::new(res))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// payload is all valid and accessible at this point
|
|
||||||
|
|
||||||
let mut hash: Sha512 = Sha512::new();
|
|
||||||
hash.update(&payload.file);
|
|
||||||
|
|
||||||
let hash = hex::encode(hash.finalize_fixed());
|
|
||||||
if hash != payload.metadata.sha512 {
|
|
||||||
return Ok(
|
|
||||||
Box::new(
|
|
||||||
with_status(
|
|
||||||
json(
|
|
||||||
&ErrorMessage {
|
|
||||||
error: Error::APIError,
|
|
||||||
details: Some("Hash does not match file".into())
|
|
||||||
}
|
|
||||||
),
|
|
||||||
StatusCode::BAD_REQUEST
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let file = File::create(
|
|
||||||
payload.file,
|
|
||||||
payload.file_type,
|
|
||||||
payload.metadata.name.clone(),
|
|
||||||
state.env,
|
|
||||||
crate::files::DeleteMode::Time,
|
|
||||||
payload.metadata.pass,
|
|
||||||
ip
|
|
||||||
).await;
|
|
||||||
|
|
||||||
if let Err(err) = file {
|
|
||||||
return Ok(
|
|
||||||
Box::new(
|
|
||||||
with_status(
|
|
||||||
json(
|
|
||||||
&ErrorMessage {
|
|
||||||
error: Error::APIError,
|
|
||||||
details: Some(
|
|
||||||
format!("Error while saving the file: {err}")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let file = file.unwrap();
|
|
||||||
|
|
||||||
let saved = state.file_mgr.save(
|
|
||||||
&file,
|
|
||||||
match payload.metadata.name {
|
|
||||||
Some(_) => LookupKind::ByName,
|
|
||||||
None => LookupKind::ByHash
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Err(err) = saved {
|
|
||||||
return Ok(
|
|
||||||
Box::new(
|
|
||||||
with_status(
|
|
||||||
json(
|
|
||||||
&ErrorMessage {
|
|
||||||
error: Error::APIError,
|
|
||||||
details: Some(
|
|
||||||
format!("Error while saving the file: {err}")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(
|
|
||||||
Box::new(
|
|
||||||
json(
|
|
||||||
&json!({
|
|
||||||
"status": "OK"
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(
|
|
||||||
Box::new(
|
|
||||||
with_status(
|
|
||||||
json(&ErrorMessage {
|
|
||||||
error: Error::APIError,
|
|
||||||
details: Some("Request payload invalid".into())
|
|
||||||
}),
|
|
||||||
StatusCode::BAD_REQUEST
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn upload_f(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
|
||||||
|
|
||||||
let proxy = state.env.proxy_addr.clone();
|
|
||||||
|
|
||||||
warp::path!("api" / "files" / "upload")
|
|
||||||
.and(warp::post())
|
|
||||||
.map(move || state.clone())
|
|
||||||
.and(warp::multipart::form())
|
|
||||||
.and(real_ip(vec![proxy]))
|
|
||||||
.and_then(upload)
|
|
||||||
}
|
|
|
@ -3,9 +3,6 @@ use serde::{Serialize, Deserialize};
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
APIDisabled,
|
APIDisabled,
|
||||||
APIFunctionDisabled,
|
|
||||||
APIError,
|
|
||||||
APIPasswordDenied
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
@ -18,10 +15,7 @@ impl ErrorMessage {
|
||||||
pub fn new(error: Error) -> ErrorMessage {
|
pub fn new(error: Error) -> ErrorMessage {
|
||||||
ErrorMessage {
|
ErrorMessage {
|
||||||
details: match error {
|
details: match error {
|
||||||
Error::APIDisabled => Some("API is disabled by the administrator. Please contact them for further details".into()),
|
Error::APIDisabled => Some("API is disabled by the administrator. Please contact them for further details".into())
|
||||||
Error::APIFunctionDisabled => Some("This API function is disabled by the administrator. Please contact them for further details.".into()),
|
|
||||||
Error::APIError => Some("An error has occured while executing the API request".into()),
|
|
||||||
Error::APIPasswordDenied => Some("API password authorization has been denied.".into())
|
|
||||||
},
|
},
|
||||||
error,
|
error,
|
||||||
}
|
}
|
||||||
|
|
|
@ -307,8 +307,7 @@ pub async fn upload(form: FormData, ip: Option<IpAddr>, state: SharedState) -> R
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_routes(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
pub fn get_routes(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||||
warp::path("upload")
|
warp::post()
|
||||||
.and(warp::post())
|
|
||||||
.and(warp::multipart::form())
|
.and(warp::multipart::form())
|
||||||
.and(real_ip(vec![state.env.proxy_addr]))
|
.and(real_ip(vec![state.env.proxy_addr]))
|
||||||
.and(
|
.and(
|
||||||
|
|
|
@ -20,10 +20,10 @@ use state::SharedState;
|
||||||
pub fn routes(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
pub fn routes(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||||
static_dir!("static")
|
static_dir!("static")
|
||||||
.or(curlapi::get_routes(state.clone()))
|
.or(curlapi::get_routes(state.clone()))
|
||||||
|
.or(pages::get_routes(state.clone()))
|
||||||
.or(forms::get_routes(state.clone()))
|
.or(forms::get_routes(state.clone()))
|
||||||
.or(api::get_routes(state.clone()))
|
.or(api::get_routes(state.clone()))
|
||||||
.or(uploaded::get_uploaded(state.clone()))
|
.or(uploaded::get_uploaded(state))
|
||||||
.or(pages::get_routes(state))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
Loading…
Reference in New Issue