format('Y-m-d\TH:i:s.uP'), $level, $msg, $ctx ? json_encode($ctx, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) : '' ); @file_put_contents($logFile, $line, FILE_APPEND|LOCK_EX); } function json_out_gs($code, $data) { http_response_code($code); echo json_encode($data, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); exit; } function parse_query_gs() { $raw = $_SERVER['QUERY_STRING'] ?? ''; parse_str(str_replace(';','&',$raw), $qs); return $qs; } function validate_apps_url_gs($url) { $p = @parse_url($url); if (!$p) return false; if (($p['scheme'] ?? '') !== 'https') return false; if (($p['host'] ?? '') !== 'script.google.com') return false; return (bool)preg_match('~^/macros/s/[A-Za-z0-9_-]+/exec$~', $p['path'] ?? ''); } // ---- nur POST zulassen (diese Seite dient als Webhook) ---- _log_gs('INFO','incoming',[ 'method'=>$_SERVER['REQUEST_METHOD'] ?? '', 'ip'=>$_SERVER['REMOTE_ADDR'] ?? '', 'path'=>parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH), ]); if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') { json_out_gs(405, ['ok'=>false,'error'=>'Only POST allowed']); } // ---- Konfig: URL > Default (wie bei deinen Skripten) ---- $qs = parse_query_gs(); $appsUrl = $qs['APPS_URL'] ?? $DEFAULT_APPS_URL; $sheetId = $qs['SHEET_ID'] ?? $DEFAULT_SHEET_ID; $sheetName = $qs['SHEET_NAME'] ?? $DEFAULT_SHEET_NAME; $token = $qs['TOKEN'] ?? $DEFAULT_TOKEN; $missing = []; if ($appsUrl === '') $missing[] = 'APPS_URL'; if ($sheetId === '') $missing[] = 'SHEET_ID'; if ($sheetName === '')$missing[] = 'SHEET_NAME'; if ($token === '') $missing[] = 'TOKEN'; if ($missing) json_out_gs(400, ['ok'=>false,'error'=>'Missing: '.implode(', ', $missing),'missing'=>$missing]); if (!validate_apps_url_gs($appsUrl)) { json_out_gs(400, ['ok'=>false,'error'=>'Invalid APPS_URL']); } if (!preg_match('~^[A-Za-z0-9_-]{20,}$~', $sheetId)) { json_out_gs(400, ['ok'=>false,'error'=>'Invalid SHEET_ID']); } // ---- Payload einlesen (JSON oder Form) ---- $ctype = $_SERVER['CONTENT_TYPE'] ?? $_SERVER['HTTP_CONTENT_TYPE'] ?? ''; $rawBody = file_get_contents('php://input') ?: ''; $payload = []; if (stripos($ctype, 'application/json') !== false) { $tmp = json_decode($rawBody, true); if (is_array($tmp)) $payload = $tmp; } if (!$payload) $payload = $_POST ?: []; // Honeypot? if (!empty($payload['hp'])) { _log_gs('INFO','honeypot'); json_out_gs(200, ['ok'=>true]); } // Eingehende „reservierte“ Felder entfernen $RESERVED_KEYS = ['hp','config_path','source','Raw JSON','token','SHEET_ID','SHEET_NAME']; $clean = []; foreach ($payload as $k=>$v) { if ($k === '' || in_array((string)$k, $RESERVED_KEYS, true)) continue; $clean[$k] = is_scalar($v) ? (string)$v : json_encode($v, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); } // optionale Meta $clean['_server_ts'] = (new DateTimeImmutable())->format('c'); $clean['_remote_ip'] = $_SERVER['REMOTE_ADDR'] ?? ''; $clean['_content_type'] = $ctype; $clean['_user_agent'] = $_SERVER['HTTP_USER_AGENT'] ?? ''; $clean['_source'] = 'elements_google_sheets_page'; // Pflichtwerte anhängen $clean['token'] = $token; $clean['SHEET_ID'] = $sheetId; $clean['SHEET_NAME'] = $sheetName; // ---- Forward per cURL ---- $postFields = http_build_query($clean, '', '&', PHP_QUERY_RFC3986); $ch = curl_init($appsUrl); if ($ch === false) json_out_gs(500, ['ok'=>false,'error'=>'cURL init failed']); $opts = [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded; charset=UTF-8'], CURLOPT_POSTFIELDS => $postFields, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 20, CURLOPT_FOLLOWLOCATION => false, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_USERAGENT => 'elements-webhook/1.0', ]; curl_setopt_array($ch, $opts); $respBody = curl_exec($ch); $errno = curl_errno($ch); $error = $errno ? curl_error($ch) : ''; $status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($errno) json_out_gs(502, ['ok'=>false,'error'=>"Upstream TLS/HTTP error: $error"]); if ($status >= 200 && $status < 400) { _log_gs('INFO','forwarded',['status'=>$status]); json_out_gs(200, ['ok'=>true,'status'=>$status]); } _log_gs('ERROR','upstream',['status'=>$status]); json_out_gs($status ?: 500, ['ok'=>false,'error'=>'Upstream HTTP '.($status ?: 0)]);