Submissions: reject XML project files, show submitted file names to admin
This commit is contained in:
@@ -38,6 +38,14 @@ class SubmissionService(
|
|||||||
val last = submissions.findFirstByStudentIdAndLabIdOrderBySeqDesc(studentId, labId)
|
val last = submissions.findFirstByStudentIdAndLabIdOrderBySeqDesc(studentId, labId)
|
||||||
val seq = (last?.seq ?: 0) + 1
|
val seq = (last?.seq ?: 0) + 1
|
||||||
val payload = files.mapIndexed { i, f -> (f.originalFilename ?: "file$i") to f.bytes }
|
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 stored = storage.store(studentId, labId, seq, payload)
|
||||||
val entity = submissions.save(Submission(studentId = studentId, labId = labId, seq = seq))
|
val entity = submissions.save(Submission(studentId = studentId, labId = labId, seq = seq))
|
||||||
return toDto(entity, stored.files)
|
return toDto(entity, stored.files)
|
||||||
@@ -70,6 +78,22 @@ class SubmissionService(
|
|||||||
return perStudent.values.map { toDto(it, filesOf(it)) }
|
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> =
|
private fun filesOf(s: Submission): List<String> =
|
||||||
storage.listFiles(storage.dirFor(s.studentId, s.labId, s.seq))
|
storage.listFiles(storage.dirFor(s.studentId, s.labId, s.seq))
|
||||||
|
|
||||||
|
|||||||
@@ -326,6 +326,7 @@ class AdminController(
|
|||||||
source = if (va?.manual == true) "вручную" else "авто",
|
source = if (va?.manual == true) "вручную" else "авто",
|
||||||
submissionId = submissionsByStudent[s.id]?.id,
|
submissionId = submissionsByStudent[s.id]?.id,
|
||||||
submissionSeq = submissionsByStudent[s.id]?.seq,
|
submissionSeq = submissionsByStudent[s.id]?.seq,
|
||||||
|
submissionFiles = submissionsByStudent[s.id]?.files ?: emptyList(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ data class AdminLabStudent(
|
|||||||
val source: String,
|
val source: String,
|
||||||
val submissionId: String? = null,
|
val submissionId: String? = null,
|
||||||
val submissionSeq: Int? = null,
|
val submissionSeq: Int? = null,
|
||||||
|
val submissionFiles: List<String> = emptyList(),
|
||||||
val defenseStatus: String? = null,
|
val defenseStatus: String? = null,
|
||||||
val defenseCode: String? = null,
|
val defenseCode: String? = null,
|
||||||
val defenseSessionId: String? = null,
|
val defenseSessionId: String? = null,
|
||||||
|
|||||||
@@ -129,6 +129,9 @@
|
|||||||
<a th:if="${s.submissionId}" class="cell-link"
|
<a th:if="${s.submissionId}" class="cell-link"
|
||||||
th:href="'/admin/submissions/' + ${s.submissionId} + '/download'">
|
th:href="'/admin/submissions/' + ${s.submissionId} + '/download'">
|
||||||
попытка <span th:text="${s.submissionSeq}">1</span> ↓</a>
|
попытка <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>
|
<span th:unless="${s.submissionId}" class="text-muted">—</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user