Telodendria/src/Matrix.c

362 lines
12 KiB
C
Raw Normal View History

2022-08-28 19:55:48 +00:00
/*
2022-12-26 15:52:52 +00:00
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
2022-08-28 19:55:48 +00:00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Matrix.h>
#include <string.h>
#include <ctype.h>
Use `Makefile`s instead of a custom script (#38) This pull request also requires the use of the external [Cytoplasm](/Telodendria/Cytoplasm) repository by removing the in-tree copy of Cytoplasm. The increased modularity requires a little more complex build process, but is overall better. Closes #19 The appropriate documentation has been updated. Closes #18 --- Please review the developer certificate of origin: 1. The contribution was created in whole or in part by me, and I have the right to submit it under the open source licenses of the Telodendria project; or 1. The contribution is based upon a previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the Telodendria project license; or 1. The contribution was provided directly to me by some other person who certified (1), (2), or (3), and I have not modified it. 1. I understand and agree that this project and the contribution are made public and that a record of the contribution&mdash;including all personal information I submit with it&mdash;is maintained indefinitely and may be redistributed consistent with this project or the open source licenses involved. - [x] I have read the Telodendria Project development certificate of origin, and I certify that I have permission to submit this patch under the conditions specified in it. Reviewed-on: https://git.telodendria.io/Telodendria/Telodendria/pulls/38
2023-11-01 16:27:45 +00:00
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/HttpServer.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Str.h>
2022-08-28 19:55:48 +00:00
Use `Makefile`s instead of a custom script (#38) This pull request also requires the use of the external [Cytoplasm](/Telodendria/Cytoplasm) repository by removing the in-tree copy of Cytoplasm. The increased modularity requires a little more complex build process, but is overall better. Closes #19 The appropriate documentation has been updated. Closes #18 --- Please review the developer certificate of origin: 1. The contribution was created in whole or in part by me, and I have the right to submit it under the open source licenses of the Telodendria project; or 1. The contribution is based upon a previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the Telodendria project license; or 1. The contribution was provided directly to me by some other person who certified (1), (2), or (3), and I have not modified it. 1. I understand and agree that this project and the contribution are made public and that a record of the contribution&mdash;including all personal information I submit with it&mdash;is maintained indefinitely and may be redistributed consistent with this project or the open source licenses involved. - [x] I have read the Telodendria Project development certificate of origin, and I certify that I have permission to submit this patch under the conditions specified in it. Reviewed-on: https://git.telodendria.io/Telodendria/Telodendria/pulls/38
2023-11-01 16:27:45 +00:00
#include <Cytoplasm/HttpRouter.h>
#include <Routes.h>
2022-08-28 19:55:48 +00:00
void
MatrixHttpHandler(HttpServerContext * context, void *argp)
{
MatrixHttpHandlerArgs *args = (MatrixHttpHandlerArgs *) argp;
Stream *stream;
HashMap *response = NULL;
2022-08-28 19:55:48 +00:00
char *requestPath;
RouteArgs routeArgs;
2022-09-23 12:06:24 +00:00
requestPath = HttpRequestPath(context);
stream = HttpServerStream(context);
2022-09-23 12:06:24 +00:00
Log(LOG_DEBUG, "%s %s",
HttpRequestMethodToString(HttpRequestMethodGet(context)),
requestPath);
2022-08-28 19:55:48 +00:00
HttpResponseStatus(context, HTTP_OK);
HttpResponseHeader(context, "Server", "Telodendria/" TELODENDRIA_VERSION);
2022-08-28 19:55:48 +00:00
2022-09-14 21:15:05 +00:00
/* CORS */
HttpResponseHeader(context, "Access-Control-Allow-Origin", "*");
HttpResponseHeader(context, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
HttpResponseHeader(context, "Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization");
HttpResponseHeader(context, "Connection", "close");
2022-09-14 21:15:05 +00:00
/*
* Web Browser Clients: Servers MUST expect that clients will approach them
* with OPTIONS requests... the server MUST NOT perform any logic defined
* for the endpoints when approached with an OPTIONS request.
*/
if (HttpRequestMethodGet(context) == HTTP_OPTIONS)
{
HttpResponseStatus(context, HTTP_NO_CONTENT);
HttpSendHeaders(context);
return;
2022-09-14 21:15:05 +00:00
}
routeArgs.matrixArgs = args;
routeArgs.context = context;
if (!HttpRouterRoute(args->router, requestPath, &routeArgs, (void **) &response))
{
HttpResponseHeader(context, "Content-Type", "application/json");
HttpResponseStatus(context, HTTP_NOT_FOUND);
response = MatrixErrorCreate(M_NOT_FOUND, NULL);
}
/*
* If the route handler returned a JSON object, take care
* of sending it here.
*
* Otherwise, if the route handler returned NULL, assume
* that it sent its own headers and and body.
*/
if (response)
{
char *contentLen = StrInt(JsonEncode(response, NULL, JSON_DEFAULT));
HttpResponseHeader(context, "Content-Type", "application/json");
HttpResponseHeader(context, "Content-Length", contentLen);
HttpSendHeaders(context);
Free(contentLen);
JsonEncode(response, stream, JSON_DEFAULT);
JsonFree(response);
StreamPrintf(stream, "\n");
}
Log(LOG_INFO, "%s %s (%d %s)",
HttpRequestMethodToString(HttpRequestMethodGet(context)),
requestPath,
HttpResponseStatusGet(context),
HttpStatusToString(HttpResponseStatusGet(context)));
2022-08-28 19:55:48 +00:00
}
2022-09-16 15:27:34 +00:00
HashMap *
MatrixErrorCreate(MatrixError errorArg, char *msg)
2022-09-16 15:27:34 +00:00
{
HashMap *errorObj;
char *errcode;
char *error;
switch (errorArg)
{
case M_FORBIDDEN:
errcode = "M_FORBIDDEN";
error = "Forbidden access. Bad permissions or not authenticated.";
break;
case M_UNKNOWN_TOKEN:
errcode = "M_UNKNOWN_TOKEN";
error = "The access or refresh token specified was not recognized.";
break;
case M_MISSING_TOKEN:
errcode = "M_MISSING_TOKEN";
error = "No access token was specified for the request.";
break;
case M_BAD_JSON:
errcode = "M_BAD_JSON";
error = "Request contained valid JSON, but it was malformed in some way.";
break;
case M_NOT_JSON:
errcode = "M_NOT_JSON";
error = "Request did not contain valid JSON.";
break;
case M_NOT_FOUND:
errcode = "M_NOT_FOUND";
error = "No resource was found for this request.";
break;
case M_LIMIT_EXCEEDED:
errcode = "M_LIMIT_EXCEEDED";
error = "Too many requests have been sent in a short period of time. "
"Wait a while then try again.";
break;
case M_UNKNOWN:
errcode = "M_UNKNOWN";
error = "An unknown error has occurred.";
break;
case M_UNRECOGNIZED:
errcode = "M_UNRECOGNIZED";
error = "The server did not understand the request.";
break;
case M_UNAUTHORIZED:
errcode = "M_UNAUTHORIZED";
error = "The request was not correctly authorized.";
break;
case M_USER_DEACTIVATED:
errcode = "M_USER_DEACTIVATED";
error = "The user ID assocated with the request has been deactivated.";
break;
case M_USER_IN_USE:
errcode = "M_USER_IN_USE";
error = "The user ID specified has already been taken.";
break;
case M_INVALID_USERNAME:
errcode = "M_INVALID_USERNAME";
error = "The user ID specified is not valid.";
break;
case M_ROOM_IN_USE:
errcode = "M_ROOM_IN_USE";
error = "The room alias given is already in use.";
break;
case M_INVALID_ROOM_STATE:
errcode = "M_INVALID_ROOM_STATE";
error = "The initial room state is invalid.";
break;
case M_THREEPID_IN_USE:
errcode = "M_THREEPID_IN_USE";
error = "The given threepid cannot be used because the same threepid is already in use.";
break;
case M_THREEPID_NOT_FOUND:
errcode = "M_THREEPID_NOT_FOUND";
error = "The given threepid cannot be used because no record matching the threepid "
"was found.";
break;
case M_THREEPID_AUTH_FAILED:
errcode = "M_THREEPID_AUTH_FAILED";
error = "Authentication could not be performed on the third party identifier.";
break;
case M_THREEPID_DENIED:
errcode = "M_THREEPID_DENIED";
error = "The server does not permit this third party identifier.";
break;
case M_SERVER_NOT_TRUSTED:
errcode = "M_SERVER_NOT_TRUSTED";
error = "The request used a third party server that this server does not trust.";
break;
case M_UNSUPPORTED_ROOM_VERSION:
errcode = "M_UNSUPPORTED_ROOM_VERSION";
error = "The request to create a room used a room version that the server "
"does not support.";
break;
case M_INCOMPATIBLE_ROOM_VERSION:
errcode = "M_INCOMPATIBLE_ROOM_VERSION";
error = "Attempted to join a room that has a version the server does not support.";
break;
case M_BAD_STATE:
errcode = "M_BAD_STATE";
error = "The state change requested cannot be performed.";
break;
case M_GUEST_ACCESS_FORBIDDEN:
errcode = "M_GUEST_ACCESS_FORBIDDEN";
error = "The room or resource does not permit guests to access it.";
break;
case M_CAPTCHA_NEEDED:
errcode = "M_CAPTCHA_NEEDED";
error = "A Captcha is required to complete the request.";
break;
case M_CAPTCHA_INVALID:
errcode = "M_CAPTCHA_INVALID";
error = "The Captcha provided did not match what was expected.";
break;
case M_MISSING_PARAM:
errcode = "M_MISSING_PARAM";
error = "A required parameter was missing from the request.";
break;
case M_INVALID_PARAM:
errcode = "M_INVALID_PARAM";
error = "A required parameter was invalid in some way.";
break;
2022-09-16 15:27:34 +00:00
case M_TOO_LARGE:
errcode = "M_TOO_LARGE";
error = "The request or entity was too large.";
break;
case M_EXCLUSIVE:
errcode = "M_EXCLUSIVE";
error = "The resource being requested is reserved by an application service, "
"or the application service making the request has not created the resource.";
break;
case M_RESOURCE_LIMIT_EXCEEDED:
errcode = "M_RESOURCE_LIMIT_EXCEEDED";
error = "The request cannot be completed because the homeserver has reached "
"a resource limit imposed on it.";
break;
case M_CANNOT_LEAVE_SERVER_NOTICE_ROOM:
errcode = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM";
error = "The user is unable to reject an invite to join the server notices room.";
break;
default:
return NULL;
}
if (msg)
{
error = msg;
}
2022-09-16 15:27:34 +00:00
errorObj = HashMapCreate();
if (!errorObj)
{
return NULL;
}
HashMapSet(errorObj, "errcode", JsonValueString(errcode));
HashMapSet(errorObj, "error", JsonValueString(error));
2022-09-16 15:27:34 +00:00
return errorObj;
}
HashMap *
MatrixGetAccessToken(HttpServerContext * context, char **accessToken)
{
HashMap *params;
char *token;
params = HttpRequestHeaders(context);
token = HashMapGet(params, "authorization");
if (token)
{
/* If the header was provided but it's not given correctly,
* that's an error */
if (strncmp(token, "Bearer ", 7) != 0)
{
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
return MatrixErrorCreate(M_MISSING_TOKEN, NULL);
}
/* Seek past "Bearer" */
token += 7;
/* Seek past any spaces between "Bearer" and the token */
while (*token && isspace((unsigned char) *token))
{
token++;
}
}
else
{
/* Header was not provided, we must check for ?access_token */
params = HttpRequestParams(context);
token = HashMapGet(params, "access_token");
if (!token)
{
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
return MatrixErrorCreate(M_MISSING_TOKEN, NULL);
}
}
*accessToken = token;
return NULL;
}
HashMap *
2022-12-19 21:54:01 +00:00
MatrixRateLimit(HttpServerContext * context, Db * db)
{
/* TODO: Implement rate limiting */
(void) context;
(void) db;
return NULL;
}
HashMap *
MatrixClientWellKnown(char *base, char *identity)
{
HashMap *response;
HashMap *homeserver;
if (!base)
{
return NULL;
}
response = HashMapCreate();
homeserver = HashMapCreate();
HashMapSet(homeserver, "base_url", JsonValueString(base));
HashMapSet(response, "m.homeserver", JsonValueObject(homeserver));
if (identity)
{
HashMap *identityServer = HashMapCreate();
HashMapSet(identityServer, "base_url", JsonValueString(identity));
HashMapSet(response, "m.identity_server", JsonValueObject(identityServer));
}
return response;
}