"MISSING_TOKEN", "message" => "Download token required"]); exit; } $download_start = microtime(true); // URL decode the token in case it was encoded during transmission $url_token = urldecode($_GET['token']); // STEP 2: Validate and decode URL token using standalone secure function $token_data = validate_secure_download_token($url_token); if (isset($token_data['error'])) { http_response_code(403); echo json_encode([ "error" => $token_data['error'], "message" => $token_data['message'] ]); exit; } $serial_number = $token_data['sn']; $version_id = $token_data['version_id']; // STEP 3: Get equipment data (reuse software_update.php logic) $sql = 'SELECT e.rowID as equipment_rowid, e.productrowid, e.sw_version as current_sw_version, e.hw_version, e.sw_version_license, e.accounthierarchy, p.productcode FROM equipment e JOIN products p ON e.productrowid = p.rowID WHERE e.serialnumber = ?'; $stmt = $pdo->prepare($sql); $stmt->execute([$serial_number]); $equipment = $stmt->fetch(PDO::FETCH_ASSOC); if (!$equipment) { http_response_code(404); log_download([ 'user_id' => $user_data['id'], 'version_id' => $version_id, 'status' => 'failed', 'error_message' => 'Equipment not found', 'createdby' => $username ]); echo json_encode(["error" => "EQUIPMENT_NOT_FOUND", "message" => "Equipment not found"]); exit; } // STEP 4: Get version data $sql = 'SELECT psv.rowID, psv.version, psv.name, psv.file_path, psv.hw_version, psv.status FROM products_software_versions psv WHERE psv.rowID = ?'; $stmt = $pdo->prepare($sql); $stmt->execute([$version_id]); $version = $stmt->fetch(PDO::FETCH_ASSOC); if (!$version) { http_response_code(404); log_download([ 'user_id' => $user_data['id'], 'version_id' => $version_id, 'status' => 'failed', 'error_message' => 'Version not found', 'accounthierarchy' => $equipment['accounthierarchy'], 'createdby' => $username ]); echo json_encode(["error" => "VERSION_NOT_FOUND", "message" => "Version not found"]); exit; } if ($version['status'] != 1) { http_response_code(403); log_download([ 'user_id' => $user_data['id'], 'version_id' => $version_id, 'status' => 'failed', 'error_message' => 'Version inactive', 'accounthierarchy' => $equipment['accounthierarchy'], 'createdby' => $username ]); echo json_encode(["error" => "VERSION_INACTIVE", "message" => "Version is not active"]); exit; } // STEP 5: Check version is assigned to product $sql = 'SELECT COUNT(*) as assigned FROM products_software_assignment WHERE product_id = ? AND software_version_id = ? AND status = 1'; $stmt = $pdo->prepare($sql); $stmt->execute([$equipment['productrowid'], $version_id]); $assignment = $stmt->fetch(PDO::FETCH_ASSOC); if ($assignment['assigned'] == 0) { http_response_code(403); log_download([ 'user_id' => $user_data['id'], 'version_id' => $version_id, 'status' => 'failed', 'error_message' => 'Version not assigned to product', 'accounthierarchy' => $equipment['accounthierarchy'], 'createdby' => $username ]); echo json_encode(["error" => "VERSION_NOT_ASSIGNED", "message" => "Version not assigned to product"]); exit; } // STEP 6: Hardware version compatibility // Only check if version has hw_version requirement (not NULL or empty) // Match logic from software_update.php line 103 if ($version['hw_version'] && $version['hw_version'] != '') { if ($equipment['hw_version'] && $version['hw_version'] != $equipment['hw_version']) { http_response_code(403); log_download([ 'user_id' => $user_data['id'], 'version_id' => $version_id, 'status' => 'failed', 'error_message' => 'Hardware version mismatch', 'accounthierarchy' => $equipment['accounthierarchy'], 'createdby' => $username ]); echo json_encode(["error" => "HW_VERSION_MISMATCH", "message" => "Hardware version incompatible"]); exit; } } // STEP 7: License validation (reuse software_update.php logic) $current_sw_version = $equipment['current_sw_version']; // Get upgrade pricing $sql = 'SELECT price, currency FROM products_software_upgrade_paths pup JOIN products_software_versions from_ver ON pup.from_version_id = from_ver.rowID WHERE pup.to_version_id = ? AND from_ver.version = ? AND pup.is_active = 1'; $stmt = $pdo->prepare($sql); $stmt->execute([$version_id, $current_sw_version]); $upgrade_pricing = $stmt->fetch(PDO::FETCH_ASSOC); $final_price = $upgrade_pricing['price'] ?? '0.00'; if ($final_price > 0) { // Paid upgrade - check license $sw_version_license = $equipment['sw_version_license']; if (!$sw_version_license) { http_response_code(402); log_download([ 'user_id' => $user_data['id'], 'version_id' => $version_id, 'status' => 'failed', 'error_message' => 'License required', 'accounthierarchy' => $equipment['accounthierarchy'], 'createdby' => $username ]); echo json_encode([ "error" => "LICENSE_REQUIRED", "message" => "Valid license required", "price" => $final_price, "currency" => $upgrade_pricing['currency'] ]); exit; } // Validate license $sql = 'SELECT status, starts_at, expires_at FROM products_software_licenses WHERE license_key = ? AND equipment_id = ?'; $stmt = $pdo->prepare($sql); $stmt->execute([$sw_version_license, $equipment['equipment_rowid']]); $license = $stmt->fetch(PDO::FETCH_ASSOC); if (!$license || $license['status'] != 1) { http_response_code(402); log_download([ 'user_id' => $user_data['id'], 'version_id' => $version_id, 'status' => 'failed', 'error_message' => 'Invalid license', 'accounthierarchy' => $equipment['accounthierarchy'], 'createdby' => $username ]); echo json_encode(["error" => "INVALID_LICENSE", "message" => "License is invalid"]); exit; } // Check license date validity $now = date('Y-m-d H:i:s'); if (($license['starts_at'] && $license['starts_at'] > $now) || ($license['expires_at'] && $license['expires_at'] < $now)) { http_response_code(402); log_download([ 'user_id' => $user_data['id'], 'version_id' => $version_id, 'status' => 'failed', 'error_message' => 'License expired', 'accounthierarchy' => $equipment['accounthierarchy'], 'createdby' => $username ]); echo json_encode(["error" => "LICENSE_EXPIRED", "message" => "License is expired"]); exit; } } // STEP 8: Build file path and verify exists $firmware_path = dirname(__FILE__, 4) . '/firmware/' . $version['file_path']; if (!file_exists($firmware_path)) { http_response_code(404); log_download([ 'user_id' => $user_data['id'], 'version_id' => $version_id, 'status' => 'failed', 'error_message' => 'File not found on server', 'accounthierarchy' => $equipment['accounthierarchy'], 'createdby' => $username ]); echo json_encode(["error" => "FILE_NOT_FOUND", "message" => "Firmware file not available"]); exit; } // STEP 9: Stream file and log $file_size = filesize($firmware_path); try { // Log successful download before streaming $download_time = round(microtime(true) - $download_start); log_download([ 'user_id' => $user_data['id'], 'version_id' => $version_id, 'file_size' => $file_size, 'download_time_seconds' => $download_time, 'status' => 'success', 'accounthierarchy' => $equipment['accounthierarchy'], 'createdby' => $username ]); // Stream file (function handles path traversal check and exits after streaming) stream_file_download($firmware_path, $version['file_path']); } catch (Exception $e) { log_download([ 'user_id' => $user_data['id'], 'version_id' => $version_id, 'file_size' => $file_size, 'status' => 'failed', 'error_message' => $e->getMessage(), 'accounthierarchy' => $equipment['accounthierarchy'], 'createdby' => $username ]); http_response_code(500); echo json_encode(["error" => "DOWNLOAD_FAILED", "message" => "Download failed"]); } ?>