From a3d8e1eb864e11dbb55c063c4131a0f1caadc966 Mon Sep 17 00:00:00 2001
From: Nicolas Werner <nicolas.werner@hotmail.de>
Date: Thu, 27 Jun 2019 13:30:28 +0200
Subject: [PATCH] Add .well-known support

---
 CMakeLists.txt                       |  3 ++-
 include/mtx/responses.hpp            |  1 +
 include/mtx/responses/login.hpp      |  9 +++++++-
 include/mtx/responses/well-known.hpp | 31 ++++++++++++++++++++++++++++
 include/mtxclient/http/client.hpp    | 12 ++++++++---
 include/mtxclient/http/session.hpp   |  7 ++++---
 lib/http/client.cpp                  | 13 ++++++++++++
 lib/structs/responses/login.cpp      |  3 +++
 lib/structs/responses/well-known.cpp | 26 +++++++++++++++++++++++
 tests/responses.cpp                  | 31 +++++++++++++++++++++++++++-
 10 files changed, 127 insertions(+), 9 deletions(-)
 create mode 100644 include/mtx/responses/well-known.hpp
 create mode 100644 lib/structs/responses/well-known.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 554bcbe13..a02e13938 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -153,7 +153,8 @@ add_library(matrix_client
             lib/structs/responses/profile.cpp
             lib/structs/responses/register.cpp
             lib/structs/responses/sync.cpp
-            lib/structs/responses/version.cpp)
+            lib/structs/responses/version.cpp
+            lib/structs/responses/well-known.cpp)
 add_library(MatrixClient::MatrixClient ALIAS matrix_client)
 target_include_directories(matrix_client
                            SYSTEM
diff --git a/include/mtx/responses.hpp b/include/mtx/responses.hpp
index 120915d52..0f62b848f 100644
--- a/include/mtx/responses.hpp
+++ b/include/mtx/responses.hpp
@@ -14,3 +14,4 @@
 #include "responses/register.hpp"
 #include "responses/sync.hpp"
 #include "responses/version.hpp"
+#include "responses/well-known.hpp"
diff --git a/include/mtx/responses/login.hpp b/include/mtx/responses/login.hpp
index 5aaf4e7d7..f2c54bd72 100644
--- a/include/mtx/responses/login.hpp
+++ b/include/mtx/responses/login.hpp
@@ -5,6 +5,7 @@
 #include <nlohmann/json.hpp>
 
 #include "mtx/identifiers.hpp"
+#include "well-known.hpp"
 
 namespace mtx {
 namespace responses {
@@ -18,10 +19,16 @@ struct Login
         //! This access token can then be used to authorize other requests.
         std::string access_token;
         //! The hostname of the homeserver on which the account has been registered.
-        std::string home_server;
+        [[deprecated("Clients should extract the server_name from user_id (by splitting at the "
+                     "first colon) if they require it.")]] std::string home_server;
         //! ID of the logged-in device.
         //! Will be the same as the corresponding parameter in the request, if one was specified.
         std::string device_id;
+
+        //! Optional client configuration provided by the server.
+        //! If present, clients SHOULD use the provided object to reconfigure themselves,
+        //! optionally validating the URLs within.
+        boost::optional<WellKnown> well_known;
 };
 
 void
diff --git a/include/mtx/responses/well-known.hpp b/include/mtx/responses/well-known.hpp
new file mode 100644
index 000000000..792c00679
--- /dev/null
+++ b/include/mtx/responses/well-known.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <boost/optional.hpp>
+#include <nlohmann/json.hpp>
+
+namespace mtx {
+namespace responses {
+struct ServerInformation
+{
+        //! Required. The base URL for client-server connections.
+        std::string base_url;
+};
+
+//! Response from the `GET /.well-known/matrix/client` endpoint.
+//! May also be returned from `POST /_matrix/client/r0/login`.
+//
+//! Gets discovery information about the domain
+struct WellKnown
+{
+        //! Required. Used by clients to discover homeserver information.
+        ServerInformation homeserver;
+        //! Used by clients to discover identity server information.
+        boost::optional<ServerInformation> identity_server;
+};
+
+void
+from_json(const nlohmann::json &obj, WellKnown &response);
+void
+from_json(const nlohmann::json &obj, ServerInformation &response);
+}
+}
diff --git a/include/mtxclient/http/client.hpp b/include/mtxclient/http/client.hpp
index 635d3a520..00376a25d 100644
--- a/include/mtxclient/http/client.hpp
+++ b/include/mtxclient/http/client.hpp
@@ -144,6 +144,9 @@ public:
                    const std::string &device_name,
                    Callback<mtx::responses::Login> cb);
         void login(const mtx::requests::Login &req, Callback<mtx::responses::Login> cb);
+        //! Lookup real server to connect to.
+        //! Call set_server with the returned homeserver url after this
+        void well_known(Callback<mtx::responses::WellKnown> cb);
 
         //! Register by not expecting a registration flow.
         void registration(const std::string &user,
@@ -343,7 +346,8 @@ private:
         template<class Response>
         void get(const std::string &endpoint,
                  HeadersCallback<Response> cb,
-                 bool requires_auth = true);
+                 bool requires_auth                    = true,
+                 const std::string &endpoint_namespace = "/_matrix");
 
         template<class Response>
         std::shared_ptr<Session> create_session(HeadersCallback<Response> callback);
@@ -437,7 +441,8 @@ template<class Response>
 void
 mtx::http::Client::get(const std::string &endpoint,
                        HeadersCallback<Response> callback,
-                       bool requires_auth)
+                       bool requires_auth,
+                       const std::string &endpoint_namespace)
 {
         auto session = create_session<Response>(callback);
 
@@ -445,7 +450,8 @@ mtx::http::Client::get(const std::string &endpoint,
                 return;
 
         setup_auth(session.get(), requires_auth);
-        setup_headers<std::string, boost::beast::http::verb::get>(session.get(), {}, endpoint);
+        setup_headers<std::string, boost::beast::http::verb::get>(
+          session.get(), {}, endpoint, "", endpoint_namespace);
 
         session->run();
 }
diff --git a/include/mtxclient/http/session.hpp b/include/mtxclient/http/session.hpp
index 21fe948c3..2fde50361 100644
--- a/include/mtxclient/http/session.hpp
+++ b/include/mtxclient/http/session.hpp
@@ -83,14 +83,15 @@ void
 setup_headers(mtx::http::Session *session,
               const Request &req,
               const std::string &endpoint,
-              const std::string &content_type = "")
+              const std::string &content_type       = "",
+              const std::string &endpoint_namespace = "/_matrix")
 {
-        session->request.set(boost::beast::http::field::user_agent, "mtxclient v0.2.0");
+        session->request.set(boost::beast::http::field::user_agent, "mtxclient v0.3.0");
         session->request.set(boost::beast::http::field::accept_encoding, "gzip,deflate");
         session->request.set(boost::beast::http::field::host, session->host);
 
         session->request.method(HttpVerb);
-        session->request.target("/_matrix" + endpoint);
+        session->request.target(endpoint_namespace + endpoint);
         session->request.body() = client::utils::serialize(req);
         session->request.prepare_payload();
 
diff --git a/lib/http/client.cpp b/lib/http/client.cpp
index 97feceb2f..24ab2b557 100644
--- a/lib/http/client.cpp
+++ b/lib/http/client.cpp
@@ -111,6 +111,19 @@ Client::login(const mtx::requests::Login &req, Callback<mtx::responses::Login> c
           },
           false);
 }
