|
| 1 | +package gomatrixserverlib |
| 2 | + |
| 3 | +import ( |
| 4 | + "bytes" |
| 5 | + "encoding/json" |
| 6 | + "fmt" |
| 7 | + "strings" |
| 8 | + |
| 9 | + "github.com/matrix-org/gomatrixserverlib/spec" |
| 10 | + "github.com/tidwall/gjson" |
| 11 | + "github.com/tidwall/sjson" |
| 12 | +) |
| 13 | + |
| 14 | +type eventV3 struct { |
| 15 | + eventV2 |
| 16 | +} |
| 17 | + |
| 18 | +func (e *eventV3) RoomID() spec.RoomID { |
| 19 | + roomIDStr := e.eventFields.RoomID |
| 20 | + isCreateEvent := e.Type() == spec.MRoomCreate && e.StateKeyEquals("") |
| 21 | + if isCreateEvent { |
| 22 | + roomIDStr = fmt.Sprintf("!%s", e.EventID()[1:]) |
| 23 | + } |
| 24 | + roomID, err := spec.NewRoomID(roomIDStr) |
| 25 | + if err != nil { |
| 26 | + panic(fmt.Errorf("RoomID is invalid: %w", err)) |
| 27 | + } |
| 28 | + return *roomID |
| 29 | +} |
| 30 | + |
| 31 | +func (e *eventV3) AuthEventIDs() []string { |
| 32 | + isCreateEvent := e.Type() == spec.MRoomCreate && e.StateKeyEquals("") |
| 33 | + if isCreateEvent { |
| 34 | + return []string{} |
| 35 | + } |
| 36 | + createEventID := fmt.Sprintf("$%s", e.eventFields.RoomID[1:]) |
| 37 | + if len(e.AuthEvents) > 0 { |
| 38 | + // always include the create event |
| 39 | + return append([]string{createEventID}, e.AuthEvents...) |
| 40 | + } |
| 41 | + return []string{createEventID} |
| 42 | +} |
| 43 | + |
| 44 | +func newEventFromUntrustedJSONV3(eventJSON []byte, roomVersion IRoomVersion) (PDU, error) { |
| 45 | + if r := gjson.GetBytes(eventJSON, "_*"); r.Exists() { |
| 46 | + return nil, fmt.Errorf("gomatrixserverlib NewEventFromUntrustedJSON: found top-level '_' key, is this a headered event: %v", string(eventJSON)) |
| 47 | + } |
| 48 | + if err := roomVersion.CheckCanonicalJSON(eventJSON); err != nil { |
| 49 | + return nil, BadJSONError{err} |
| 50 | + } |
| 51 | + |
| 52 | + res := &eventV3{} |
| 53 | + var err error |
| 54 | + // Synapse removes these keys from events in case a server accidentally added them. |
| 55 | + // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/crypto/event_signing.py#L57-L62 |
| 56 | + for _, key := range []string{"outlier", "destinations", "age_ts", "unsigned", "event_id"} { |
| 57 | + if eventJSON, err = sjson.DeleteBytes(eventJSON, key); err != nil { |
| 58 | + return nil, err |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + if err = json.Unmarshal(eventJSON, &res); err != nil { |
| 63 | + return nil, err |
| 64 | + } |
| 65 | + |
| 66 | + // v3 events have room IDs as the create event ID. |
| 67 | + // TODO: allow validation to be enhanced/relaxed to help users like Complement. |
| 68 | + if err := checkRoomID(res); err != nil { |
| 69 | + return nil, err |
| 70 | + } |
| 71 | + |
| 72 | + res.roomVersion = roomVersion.Version() |
| 73 | + |
| 74 | + // We know the JSON must be valid here. |
| 75 | + eventJSON = CanonicalJSONAssumeValid(eventJSON) |
| 76 | + res.eventJSON = eventJSON |
| 77 | + |
| 78 | + if err = checkEventContentHash(eventJSON); err != nil { |
| 79 | + res.redacted = true |
| 80 | + |
| 81 | + // If the content hash doesn't match then we have to discard all non-essential fields |
| 82 | + // because they've been tampered with. |
| 83 | + var redactedJSON []byte |
| 84 | + if redactedJSON, err = roomVersion.RedactEventJSON(eventJSON); err != nil { |
| 85 | + return nil, err |
| 86 | + } |
| 87 | + |
| 88 | + redactedJSON = CanonicalJSONAssumeValid(redactedJSON) |
| 89 | + |
| 90 | + // We need to ensure that `result` is the redacted event. |
| 91 | + // If redactedJSON is the same as eventJSON then `result` is already |
| 92 | + // correct. If not then we need to reparse. |
| 93 | + // |
| 94 | + // Yes, this means that for some events we parse twice (which is slow), |
| 95 | + // but means that parsing unredacted events is fast. |
| 96 | + if !bytes.Equal(redactedJSON, eventJSON) { |
| 97 | + result, err := roomVersion.NewEventFromTrustedJSON(redactedJSON, true) |
| 98 | + if err != nil { |
| 99 | + return nil, err |
| 100 | + } |
| 101 | + err = CheckFields(result) |
| 102 | + return result, err |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + err = CheckFields(res) |
| 107 | + |
| 108 | + return res, err |
| 109 | +} |
| 110 | + |
| 111 | +func newEventFromTrustedJSONV3(eventJSON []byte, redacted bool, roomVersion IRoomVersion) (PDU, error) { |
| 112 | + res := eventV3{} |
| 113 | + if err := json.Unmarshal(eventJSON, &res); err != nil { |
| 114 | + return nil, err |
| 115 | + } |
| 116 | + |
| 117 | + // v3 events have room IDs as the create event ID. |
| 118 | + // TODO: allow validation to be enhanced/relaxed to help users like Complement. |
| 119 | + // TODO: feels weird to only have this validation here and not length checks etc :S |
| 120 | + if err := checkRoomID(&res); err != nil { |
| 121 | + return nil, err |
| 122 | + } |
| 123 | + |
| 124 | + res.roomVersion = roomVersion.Version() |
| 125 | + res.redacted = redacted |
| 126 | + res.eventJSON = eventJSON |
| 127 | + return &res, nil |
| 128 | +} |
| 129 | + |
| 130 | +func newEventFromTrustedJSONWithEventIDV3(eventID string, eventJSON []byte, redacted bool, roomVersion IRoomVersion) (PDU, error) { |
| 131 | + res := &eventV3{} |
| 132 | + if err := json.Unmarshal(eventJSON, &res); err != nil { |
| 133 | + return nil, err |
| 134 | + } |
| 135 | + |
| 136 | + // v3 events have room IDs as the create event ID. |
| 137 | + // TODO: allow validation to be enhanced/relaxed to help users like Complement. |
| 138 | + if err := checkRoomID(res); err != nil { |
| 139 | + return nil, err |
| 140 | + } |
| 141 | + |
| 142 | + res.roomVersion = roomVersion.Version() |
| 143 | + res.eventJSON = eventJSON |
| 144 | + res.EventIDRaw = eventID |
| 145 | + res.redacted = redacted |
| 146 | + return res, nil |
| 147 | +} |
| 148 | + |
| 149 | +func checkRoomID(res *eventV3) error { |
| 150 | + isCreateEvent := res.Type() == spec.MRoomCreate && res.StateKeyEquals("") |
| 151 | + // TODO: We can't do this so long as we support partial Hydra impls in Complement |
| 152 | + // because otherwise if MSC4291=0 and MSC4289=1 then this check fails as the create |
| 153 | + // event will have a room_id. |
| 154 | + if isCreateEvent && res.eventFields.RoomID != "" { |
| 155 | + //return fmt.Errorf("gomatrixserverlib: room_id must not exist on create event") |
| 156 | + } |
| 157 | + if !isCreateEvent && !strings.HasPrefix(res.eventFields.RoomID, "!") { |
| 158 | + return fmt.Errorf("gomatrixserverlib: room_id must start with !") |
| 159 | + } |
| 160 | + return nil |
| 161 | +} |
0 commit comments