function customize_amt_schemaorg_metadata_content($metatags) { $idx = -1; $mts = array_slice($metatags, 0); while (($i = find_property($mts, 'name', 'itemprop')) > -1) { $idx = $idx == -1 ? $i : $idx + $i + 1; $mts = array_slice($metatags, $idx + 1); } $contestID = get_query_var('contestID'); $contestType = $_GET['type']; if (isset($contestID) && isset($contestType)) { $contest = get_contest_detail('', $contestID, $contestType); if (isset($contest)) { if ($idx == -1) { $metatags[] = '<meta itemprop="name" content="' . $contest->challengeName . '" />'; } else { $metatags[$idx] = '<meta itemprop="name" content="' . $contest->challengeName . '" />'; } } } return $metatags; }
function var_assign($file, $namespace, $ast, $current_scope, $current_class, &$vars) { global $classes, $functions, $scope; $left = $ast->children[0]; $right = $ast->children[1]; $left_type = $right_type = null; $parent = $ast; $taint = false; // Deal with $a=$b=$c=1; and trickle the right-most value to the top through recursion if ($right instanceof \ast\Node && $right->kind == \ast\AST_ASSIGN) { $right_type = var_assign($file, $namespace, $right, $current_scope, $current_class, $vars); } if ($left instanceof \ast\Node && $left->kind != \ast\AST_VAR && $left->kind != \ast\AST_STATIC_PROP) { // Walk multi-level arrays and chained stuff // eg. $var->prop[1][2]->prop while ($left instanceof \ast\Node && $left->kind != \ast\AST_VAR) { $parent = $left; $left = $left->children[0]; } } if ($left == null) { // No variable name for assignment?? // This is generally for something like list(,$var) = [1,2] return $right_type; } if (!is_object($left)) { if ($left == "self") { // TODO: Looks like a self::$var assignment - do something smart here return $right_type; } else { if ($left == 'static') { // TODO: static::$prop assignment return $right_type; } else { return $right_type; } } } else { if ($left->kind == \ast\AST_LIST) { return ''; } } // DEBUG if (!$left instanceof \ast\Node) { echo "Check this {$file}\n" . ast_dump($left) . "\n" . ast_dump($parent) . "\n"; } if ($left->kind == \ast\AST_STATIC_PROP && $left->children[0]->kind == \ast\AST_NAME) { if ($left->children[1] instanceof \ast\Node) { // This is some sort of self::${$key} thing, give up return $right_type; } if ($left->children[0]->flags & \ast\flags\NAME_NOT_FQ) { $left_name = $namespace . $left->children[0]->children[0] . '::' . $left->children[1]; } else { $left_name = $left->children[0]->children[0] . '::' . $left->children[1]; } } else { $left_name = $left->children[0]; } if ($right instanceof \ast\Node && $right->kind == \ast\AST_CLOSURE) { $right_type = 'callable:{closure ' . $right->id . '}'; $vars[$left_name]['type'] = $right_type; $vars[$left_name]['tainted'] = false; $vars[$left_name]['tainted_by'] = ''; return $right_type; } if (!$left_type && $right_type) { $left_type = $right_type; } else { if (!$left_type) { // We didn't figure out the type simply by looking at the left side of the assignment, check the right $right_type = node_type($file, $namespace, $right, $current_scope, $current_class, $taint); $left_type = $right_type; } } if ($parent->kind == \ast\AST_DIM && $left->kind == \ast\AST_VAR) { // Generics check - can't really do this without some special hint forcing a strict generics type /* if(!($left->children[0] instanceof \ast\Node)) { if($right_type === "NULL") return ''; // You can assign null to any generic $var_type = $scope[$current_scope]['vars'][$left->children[0]]['type'] ?? ''; if(!empty($var_type) && !nongenerics($var_type) && strpos($var_type, '[]') !== false) { if(!type_check($right_type, generics($var_type))) { Log::err(Log::ETYPE, "Assigning {$right_type} to \${$left->children[0]} which is {$var_type}", $file, $ast->lineno); return ''; } } else { $left_type = mkgenerics($right_type); } } */ $left_type = mkgenerics($right_type); } // $var->prop = ... if ($parent->kind == \ast\AST_PROP && $left->kind == \ast\AST_VAR) { // Check for $$var-> weirdness if (!$left->children[0] instanceof \ast\Node) { $prop = $parent->children[1]; // Check for $var->$... if (!$prop instanceof \ast\Node) { $lclass = ''; if ($left->children[0] == 'this') { // $this->prop = $class_name = $current_class['name']; } else { $class_name = find_class_name($file, $parent, $namespace, $current_class, $current_scope); } $lclass = strtolower($class_name); if (empty($lclass) || empty($classes[$lclass])) { return ''; } $ltemp = find_property($file, $ast, $class_name, $prop, $current_class); if ($ltemp === false) { return $right_type; } if (!empty($ltemp)) { $lclass = $ltemp; } if (empty($classes[$lclass]['properties'][$prop])) { $classes[$lclass]['properties'][$prop] = ['flags' => \ast\flags\MODIFIER_PUBLIC, 'name' => $prop, 'lineno' => 0, 'type' => $right_type]; } else { if (!empty($classes[$lclass]['properties'][$prop]['dtype'])) { if ($ast->children[0]->kind == \ast\AST_DIM) { $right_type = mkgenerics($right_type); } if (!type_check(all_types($right_type), all_types($classes[$lclass]['properties'][$prop]['dtype']))) { Log::err(Log::ETYPE, "property is declared to be {$classes[$lclass]['properties'][$prop]['dtype']} but was assigned {$right_type}", $file, $ast->lineno); } } $classes[$lclass]['properties'][$prop]['type'] = merge_type($classes[$lclass]['properties'][$prop]['type'], $right_type); } return $right_type; } } } if ($left_name instanceof \ast\Node) { // TODO: Deal with $$var } else { $vars[$left_name]['type'] = $left_type ?? ''; $vars[$left_name]['tainted'] = $taint; $vars[$left_name]['tainted_by'] = $taint ? "{$file}:{$left->lineno}" : ''; } return $right_type; }
/** * Alias for find_property * * @author Anthony Short * @param $property * @param $css * @return array */ public static function find_properties($property, $css = "") { if ($css == "") { $css =& self::$css; } return find_property($property, $css); }
function node_type($file, $namespace, $node, $current_scope, $current_class, &$taint = null, $check_var_exists = true) { global $classes, $functions, $scope, $namespace_map, $internal_arginfo; if (!$node instanceof \ast\Node) { if ($node === null) { return ''; } return type_map(gettype($node)); } else { if ($node->kind == \ast\AST_ARRAY) { if (!empty($node->children) && $node->children[0] instanceof \ast\Node && $node->children[0]->kind == \ast\AST_ARRAY_ELEM) { // Check the first 5 (completely arbitrary) elements and assume the rest are the same type $etypes = []; for ($i = 0; $i < 5; $i++) { if (empty($node->children[$i])) { break; } if ($node->children[$i]->children[0] instanceof \ast\Node) { $etypes[] = node_type($file, $namespace, $node->children[$i]->children[0], $current_scope, $current_class, $temp_taint); } else { $etypes[] = type_map(gettype($node->children[$i]->children[0])); } } $types = array_unique($etypes); if (count($types) == 1 && !empty($types[0])) { return mkgenerics($types[0]); } } return 'array'; } else { if ($node->kind == \ast\AST_BINARY_OP || $node->kind == \ast\AST_GREATER || $node->kind == \ast\AST_GREATER_EQUAL) { if ($node->kind == \ast\AST_BINARY_OP) { $node_flags = $node->flags; } else { $node_flags = $node->kind; } $taint = var_taint_check($file, $node, $current_scope); switch ($node_flags) { // Always a string from a concat case \ast\flags\BINARY_CONCAT: $temp_taint = false; node_type($file, $namespace, $node->children[0], $current_scope, $current_class, $temp_taint); if ($temp_taint) { $taint = true; return 'string'; } node_type($file, $namespace, $node->children[1], $current_scope, $current_class, $temp_taint); if ($temp_taint) { $taint = true; } return 'string'; break; // Boolean unless invalid operands // Boolean unless invalid operands case \ast\flags\BINARY_IS_IDENTICAL: case \ast\flags\BINARY_IS_NOT_IDENTICAL: case \ast\flags\BINARY_IS_EQUAL: case \ast\flags\BINARY_IS_NOT_EQUAL: case \ast\flags\BINARY_IS_SMALLER: case \ast\flags\BINARY_IS_SMALLER_OR_EQUAL: case \ast\AST_GREATER: case \ast\AST_GREATER_EQUAL: $temp = node_type($file, $namespace, $node->children[0], $current_scope, $current_class); if (!$temp) { $left = ''; } else { $left = type_map($temp); } $temp = node_type($file, $namespace, $node->children[1], $current_scope, $current_class); if (!$temp) { $right = ''; } else { $right = type_map($temp); } $taint = false; // If we have generics and no non-generics on the left and the right is not array-like ... if (!empty(generics($left)) && empty(nongenerics($left)) && !type_check($right, 'array')) { Log::err(Log::ETYPE, "array to {$right} comparison", $file, $node->lineno); } else { // and the same for the right side if (!empty(generics($right)) && empty(nongenerics($right)) && !type_check($left, 'array')) { Log::err(Log::ETYPE, "{$left} to array comparison", $file, $node->lineno); } } return 'bool'; break; // Add is special because you can add arrays // Add is special because you can add arrays case \ast\flags\BINARY_ADD: $temp = node_type($file, $namespace, $node->children[0], $current_scope, $current_class); if (!$temp) { $left = ''; } else { $left = type_map($temp); } $temp = node_type($file, $namespace, $node->children[1], $current_scope, $current_class); if (!$temp) { $right = ''; } else { $right = type_map($temp); } // fast-track common cases if ($left == 'int' && $right == 'int') { return 'int'; } if (($left == 'int' || $left == 'float') && ($right == 'int' || $right == 'float')) { return 'float'; } $left_is_array = !empty(generics($left)) && empty(nongenerics($left)); $right_is_array = !empty(generics($right)) && empty(nongenerics($right)); if ($left_is_array && !type_check($right, 'array')) { Log::err(Log::ETYPE, "invalid operator: left operand is array and right is not", $file, $node->lineno); return ''; } else { if ($right_is_array && !type_check($left, 'array')) { Log::err(Log::ETYPE, "invalid operator: right operand is array and left is not", $file, $node->lineno); return ''; } else { if ($left_is_array || $right_is_array) { // If it is a '+' and we know one side is an array and the other is unknown, assume array return 'array'; } } } return 'int|float'; $taint = false; break; // Everything else should be an int/float // Everything else should be an int/float default: $temp = node_type($file, $namespace, $node->children[0], $current_scope, $current_class); if (!$temp) { $left = ''; } else { $left = type_map($temp); } $temp = node_type($file, $namespace, $node->children[1], $current_scope, $current_class); if (!$temp) { $right = ''; } else { $right = type_map($temp); } if ($left == 'array' || $right == 'array') { Log::err(Log::ETYPE, "invalid array operator", $file, $node->lineno); return ''; } else { if ($left == 'int' && $right == 'int') { return 'int'; } else { if ($left == 'float' || $right == 'float') { return 'float'; } } } return 'int|float'; $taint = false; break; } } else { if ($node->kind == \ast\AST_CAST) { $taint = var_taint_check($file, $node->children[0], $current_scope); switch ($node->flags) { case \ast\flags\TYPE_NULL: return 'null'; break; case \ast\flags\TYPE_BOOL: $taint = false; return 'bool'; break; case \ast\flags\TYPE_LONG: $taint = false; return 'int'; break; case \ast\flags\TYPE_DOUBLE: $taint = false; return 'float'; break; case \ast\flags\TYPE_STRING: return 'string'; break; case \ast\flags\TYPE_ARRAY: return 'array'; break; case \ast\flags\TYPE_OBJECT: return 'object'; break; default: Log::err(Log::EFATAL, "Unknown type (" . $node->flags . ") in cast"); } } else { if ($node->kind == \ast\AST_NEW) { $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope); if ($class_name) { return $classes[strtolower($class_name)]['type']; } return 'object'; } else { if ($node->kind == \ast\AST_DIM) { $taint = var_taint_check($file, $node->children[0], $current_scope); $type = node_type($file, $namespace, $node->children[0], $current_scope, $current_class); if (!empty($type)) { $gen = generics($type); if (empty($gen)) { if ($type !== 'null' && !type_check($type, 'string|ArrayAccess')) { // array offsets work on strings, unfortunately // Double check that any classes in the type don't have ArrayAccess $ok = false; foreach (explode('|', $type) as $t) { if (!empty($t) && !is_native_type($t)) { if (!empty($classes[strtolower($t)]['type'])) { if (strpos('|' . $classes[strtolower($t)]['type'] . '|', '|ArrayAccess|') !== false) { $ok = true; break; } } } } if (!$ok) { Log::err(Log::ETYPE, "Suspicious array access to {$type}", $file, $node->lineno); } } return ''; } } else { return ''; } return $gen; } else { if ($node->kind == \ast\AST_VAR) { return var_type($file, $node, $current_scope, $taint, $check_var_exists); } else { if ($node->kind == \ast\AST_ENCAPS_LIST) { foreach ($node->children as $encap) { if ($encap instanceof \ast\Node) { if (var_taint_check($file, $encap, $current_scope)) { $taint = true; } } } return "string"; } else { if ($node->kind == \ast\AST_CONST) { if ($node->children[0]->kind == \ast\AST_NAME) { if (defined($node->children[0]->children[0])) { return type_map(gettype(constant($node->children[0]->children[0]))); } else { // Todo: user-defined constant } } } else { if ($node->kind == \ast\AST_CLASS_CONST) { if ($node->children[1] == 'class') { return 'string'; } // class name fetch $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope); if (!$class_name) { return ''; } $ltemp = strtolower($class_name); while ($ltemp && !array_key_exists($node->children[1], $classes[$ltemp]['constants'])) { $ltemp = strtolower($classes[$ltemp]['parent']); if (empty($classes[$ltemp])) { return ''; } // undeclared class - will be caught elsewhere } if (!$ltemp || !array_key_exists($node->children[1], $classes[$ltemp]['constants'])) { Log::err(Log::EUNDEF, "can't access undeclared constant {$class_name}::{$node->children[1]}", $file, $node->lineno); return ''; } return $classes[$ltemp]['constants'][$node->children[1]]['type']; } else { if ($node->kind == \ast\AST_PROP) { if ($node->children[0]->kind == \ast\AST_VAR) { $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope); if ($class_name && !$node->children[1] instanceof \ast\Node) { $ltemp = find_property($file, $node, $class_name, $node->children[1], $class_name, false); if (empty($ltemp)) { return ''; } return $classes[$ltemp]['properties'][$node->children[1]]['type']; } } } else { if ($node->kind == \ast\AST_STATIC_PROP) { if ($node->children[0]->kind == \ast\AST_NAME) { $class_name = qualified_name($file, $node->children[0], $namespace); if ($class_name && !$node->children[1] instanceof \ast\Node) { $ltemp = find_property($file, $node, $class_name, $node->children[1], $class_name, false); if (empty($ltemp)) { return ''; } return $classes[$ltemp]['properties'][$node->children[1]]['type']; } } } else { if ($node->kind == \ast\AST_CALL) { if ($node->children[0]->kind == \ast\AST_NAME) { $func_name = $node->children[0]->children[0]; if ($node->children[0]->flags & \ast\flags\NAME_NOT_FQ) { $func = $namespace_map[T_FUNCTION][$file][strtolower($namespace . $func_name)] ?? $namespace_map[T_FUNCTION][$file][strtolower($func_name)] ?? $functions[strtolower($namespace . $func_name)] ?? $functions[strtolower($func_name)] ?? null; } else { $func = $functions[strtolower($func_name)] ?? null; } if ($func['file'] == 'internal' && empty($func['ret'])) { if (!empty($internal_arginfo[$func_name])) { return $internal_arginfo[$func_name][0] ?? ''; } } else { return $func['ret'] ?? ''; } } else { // TODO: Handle $func() and other cases that get here } } else { if ($node->kind == \ast\AST_STATIC_CALL) { $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope); $method = find_method($class_name, $node->children[1]); if ($method) { return $method['ret'] ?? ''; } } else { if ($node->kind == \ast\AST_METHOD_CALL) { $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope); if ($class_name) { $method_name = $node->children[1]; $method = find_method($class_name, $method_name); if ($method === false) { Log::err(Log::EUNDEF, "call to undeclared method {$class_name}->{$method_name}()", $file, $node->lineno); } else { if ($method != 'dynamic') { return $method['ret']; } } } } } } } } } } } } } } } } } } return ''; }