Skip to content

Commit 4900a27

Browse files
committed
Refactor API icon validation
1 parent 2a628a5 commit 4900a27

6 files changed

Lines changed: 67 additions & 91 deletions

File tree

Refresh.Interfaces.APIv3/Endpoints/Admin/AdminLevelApiEndpoints.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Refresh.Interfaces.APIv3.Endpoints.ApiTypes.Errors;
1414
using Refresh.Interfaces.APIv3.Endpoints.DataTypes.Request;
1515
using Refresh.Interfaces.APIv3.Endpoints.DataTypes.Response.Levels;
16+
using Refresh.Interfaces.APIv3.Extensions;
1617

1718
namespace Refresh.Interfaces.APIv3.Endpoints.Admin;
1819

@@ -54,9 +55,8 @@ public ApiResponse<ApiGameLevelResponse> EditLevelById(RequestContext context, G
5455
GameLevel? level = database.GetLevelById(id);
5556
if (level == null) return ApiNotFoundError.LevelMissingError;
5657

57-
if (body.IconHash != null && body.IconHash.StartsWith('g') &&
58-
!dataContext.GuidChecker.IsTextureGuid(level.GameVersion, long.Parse(body.IconHash)))
59-
return ApiValidationError.InvalidTextureGuidError;
58+
(body.IconHash, ApiError? iconError) = body.IconHash.ValidateIcon(dataContext);
59+
if (iconError != null) return iconError;
6060

6161
// Trim title and description
6262
if (body.Title != null && body.Title.Length > UgcLimits.TitleLimit)

Refresh.Interfaces.APIv3/Endpoints/Admin/AdminUserApiEndpoints.cs

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -157,29 +157,14 @@ public ApiResponse<ApiExtendedGameUserResponse> UpdateUser(RequestContext contex
157157
return ApiValidationError.WrongRoleUpdateMethodError;
158158
}
159159

160-
if (body.IconHash != null)
161-
{
162-
if (body.IconHash.IsBlankHash())
163-
body.IconHash = "0";
164-
else if (database.GetAssetFromHash(body.IconHash) == null)
165-
return ApiNotFoundError.IconMissingError;
166-
}
160+
(body.IconHash, ApiError? mainIconError) = body.IconHash.ValidateIcon(dataContext);
161+
if (mainIconError != null) return mainIconError;
167162

168-
if (body.VitaIconHash != null)
169-
{
170-
if (body.VitaIconHash.IsBlankHash())
171-
body.VitaIconHash = "0";
172-
else if (database.GetAssetFromHash(body.VitaIconHash) == null)
173-
return ApiNotFoundError.IconMissingError;
174-
}
163+
(body.VitaIconHash, ApiError? vitaIconError) = body.VitaIconHash.ValidateIcon(dataContext);
164+
if (vitaIconError != null) return vitaIconError;
175165

176-
if (body.BetaIconHash != null)
177-
{
178-
if (body.BetaIconHash.IsBlankHash())
179-
body.BetaIconHash = "0";
180-
else if (database.GetAssetFromHash(body.BetaIconHash) == null)
181-
return ApiNotFoundError.IconMissingError;
182-
}
166+
(body.BetaIconHash, ApiError? betaIconError) = body.BetaIconHash.ValidateIcon(dataContext);
167+
if (betaIconError != null) return betaIconError;
183168

184169
// Do nothing if the username entered is actually the same as the one already set
185170
if (body.Username != null && body.Username != targetUser.Username)

Refresh.Interfaces.APIv3/Endpoints/LevelApiEndpoints.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using Refresh.Interfaces.APIv3.Endpoints.ApiTypes.Errors;
1818
using Refresh.Interfaces.APIv3.Endpoints.DataTypes.Request;
1919
using Refresh.Interfaces.APIv3.Endpoints.DataTypes.Response.Levels;
20+
using Refresh.Interfaces.APIv3.Extensions;
2021

2122
namespace Refresh.Interfaces.APIv3.Endpoints;
2223

@@ -61,9 +62,8 @@ public ApiResponse<ApiGameLevelResponse> EditLevelById(RequestContext context,
6162
if (level.Publisher?.UserId != dataContext.User!.UserId)
6263
return ApiAuthenticationError.NoPermissionsForObject;
6364

64-
if (body.IconHash != null && body.IconHash.StartsWith('g') &&
65-
!dataContext.GuidChecker.IsTextureGuid(level.GameVersion, long.Parse(body.IconHash)))
66-
return ApiValidationError.InvalidTextureGuidError;
65+
(body.IconHash, ApiError? iconError) = body.IconHash.ValidateIcon(dataContext);
66+
if (iconError != null) return iconError;
6767

6868
// Trim title and description
6969
if (body.Title != null && body.Title.Length > UgcLimits.TitleLimit)

Refresh.Interfaces.APIv3/Endpoints/PlaylistApiEndpoints.cs

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,25 @@
44
using Bunkum.Core.RateLimit;
55
using Bunkum.Protocols.Http;
66
using Refresh.Common.Constants;
7-
using Refresh.Core.Services;
87
using Refresh.Core.Types.Data;
9-
using Refresh.Database;
10-
using Refresh.Database.Models.Assets;
11-
using Refresh.Database.Models.Authentication;
128
using Refresh.Database.Models.Levels;
139
using Refresh.Database.Models.Playlists;
1410
using Refresh.Database.Models.Users;
1511
using Refresh.Interfaces.APIv3.Endpoints.ApiTypes;
1612
using Refresh.Interfaces.APIv3.Endpoints.ApiTypes.Errors;
1713
using Refresh.Interfaces.APIv3.Endpoints.DataTypes.Request;
1814
using Refresh.Interfaces.APIv3.Endpoints.DataTypes.Response.Playlists;
15+
using Refresh.Interfaces.APIv3.Extensions;
1916
using static Refresh.Core.RateLimits.PlaylistEndpointLimits;
2017

2118
namespace Refresh.Interfaces.APIv3.Endpoints;
2219

2320
public class PlaylistApiEndpoints : EndpointGroup
2421
{
25-
private ApiError? ValidatePlaylist(ApiPlaylistCreationRequest body, GuidCheckerService guidChecker, GameDatabaseContext database)
22+
private ApiError? ValidatePlaylist(ApiPlaylistCreationRequest body, DataContext dataContext)
2623
{
27-
if (body.Icon != null)
28-
{
29-
if (body.Icon.IsBlankHash())
30-
body.Icon = "0";
31-
32-
else if (body.Icon!.StartsWith('g') && body.Icon.Length > 1)
33-
{
34-
bool isGuid = long.TryParse(body.Icon[1..], out long guid);
35-
if (!isGuid || (isGuid && !guidChecker.IsTextureGuid(TokenGame.LittleBigPlanet1, guid)))
36-
return ApiValidationError.InvalidTextureGuidError;
37-
}
38-
else
39-
{
40-
GameAsset? icon = database.GetAssetFromHash(body.Icon);
41-
42-
if (icon == null)
43-
return ApiValidationError.IconMissingError;
44-
45-
if (icon.AssetType is not GameAssetType.Jpeg and not GameAssetType.Png)
46-
return ApiValidationError.IconMustBeImageError;
47-
}
48-
}
24+
(body.Icon, ApiError? iconError) = body.Icon.ValidateIcon(dataContext);
25+
if (iconError != null) return iconError;
4926

5027
// Trim name and description
5128
if (body.Name != null && body.Name.Length > UgcLimits.TitleLimit)
@@ -78,7 +55,7 @@ public class PlaylistApiEndpoints : EndpointGroup
7855
public ApiResponse<ApiGamePlaylistResponse> CreatePlaylist(RequestContext context, DataContext dataContext,
7956
GameUser user, ApiPlaylistCreationRequest body)
8057
{
81-
ApiError? error = this.ValidatePlaylist(body, dataContext.GuidChecker, dataContext.Database);
58+
ApiError? error = this.ValidatePlaylist(body, dataContext);
8259
if (error != null) return error;
8360

8461
string? parentIdStr = context.QueryString.Get("parentId");
@@ -125,7 +102,7 @@ public ApiResponse<ApiGamePlaylistResponse> UpdatePlaylist(RequestContext contex
125102
if (user.UserId != playlist.PublisherId)
126103
return ApiValidationError.NoPlaylistEditPermissionError;
127104

128-
ApiError? error = this.ValidatePlaylist(body, dataContext.GuidChecker, dataContext.Database);
105+
ApiError? error = this.ValidatePlaylist(body, dataContext);
129106
if (error != null) return error;
130107

131108
playlist = dataContext.Database.UpdatePlaylist(playlist, body);

Refresh.Interfaces.APIv3/Endpoints/UserApiEndpoints.cs

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using Refresh.Interfaces.APIv3.Endpoints.ApiTypes.Errors;
1818
using Refresh.Interfaces.APIv3.Endpoints.DataTypes.Request;
1919
using Refresh.Interfaces.APIv3.Endpoints.DataTypes.Response.Users;
20+
using Refresh.Interfaces.APIv3.Extensions;
2021

2122
namespace Refresh.Interfaces.APIv3.Endpoints;
2223

@@ -86,43 +87,14 @@ public ApiResponse<ApiExtendedGameUserResponse> UpdateUser(RequestContext contex
8687
GameUser user, ApiUpdateUserRequest body, IDataStore dataStore, DataContext dataContext, IntegrationConfig integrationConfig,
8788
SmtpService smtpService)
8889
{
89-
// If any icon is requested to be reset, force its hash to be a specific value,
90-
// to not allow uncontrolled values which would still count as blank/empty hash (e.g. unlimited whitespaces)
91-
if (body.IconHash != null)
92-
{
93-
if (body.IconHash.IsBlankHash())
94-
{
95-
body.IconHash = "0";
96-
}
97-
else if (database.GetAssetFromHash(body.IconHash) == null)
98-
{
99-
return ApiNotFoundError.Instance;
100-
}
101-
}
90+
(body.IconHash, ApiError? mainIconError) = body.IconHash.ValidateIcon(dataContext);
91+
if (mainIconError != null) return mainIconError;
10292

103-
if (body.VitaIconHash != null)
104-
{
105-
if (body.VitaIconHash.IsBlankHash())
106-
{
107-
body.VitaIconHash = "0";
108-
}
109-
else if (database.GetAssetFromHash(body.VitaIconHash) == null)
110-
{
111-
return ApiNotFoundError.Instance;
112-
}
113-
}
93+
(body.VitaIconHash, ApiError? vitaIconError) = body.VitaIconHash.ValidateIcon(dataContext);
94+
if (vitaIconError != null) return vitaIconError;
11495

115-
if (body.BetaIconHash != null)
116-
{
117-
if (body.BetaIconHash.IsBlankHash())
118-
{
119-
body.BetaIconHash = "0";
120-
}
121-
else if (database.GetAssetFromHash(body.BetaIconHash) == null)
122-
{
123-
return ApiNotFoundError.Instance;
124-
}
125-
}
96+
(body.BetaIconHash, ApiError? betaIconError) = body.BetaIconHash.ValidateIcon(dataContext);
97+
if (betaIconError != null) return betaIconError;
12698

12799
if (body.EmailAddress != null && !smtpService.CheckEmailDomainValidity(body.EmailAddress))
128100
return ApiValidationError.EmailDoesNotActuallyExistError;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using Refresh.Core.Types.Data;
2+
using Refresh.Database.Models.Assets;
3+
using Refresh.Database.Models.Authentication;
4+
using Refresh.Interfaces.APIv3.Endpoints.ApiTypes;
5+
using Refresh.Interfaces.APIv3.Endpoints.ApiTypes.Errors;
6+
7+
namespace Refresh.Interfaces.APIv3.Extensions;
8+
9+
public static class StringExtensions
10+
{
11+
/// <summary>
12+
/// Validates the given icon reference (may be null, blank, a GUID or a hash).
13+
/// Returns the icon reference to use, and an ApiError if validation failed.
14+
/// </summary>
15+
public static (string?, ApiError?) ValidateIcon(this string? iconReference, DataContext dataContext)
16+
{
17+
if (iconReference == null)
18+
return (null, null);
19+
20+
else if (iconReference.IsBlankHash())
21+
return ("0", null);
22+
23+
else if (iconReference.StartsWith('g') && iconReference.Length > 1)
24+
{
25+
bool isGuid = long.TryParse(iconReference[1..], out long guid);
26+
if (!isGuid || (isGuid && !dataContext.GuidChecker.IsTextureGuid(TokenGame.LittleBigPlanet1, guid)))
27+
return (null, ApiValidationError.InvalidTextureGuidError);
28+
}
29+
else
30+
{
31+
GameAsset? asset = dataContext.Database.GetAssetFromHash(iconReference);
32+
33+
if (asset == null)
34+
return (null, ApiValidationError.IconMissingError);
35+
36+
if (asset.AssetType is not GameAssetType.Jpeg and not GameAssetType.Png)
37+
return (null, ApiValidationError.IconMustBeImageError);
38+
}
39+
40+
return (iconReference, null);
41+
}
42+
}

0 commit comments

Comments
 (0)