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);
if (!importResult.Fatal)
{
music.Save();
music.Refresh();
}

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