2023-03-12 15:00:57 +00:00
|
|
|
// GoToSocial
|
|
|
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2021-03-12 16:15:24 +00:00
|
|
|
|
2023-01-02 12:10:50 +00:00
|
|
|
package util
|
2022-06-08 18:38:03 +00:00
|
|
|
|
2024-02-14 11:13:38 +00:00
|
|
|
import "strings"
|
|
|
|
|
2022-06-08 18:38:03 +00:00
|
|
|
const (
|
2023-11-27 14:00:57 +00:00
|
|
|
// Possible GoToSocial mimetypes.
|
|
|
|
AppJSON = `application/json`
|
|
|
|
AppXML = `application/xml`
|
2024-02-14 12:07:58 +00:00
|
|
|
appXMLText = `text/xml` // AppXML is only *recommended* in RFC7303
|
2023-11-27 14:00:57 +00:00
|
|
|
AppXMLXRD = `application/xrd+xml`
|
|
|
|
AppRSSXML = `application/rss+xml`
|
|
|
|
AppActivityJSON = `application/activity+json`
|
2024-02-14 11:13:38 +00:00
|
|
|
appActivityLDJSON = `application/ld+json` // without profile
|
|
|
|
AppActivityLDJSON = appActivityLDJSON + `; profile="https://www.w3.org/ns/activitystreams"`
|
2023-11-27 14:00:57 +00:00
|
|
|
AppJRDJSON = `application/jrd+json` // https://www.rfc-editor.org/rfc/rfc7033#section-10.2
|
|
|
|
AppForm = `application/x-www-form-urlencoded`
|
|
|
|
MultipartForm = `multipart/form-data`
|
|
|
|
TextXML = `text/xml`
|
|
|
|
TextHTML = `text/html`
|
|
|
|
TextCSS = `text/css`
|
2024-07-31 14:03:34 +00:00
|
|
|
TextCSV = `text/csv`
|
2022-06-08 18:38:03 +00:00
|
|
|
)
|
2024-02-14 11:13:38 +00:00
|
|
|
|
|
|
|
// JSONContentType returns whether is application/json(;charset=utf-8)? content-type.
|
|
|
|
func JSONContentType(ct string) bool {
|
|
|
|
p := splitContentType(ct)
|
|
|
|
p, ok := isUTF8ContentType(p)
|
|
|
|
return ok && len(p) == 1 &&
|
|
|
|
p[0] == AppJSON
|
|
|
|
}
|
|
|
|
|
|
|
|
// JSONJRDContentType returns whether is application/(jrd+)?json(;charset=utf-8)? content-type.
|
|
|
|
func JSONJRDContentType(ct string) bool {
|
|
|
|
p := splitContentType(ct)
|
|
|
|
p, ok := isUTF8ContentType(p)
|
|
|
|
return ok && len(p) == 1 &&
|
|
|
|
p[0] == AppJSON ||
|
|
|
|
p[0] == AppJRDJSON
|
|
|
|
}
|
|
|
|
|
|
|
|
// XMLContentType returns whether is application/xml(;charset=utf-8)? content-type.
|
|
|
|
func XMLContentType(ct string) bool {
|
|
|
|
p := splitContentType(ct)
|
|
|
|
p, ok := isUTF8ContentType(p)
|
|
|
|
return ok && len(p) == 1 &&
|
2024-02-14 12:07:58 +00:00
|
|
|
p[0] == AppXML ||
|
|
|
|
p[0] == appXMLText
|
2024-02-14 11:13:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// XMLXRDContentType returns whether is application/(xrd+)?xml(;charset=utf-8)? content-type.
|
|
|
|
func XMLXRDContentType(ct string) bool {
|
|
|
|
p := splitContentType(ct)
|
|
|
|
p, ok := isUTF8ContentType(p)
|
|
|
|
return ok && len(p) == 1 &&
|
|
|
|
p[0] == AppXML ||
|
2024-02-14 12:07:58 +00:00
|
|
|
p[0] == appXMLText ||
|
2024-02-14 11:13:38 +00:00
|
|
|
p[0] == AppXMLXRD
|
|
|
|
}
|
|
|
|
|
|
|
|
// ASContentType returns whether is valid ActivityStreams content-types:
|
|
|
|
// - application/activity+json
|
|
|
|
// - application/ld+json;profile=https://w3.org/ns/activitystreams
|
|
|
|
func ASContentType(ct string) bool {
|
|
|
|
p := splitContentType(ct)
|
|
|
|
p, ok := isUTF8ContentType(p)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
switch len(p) {
|
|
|
|
case 1:
|
|
|
|
return p[0] == AppActivityJSON
|
|
|
|
case 2:
|
|
|
|
return p[0] == appActivityLDJSON &&
|
|
|
|
p[1] == "profile=https://www.w3.org/ns/activitystreams" ||
|
|
|
|
p[1] == "profile=\"https://www.w3.org/ns/activitystreams\""
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NodeInfo2ContentType returns whether is nodeinfo schema 2.0 content-type.
|
|
|
|
func NodeInfo2ContentType(ct string) bool {
|
|
|
|
p := splitContentType(ct)
|
|
|
|
p, ok := isUTF8ContentType(p)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
switch len(p) {
|
|
|
|
case 1:
|
|
|
|
return p[0] == AppJSON
|
|
|
|
case 2:
|
|
|
|
return p[0] == AppJSON &&
|
|
|
|
p[1] == "profile=\"http://nodeinfo.diaspora.software/ns/schema/2.0#\"" ||
|
|
|
|
p[1] == "profile=http://nodeinfo.diaspora.software/ns/schema/2.0#"
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// isUTF8ContentType checks for a provided charset in given
|
|
|
|
// type parts list, removes it and returns whether is utf-8.
|
|
|
|
func isUTF8ContentType(p []string) ([]string, bool) {
|
|
|
|
const charset = "charset="
|
|
|
|
const charsetUTF8 = charset + "utf-8"
|
|
|
|
for i, part := range p {
|
|
|
|
|
|
|
|
// Only handle charset slice parts.
|
2024-04-04 15:45:59 +00:00
|
|
|
if strings.HasPrefix(part, charset) {
|
2024-02-14 11:13:38 +00:00
|
|
|
|
|
|
|
// Check if is UTF-8 charset.
|
|
|
|
ok := (part == charsetUTF8)
|
|
|
|
|
|
|
|
// Drop this slice part.
|
|
|
|
_ = copy(p[i:], p[i+1:])
|
|
|
|
p = p[:len(p)-1]
|
|
|
|
|
|
|
|
return p, ok
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return p, true
|
|
|
|
}
|
|
|
|
|
|
|
|
// splitContentType splits content-type into semi-colon
|
|
|
|
// separated parts. useful when a charset is provided.
|
|
|
|
// note this also maps all chars to their lowercase form.
|
|
|
|
func splitContentType(ct string) []string {
|
|
|
|
s := strings.Split(ct, ";")
|
|
|
|
for i := range s {
|
|
|
|
s[i] = strings.TrimSpace(s[i])
|
|
|
|
s[i] = strings.ToLower(s[i])
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|