uri.go (14685B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package uris 19 20 import ( 21 "fmt" 22 "net/url" 23 24 "github.com/superseriousbusiness/gotosocial/internal/config" 25 "github.com/superseriousbusiness/gotosocial/internal/regexes" 26 ) 27 28 const ( 29 UsersPath = "users" // UsersPath is for serving users info 30 StatusesPath = "statuses" // StatusesPath is for serving statuses 31 InboxPath = "inbox" // InboxPath represents the activitypub inbox location 32 OutboxPath = "outbox" // OutboxPath represents the activitypub outbox location 33 FollowersPath = "followers" // FollowersPath represents the activitypub followers location 34 FollowingPath = "following" // FollowingPath represents the activitypub following location 35 LikedPath = "liked" // LikedPath represents the activitypub liked location 36 CollectionsPath = "collections" // CollectionsPath represents the activitypub collections location 37 FeaturedPath = "featured" // FeaturedPath represents the activitypub featured location 38 PublicKeyPath = "main-key" // PublicKeyPath is for serving an account's public key 39 FollowPath = "follow" // FollowPath used to generate the URI for an individual follow or follow request 40 UpdatePath = "updates" // UpdatePath is used to generate the URI for an account update 41 BlocksPath = "blocks" // BlocksPath is used to generate the URI for a block 42 ReportsPath = "reports" // ReportsPath is used to generate the URI for a report/flag 43 ConfirmEmailPath = "confirm_email" // ConfirmEmailPath is used to generate the URI for an email confirmation link 44 FileserverPath = "fileserver" // FileserverPath is a path component for serving attachments + media 45 EmojiPath = "emoji" // EmojiPath represents the activitypub emoji location 46 ) 47 48 // UserURIs contains a bunch of UserURIs and URLs for a user, host, account, etc. 49 type UserURIs struct { 50 // The web URL of the instance host, eg https://example.org 51 HostURL string 52 // The web URL of the user, eg., https://example.org/@example_user 53 UserURL string 54 // The web URL for statuses of this user, eg., https://example.org/@example_user/statuses 55 StatusesURL string 56 57 // The activitypub URI of this user, eg., https://example.org/users/example_user 58 UserURI string 59 // The activitypub URI for this user's statuses, eg., https://example.org/users/example_user/statuses 60 StatusesURI string 61 // The activitypub URI for this user's activitypub inbox, eg., https://example.org/users/example_user/inbox 62 InboxURI string 63 // The activitypub URI for this user's activitypub outbox, eg., https://example.org/users/example_user/outbox 64 OutboxURI string 65 // The activitypub URI for this user's followers, eg., https://example.org/users/example_user/followers 66 FollowersURI string 67 // The activitypub URI for this user's following, eg., https://example.org/users/example_user/following 68 FollowingURI string 69 // The activitypub URI for this user's liked posts eg., https://example.org/users/example_user/liked 70 LikedURI string 71 // The activitypub URI for this user's featured collections, eg., https://example.org/users/example_user/collections/featured 72 FeaturedCollectionURI string 73 // The URI for this user's public key, eg., https://example.org/users/example_user/publickey 74 PublicKeyURI string 75 } 76 77 // GenerateURIForFollow returns the AP URI for a new follow -- something like: 78 // https://example.org/users/whatever_user/follow/01F7XTH1QGBAPMGF49WJZ91XGC 79 func GenerateURIForFollow(username string, thisFollowID string) string { 80 protocol := config.GetProtocol() 81 host := config.GetHost() 82 return fmt.Sprintf("%s://%s/%s/%s/%s/%s", protocol, host, UsersPath, username, FollowPath, thisFollowID) 83 } 84 85 // GenerateURIForLike returns the AP URI for a new like/fave -- something like: 86 // https://example.org/users/whatever_user/liked/01F7XTH1QGBAPMGF49WJZ91XGC 87 func GenerateURIForLike(username string, thisFavedID string) string { 88 protocol := config.GetProtocol() 89 host := config.GetHost() 90 return fmt.Sprintf("%s://%s/%s/%s/%s/%s", protocol, host, UsersPath, username, LikedPath, thisFavedID) 91 } 92 93 // GenerateURIForUpdate returns the AP URI for a new update activity -- something like: 94 // https://example.org/users/whatever_user#updates/01F7XTH1QGBAPMGF49WJZ91XGC 95 func GenerateURIForUpdate(username string, thisUpdateID string) string { 96 protocol := config.GetProtocol() 97 host := config.GetHost() 98 return fmt.Sprintf("%s://%s/%s/%s#%s/%s", protocol, host, UsersPath, username, UpdatePath, thisUpdateID) 99 } 100 101 // GenerateURIForBlock returns the AP URI for a new block activity -- something like: 102 // https://example.org/users/whatever_user/blocks/01F7XTH1QGBAPMGF49WJZ91XGC 103 func GenerateURIForBlock(username string, thisBlockID string) string { 104 protocol := config.GetProtocol() 105 host := config.GetHost() 106 return fmt.Sprintf("%s://%s/%s/%s/%s/%s", protocol, host, UsersPath, username, BlocksPath, thisBlockID) 107 } 108 109 // GenerateURIForReport returns the API URI for a new Flag activity -- something like: 110 // https://example.org/reports/01GP3AWY4CRDVRNZKW0TEAMB5R 111 // 112 // This path specifically doesn't contain any info about the user who did the reporting, 113 // to protect their privacy. 114 func GenerateURIForReport(thisReportID string) string { 115 protocol := config.GetProtocol() 116 host := config.GetHost() 117 return fmt.Sprintf("%s://%s/%s/%s", protocol, host, ReportsPath, thisReportID) 118 } 119 120 // GenerateURIForEmailConfirm returns a link for email confirmation -- something like: 121 // https://example.org/confirm_email?token=490e337c-0162-454f-ac48-4b22bb92a205 122 func GenerateURIForEmailConfirm(token string) string { 123 protocol := config.GetProtocol() 124 host := config.GetHost() 125 return fmt.Sprintf("%s://%s/%s?token=%s", protocol, host, ConfirmEmailPath, token) 126 } 127 128 // GenerateURIsForAccount throws together a bunch of URIs for the given username, with the given protocol and host. 129 func GenerateURIsForAccount(username string) *UserURIs { 130 protocol := config.GetProtocol() 131 host := config.GetHost() 132 133 // The below URLs are used for serving web requests 134 hostURL := fmt.Sprintf("%s://%s", protocol, host) 135 userURL := fmt.Sprintf("%s/@%s", hostURL, username) 136 statusesURL := fmt.Sprintf("%s/%s", userURL, StatusesPath) 137 138 // the below URIs are used in ActivityPub and Webfinger 139 userURI := fmt.Sprintf("%s/%s/%s", hostURL, UsersPath, username) 140 statusesURI := fmt.Sprintf("%s/%s", userURI, StatusesPath) 141 inboxURI := fmt.Sprintf("%s/%s", userURI, InboxPath) 142 outboxURI := fmt.Sprintf("%s/%s", userURI, OutboxPath) 143 followersURI := fmt.Sprintf("%s/%s", userURI, FollowersPath) 144 followingURI := fmt.Sprintf("%s/%s", userURI, FollowingPath) 145 likedURI := fmt.Sprintf("%s/%s", userURI, LikedPath) 146 collectionURI := fmt.Sprintf("%s/%s/%s", userURI, CollectionsPath, FeaturedPath) 147 publicKeyURI := fmt.Sprintf("%s/%s", userURI, PublicKeyPath) 148 149 return &UserURIs{ 150 HostURL: hostURL, 151 UserURL: userURL, 152 StatusesURL: statusesURL, 153 154 UserURI: userURI, 155 StatusesURI: statusesURI, 156 InboxURI: inboxURI, 157 OutboxURI: outboxURI, 158 FollowersURI: followersURI, 159 FollowingURI: followingURI, 160 LikedURI: likedURI, 161 FeaturedCollectionURI: collectionURI, 162 PublicKeyURI: publicKeyURI, 163 } 164 } 165 166 // GenerateURIForAttachment generates a URI for an attachment/emoji/header etc. 167 // Will produced something like https://example.org/fileserver/01FPST95B8FC3HG3AGCDKPQNQ2/attachment/original/01FPST9QK4V5XWS3F9Z4F2G1X7.gif 168 func GenerateURIForAttachment(accountID string, mediaType string, mediaSize string, mediaID string, extension string) string { 169 protocol := config.GetProtocol() 170 host := config.GetHost() 171 return fmt.Sprintf("%s://%s/%s/%s/%s/%s/%s.%s", protocol, host, FileserverPath, accountID, mediaType, mediaSize, mediaID, extension) 172 } 173 174 // GenerateURIForEmoji generates an activitypub uri for a new emoji. 175 func GenerateURIForEmoji(emojiID string) string { 176 protocol := config.GetProtocol() 177 host := config.GetHost() 178 return fmt.Sprintf("%s://%s/%s/%s", protocol, host, EmojiPath, emojiID) 179 } 180 181 // IsUserPath returns true if the given URL path corresponds to eg /users/example_username 182 func IsUserPath(id *url.URL) bool { 183 return regexes.UserPath.MatchString(id.Path) 184 } 185 186 // IsInboxPath returns true if the given URL path corresponds to eg /users/example_username/inbox 187 func IsInboxPath(id *url.URL) bool { 188 return regexes.InboxPath.MatchString(id.Path) 189 } 190 191 // IsOutboxPath returns true if the given URL path corresponds to eg /users/example_username/outbox 192 func IsOutboxPath(id *url.URL) bool { 193 return regexes.OutboxPath.MatchString(id.Path) 194 } 195 196 // IsFollowersPath returns true if the given URL path corresponds to eg /users/example_username/followers 197 func IsFollowersPath(id *url.URL) bool { 198 return regexes.FollowersPath.MatchString(id.Path) 199 } 200 201 // IsFollowingPath returns true if the given URL path corresponds to eg /users/example_username/following 202 func IsFollowingPath(id *url.URL) bool { 203 return regexes.FollowingPath.MatchString(id.Path) 204 } 205 206 // IsFollowPath returns true if the given URL path corresponds to eg /users/example_username/follow/SOME_ULID_OF_A_FOLLOW 207 func IsFollowPath(id *url.URL) bool { 208 return regexes.FollowPath.MatchString(id.Path) 209 } 210 211 // IsLikedPath returns true if the given URL path corresponds to eg /users/example_username/liked 212 func IsLikedPath(id *url.URL) bool { 213 return regexes.LikedPath.MatchString(id.Path) 214 } 215 216 // IsLikePath returns true if the given URL path corresponds to eg /users/example_username/liked/SOME_ULID_OF_A_STATUS 217 func IsLikePath(id *url.URL) bool { 218 return regexes.LikePath.MatchString(id.Path) 219 } 220 221 // IsStatusesPath returns true if the given URL path corresponds to eg /users/example_username/statuses/SOME_ULID_OF_A_STATUS 222 func IsStatusesPath(id *url.URL) bool { 223 return regexes.StatusesPath.MatchString(id.Path) 224 } 225 226 // IsPublicKeyPath returns true if the given URL path corresponds to eg /users/example_username/main-key 227 func IsPublicKeyPath(id *url.URL) bool { 228 return regexes.PublicKeyPath.MatchString(id.Path) 229 } 230 231 // IsBlockPath returns true if the given URL path corresponds to eg /users/example_username/blocks/SOME_ULID_OF_A_BLOCK 232 func IsBlockPath(id *url.URL) bool { 233 return regexes.BlockPath.MatchString(id.Path) 234 } 235 236 // IsReportPath returns true if the given URL path corresponds to eg /reports/SOME_ULID_OF_A_REPORT 237 func IsReportPath(id *url.URL) bool { 238 return regexes.ReportPath.MatchString(id.Path) 239 } 240 241 // ParseStatusesPath returns the username and ulid from a path such as /users/example_username/statuses/SOME_ULID_OF_A_STATUS 242 func ParseStatusesPath(id *url.URL) (username string, ulid string, err error) { 243 matches := regexes.StatusesPath.FindStringSubmatch(id.Path) 244 if len(matches) != 3 { 245 err = fmt.Errorf("expected 3 matches but matches length was %d", len(matches)) 246 return 247 } 248 username = matches[1] 249 ulid = matches[2] 250 return 251 } 252 253 // ParseUserPath returns the username from a path such as /users/example_username 254 func ParseUserPath(id *url.URL) (username string, err error) { 255 matches := regexes.UserPath.FindStringSubmatch(id.Path) 256 if len(matches) != 2 { 257 err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches)) 258 return 259 } 260 username = matches[1] 261 return 262 } 263 264 // ParseInboxPath returns the username from a path such as /users/example_username/inbox 265 func ParseInboxPath(id *url.URL) (username string, err error) { 266 matches := regexes.InboxPath.FindStringSubmatch(id.Path) 267 if len(matches) != 2 { 268 err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches)) 269 return 270 } 271 username = matches[1] 272 return 273 } 274 275 // ParseOutboxPath returns the username from a path such as /users/example_username/outbox 276 func ParseOutboxPath(id *url.URL) (username string, err error) { 277 matches := regexes.OutboxPath.FindStringSubmatch(id.Path) 278 if len(matches) != 2 { 279 err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches)) 280 return 281 } 282 username = matches[1] 283 return 284 } 285 286 // ParseFollowersPath returns the username from a path such as /users/example_username/followers 287 func ParseFollowersPath(id *url.URL) (username string, err error) { 288 matches := regexes.FollowersPath.FindStringSubmatch(id.Path) 289 if len(matches) != 2 { 290 err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches)) 291 return 292 } 293 username = matches[1] 294 return 295 } 296 297 // ParseFollowingPath returns the username from a path such as /users/example_username/following 298 func ParseFollowingPath(id *url.URL) (username string, err error) { 299 matches := regexes.FollowingPath.FindStringSubmatch(id.Path) 300 if len(matches) != 2 { 301 err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches)) 302 return 303 } 304 username = matches[1] 305 return 306 } 307 308 // ParseLikedPath returns the username and ulid from a path such as /users/example_username/liked/SOME_ULID_OF_A_STATUS 309 func ParseLikedPath(id *url.URL) (username string, ulid string, err error) { 310 matches := regexes.LikePath.FindStringSubmatch(id.Path) 311 if len(matches) != 3 { 312 err = fmt.Errorf("expected 3 matches but matches length was %d", len(matches)) 313 return 314 } 315 username = matches[1] 316 ulid = matches[2] 317 return 318 } 319 320 // ParseBlockPath returns the username and ulid from a path such as /users/example_username/blocks/SOME_ULID_OF_A_BLOCK 321 func ParseBlockPath(id *url.URL) (username string, ulid string, err error) { 322 matches := regexes.BlockPath.FindStringSubmatch(id.Path) 323 if len(matches) != 3 { 324 err = fmt.Errorf("expected 3 matches but matches length was %d", len(matches)) 325 return 326 } 327 username = matches[1] 328 ulid = matches[2] 329 return 330 } 331 332 // ParseReportPath returns the ulid from a path such as /reports/SOME_ULID_OF_A_REPORT 333 func ParseReportPath(id *url.URL) (ulid string, err error) { 334 matches := regexes.ReportPath.FindStringSubmatch(id.Path) 335 if len(matches) != 2 { 336 err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches)) 337 return 338 } 339 ulid = matches[1] 340 return 341 }