Submissions: reject XML project files, show submitted file names to admin

This commit is contained in:
2026-09-24 11:32:19 +03:00
parent e6e11dd32c
commit a91cd34e30
4 changed files with 29 additions and 0 deletions

View File

@@ -38,6 +38,14 @@ class SubmissionService(
val last = submissions.findFirstByStudentIdAndLabIdOrderBySeqDesc(studentId, labId)
val seq = (last?.seq ?: 0) + 1
val payload = files.mapIndexed { i, f -> (f.originalFilename ?: "file$i") to f.bytes }
payload.forEach { (name, bytes) ->
if (looksLikeProjectFile(name, bytes)) {
throw BadRequestException(
"Файл «$name» похож на файл проекта (XML), а не на исходник. " +
"Приложите файл .c/.h или zip с проектом.",
)
}
}
val stored = storage.store(studentId, labId, seq, payload)
val entity = submissions.save(Submission(studentId = studentId, labId = labId, seq = seq))
return toDto(entity, stored.files)
@@ -70,6 +78,22 @@ class SubmissionService(
return perStudent.values.map { toDto(it, filesOf(it)) }
}
/** Файлы с "исходниковым" расширением, содержимое которых — XML-файл проекта (VS/IDE). */
private val sourceExtensions = setOf("c", "h", "cpp", "hpp", "cc", "cxx", "txt")
private fun looksLikeProjectFile(name: String, bytes: ByteArray): Boolean {
val ext = name.substringAfterLast('.', "").lowercase()
if (ext !in sourceExtensions) return false
val head = String(bytes, 0, minOf(bytes.size, 512), Charsets.UTF_8)
.removePrefix("\uFEFF")
.trimStart()
.lowercase()
return head.startsWith("<?xml") ||
head.startsWith("<solution") ||
head.startsWith("<project") ||
head.startsWith("<configurations")
}
private fun filesOf(s: Submission): List<String> =
storage.listFiles(storage.dirFor(s.studentId, s.labId, s.seq))

View File

@@ -326,6 +326,7 @@ class AdminController(
source = if (va?.manual == true) "вручную" else "авто",
submissionId = submissionsByStudent[s.id]?.id,
submissionSeq = submissionsByStudent[s.id]?.seq,
submissionFiles = submissionsByStudent[s.id]?.files ?: emptyList(),
)
},
)

View File

@@ -17,6 +17,7 @@ data class AdminLabStudent(
val source: String,
val submissionId: String? = null,
val submissionSeq: Int? = null,
val submissionFiles: List<String> = emptyList(),
val defenseStatus: String? = null,
val defenseCode: String? = null,
val defenseSessionId: String? = null,

View File

@@ -129,6 +129,9 @@
<a th:if="${s.submissionId}" class="cell-link"
th:href="'/admin/submissions/' + ${s.submissionId} + '/download'">
попытка <span th:text="${s.submissionSeq}">1</span> ↓</a>
<div th:if="${!#lists.isEmpty(s.submissionFiles)}" class="text-muted text-sm font-mono">
<span th:text="${#strings.listJoin(s.submissionFiles, ', ')}">main.c</span>
</div>
<span th:unless="${s.submissionId}" class="text-muted">—</span>
</td>
</tr>