Update version of gomatrixserverlib (#111)

This commit is contained in:
Mark Haines 2017-05-19 10:46:17 +01:00 committed by GitHub
parent 9d4d18ae7f
commit aa179d451c
24 changed files with 2363 additions and 20 deletions

View file

@ -40,8 +40,16 @@ type UserInfo struct {
// NewClient makes a new Client
func NewClient() *Client {
return &Client{client: http.Client{Transport: newFederationTripper()}}
}
type federationTripper struct {
transport http.RoundTripper
}
func newFederationTripper() *federationTripper {
// TODO: Verify ceritificates
tripper := federationTripper{
return &federationTripper{
transport: &http.Transport{
// Set our own DialTLS function to avoid the default net/http SNI.
// By default net/http and crypto/tls set the SNI to the target host.
@ -66,14 +74,6 @@ func NewClient() *Client {
},
},
}
return &Client{
client: http.Client{Transport: &tripper},
}
}
type federationTripper struct {
transport http.RoundTripper
}
func makeHTTPSURL(u *url.URL, addr string) (httpsURL url.URL) {

View file

@ -43,6 +43,8 @@ type EventReference struct {
}
// An EventBuilder is used to build a new event.
// These can be exchanged between matrix servers in the federation APIs when
// joining or leaving a room.
type EventBuilder struct {
// The user ID of the user sending the event.
Sender string `json:"sender"`
@ -60,20 +62,22 @@ type EventBuilder struct {
Redacts string `json:"redacts,omitempty"`
// The depth of the event, This should be one greater than the maximum depth of the previous events.
// The create event has a depth of 1.
Depth int64 `json:"depth"`
content []byte
unsigned []byte
Depth int64 `json:"depth"`
// The JSON object for "content" key of the event.
Content rawJSON `json:"content"`
// The JSON object for the "unsigned" key
Unsigned rawJSON `json:"unsigned,omitempty"`
}
// SetContent sets the JSON content key of the event.
func (eb *EventBuilder) SetContent(content interface{}) (err error) {
eb.content, err = json.Marshal(content)
eb.Content, err = json.Marshal(content)
return
}
// SetUnsigned sets the JSON unsigned key of the event.
func (eb *EventBuilder) SetUnsigned(unsigned interface{}) (err error) {
eb.unsigned, err = json.Marshal(unsigned)
eb.Unsigned, err = json.Marshal(unsigned)
return
}
@ -113,8 +117,6 @@ func (eb *EventBuilder) Build(eventID string, now time.Time, origin ServerName,
var event struct {
EventBuilder
EventID string `json:"event_id"`
RawContent rawJSON `json:"content"`
RawUnsigned rawJSON `json:"unsigned,omitempty"`
OriginServerTS Timestamp `json:"origin_server_ts"`
Origin ServerName `json:"origin"`
}
@ -125,8 +127,6 @@ func (eb *EventBuilder) Build(eventID string, now time.Time, origin ServerName,
if event.AuthEvents == nil {
event.AuthEvents = emptyEventReferenceList
}
event.RawContent = rawJSON(event.content)
event.RawUnsigned = rawJSON(event.unsigned)
event.OriginServerTS = AsTimestamp(now)
event.Origin = origin
event.EventID = eventID

View file

@ -115,7 +115,7 @@ func StateNeededForEventBuilder(builder *EventBuilder) (result StateNeeded, err
// Extract the 'content' object from the event if it is m.room.member as we need to know 'membership'
var content *memberContent
if builder.Type == "m.room.member" {
if err = json.Unmarshal(builder.content, &content); err != nil {
if err = json.Unmarshal(builder.Content, &content); err != nil {
err = errorf("unparsable member event content: %s", err.Error())
return
}

View file

@ -0,0 +1,162 @@
package gomatrixserverlib
import (
"encoding/json"
"github.com/matrix-org/gomatrix"
"golang.org/x/crypto/ed25519"
"io/ioutil"
"net/http"
"net/url"
)
// An FederationClient is a matrix federation client that adds
// "Authorization: X-Matrix" headers to requests that need ed25519 signatures
type FederationClient struct {
Client
serverName ServerName
serverKeyID KeyID
serverPrivateKey ed25519.PrivateKey
}
// NewFederationClient makes a new FederationClient
func NewFederationClient(
serverName ServerName, keyID KeyID, privateKey ed25519.PrivateKey,
) *FederationClient {
return &FederationClient{
Client: Client{client: http.Client{Transport: newFederationTripper()}},
serverName: serverName,
serverKeyID: keyID,
serverPrivateKey: privateKey,
}
}
func (ac *FederationClient) doRequest(r FederationRequest, resBody interface{}) error {
if err := r.Sign(ac.serverName, ac.serverKeyID, ac.serverPrivateKey); err != nil {
return err
}
req, err := r.HTTPRequest()
if err != nil {
return err
}
res, err := ac.client.Do(req)
if res != nil {
defer res.Body.Close()
}
if err != nil {
return err
}
contents, err := ioutil.ReadAll(res.Body)
if res.StatusCode/100 != 2 { // not 2xx
// Adapted from https://github.com/matrix-org/gomatrix/blob/master/client.go
var wrap error
var respErr gomatrix.RespError
if _ = json.Unmarshal(contents, &respErr); respErr.ErrCode != "" {
wrap = respErr
}
// If we failed to decode as RespError, don't just drop the HTTP body, include it in the
// HTTP error instead (e.g proxy errors which return HTML).
msg := "Failed to " + r.Method() + " JSON to " + r.RequestURI()
if wrap == nil {
msg = msg + ": " + string(contents)
}
return gomatrix.HTTPError{
Code: res.StatusCode,
Message: msg,
WrappedError: wrap,
}
}
if err != nil {
return err
}
return json.Unmarshal(contents, resBody)
}
// SendTransaction sends a transaction
func (ac *FederationClient) SendTransaction(t Transaction) (res RespSend, err error) {
path := "/_matrix/federation/v1/send/" + string(t.TransactionID) + "/"
req := NewFederationRequest("PUT", t.Destination, path)
if err = req.SetContent(t); err != nil {
return
}
err = ac.doRequest(req, &res)
return
}
// MakeJoin makes a join m.room.member event for a room on a remote matrix server.
// This is used to join a room the local server isn't a member of.
// We need to query a remote server because if we aren't in the room we don't
// know what to use for the "prev_events" in the join event.
// The remote server should return us a m.room.member event for our local user
// with the "prev_events" filled out.
// If this successfully returns an acceptable event we will sign it with our
// server's key and pass it to SendJoin.
// See https://matrix.org/docs/spec/server_server/unstable.html#joining-rooms
func (ac *FederationClient) MakeJoin(s ServerName, roomID, userID string) (res RespMakeJoin, err error) {
path := "/_matrix/federation/v1/make_join/" +
url.PathEscape(roomID) + "/" +
url.PathEscape(userID)
req := NewFederationRequest("GET", s, path)
err = ac.doRequest(req, &res)
return
}
// SendJoin sends a join m.room.member event obtained using MakeJoin via a
// remote matrix server.
// This is used to join a room the local server isn't a member of.
// See https://matrix.org/docs/spec/server_server/unstable.html#joining-rooms
func (ac *FederationClient) SendJoin(s ServerName, event Event) (res RespSendJoin, err error) {
path := "/_matrix/federation/v1/send_join/" +
url.PathEscape(event.RoomID()) + "/" +
url.PathEscape(event.EventID())
req := NewFederationRequest("PUT", s, path)
if err = req.SetContent(event); err != nil {
return
}
err = ac.doRequest(req, &res)
return
}
// LookupState retrieves the room state for a room at an event from a
// remote matrix server as full matrix events.
func (ac *FederationClient) LookupState(s ServerName, roomID, eventID string) (res RespState, err error) {
path := "/_matrix/federation/v1/state/" +
url.PathEscape(roomID) +
"/?event_id=" +
url.QueryEscape(eventID)
req := NewFederationRequest("GET", s, path)
err = ac.doRequest(req, &res)
return
}
// LookupStateIDs retrieves the room state for a room at an event from a
// remote matrix server as lists of matrix event IDs.
func (ac *FederationClient) LookupStateIDs(s ServerName, roomID, eventID string) (res RespStateIDs, err error) {
path := "/_matrix/federation/v1/state_ids/" +
url.PathEscape(roomID) +
"/?event_id=" +
url.QueryEscape(eventID)
req := NewFederationRequest("GET", s, path)
err = ac.doRequest(req, &res)
return
}
// LookupRoomAlias looks up a room alias hosted on the remote server.
// The domain part of the roomAlias must match the name of the server it is
// being looked up on.
// If the room alias doesn't exist on the remote server then a 404 gomatrix.HTTPError
// is returned.
func (ac *FederationClient) LookupRoomAlias(s ServerName, roomAlias string) (res RespDirectory, err error) {
path := "/_matrix/federation/v1/query/directory?room_alias=" +
url.QueryEscape(roomAlias)
req := NewFederationRequest("GET", s, path)
err = ac.doRequest(req, &res)
return
}

View file

@ -0,0 +1,106 @@
package gomatrixserverlib
import (
"encoding/json"
"fmt"
)
// A RespSend is the content of a response to PUT /_matrix/federation/v1/send/{txnID}/
type RespSend struct {
// Map of event ID to the result of processing that event.
PDUs map[string]PDUResult `json:"pdus"`
}
// A PDUResult is the result of processing a matrix room event.
type PDUResult struct {
// If not empty then this is a human readable description of a problem
// encountered processing an event.
Error string `json:"error,omitempty"`
}
// A RespStateIDs is the content of a response to GET /_matrix/federation/v1/state_ids/{roomID}/{eventID}
type RespStateIDs struct {
// A list of state event IDs for the state of the room before the requested event.
StateEventIDs []string `json:"pdu_ids"`
// A list of event IDs needed to authenticate the state events.
AuthEventIDs []string `json:"auth_chain_ids"`
}
// A RespState is the content of a response to GET /_matrix/federation/v1/state/{roomID}/{eventID}
type RespState struct {
// A list of events giving the state of the room before the request event.
StateEvents []Event `json:"pdus"`
// A list of events needed to authenticate the state events.
AuthEvents []Event `json:"auth_chain"`
}
// A RespMakeJoin is the content of a response to GET /_matrix/federation/v1/make_join/{roomID}/{userID}
type RespMakeJoin struct {
// An incomplete m.room.member event for a user on the requesting server
// generated by the responding server.
// See https://matrix.org/docs/spec/server_server/unstable.html#joining-rooms
JoinEvent EventBuilder `json:"event"`
}
// A RespSendJoin is the content of a response to PUT /_matrix/federation/v1/send_join/{roomID}/{eventID}
type RespSendJoin RespState
// MarshalJSON implements json.Marshaller
func (r RespSendJoin) MarshalJSON() ([]byte, error) {
// SendJoinResponses contain the same data as a StateResponse but are
// formatted slightly differently on the wire:
// 1) The "pdus" field is renamed to "state".
// 2) The object is placed as the second element of a two element list
// where the first element is the constant integer 200.
//
//
// So a state response of:
//
// {"pdus": x, "auth_chain": y}
//
// Becomes:
//
// [200, {"state": x, "auth_chain": y}]
//
// (This protocol oddity is the result of a typo in the synapse matrix
// server, and is preserved to maintain compatibility.)
return json.Marshal([]interface{}{200, respSendJoinFields{
r.StateEvents, r.AuthEvents,
}})
}
// UnmarshalJSON implements json.Unmarshaller
func (r *RespSendJoin) UnmarshalJSON(data []byte) error {
var tuple []rawJSON
if err := json.Unmarshal(data, &tuple); err != nil {
return err
}
if len(tuple) != 2 {
return fmt.Errorf("gomatrixserverlib: invalid send join response, invalid length: %d != 2", len(tuple))
}
var fields respSendJoinFields
if err := json.Unmarshal(tuple[1], &fields); err != nil {
return err
}
r.StateEvents = fields.StateEvents
r.AuthEvents = fields.AuthEvents
return nil
}
type respSendJoinFields struct {
StateEvents []Event `json:"state"`
AuthEvents []Event `json:"auth_chain"`
}
// A RespDirectory is the content of a response to GET /_matrix/federation/v1/query/directory
// This is returned when looking up a room alias from a remote server.
// See https://matrix.org/docs/spec/server_server/unstable.html#directory
type RespDirectory struct {
// The matrix room ID the room alias corresponds to.
RoomID string `json:"room_id"`
// A list of matrix servers that the directory server thinks could be used
// to join the room. The joining server may need to try multiple servers
// before it finds one that it can use to join the room.
Servers []ServerName `json:"servers"`
}

View file

@ -0,0 +1,47 @@
package gomatrixserverlib
import (
"encoding/json"
"testing"
)
func TestRespSendJoinMarshalJSON(t *testing.T) {
inputData := `{"pdus":[],"auth_chain":[]}`
var input RespState
if err := json.Unmarshal([]byte(inputData), &input); err != nil {
t.Fatal(err)
}
gotBytes, err := json.Marshal(RespSendJoin(input))
if err != nil {
t.Fatal(err)
}
want := `[200,{"state":[],"auth_chain":[]}]`
got := string(gotBytes)
if want != got {
t.Errorf("json.Marshal(RespSendJoin(%q)): wanted %q, got %q", inputData, want, got)
}
}
func TestRespSendJoinUnmarshalJSON(t *testing.T) {
inputData := `[200,{"state":[],"auth_chain":[]}]`
var input RespSendJoin
if err := json.Unmarshal([]byte(inputData), &input); err != nil {
t.Fatal(err)
}
gotBytes, err := json.Marshal(RespState(input))
if err != nil {
t.Fatal(err)
}
want := `{"pdus":[],"auth_chain":[]}`
got := string(gotBytes)
if want != got {
t.Errorf("json.Marshal(RespSendJoin(%q)): wanted %q, got %q", inputData, want, got)
}
}

View file

@ -0,0 +1,31 @@
package gomatrixserverlib
// A Transaction is used to push data from one matrix server to another matrix
// server.
type Transaction struct {
// The ID of the transaction.
TransactionID TransactionID `json:"transaction_id"`
// The server that sent the transaction.
Origin ServerName `json:"origin"`
// The server that should receive the transaction.
Destination ServerName `json:"destination"`
// The millisecond posix timestamp on the origin server when the
// transaction was created.
OriginServerTS Timestamp `json:"origin_server_ts"`
// The IDs of the most recent transactions sent by the origin server to
// the destination server. Multiple transactions can be sent by the origin
// server to the destination server in parallel so there may be more than
// one previous transaction.
PreviousIDs []TransactionID `json:"previous_ids"`
// The room events pushed from the origin server to the destination server
// by this transaction. The events should either be events that originate
// on the origin server or be join m.room.member events.
PDUs []Event `json:"pdus"`
}
// A TransactionID identifies a transaction sent by a matrix server to another
// matrix server. The ID must be unique amoungst the transactions sent from the
// origin server to the destination, but doesn't have to be globally unique.
// The ID must be safe to insert into a URL path segment. The ID should have a
// format matching '^[0-9A-Za-z\-_]*$'
type TransactionID string