reflection = $reflection; } private function callHandler($handler, $record, string $operation, ReflectedTable $table) /*: ResponseInterface?*/ { $context = (array) $record; $details = array(); $tableName = $table->getName(); foreach ($context as $columnName => $value) { if ($table->hasColumn($columnName)) { $column = $table->getColumn($columnName); $valid = call_user_func($handler, $operation, $tableName, $column->serialize(), $value, $context); if ($valid === true || $valid === '') { $valid = $this->validateType($column, $value); } if ($valid !== true && $valid !== '') { $details[$columnName] = $valid; } } } if (count($details) > 0) { return $this->responder->error(ErrorCode::INPUT_VALIDATION_FAILED, $tableName, $details); } return null; } private function validateType(ReflectedColumn $column, $value) { $types = $this->getArrayProperty('types', 'all'); if (in_array('all', $types) || in_array($column->getType(), $types)) { if (is_null($value)) { return ($column->getNullable() ? true : "cannot be null"); } if (is_string($value)) { // check for whitespace switch ($column->getType()) { case 'varchar': case 'clob': break; default: if (strlen(trim($value)) != strlen($value)) { return 'illegal whitespace'; } break; } // try to parse switch ($column->getType()) { case 'integer': case 'bigint': if ( filter_var($value, FILTER_SANITIZE_NUMBER_INT) !== $value || filter_var($value, FILTER_VALIDATE_INT) === false ) { return 'invalid integer'; } break; case 'varchar': if (mb_strlen($value, 'UTF-8') > $column->getLength()) { return 'string too long'; } break; case 'decimal': if (!is_numeric($value)) { return 'invalid decimal'; } break; case 'float': case 'double': if ( filter_var($value, FILTER_SANITIZE_NUMBER_FLOAT) !== $value || filter_var($value, FILTER_VALIDATE_FLOAT) === false ) { return 'invalid float'; } break; case 'boolean': if ( filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) === null ) { return 'invalid boolean'; } break; case 'date': if (date_create_from_format('Y-m-d', $value) === false) { return 'invalid date'; } break; case 'time': if (date_create_from_format('H:i:s', $value) === false) { return 'invalid time'; } break; case 'timestamp': if (date_create_from_format('Y-m-d H:i:s', $value) === false) { return 'invalid timestamp'; } break; case 'clob': // no checks needed break; case 'blob': case 'varbinary': if (base64_decode($value, true) === false) { return 'invalid base64'; } break; case 'geometry': // no checks yet break; } } else { // check non-string types switch ($column->getType()) { case 'integer': case 'bigint': if (!is_int($value)) { return 'invalid integer'; } break; case 'decimal': case 'float': case 'double': if (!is_float($value) && !is_int($value)) { return 'invalid float'; } break; case 'boolean': if (!(is_int($value) && ($value === 1 || $value === 0)) && !is_bool($value)) { return 'invalid boolean'; } break; default: return 'invalid ' . $column->getType(); } } // extra checks switch ($column->getType()) { case 'integer': // 4 byte signed $value = filter_var($value, FILTER_VALIDATE_INT); if ($value > 2147483647 || $value < -2147483648) { return 'invalid integer'; } break; case 'decimal': $value = "$value"; if (strpos($value, '.') !== false) { list($whole, $decimals) = explode('.', $value, 2); } else { list($whole, $decimals) = array($value, ''); } if (strlen($whole) > 0 && !ctype_digit($whole)) { return 'invalid decimal'; } if (strlen($decimals) > 0 && !ctype_digit($decimals)) { return 'invalid decimal'; } if (strlen($whole) > $column->getPrecision() - $column->getScale()) { return 'decimal too large'; } if (strlen($decimals) > $column->getScale()) { return 'decimal too precise'; } break; case 'varbinary': if (strlen(base64_decode($value)) > $column->getLength()) { return 'string too long'; } break; } } return (true); } public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface { $operation = RequestUtils::getOperation($request); if (in_array($operation, ['create', 'update', 'increment'])) { $tableName = RequestUtils::getPathSegment($request, 2); if ($this->reflection->hasTable($tableName)) { $record = $request->getParsedBody(); if ($record !== null) { $handler = $this->getProperty('handler', ''); if ($handler !== '') { $table = $this->reflection->getTable($tableName); if (is_array($record)) { foreach ($record as $r) { $response = $this->callHandler($handler, $r, $operation, $table); if ($response !== null) { return $response; } } } else { $response = $this->callHandler($handler, $record, $operation, $table); if ($response !== null) { return $response; } } } } } } return $next->handle($request); } }