summaryrefslogtreecommitdiffstats
path: root/vendor/maunium.net/go/mautrix/id/contenturi.go
blob: cfd00c3e4420c6650881163db37a3beabe342593 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Copyright (c) 2020 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package id

import (
	"bytes"
	"database/sql/driver"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
)

var (
	InvalidContentURI  = errors.New("invalid Matrix content URI")
	InputNotJSONString = errors.New("input doesn't look like a JSON string")
)

// ContentURIString is a string that's expected to be a Matrix content URI.
// It's useful for delaying the parsing of the content URI to move errors from the event content
// JSON parsing step to a later step where more appropriate errors can be produced.
type ContentURIString string

func (uriString ContentURIString) Parse() (ContentURI, error) {
	return ParseContentURI(string(uriString))
}

func (uriString ContentURIString) ParseOrIgnore() ContentURI {
	parsed, _ := ParseContentURI(string(uriString))
	return parsed
}

// ContentURI represents a Matrix content URI.
// https://spec.matrix.org/v1.2/client-server-api/#matrix-content-mxc-uris
type ContentURI struct {
	Homeserver string
	FileID     string
}

func MustParseContentURI(uri string) ContentURI {
	parsed, err := ParseContentURI(uri)
	if err != nil {
		panic(err)
	}
	return parsed
}

// ParseContentURI parses a Matrix content URI.
func ParseContentURI(uri string) (parsed ContentURI, err error) {
	if len(uri) == 0 {
		return
	} else if !strings.HasPrefix(uri, "mxc://") {
		err = InvalidContentURI
	} else if index := strings.IndexRune(uri[6:], '/'); index == -1 || index == len(uri)-7 {
		err = InvalidContentURI
	} else {
		parsed.Homeserver = uri[6 : 6+index]
		parsed.FileID = uri[6+index+1:]
	}
	return
}

var mxcBytes = []byte("mxc://")

func ParseContentURIBytes(uri []byte) (parsed ContentURI, err error) {
	if len(uri) == 0 {
		return
	} else if !bytes.HasPrefix(uri, mxcBytes) {
		err = InvalidContentURI
	} else if index := bytes.IndexRune(uri[6:], '/'); index == -1 || index == len(uri)-7 {
		err = InvalidContentURI
	} else {
		parsed.Homeserver = string(uri[6 : 6+index])
		parsed.FileID = string(uri[6+index+1:])
	}
	return
}

func (uri *ContentURI) UnmarshalJSON(raw []byte) (err error) {
	if string(raw) == "null" {
		*uri = ContentURI{}
		return nil
	} else if len(raw) < 2 || raw[0] != '"' || raw[len(raw)-1] != '"' {
		return InputNotJSONString
	}
	parsed, err := ParseContentURIBytes(raw[1 : len(raw)-1])
	if err != nil {
		return err
	}
	*uri = parsed
	return nil
}

func (uri *ContentURI) MarshalJSON() ([]byte, error) {
	if uri == nil || uri.IsEmpty() {
		return []byte("null"), nil
	}
	return json.Marshal(uri.String())
}

func (uri *ContentURI) UnmarshalText(raw []byte) (err error) {
	parsed, err := ParseContentURIBytes(raw)
	if err != nil {
		return err
	}
	*uri = parsed
	return nil
}

func (uri ContentURI) MarshalText() ([]byte, error) {
	return []byte(uri.String()), nil
}

func (uri *ContentURI) Scan(i interface{}) error {
	var parsed ContentURI
	var err error
	switch value := i.(type) {
	case nil:
		// don't do anything, set uri to empty
	case []byte:
		parsed, err = ParseContentURIBytes(value)
	case string:
		parsed, err = ParseContentURI(value)
	default:
		return fmt.Errorf("invalid type %T for ContentURI.Scan", i)
	}
	if err != nil {
		return err
	}
	*uri = parsed
	return nil
}

func (uri *ContentURI) Value() (driver.Value, error) {
	if uri == nil {
		return nil, nil
	}
	return uri.String(), nil
}

func (uri ContentURI) String() string {
	if uri.IsEmpty() {
		return ""
	}
	return fmt.Sprintf("mxc://%s/%s", uri.Homeserver, uri.FileID)
}

func (uri ContentURI) CUString() ContentURIString {
	return ContentURIString(uri.String())
}

func (uri ContentURI) IsEmpty() bool {
	return len(uri.Homeserver) == 0 || len(uri.FileID) == 0
}