Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 49 additions & 10 deletions MaiChartManager/Controllers/Charts/ChartController.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using Microsoft.AspNetCore.Mvc;
using MaiChartManager.Services;
using Microsoft.AspNetCore.Mvc;

namespace MaiChartManager.Controllers.Charts;

[ApiController]
[Route("MaiChartManagerServlet/[action]Api/{assetDir}/{id:int}/{level:int}")]
public class ChartController(StaticSettings settings, ILogger<StaticSettings> logger) : ControllerBase
public class ChartController(StaticSettings settings, ILogger<StaticSettings> logger, MaidataImportService importService) : ControllerBase
{
[HttpPost]
public void EditChartLevel(int id, int level, [FromBody] int value, string assetDir)
Expand Down Expand Up @@ -95,15 +96,53 @@ public void EditChartEnable(int id, int level, [FromBody] bool value, string ass
}

[HttpPost]
public void ReplaceChart(int id, int level, IFormFile file, string assetDir)
public ImportChartResult ReplaceChart(int id, int level, IFormFile file, string assetDir,
[FromForm] ShiftMethod? shift)
{
var music = settings.GetMusic(id, assetDir);
if (music == null || file == null) return;
var targetChart = music.Charts[level];
targetChart.Path = $"{id:000000}_0{level}.ma2";
using var stream = System.IO.File.Open(Path.Combine(StaticSettings.StreamingAssets, assetDir, "music", $"music{id:000000}", targetChart.Path), FileMode.Create);
file.CopyTo(stream);
targetChart.Problems.Clear();
stream.Close();
if (music == null || file == null) return new ImportChartResult([new ImportChartMessage("文件上传失败", MessageLevel.Fatal)], true);
if (file.FileName.EndsWith(".ma2"))
{
var targetChart = music.Charts[level];
targetChart.Path = $"{id:000000}_0{level}.ma2";
using var stream = System.IO.File.Open(Path.Combine(StaticSettings.StreamingAssets, assetDir, "music", $"music{id:000000}", targetChart.Path), FileMode.Create);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The assetDir parameter is used to construct a file path without validation. An attacker could provide a value like ../../.. to perform a path traversal attack and write files outside the intended directory. This is especially dangerous here as the content of the written file is controlled by the user upload. Consider validating assetDir against an allow-list of expected directory names.

file.CopyTo(stream);
targetChart.Problems.Clear();

// 检查新谱面ma2的音符数量是否有变化,如果有修正之
string fileContent;
using (var reader = new StreamReader(file.OpenReadStream()))
{
fileContent = reader.ReadToEnd();
}
var newMaxNotes = MaidataImportService.ParseTNumAllFromMa2(fileContent);
if (newMaxNotes != 0 && targetChart.MaxNotes != newMaxNotes)
{
targetChart.MaxNotes = newMaxNotes;
}
music.Save();

return new ImportChartResult([], false);
}
else if (file.FileName.EndsWith("maidata.txt"))
{
if (level != -1) throw new NotImplementedException("使用maidata时暂不支持只替换单个难度谱面,只能同时替换全部的");
// 通过此前的谱面的定数是否为0,判断是否需要ignoreLevelNum
bool ignoreLevelNum = true;
foreach (var chart in music.Charts)
{
if (music.Id < 100000 && chart.Enable && chart.Level > 0) ignoreLevelNum = false;
}
var importResult = importService.ImportMaidata(music, file, (ShiftMethod)shift, ignoreLevelNum, false, true);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

shift 参数是可空的 (ShiftMethod?),但在 137 行直接强制转换为 (ShiftMethod)shift。如果前端在提交 maidata.txt 时没有附带 shift 参数,这里会导致运行时抛出 InvalidOperationException。为了增强API的健壮性,建议在转换前处理 null 的情况,例如使用一个默认值。

            var importResult = importService.ImportMaidata(music, file, shift ?? ShiftMethod.Bar, ignoreLevelNum, false, true);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我觉得这里就这么做就ok,正常用户只要不是自己发请求,shift就不该是null。万一是null,报个异常给前端我觉得刚刚好

if (!importResult.Fatal)
{
music.Save();
music.Refresh();
}

return importResult;
}
// 正常来说是不会进到这里的,因为前端已经对文件名做了校验了,所以这个报错用户正常来说是看不到的,就不做i18n了。
else return new ImportChartResult([new ImportChartMessage("不支持的文件格式!", MessageLevel.Fatal)], true);
}
}
Loading