+
+void
+Client::well_known(Callback<mtx::responses::WellKnown> callback)
+{
+        get<mtx::responses::WellKnown>(
+          "/matrix/client",
+          [callback](const mtx::responses::WellKnown &res, HeaderFields, RequestErr err) {
+                  callback(res, err);
+          },
+          false,
+          "/.well-known");
+}
+
 void
 Client::logout(Callback<mtx::responses::Logout> callback)
 {
diff --git a/lib/structs/responses/login.cpp b/lib/structs/responses/login.cpp
index c7097ca61..f63763daf 100644
--- a/lib/structs/responses/login.cpp
+++ b/lib/structs/responses/login.cpp
@@ -16,6 +16,9 @@ from_json(const json &obj, Login &response)
 
         if (obj.count("device_id") != 0)
                 response.device_id = obj.at("device_id").get<std::string>();
+
+        if (obj.count("well_known") != 0)
+                response.well_known = obj.at("well_known").get<WellKnown>();
 }
 }
 }
diff --git a/lib/structs/responses/well-known.cpp b/lib/structs/responses/well-known.cpp
new file mode 100644
index 000000000..660ee371e
--- /dev/null
+++ b/lib/structs/responses/well-known.cpp
@@ -0,0 +1,26 @@
+#include <regex>
+#include <string>
+
+#include "mtx/responses/well-known.hpp"
+
+using json = nlohmann::json;
+
+namespace mtx {
+namespace responses {
+
+void
+from_json(const json &obj, WellKnown &response)
+{
+        response.homeserver = obj.at("m.homeserver").get<ServerInformation>();
+
+        if (obj.count("m.identity_server"))
+                response.identity_server = obj.at("m.identity_server").get<ServerInformation>();
+}
+
+void
+from_json(const json &obj, ServerInformation &response)
+{
+        response.base_url = obj.at("base_url");
+}
+}
+}
diff --git a/tests/responses.cpp b/tests/responses.cpp
index acdf677f9..d4f598792 100644
--- a/tests/responses.cpp
+++ b/tests/responses.cpp
@@ -457,6 +457,25 @@ TEST(Responses, Versions)
         ASSERT_THROW(Versions versions = error_data, std::invalid_argument);
 }
 
