Skip to content

Commit 3167073

Browse files
committed
don't allow uploading of incorrect addons
1 parent 9f008b1 commit 3167073

File tree

3 files changed

+132
-109
lines changed

3 files changed

+132
-109
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>0.11.0</Version>
3+
<Version>0.11.1</Version>
44
<TargetFramework>net8.0</TargetFramework>
55
<Nullable>enable</Nullable>
66
<LangVersion>latest</LangVersion>

src/Avalonia/Core/ViewModels/DevViewModel.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -739,15 +739,17 @@ private async Task UploadAddonAsync()
739739
return;
740740
}
741741

742-
if (new FileInfo(files[0].Path.LocalPath).Length > 1e+9)
742+
var checkResult = _filesUploader.CheckAddonBeforeUploading(files[0].Path.LocalPath);
743+
744+
if (checkResult is not null)
743745
{
744-
UploadStatus = "Can't upload file larger than 1Gb.";
746+
UploadStatus = checkResult;
745747
return;
746748
}
747749

748-
var result = await _filesUploader.UploadFilesToFtpAsync(files[0].Path.LocalPath, CancellationToken.None);
750+
var uploadResult = await _filesUploader.UploadFilesToFtpAsync(files[0].Path.LocalPath, CancellationToken.None);
749751

750-
if (result)
752+
if (uploadResult)
751753
{
752754
UploadStatus = "Uploaded successfully";
753755
}

src/Mods/FilesUploader.cs

Lines changed: 125 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -4,146 +4,167 @@
44
using Common.Helpers;
55
using Mods.Serializable;
66
using SharpCompress.Archives.Zip;
7-
using System;
8-
using System.Net.Http;
97
using System.Text.Json;
10-
using System.Web;
118

12-
namespace Mods
9+
namespace Mods;
10+
11+
public sealed class FilesUploader
1312
{
14-
public sealed class FilesUploader
13+
private readonly ApiInterface _apiInterface;
14+
15+
16+
public FilesUploader(ApiInterface apiInterface)
1517
{
16-
private readonly ApiInterface _apiInterface;
18+
_apiInterface = apiInterface;
19+
}
1720

1821

19-
public FilesUploader(ApiInterface apiInterface)
22+
public async Task<bool> AddAddonToDatabaseAsync(string pathToFile)
23+
{
24+
var entity = await GetDownloadableAddonDtoAsync(pathToFile);
25+
26+
if (entity is null)
2027
{
21-
_apiInterface = apiInterface;
28+
return false;
2229
}
2330

31+
var result = await _apiInterface.AddAddonToDatabaseAsync(entity);
2432

25-
public async Task<bool> AddAddonToDatabaseAsync(string pathToFile)
33+
return result;
34+
}
35+
36+
37+
private async Task<AddonsJsonEntity?> GetDownloadableAddonDtoAsync(string pathToFile)
38+
{
39+
using var archive = ZipArchive.Open(pathToFile);
40+
var addonJson = archive.Entries.FirstOrDefault(static x => x.Key == "addon.json");
41+
42+
if (addonJson is null)
2643
{
27-
var entity = await GetDownloadableAddonDtoAsync(pathToFile);
44+
return null;
45+
}
2846

29-
if (entity is null)
30-
{
31-
return false;
32-
}
47+
var manifest = JsonSerializer.Deserialize(
48+
addonJson.OpenEntryStream(),
49+
AddonManifestContext.Default.AddonDto
50+
);
3351

34-
var result = await _apiInterface.AddAddonToDatabaseAsync(entity);
52+
FileInfo fileInfo = new(pathToFile);
53+
var fileSize = fileInfo.Length;
3554

36-
return result;
55+
if (manifest is null)
56+
{
57+
return null;
3758
}
3859

60+
var folderName = manifest.AddonType switch
61+
{
62+
AddonTypeEnum.TC => "Campaigns",
63+
AddonTypeEnum.Map => "Maps",
64+
AddonTypeEnum.Mod => "Mods",
65+
_ => throw new NotImplementedException(),
66+
};
3967

40-
private async Task<AddonsJsonEntity?> GetDownloadableAddonDtoAsync(string pathToFile)
68+
var gameName = manifest.SupportedGame.Game switch
4169
{
42-
using var archive = ZipArchive.Open(pathToFile);
43-
var addonJson = archive.Entries.FirstOrDefault(static x => x.Key == "addon.json");
70+
GameEnum.Duke3D => "Duke3D",
71+
GameEnum.Duke64 => "Duke64",
72+
GameEnum.Blood => "Blood",
73+
GameEnum.ShadowWarrior => "Wang",
74+
GameEnum.Fury => "Fury",
75+
GameEnum.Exhumed => "Slave",
76+
GameEnum.NAM => "NAM",
77+
GameEnum.WWIIGI => "WWII",
78+
GameEnum.Redneck => "Redneck",
79+
GameEnum.RidesAgain => "Redneck",
80+
GameEnum.TekWar => "TekWar",
81+
GameEnum.Witchaven => "WH",
82+
GameEnum.Witchaven2 => "WH2",
83+
_ => throw new NotImplementedException(),
84+
};
85+
86+
var downloadUrl = $"{Consts.FilesRepo}/{gameName}/{folderName}/{Path.GetFileName(pathToFile)}";
87+
88+
using HttpClient httpClient = new();
89+
var response = await httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead);
90+
91+
if (!response.IsSuccessStatusCode)
92+
{
93+
return null;
94+
}
4495

45-
if (addonJson is null)
46-
{
47-
return null;
48-
}
96+
AddonsJsonEntity downMod = new()
97+
{
98+
Id = manifest.Id,
99+
Title = manifest.Title,
100+
DownloadUrl = downloadUrl,
101+
Game = manifest.SupportedGame.Game,
102+
AddonType = manifest.AddonType,
103+
Version = manifest.Version,
104+
Description = manifest.Description,
105+
Author = manifest.Author,
106+
FileSize = fileSize,
107+
Dependencies = manifest.Dependencies?.Addons is null ? null : manifest.Dependencies.Addons.ToDictionary(x => x.Id, y => y.Version)
108+
};
109+
110+
return downMod;
111+
}
49112

