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
13 changes: 13 additions & 0 deletions MaiChartManager/Controllers/Music/MusicController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ public void EditMusicName(int id, [FromBody] string value, string assetDir)
music.Name = value;
}
}

[HttpPost]
public string EditMusicSortName(int id, [FromBody] string value, string assetDir)
{
var music = settings.GetMusic(id, assetDir);
if (music != null)
{
music.SortName = value;
return music.SortName; // 因为music.SortName的setter中会对内容做格式化、确保符合要求。所以把转化的结果返回前端供前端更新。
}

return "";
}
Comment on lines +32 to +42

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 directly obtained from the route and used to retrieve music data. This is highly susceptible to a Path Traversal vulnerability, as an attacker could use .. sequences to access or manipulate files outside the intended directory. Additionally, when a music track is not found, the API currently returns an empty string "". This can lead to unexpected frontend behavior, potentially clearing the sortName field. It's more RESTful and robust to return NotFound() (HTTP 404) to clearly indicate that the resource was not found. This change will also require adding a try-catch block in the frontend's sync function to handle API errors.

    public ActionResult<string> EditMusicSortName(int id, [FromBody] string value, string assetDir)
    {
        // TODO: Implement validation for 'assetDir' to prevent Path Traversal vulnerability.
        var music = settings.GetMusic(id, assetDir);
        if (music != null)
        {
            music.SortName = value;
            return music.SortName; // 因为music.SortName的setter中会对内容做格式化、确保符合要求。所以把转化的结果返回前端供前端更新。
        }

        return NotFound();
    }


[HttpPost]
public void EditMusicArtist(int id, [FromBody] string value, string assetDir)
Expand Down
18 changes: 18 additions & 0 deletions MaiChartManager/Front/src/client/apiGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ export interface MusicXmlWithABJacket {
nonDxId?: number;
modified?: boolean;
name?: string | null;
sortName?: string | null;
/** @format int32 */
genreId?: number;
/** @format int32 */
Expand Down Expand Up @@ -1593,6 +1594,23 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
...params,
}),

/**
* No description
*
* @tags Music
* @name EditMusicSortName
* @request POST:/MaiChartManagerServlet/EditMusicSortNameApi/{assetDir}/{id}
*/
EditMusicSortName: (id: number, assetDir: string, data: string, params: RequestParams = {}) =>
this.request<string, any>({
path: `/MaiChartManagerServlet/EditMusicSortNameApi/${assetDir}/${id}`,
method: "POST",
body: data,
type: ContentType.Json,
format: "json",
...params,
}),