+TEST(Responses, WellKnown)
+{
+        json data = R"({
+          "m.homeserver": {
+            "base_url": "https://matrix.example.com"
+          },
+          "m.identity_server": {
+            "base_url": "https://identity.example.com"
+          },
+          "org.example.custom.property": {
+            "app_url": "https://custom.app.example.org"
+          }
+        })"_json;
+
+        WellKnown wellknown = data;
+        EXPECT_EQ(wellknown.homeserver.base_url, "https://matrix.example.com");
+        EXPECT_EQ(wellknown.identity_server->base_url, "https://identity.example.com");
+}
+
 TEST(Responses, CreateRoom)
 {
         json data = R"({"room_id" : "!sefiuhWgwghwWgh:example.com"})"_json;
@@ -475,7 +494,15 @@ TEST(Responses, Login)
           "user_id": "@cheeky_monkey:matrix.org",
           "access_token": "abc123", 
 	  "home_server": "matrix.org",
-          "device_id": "GHTYAJCE"
+          "device_id": "GHTYAJCE",
+	  "well_known": {
+	     "m.homeserver": {
+	       "base_url": "https://example.org"
+	     },
+	     "m.identity_server": {
+	       "base_url": "https://id.example.org"
+	     }
+	  }
         })"_json;
 
         Login login = data;
@@ -483,6 +510,8 @@ TEST(Responses, Login)
         EXPECT_EQ(login.access_token, "abc123");
         EXPECT_EQ(login.home_server, "matrix.org");
         EXPECT_EQ(login.device_id, "GHTYAJCE");
+        EXPECT_EQ(login.well_known->homeserver.base_url, "https://example.org");
+        EXPECT_EQ(login.well_known->identity_server->base_url, "https://id.example.org");
 
         json data2 = R"({
           "user_id": "@cheeky_monkey:matrix.org",
-- 
GitLab