adding file name validation

This commit is contained in:
2026-01-05 10:30:26 -07:00
parent 8c01cb2422
commit 767528560c
5 changed files with 63 additions and 48 deletions

View File

@@ -17,6 +17,7 @@ import {
getDateFromStringOrThrow, getDateFromStringOrThrow,
} from "@/features/local/utils/timeUtils"; } from "@/features/local/utils/timeUtils";
import { useCreateAssignmentMutation } from "@/features/local/assignments/assignmentHooks"; import { useCreateAssignmentMutation } from "@/features/local/assignments/assignmentHooks";
import { validateFileName } from "@/services/fileNameValidation";
export default function NewItemForm({ export default function NewItemForm({
moduleName: defaultModuleName, moduleName: defaultModuleName,
@@ -41,20 +42,6 @@ export default function NewItemForm({
const [name, setName] = useState(""); const [name, setName] = useState("");
const [nameError, setNameError] = useState(""); const [nameError, setNameError] = useState("");
const validateFileName = (fileName: string): string => {
// Check for invalid file system characters
const invalidChars = [":", "/", "\\", "*", '"', "<", ">", "|"];
for (const char of fileName) {
if (invalidChars.includes(char)) {
return `Name contains invalid character: "${char}". Please avoid: ${invalidChars.join(
" "
)}`;
}
}
return "";
};
const handleNameChange = (newName: string) => { const handleNameChange = (newName: string) => {
setName(newName); setName(newName);
const error = validateFileName(newName); const error = validateFileName(newName);

View File

@@ -10,6 +10,7 @@ import { getCoursePathByName } from "../globalSettings/globalSettingsFileStorage
import { promises as fs } from "fs"; import { promises as fs } from "fs";
import { courseItemFileStorageService } from "../course/courseItemFileStorageService"; import { courseItemFileStorageService } from "../course/courseItemFileStorageService";
import { assignmentMarkdownSerializer } from "./models/utils/assignmentMarkdownSerializer"; import { assignmentMarkdownSerializer } from "./models/utils/assignmentMarkdownSerializer";
import { assertValidFileName } from "@/services/fileNameValidation";
export const assignmentRouter = router({ export const assignmentRouter = router({
getAssignment: publicProcedure getAssignment: publicProcedure
@@ -133,16 +134,7 @@ export async function updateOrCreateAssignmentFile({
assignmentName: string; assignmentName: string;
assignment: LocalAssignment; assignment: LocalAssignment;
}) { }) {
const illegalCharacters = ["<", ">", ":", '"', "/", "\\", "|", "?", "*"]; assertValidFileName(assignmentName);
const foundIllegalCharacters = illegalCharacters.filter((char) =>
assignmentName.includes(char)
);
if (foundIllegalCharacters.length > 0) {
throw new Error(
`"${assignmentName}" cannot contain the following characters: ${foundIllegalCharacters.join(
" "
)}`
);
} }
const courseDirectory = await getCoursePathByName(courseName); const courseDirectory = await getCoursePathByName(courseName);

View File

@@ -1,11 +1,16 @@
import publicProcedure from "../../../services/serverFunctions/publicProcedure"; import publicProcedure from "../../../services/serverFunctions/publicProcedure";
import { z } from "zod"; import { z } from "zod";
import { router } from "../../../services/serverFunctions/trpcSetup"; import { router } from "../../../services/serverFunctions/trpcSetup";
import { LocalCoursePage, localPageMarkdownUtils, zodLocalCoursePage } from "@/features/local/pages/localCoursePageModels"; import {
LocalCoursePage,
localPageMarkdownUtils,
zodLocalCoursePage,
} from "@/features/local/pages/localCoursePageModels";
import { courseItemFileStorageService } from "../course/courseItemFileStorageService"; import { courseItemFileStorageService } from "../course/courseItemFileStorageService";
import { promises as fs } from "fs"; import { promises as fs } from "fs";
import path from "path"; import path from "path";
import { getCoursePathByName } from "../globalSettings/globalSettingsFileStorageService"; import { getCoursePathByName } from "../globalSettings/globalSettingsFileStorageService";
import { assertValidFileName } from "@/services/fileNameValidation";
export const pageRouter = router({ export const pageRouter = router({
getPage: publicProcedure getPage: publicProcedure
@@ -125,6 +130,7 @@ export async function updatePageFile({
pageName: string; pageName: string;
page: LocalCoursePage; page: LocalCoursePage;
}) { }) {
assertValidFileName(pageName);
const courseDirectory = await getCoursePathByName(courseName); const courseDirectory = await getCoursePathByName(courseName);
const folder = path.join(courseDirectory, moduleName, "pages"); const folder = path.join(courseDirectory, moduleName, "pages");
await fs.mkdir(folder, { recursive: true }); await fs.mkdir(folder, { recursive: true });

View File

@@ -10,6 +10,7 @@ import path from "path";
import { promises as fs } from "fs"; import { promises as fs } from "fs";
import { quizMarkdownUtils } from "./models/utils/quizMarkdownUtils"; import { quizMarkdownUtils } from "./models/utils/quizMarkdownUtils";
import { courseItemFileStorageService } from "../course/courseItemFileStorageService"; import { courseItemFileStorageService } from "../course/courseItemFileStorageService";
import { assertValidFileName } from "@/services/fileNameValidation";
export const quizRouter = router({ export const quizRouter = router({
getQuiz: publicProcedure getQuiz: publicProcedure
@@ -149,6 +150,7 @@ export async function updateQuizFile({
quizName: string; quizName: string;
quiz: LocalQuiz; quiz: LocalQuiz;
}) { }) {
assertValidFileName(quizName);
const courseDirectory = await getCoursePathByName(courseName); const courseDirectory = await getCoursePathByName(courseName);
const folder = path.join(courseDirectory, moduleName, "quizzes"); const folder = path.join(courseDirectory, moduleName, "quizzes");
await fs.mkdir(folder, { recursive: true }); await fs.mkdir(folder, { recursive: true });

View File

@@ -0,0 +1,28 @@
export function validateFileName(fileName: string): string {
if (!fileName || fileName.trim() === "") {
return "Name cannot be empty";
}
const invalidChars = [":", "/", "\\", "*", "?", '"', "<", ">", "|"];
for (const char of fileName) {
if (invalidChars.includes(char)) {
return `Name contains invalid character: "${char}". Please avoid: ${invalidChars.join(
" "
)}`;
}
}
if (fileName !== fileName.trimEnd()) {
return "Name cannot end with whitespace";
}
return "";
}
export function assertValidFileName(fileName: string): void {
const error = validateFileName(fileName);
if (error) {
throw new Error(error);
}
}