commit 1e5b5812de10eff07fcfd1961b84398c3a267224
Author: Marcel <mtrnord1@gmail.com>
Date: Sun, 7 Jun 2020 19:07:01 +0200
Initial Commit
Took 1 hour 42 minutes
Diffstat:
11 files changed, 382 insertions(+), 0 deletions(-)
diff --git a/.idea/.gitignore b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/misc.xml b/.idea/misc.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectRootManager">
+ <output url="file://$PROJECT_DIR$/out" />
+ </component>
+</project>
+\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/mxctest.iml" filepath="$PROJECT_DIR$/mxctest.iml" />
+ </modules>
+ </component>
+</project>
+\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="Git" />
+ </component>
+</project>
+\ No newline at end of file
diff --git a/Rocket.toml b/Rocket.toml
@@ -0,0 +1,7 @@
+[production]
+address = "localhost"
+port = 8448
+
+[development]
+address = "localhost"
+port = 8448
diff --git a/mxctest.iml b/mxctest.iml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="RUST_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ <excludeFolder url="file://$MODULE_DIR$/target" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
+\ No newline at end of file
diff --git a/src/data/mod.rs b/src/data/mod.rs
@@ -0,0 +1,31 @@
+use crate::error::Result;
+use crate::tests::Tests;
+use ruma::identifiers::UserId;
+use std::convert::TryFrom;
+use std::sync::Mutex;
+
+#[derive(Debug, Default)]
+pub struct Data {
+ pub users: Users,
+ pub current_test: Mutex<Tests>,
+}
+
+impl Data {
+ pub fn set_current_test(&self, next_test: Tests) {
+ let mut state = self.current_test.lock().expect("Could not lock mutex");
+ *state = next_test;
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct Users;
+
+impl Users {
+ pub fn find_from_token(&self, _token: &str) -> Result<Option<(UserId, String)>> {
+ // TODO run tests
+ Ok(Some((
+ UserId::try_from("@carl:example.com").unwrap(),
+ "KCZFUCGSLZ".to_string(),
+ )))
+ }
+}
diff --git a/src/error.rs b/src/error.rs
@@ -0,0 +1,36 @@
+use std::io;
+use thiserror::Error;
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Error, Debug)]
+pub enum Error {
+ #[error("tried to parse invalid string")]
+ StringFromBytesError {
+ #[from]
+ source: std::string::FromUtf8Error,
+ },
+ #[error("tried to parse invalid identifier")]
+ SerdeJsonError {
+ #[from]
+ source: serde_json::Error,
+ },
+ #[error("tried to parse invalid identifier")]
+ RumaIdentifierError {
+ #[from]
+ source: ruma::identifiers::Error,
+ },
+ #[error("tried to parse invalid event")]
+ RumaEventError {
+ #[from]
+ source: ruma::events::InvalidEvent,
+ },
+ #[error("bad request")]
+ BadRequest(&'static str),
+}
+
+#[derive(Error, Debug)]
+pub enum TestErrors {
+ #[error(transparent)]
+ Io(#[from] io::Error),
+}
diff --git a/src/ruma_wrapper.rs b/src/ruma_wrapper.rs
@@ -0,0 +1,179 @@
+/// From https://git.koesters.xyz/timo/conduit/src/commit/88d091fca1c1db9f238c7a42467f63a619201f8c/src/ruma_wrapper.rs
+use crate::utils;
+use log::warn;
+use rocket::{
+ data::{Data, FromData, FromDataFuture, Transform, TransformFuture, Transformed},
+ http::Status,
+ response::{self, Responder},
+ Outcome::*,
+ Request, State,
+};
+use ruma::{api::Endpoint, identifiers::UserId};
+use std::{convert::TryInto, io::Cursor, ops::Deref};
+use tokio::io::AsyncReadExt;
+
+const MESSAGE_LIMIT: u64 = 20 * 1024 * 1024; // 20 MB
+
+/// This struct converts rocket requests into ruma structs by converting them into http requests
+/// first.
+pub struct Ruma<T> {
+ pub body: T,
+ pub user_id: Option<UserId>,
+ pub device_id: Option<String>,
+ pub json_body: Option<Box<serde_json::value::RawValue>>, // This is None when body is not a valid string
+}
+
+impl<'a, T: Endpoint> FromData<'a> for Ruma<T> {
+ type Error = (); // TODO: Better error handling
+ type Owned = Data;
+ type Borrowed = Self::Owned;
+
+ fn transform<'r>(
+ _req: &'r Request<'_>,
+ data: Data,
+ ) -> TransformFuture<'r, Self::Owned, Self::Error> {
+ Box::pin(async move { Transform::Owned(Success(data)) })
+ }
+
+ fn from_data(
+ request: &'a Request<'_>,
+ outcome: Transformed<'a, Self>,
+ ) -> FromDataFuture<'a, Self, Self::Error> {
+ Box::pin(async move {
+ let data = rocket::try_outcome!(outcome.owned());
+
+ let (user_id, device_id) = if T::METADATA.requires_authentication {
+ let db = request
+ .guard::<State<'_, crate::data::Data>>()
+ .await
+ .unwrap();
+
+ // Get token from header or query value
+ let token = match request
+ .headers()
+ .get_one("Authorization")
+ .map(|s| s[7..].to_owned()) // Split off "Bearer "
+ .or_else(|| request.get_query_value("access_token").and_then(|r| r.ok()))
+ {
+ // TODO: M_MISSING_TOKEN
+ None => return Failure((Status::Unauthorized, ())),
+ Some(token) => token,
+ };
+
+ // Check if token is valid
+ match db.users.find_from_token(&token).unwrap() {
+ // TODO: M_UNKNOWN_TOKEN
+ None => return Failure((Status::Unauthorized, ())),
+ Some((user_id, device_id)) => (Some(user_id), Some(device_id)),
+ }
+ } else {
+ (None, None)
+ };
+
+ let mut http_request = http::Request::builder()
+ .uri(request.uri().to_string())
+ .method(&*request.method().to_string());
+ for header in request.headers().iter() {
+ http_request = http_request.header(header.name.as_str(), &*header.value);
+ }
+
+ let mut handle = data.open().take(MESSAGE_LIMIT);
+ let mut body = Vec::new();
+ handle.read_to_end(&mut body).await.unwrap();
+
+ let http_request = http_request.body(body.clone()).unwrap();
+ log::info!("{:?}", http_request);
+
+ match T::try_from(http_request) {
+ Ok(t) => Success(Ruma {
+ body: t,
+ user_id,
+ device_id,
+ // TODO: Can we avoid parsing it again? (We only need this for append_pdu)
+ json_body: utils::string_from_bytes(&body)
+ .ok()
+ .and_then(|s| serde_json::value::RawValue::from_string(s).ok()),
+ }),
+ Err(e) => {
+ warn!("{:?}", e);
+ Failure((Status::BadRequest, ()))
+ }
+ }
+ })
+ }
+}
+
+impl<T> Deref for Ruma<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.body
+ }
+}
+
+/// This struct converts ruma responses into rocket http responses.
+pub struct MatrixResult<T, E = ruma::api::client::Error>(pub std::result::Result<T, E>);
+
+impl<T, E> TryInto<http::Response<Vec<u8>>> for MatrixResult<T, E>
+where
+ T: TryInto<http::Response<Vec<u8>>>,
+ E: Into<http::Response<Vec<u8>>>,
+{
+ type Error = T::Error;
+
+ fn try_into(self) -> Result<http::Response<Vec<u8>>, T::Error> {
+ match self.0 {
+ Ok(t) => t.try_into(),
+ Err(e) => Ok(e.into()),
+ }
+ }
+}
+
+#[rocket::async_trait]
+impl<'r, T, E> Responder<'r> for MatrixResult<T, E>
+where
+ T: Send + TryInto<http::Response<Vec<u8>>>,
+ T::Error: Send,
+ E: Into<http::Response<Vec<u8>>> + Send,
+{
+ async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
+ let http_response: Result<http::Response<_>, _> = self.try_into();
+ match http_response {
+ Ok(http_response) => {
+ let mut response = rocket::response::Response::build();
+
+ let status = http_response.status();
+ response.raw_status(status.into(), "");
+
+ for header in http_response.headers() {
+ response
+ .raw_header(header.0.to_string(), header.1.to_str().unwrap().to_owned());
+ }
+
+ response
+ .sized_body(Cursor::new(http_response.into_body()))
+ .await;
+
+ response.raw_header("Access-Control-Allow-Origin", "*");
+ response.raw_header(
+ "Access-Control-Allow-Methods",
+ "GET, POST, PUT, DELETE, OPTIONS",
+ );
+ response.raw_header(
+ "Access-Control-Allow-Headers",
+ "Origin, X-Requested-With, Content-Type, Accept, Authorization",
+ );
+ response.ok()
+ }
+ Err(_) => Err(Status::InternalServerError),
+ }
+ }
+}
+
+impl<T, E> Deref for MatrixResult<T, E> {
+ type Target = Result<T, E>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
diff --git a/src/tests.rs b/src/tests.rs
@@ -0,0 +1,79 @@
+use crate::error::TestErrors;
+use rocket::data::{FromData, FromDataFuture, Transform, TransformFuture, Transformed};
+use rocket::http::ContentType;
+use rocket::http::Status;
+use rocket::response::Responder;
+use rocket::response::{self, Response};
+use rocket::{Data, Outcome::*, Request};
+use serde::{Deserialize, Serialize};
+use std::io::Cursor;
+use tokio::io::AsyncReadExt;
+
+#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
+#[serde(tag = "test")]
+#[serde(rename_all = "snake_case")]
+pub enum Tests {
+ Start,
+ SyncTimeout,
+}
+
+impl Default for Tests {
+ fn default() -> Self {
+ Tests::Start
+ }
+}
+
+impl<'a> FromData<'a> for Tests {
+ type Error = TestErrors;
+ type Owned = String;
+ type Borrowed = str;
+
+ fn transform<'r>(
+ request: &'r Request<'_>,
+ data: Data,
+ ) -> TransformFuture<'r, Self::Owned, Self::Error> {
+ Box::pin(async move {
+ let mut stream = data.open();
+ let mut string = String::new();
+ let outcome = match stream.read_to_string(&mut string).await {
+ Ok(_) => Success(string),
+ Err(e) => Failure((Status::InternalServerError, TestErrors::Io(e))),
+ };
+
+ // Returning `Borrowed` here means we get `Borrowed` in `from_data`.
+ Transform::Borrowed(outcome)
+ })
+ }
+
+ fn from_data(
+ request: &'a Request<'_>,
+ outcome: Transformed<'a, Self>,
+ ) -> FromDataFuture<'a, Self, Self::Error> {
+ Box::pin(async move {
+ // Retrieve a borrow to the now transformed `String` (an &str). This
+ // is only correct because we know we _always_ return a `Borrowed` from
+ // `transform` above.
+ let data = try_outcome!(outcome.borrowed());
+
+ let test_enum: Tests = serde_json::from_str(data).unwrap();
+ Success(test_enum)
+ })
+ }
+}
+
+/// This struct converts test responses into rocket http responses.
+pub struct TestsResult(pub std::result::Result<Tests, TestErrors>);
+
+#[rocket::async_trait]
+impl<'r> Responder<'r> for TestsResult {
+ async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
+ match self.0 {
+ Ok(v) => Response::build()
+ .header(ContentType::JSON)
+ .sized_body(Cursor::new(serde_json::to_string(&v).unwrap()))
+ .await
+ .ok(),
+ Err(v) => Err(Status::InternalServerError),
+ }
+ }
+}
diff --git a/src/utils.rs b/src/utils.rs
@@ -0,0 +1,6 @@
+use crate::error::Result;
+
+/// Parses the bytes into a string.
+pub fn string_from_bytes(bytes: &[u8]) -> Result<String> {
+ Ok(String::from_utf8(bytes.to_vec())?)
+}