/* * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> * * 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 dirRequest.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 <Routes.h> #include <Cytoplasm/Array.h> #include <Cytoplasm/HashMap.h> #include <Cytoplasm/Json.h> #include <Cytoplasm/Str.h> #include <Cytoplasm/Memory.h> #include <Cytoplasm/Db.h> #include <Schema/UserDirectoryRequest.h> #include <User.h> ROUTE_IMPL(RouteUserDirectory, path, argp) { RouteArgs *args = argp; HashMap *response = NULL; HashMap *request = NULL; Array *users = NULL; Array *results = NULL; Db *db = args->matrixArgs->db; Config config = { .ok = 0 }; User *user = NULL; char *token = NULL; char *requesterName = NULL; char *msg = NULL; UserDirectoryRequest dirRequest; size_t i, included; (void) path; dirRequest.search_term = NULL; dirRequest.limit = 10; if (HttpRequestMethodGet(args->context) != HTTP_POST) { msg = "Request supports only POST."; HttpResponseStatus(args->context, HTTP_BAD_REQUEST); response = MatrixErrorCreate(M_UNRECOGNIZED, msg); goto finish; } request = JsonDecode(HttpServerStream(args->context)); if (!request) { HttpResponseStatus(args->context, HTTP_BAD_REQUEST); response = MatrixErrorCreate(M_NOT_JSON, NULL); goto finish; } if (!UserDirectoryRequestFromJson(request, &dirRequest, &msg)) { HttpResponseStatus(args->context, HTTP_BAD_REQUEST); response = MatrixErrorCreate(M_BAD_JSON, msg); goto finish; } if (!dirRequest.search_term) { msg = "Field 'search_term' not set."; HttpResponseStatus(args->context, HTTP_BAD_REQUEST); response = MatrixErrorCreate(M_BAD_JSON, msg); goto finish; } response = MatrixGetAccessToken(args->context, &token); if (response) { return response; } /* TODO: Actually use information related to the user. */ user = UserAuthenticate(db, token); if (!user) { HttpResponseStatus(args->context, HTTP_BAD_REQUEST); response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL); goto finish; } requesterName = UserGetName(user); response = HashMapCreate(); results = ArrayCreate(); /* TODO: Check for users matching search term and users outside our * local server. */ users = DbList(db, 1, "users"); ConfigLock(db, &config); if (!config.ok) { Log(LOG_ERR, "Directory endpoint failed to lock configuration."); HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); response = MatrixErrorCreate(M_UNKNOWN, config.err); goto finish; } #define IncludedLtLimit ((int64_t) included < dirRequest.limit) for (i = 0, included = 0; i < ArraySize(users) && IncludedLtLimit; i++) #undef IncludedLtLimit { HashMap *obj; User *currentUser; char *name = ArrayGet(users, i); char *displayName; char *lowerDisplayName; char *avatarUrl; if (!StrEquals(name, requesterName)) { currentUser = UserLock(db, name); } else { currentUser = user; } displayName = UserGetProfile(currentUser, "displayname"); lowerDisplayName = StrLower(displayName); avatarUrl = UserGetProfile(currentUser, "avatar_url"); /* Check for the user ID and display name. */ if (strstr(name, dirRequest.search_term) || (lowerDisplayName && strstr(lowerDisplayName, dirRequest.search_term))) { included++; obj = HashMapCreate(); if (displayName) { JsonSet(obj, JsonValueString(displayName), 1, "display_name"); } if (avatarUrl) { JsonSet(obj, JsonValueString(displayName), 1, "avatar_url"); } if (name) { char *uID = StrConcat(4, "@", name, ":", config.serverName); JsonSet(obj, JsonValueString(uID), 1, "user_id"); Free(uID); } ArrayAdd(results, JsonValueObject(obj)); } if (lowerDisplayName) { Free(lowerDisplayName); } if (!StrEquals(name, requesterName)) { UserUnlock(currentUser); } } JsonSet(response, JsonValueArray(results), 1, "results"); JsonSet(response, JsonValueBoolean((int64_t) included == dirRequest.limit), 1, "limited" ); finish: UserUnlock(user); JsonFree(request); DbListFree(users); ConfigUnlock(&config); UserDirectoryRequestFree(&dirRequest); return response; }