/**
* No description
*
Expand Down
20 changes: 17 additions & 3 deletions MaiChartManager/Front/src/components/MusicEdit/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { computed, defineComponent, onMounted, PropType, ref, watch } from "vue";
import { Chart, GenreXml, MusicXmlWithABJacket } from "@/client/apiGen";
import { Chart, GenreXml, MusicXmlWithABJacket, ShiftMethod } from "@/client/apiGen";
import { addVersionList, genreList, globalCapture, selectedADir, selectedMusic as info, selectMusicId, updateAddVersionList, updateGenreList, updateMusicList, selectedLevel } from "@/store/refs";
import api from "@/client/api";
import { NButton, NFlex, NForm, NFormItem, NInput, NInputNumber, NSelect, NSwitch, NTabPane, NTabs, SelectOption, useDialog, useMessage } from "naive-ui";
import { NButton, NFlex, NForm, NFormItem, NInput, NInputNumber, NPopover, NRadio, NSelect, NSwitch, NTabPane, NTabs, SelectOption, useDialog, useMessage } from "naive-ui";
import JacketBox from "../JacketBox";
import dxIcon from "@/assets/dxIcon.png";
import stdIcon from "@/assets/stdIcon.png";
Expand All @@ -29,7 +29,12 @@ const Component = defineComponent({
const sync = (key: keyof MusicXmlWithABJacket, method: Function) => async () => {
if (!info.value) return;
info.value!.modified = true;
await method(info.value.id!, info.value.assetDir, (info.value as any)[key]!);
const value = (info.value as any)[key];
const result = (await method(info.value.id!, info.value.assetDir, value)).data;
if (key === "sortName" && typeof result === "string" && result !== value) {
// 如果调用的是sortName接口,且返回的字符串(经过格式化后的实际内容)和传过去的值不同的话,则覆盖之
info.value!.sortName = result;
}
}
Comment on lines 29 to 38

Choose a reason for hiding this comment

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

critical

sync 函数目前没有处理API调用失败的情况。如果像 EditMusicSortName 这样的API调用因为找不到资源而返回404错误(如上一个评论建议的),await会抛出异常,导致一个未处理的 promise rejection。这可能会使应用处于不稳定状态。建议在 sync 函数中添加 try-catch 块来优雅地处理这些错误,并向用户显示错误信息。

Suggested change
const sync = (key: keyof MusicXmlWithABJacket, method: Function) => async () => {
if (!info.value) return;
info.value!.modified = true;
await method(info.value.id!, info.value.assetDir, (info.value as any)[key]!);
const value = (info.value as any)[key];
const result = (await method(info.value.id!, info.value.assetDir, value)).data;
if (key === "sortName" && typeof result === "string" && result !== value) {
// 如果调用的是sortName接口,且返回的字符串(经过格式化后的实际内容)和传过去的值不同的话,则覆盖之
info.value!.sortName = result;
}
}
const sync = (key: keyof MusicXmlWithABJacket, method: Function) => async () => {
if (!info.value) return;
info.value!.modified = true;
const value = (info.value as any)[key];
try {
const result = (await method(info.value.id!, info.value.assetDir, value)).data;
if (key === "sortName" && typeof result === "string" && result !== value) {
// 如果调用的是sortName接口,且返回的字符串(经过格式化后的实际内容)和传过去的值不同的话,则覆盖之
info.value!.sortName = result;
}
} catch (e) {
console.error(`Failed to sync ${key}:`, e);
message.error(t('error.unknown'));
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

是不是其实也没啥,在浏览器里unhandled promise rejection本质上和把错误console.error到控制台是一样的,(又不是node, unhandled promise rejection会崩)我是懒得改了


watch(() => info.value?.name, sync('name', api.EditMusicName));
Expand All @@ -41,6 +46,7 @@ const Component = defineComponent({
watch(() => info.value?.utageKanji, sync('utageKanji', api.EditMusicUtageKanji));
watch(() => info.value?.comment, sync('comment', api.EditMusicComment));
watch(() => info.value?.longMusic, sync('longMusic', api.EditMusicLong));
watch(() => info.value?.sortName, sync('sortName', api.EditMusicSortName))

onMounted(()=>{
if ('mediaSession' in navigator) {
Expand Down Expand Up @@ -103,6 +109,14 @@ const Component = defineComponent({
<NInput v-model:value={info.value.comment}/>
</NFormItem>
</>}
<NFormItem label={t('music.edit.sortName')}>
<NPopover trigger="hover">
{{
trigger: () => <NInput v-model:value={info.value!.sortName} class="w-0 grow"/>,
default: () => <div>{t('music.edit.sortNameTips')}</div>
}}
</NPopover>
</NFormItem>
<AcbAwb song={info.value}/>
<NTabs type="line" animated barWidth={0} v-model:value={selectedLevel.value} class="levelTabs"
style={{'--n-tab-padding': 0, '--n-pane-padding-top': 0, '--n-tab-text-color-hover': ''}}>
Expand Down
2 changes: 2 additions & 0 deletions MaiChartManager/Front/src/locales/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ music:
addVersion: Add Version
utageType: Utage Type
utageComment: Utage Comment
sortName: In-game SortName
sortNameTips: May only contain digits, uppercase letters, and katakana; invalid characters will be automatically replaced
jacket: Jacket
audioPreview: Preview
editPreview: Edit Preview
Expand Down
2 changes: 2 additions & 0 deletions MaiChartManager/Front/src/locales/zh-TW.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ music:
addVersion: 追加版本
utageType: 宴譜種類
utageComment: 宴譜備註
sortName: 遊戲內排序名
sortNameTips: 只能包含數字、大寫字母、片假名(若有不合要求的字元,會被自動替換)
jacket: 封面
audioPreview: 試聽
editPreview: 編輯預覽
Expand Down
2 changes: 2 additions & 0 deletions MaiChartManager/Front/src/locales/zh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ music:
addVersion: 追加版本
utageType: 宴谱种类
utageComment: 宴谱备注
sortName: 游戏内排序名
sortNameTips: 只能包含数字、大写字母、片假名(如有不合要求的字符,会被自动替换)
jacket: 封面
audioPreview: 试听
editPreview: 编辑预览
Expand Down
10 changes: 10 additions & 0 deletions MaiChartManager/Models/MusicXml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,16 @@ public string Name
RootNode.SelectSingleNode("name/str").InnerText = value;
RootNode.SelectSingleNode("movieName/str").InnerText = value;
RootNode.SelectSingleNode("cueName/str").InnerText = value;
SortName = value;
}
}

public string SortName
{
get => RootNode.SelectSingleNode("sortName")?.InnerText ?? "";
set
{
Modified = true;
RootNode.SelectSingleNode("sortName").InnerText = ConvertSortName(value);
}
}
Expand Down