From b55e531aa5c6ee4710daad077d05c0c5152062ea Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Thu, 1 Sep 2022 15:38:29 +0200 Subject: [PATCH] Encode group location in the status.json file. --- README.PROTOCOL | 34 ++++++++++++++++++---------------- group/group.go | 37 ++++++++++++++++++++++++++++++++++--- group/group_test.go | 2 +- static/mainpage.js | 2 +- webserver/webserver.go | 26 +++++++++++++------------- 5 files changed, 67 insertions(+), 34 deletions(-) diff --git a/README.PROTOCOL b/README.PROTOCOL index df57463..2328b60 100644 --- a/README.PROTOCOL +++ b/README.PROTOCOL @@ -28,31 +28,33 @@ server to client direction. ## Before connecting -Before it connects and joins a group, a client may perform an HTTP GET -request on the URL `/public-groups.json`. This yields a JSON array of -objects, one for each group that has been marked public in its -configuration file. Each object has the following fields: +The client needs to know the location of the group, the (user-visible) URL +at which the group is found. This may be obtained either by explicit +configuration by the user, or by parsing the `/public-groups.json` file +which may contain an array of group statuses (see below). + +A client then performs an HTTP GET request on the file `.status.json` at +the group's location. This yields a single JSON object, which contains +the following fields: - `name`: the group's name - - `displayName` (optional): a longer version of the name used for display; - - `description` (optional): a user-readable description. + - `location`: the group's location + - `endpoint`: the URL of the server's WebSocket endpoint + - `displayName`: a longer version of the name used for display; + - `description`: a user-readable description; + - `authServer`: the URL of the authentication server, if any; + - `authPortal`: the uRL of the authentication portal, if any; - `locked`: true if the group is locked; - `clientCount`: the number of clients currently in the group. -If token-based authorisation is in use for the group, then the dictionary -contains the following additional field: +All fields are optional except `name`, `location` and `endpoint`. - - `authServer`: the URL of the authorisation server. - -A client may also fetch the URL `/group/name/.status.json` to retrieve the -status of a single group. If the group has not been marked as public, -then the fields `locked` and `clientCount` are omitted. ## Connecting -The client connects to the websocket at `/ws`. Galene uses a symmetric, -asynchronous protocol: there are no requests and responses, and most -messages may be sent by either peer. +The client connects to the websocket at the URL obtained at the previous +step. Galene uses a symmetric, asynchronous protocol: there are no +requests and responses, and most messages may be sent by either peer. ## Message syntax diff --git a/group/group.go b/group/group.go index b909856..ea38658 100644 --- a/group/group.go +++ b/group/group.go @@ -1177,6 +1177,7 @@ func (desc *Description) GetPermission(group string, creds ClientCredentials) (s type Status struct { Name string `json:"name"` + Location string `json:"location"` Endpoint string `json:"endpoint"` DisplayName string `json:"displayName,omitempty"` Description string `json:"description,omitempty"` @@ -1186,10 +1187,40 @@ type Status struct { ClientCount *int `json:"clientCount,omitempty"` } -func (g *Group) Status(authentified bool, endpoint string) Status { +// Status returns a group's status. +// Base is the base URL for groups; if omitted, then both the Location and +// Endpoint members are omitted from the result. +func (g *Group) Status(authentified bool, base string) Status { desc := g.Description() + + var location, endpoint string + if base != "" { + burl, err := url.Parse(base) + if err == nil { + wss := "wss" + if burl.Scheme == "http" { + wss = "ws" + } + l := url.URL{ + Scheme: burl.Scheme, + Host: burl.Host, + Path: path.Join(burl.Path, g.name) + "/", + } + location = l.String() + e := url.URL{ + Scheme: wss, + Host: burl.Host, + Path: "/ws", + } + endpoint = e.String() + } else { + log.Printf("Couldn't parse base URL %v", base) + } + } + d := Status{ Name: g.name, + Location: location, Endpoint: endpoint, DisplayName: desc.DisplayName, AuthServer: desc.AuthServer, @@ -1207,11 +1238,11 @@ func (g *Group) Status(authentified bool, endpoint string) Status { return d } -func GetPublic() []Status { +func GetPublic(base string) []Status { gs := make([]Status, 0) Range(func(g *Group) bool { if g.Description().Public { - gs = append(gs, g.Status(false, "")) + gs = append(gs, g.Status(false, base)) } return true }) diff --git a/group/group_test.go b/group/group_test.go index 4d925f2..2df55e0 100644 --- a/group/group_test.go +++ b/group/group_test.go @@ -42,7 +42,7 @@ func TestGroup(t *testing.T) { t.Errorf("Expected [], got %v", subs) } - if public := GetPublic(); len(public) != 1 || public[0].Name != "group/subgroup" { + if public := GetPublic(""); len(public) != 1 || public[0].Name != "group/subgroup" { t.Errorf("Expected group/subgroup, got %v", public) } } diff --git a/static/mainpage.js b/static/mainpage.js index 73da379..02f6120 100644 --- a/static/mainpage.js +++ b/static/mainpage.js @@ -110,7 +110,7 @@ async function listPublicGroups() { let td = document.createElement('td'); let a = document.createElement('a'); a.textContent = group.displayName || group.name; - a.href = '/group/' + group.name + '/'; + a.href = group.location; td.appendChild(a); tr.appendChild(td); let td2 = document.createElement('td'); diff --git a/webserver/webserver.go b/webserver/webserver.go index 37f8b60..9cfe962 100644 --- a/webserver/webserver.go +++ b/webserver/webserver.go @@ -332,9 +332,18 @@ func groupHandler(w http.ResponseWriter, r *http.Request) { serveFile(w, r, filepath.Join(StaticRoot, "galene.html")) } +func groupBase(r *http.Request) string { + base := url.URL{ + Scheme: r.URL.Scheme, + Host: r.Host, + Path: "/group/", + } + return base.String() +} + func groupStatusHandler(w http.ResponseWriter, r *http.Request) { - path := path.Dir(r.URL.Path) - name := parseGroupName("/group/", path) + pth := path.Dir(r.URL.Path) + name := parseGroupName("/group/", pth) if name == "" { notFound(w) return @@ -351,16 +360,7 @@ func groupStatusHandler(w http.ResponseWriter, r *http.Request) { return } - scheme := "wss" - if Insecure { - scheme = "ws" - } - endpoint := url.URL{ - Scheme: scheme, - Host: r.Host, - Path: "/ws", - } - d := g.Status(false, endpoint.String()) + d := g.Status(false, groupBase(r)) w.Header().Set("content-type", "application/json") w.Header().Set("cache-control", "no-cache") @@ -380,7 +380,7 @@ func publicHandler(w http.ResponseWriter, r *http.Request) { return } - g := group.GetPublic() + g := group.GetPublic(groupBase(r)) e := json.NewEncoder(w) e.Encode(g) }