2023-11-16 14:49:05 +01:00
package activitypub
import (
"fmt"
"net/url"
2023-11-23 17:03:24 +01:00
"strconv"
2023-11-16 14:49:05 +01:00
"strings"
2023-11-24 12:48:14 +01:00
2023-12-06 13:06:30 +01:00
"code.gitea.io/gitea/modules/log"
2023-12-08 18:08:54 +01:00
"code.gitea.io/gitea/modules/validation"
2023-11-16 14:49:05 +01:00
)
2023-11-23 14:50:32 +01:00
type Validatable interface { // ToDo: What is the right package for this interface?
2023-11-24 11:37:29 +01:00
validate_is_not_nil ( ) error
2023-11-22 16:08:14 +01:00
validate_is_not_empty ( ) error
2023-11-22 15:25:43 +01:00
Validate ( ) error
2023-11-24 11:37:29 +01:00
IsValid ( ) ( bool , error )
PanicIfInvalid ( )
2023-11-22 15:25:43 +01:00
}
2023-12-08 18:33:26 +01:00
type ActorId struct {
2023-12-08 19:41:22 +01:00
userId string
source string
schema string
path string
host string
port string
unvalidatedInput string
2023-11-16 14:49:05 +01:00
}
2023-12-07 11:24:27 +01:00
func validate_is_not_empty ( str string ) error {
2023-11-24 11:37:29 +01:00
if str == "" {
2023-12-07 11:24:27 +01:00
return fmt . Errorf ( "the given string was empty" )
2023-11-24 11:37:29 +01:00
}
return nil
}
/ *
2023-12-06 11:24:42 +01:00
Validate collects error strings in a slice and returns this
2023-11-24 11:37:29 +01:00
* /
2023-12-08 19:41:22 +01:00
func ( value ActorId ) Validate ( ) [ ] string {
var result = [ ] string { }
result = append ( result , validation . ValidateNotEmpty ( value . userId , "userId" ) ... )
result = append ( result , validation . ValidateNotEmpty ( value . source , "source" ) ... )
result = append ( result , validation . ValidateNotEmpty ( value . schema , "schema" ) ... )
result = append ( result , validation . ValidateNotEmpty ( value . path , "path" ) ... )
result = append ( result , validation . ValidateNotEmpty ( value . host , "host" ) ... )
result = append ( result , validation . ValidateNotEmpty ( value . unvalidatedInput , "unvalidatedInput" ) ... )
result = append ( result , validation . ValidateOneOf ( value . source , [ ] string { "forgejo" , "gitea" } ) ... )
switch value . source {
2023-11-24 12:48:14 +01:00
case "forgejo" , "gitea" :
2023-12-08 19:41:22 +01:00
if ! strings . Contains ( value . path , "api/v1/activitypub/user-id" ) {
result = append ( result , fmt . Sprintf ( "path has to be a api path" ) )
2023-11-24 12:48:14 +01:00
}
2023-11-16 14:49:05 +01:00
}
2023-12-08 19:41:22 +01:00
return result
2023-11-16 14:49:05 +01:00
}
2023-12-06 11:24:42 +01:00
/ *
IsValid concatenates the error messages with newlines and returns them if there are any
* /
2023-12-08 18:33:26 +01:00
func ( a ActorId ) IsValid ( ) ( bool , error ) {
2023-11-24 11:37:29 +01:00
if err := a . Validate ( ) ; len ( err ) > 0 {
errString := strings . Join ( err , "\n" )
return false , fmt . Errorf ( errString )
}
return true , nil
}
2023-12-08 18:33:26 +01:00
func ( a ActorId ) PanicIfInvalid ( ) {
2023-11-24 11:37:29 +01:00
if valid , err := a . IsValid ( ) ; ! valid {
panic ( err )
}
}
2023-12-08 18:33:26 +01:00
func ( a ActorId ) GetUserId ( ) int {
2023-11-24 12:49:36 +01:00
result , err := strconv . Atoi ( a . userId )
if err != nil {
panic ( err )
}
return result
}
2023-12-08 18:33:26 +01:00
func ( a ActorId ) GetNormalizedUri ( ) string {
2023-12-06 09:07:09 +01:00
result := fmt . Sprintf ( "%s://%s:%s/%s/%s" , a . schema , a . host , a . port , a . path , a . userId )
return result
}
2023-11-24 12:49:36 +01:00
// Returns the combination of host:port if port exists, host otherwise
2023-12-08 18:33:26 +01:00
func ( a ActorId ) GetHostAndPort ( ) string {
2023-11-24 12:49:36 +01:00
if a . port != "" {
return strings . Join ( [ ] string { a . host , a . port } , ":" )
}
return a . host
}
2023-12-06 13:06:30 +01:00
func containsEmptyString ( ar [ ] string ) bool {
for _ , elem := range ar {
if elem == "" {
return true
}
}
return false
}
func removeEmptyStrings ( ls [ ] string ) [ ] string {
var rs [ ] string
for _ , str := range ls {
if str != "" {
rs = append ( rs , str )
}
}
return rs
}
2023-12-08 11:54:07 +01:00
func ValidateAndParseIRI ( unvalidatedIRI string ) ( url . URL , error ) { // ToDo: Validate that it is not the same host as ours.
2023-12-07 11:44:59 +01:00
err := validate_is_not_empty ( unvalidatedIRI ) // url.Parse seems to accept empty strings?
if err != nil {
return url . URL { } , err
2023-12-06 16:14:39 +01:00
}
2023-12-07 11:44:59 +01:00
validatedURL , err := url . Parse ( unvalidatedIRI )
2023-11-16 14:49:05 +01:00
if err != nil {
2023-12-07 11:44:59 +01:00
return url . URL { } , err
2023-11-16 14:49:05 +01:00
}
2023-12-07 12:03:28 +01:00
if len ( validatedURL . Path ) <= 1 {
return url . URL { } , fmt . Errorf ( "path was empty" )
}
2023-12-07 11:44:59 +01:00
return * validatedURL , nil
}
// TODO: This parsing is very Person-Specific. We should adjust the name & move to a better location (maybe forgefed package?)
2023-12-08 18:33:26 +01:00
func ParseActorID ( validatedURL url . URL , source string ) ActorId { // ToDo: Turn this into a factory function and do not split parsing and validation rigurously
2023-12-07 11:44:59 +01:00
pathWithUserID := strings . Split ( validatedURL . Path , "/" )
2023-12-06 13:06:30 +01:00
if containsEmptyString ( pathWithUserID ) {
pathWithUserID = removeEmptyStrings ( pathWithUserID )
}
length := len ( pathWithUserID )
pathWithoutUserID := strings . Join ( pathWithUserID [ 0 : length - 1 ] , "/" )
userId := pathWithUserID [ length - 1 ]
log . Info ( "Actor: pathWithUserID: %s" , pathWithUserID )
log . Info ( "Actor: pathWithoutUserID: %s" , pathWithoutUserID )
log . Info ( "Actor: UserID: %s" , userId )
2023-11-16 14:49:05 +01:00
2023-12-08 18:33:26 +01:00
return ActorId { // ToDo: maybe keep original input to validate against (maybe extra method)
2023-11-16 14:49:05 +01:00
userId : userId ,
2023-12-06 15:15:39 +01:00
source : source ,
2023-12-07 11:44:59 +01:00
schema : validatedURL . Scheme ,
host : validatedURL . Hostname ( ) , // u.Host returns hostname:port
2023-12-06 13:06:30 +01:00
path : pathWithoutUserID ,
2023-12-07 11:44:59 +01:00
port : validatedURL . Port ( ) ,
}
2023-11-16 14:49:05 +01:00
}
2023-12-08 18:08:54 +01:00
2023-12-08 18:33:26 +01:00
func NewActorId ( uri string , source string ) ( ActorId , error ) {
2023-12-08 18:08:54 +01:00
if ! validation . IsValidExternalURL ( uri ) {
2023-12-08 18:33:26 +01:00
return ActorId { } , fmt . Errorf ( "uri %s is not a valid external url" , uri )
2023-12-08 18:08:54 +01:00
}
2023-12-08 19:41:22 +01:00
validatedUri , _ := url . Parse ( uri )
pathWithUserID := strings . Split ( validatedUri . Path , "/" )
if containsEmptyString ( pathWithUserID ) {
pathWithUserID = removeEmptyStrings ( pathWithUserID )
}
length := len ( pathWithUserID )
pathWithoutUserID := strings . Join ( pathWithUserID [ 0 : length - 1 ] , "/" )
userId := pathWithUserID [ length - 1 ]
actorId := ActorId {
userId : userId ,
source : source ,
schema : validatedUri . Scheme ,
host : validatedUri . Hostname ( ) ,
path : pathWithoutUserID ,
port : validatedUri . Port ( ) ,
unvalidatedInput : uri ,
}
if valid , err := actorId . IsValid ( ) ; ! valid {
return ActorId { } , err
}
return actorId , nil
2023-12-08 18:08:54 +01:00
}