50-
var manifest = JsonSerializer.Deserialize(
51-
addonJson.OpenEntryStream(),
52-
AddonManifestContext.Default.AddonDto
53-
);
113+
public async Task<bool> UploadFilesToFtpAsync(string pathToFile, CancellationToken cancellationToken)
114+
{
115+
using HttpClient httpClient = new() { Timeout = Timeout.InfiniteTimeSpan };
54116

55-
FileInfo fileInfo = new(pathToFile);
56-
var fileSize = fileInfo.Length;
117+
try
118+
{
119+
var path = "buildlauncher_uploads/" + Guid.NewGuid() + "/" + Path.GetFileName(pathToFile);
57120

58-
if (manifest is null)
59-
{
60-
return null;
61-
}
121+
var signedUrl = await _apiInterface.GetSignedUrlAsync(path);
62122

63-
var folderName = manifest.AddonType switch
64-
{
65-
AddonTypeEnum.TC => "Campaigns",
66-
AddonTypeEnum.Map => "Maps",
67-
AddonTypeEnum.Mod => "Mods",
68-
_ => throw new NotImplementedException(),
69-
};
123+
using var fileStream = File.OpenRead(pathToFile);
124+
using StreamContent content = new(fileStream);
70125

71-
var gameName = manifest.SupportedGame.Game switch
72-
{
73-
GameEnum.Duke3D => "Duke3D",
74-
GameEnum.Duke64 => "Duke64",
75-
GameEnum.Blood => "Blood",
76-
GameEnum.ShadowWarrior => "Wang",
77-
GameEnum.Fury => "Fury",
78-
GameEnum.Exhumed => "Slave",
79-
GameEnum.NAM => "NAM",
80-
GameEnum.WWIIGI => "WWII",
81-
GameEnum.Redneck => "Redneck",
82-
GameEnum.RidesAgain => "Redneck",
83-
GameEnum.TekWar => "TekWar",
84-
GameEnum.Witchaven => "WH",
85-
GameEnum.Witchaven2 => "WH2",
86-
_ => throw new NotImplementedException(),
87-
};
88-
89-
var downloadUrl = $"{Consts.FilesRepo}/{gameName}/{folderName}/{Path.GetFileName(pathToFile)}";
90-
91-
using HttpClient httpClient = new();
92-
var response = await httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead);
126+
using var response = await httpClient.PutAsync(signedUrl, content, cancellationToken).ConfigureAwait(false);
93127

94128
if (!response.IsSuccessStatusCode)
95129
{
96-
return null;
130+
var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
131+
return false;
97132
}
98-
99-
AddonsJsonEntity downMod = new()
100-
{
101-
Id = manifest.Id,
102-
Title = manifest.Title,
103-
DownloadUrl = downloadUrl,
104-
Game = manifest.SupportedGame.Game,
105-
AddonType = manifest.AddonType,
106-
Version = manifest.Version,
107-
Description = manifest.Description,
108-
Author = manifest.Author,
109-
FileSize = fileSize,
110-
Dependencies = manifest.Dependencies?.Addons is null ? null : manifest.Dependencies.Addons.ToDictionary(x => x.Id, y => y.Version)
111-
};
112-
113-
return downMod;
114133
}
115-
116-
public async Task<bool> UploadFilesToFtpAsync(string pathToFile, CancellationToken cancellationToken)
134+
catch (TaskCanceledException)
117135
{
118-
using HttpClient httpClient = new() { Timeout = Timeout.InfiniteTimeSpan };
136+
return false;
137+
}
138+
catch (Exception)
139+
{
140+
return false;
141+
}
119142

120-
try
121-
{
122-
var path = "buildlauncher_uploads/" + Guid.NewGuid() + "/" + Path.GetFileName(pathToFile);
143+
return true;
144+
}
123145

124-
var signedUrl = await _apiInterface.GetSignedUrlAsync(path);
146+
public string? CheckAddonBeforeUploading(string pathToFile)
147+
{
148+
if (new FileInfo(pathToFile).Length > 1e+9)
149+
{
150+
return "Can't upload file larger than 1Gb.";
151+
}
125152

126-
using var fileStream = File.OpenRead(pathToFile);
127-
using StreamContent content = new(fileStream);
153+
var isArchive = ZipArchive.IsZipFile(pathToFile);
128154

129-
using var response = await httpClient.PutAsync(signedUrl, content, cancellationToken).ConfigureAwait(false);
155+
if (!isArchive)
156+
{
157+
return "File is not an archive";
158+
}
130159

131-
if (!response.IsSuccessStatusCode)
132-
{
133-
var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
134-
return false;
135-
}
136-
}
137-
catch (TaskCanceledException)
138-
{
139-
return false;
140-
}
141-
catch (Exception ex)
142-
{
143-
return false;
144-
}
160+
using var archive = ZipArchive.Open(pathToFile);
161+
var jsonExists = archive.Entries.Any(static x => x.Key.Equals("addon.json"));
145162

146-
return true;
163+
if (!jsonExists)
164+
{
165+
return "Archive doesn't have addon.json";
147166
}
167+
168+
return null;
148169
}
149170
}

0 commit comments

Comments
 (0)