/* ·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━· : : : █▀ █ █▀▀ · Blazing-fast pentesting suite : : ▄█ █ █▀ · BSD 3-Clause License : : : : (c) 2022-2025 vmfunc (Celeste Hickenlooper), xyzeva, : : lunchcat alumni & contributors : : : ·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━· */ package frameworks import ( "regexp" "unicode" ) // VersionMatch represents a version detection result with confidence. type VersionMatch struct { Version string Confidence float32 Source string // where the version was found } // compiledVersionPattern holds a pre-compiled regex for version extraction type compiledVersionPattern struct { re *regexp.Regexp confidence float32 source string } // frameworkVersionPatterns maps framework names to their pre-compiled version patterns. // Patterns are compiled once at package initialization for optimal performance. var frameworkVersionPatterns map[string][]compiledVersionPattern func init() { // Raw patterns to be compiled rawPatterns := map[string][]struct { pattern string confidence float32 source string }{ "Laravel": { {`Laravel\s+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"}, {`laravel/framework.*?(\d+\.\d+(?:\.\d+)?)`, 0.8, "composer.json"}, }, "Django": { {`Django[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"}, {`django.*?(\d+\.\d+(?:\.\d+)?)`, 0.7, "package reference"}, }, "Ruby on Rails": { {`Rails[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"}, {`rails.*?(\d+\.\d+(?:\.\d+)?)`, 0.7, "gem reference"}, }, "Express.js": { {`Express[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"}, {`"express":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"}, }, "ASP.NET": { {`X-AspNet-Version:\s*(\d+\.\d+(?:\.\d+)?)`, 0.95, "header"}, {`ASP\.NET[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"}, {`X-AspNetMvc-Version:\s*(\d+\.\d+(?:\.\d+)?)`, 0.9, "MVC header"}, }, "ASP.NET Core": { {`\.NET\s*(\d+\.\d+(?:\.\d+)?)`, 0.8, "dotnet version"}, }, "Spring": { {`Spring[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"}, {`spring-core.*?(\d+\.\d+(?:\.\d+)?)`, 0.8, "maven"}, }, "Spring Boot": { {`spring-boot.*?(\d+\.\d+(?:\.\d+)?)`, 0.9, "maven"}, }, "Flask": { {`Flask[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"}, {`Werkzeug[/\s]+(\d+\.\d+(?:\.\d+)?)`, 0.7, "werkzeug version"}, }, "Next.js": { {`Next\.js[/\s]+[Vv]?(\d{1,2}\.\d+(?:\.\d+)?)`, 0.9, "explicit version"}, {`"next":\s*"[~^]?(\d{1,2}\.\d+(?:\.\d+)?)"`, 0.85, "package.json"}, }, "Nuxt.js": { {`Nuxt[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"}, {`"nuxt":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"}, }, "Vue.js": { {`Vue\.js[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"}, {`"vue":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"}, {`vue@(\d+\.\d+(?:\.\d+)?)`, 0.8, "CDN reference"}, }, "Angular": { {`ng-version="(\d+\.\d+(?:\.\d+)?)"`, 0.95, "ng-version attribute"}, {`Angular[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"}, {`"@angular/core":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"}, }, "React": { {`React[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"}, {`"react":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"}, {`react@(\d+\.\d+(?:\.\d+)?)`, 0.8, "CDN reference"}, }, "Svelte": { {`Svelte[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"}, {`"svelte":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"}, }, "SvelteKit": { {`"@sveltejs/kit":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"}, }, "WordPress": { {` 1 && p.confidence > bestMatch.Confidence { candidate := matches[1] if isValidVersionString(candidate) { bestMatch = VersionMatch{ Version: candidate, Confidence: p.confidence, Source: p.source, } } } } if bestMatch.Version == "" { return VersionMatch{Version: "unknown", Confidence: 0, Source: ""} } return bestMatch } // isValidVersionString checks if a version string looks like a valid semver func isValidVersionString(v string) bool { if len(v) == 0 || len(v) > 20 { return false } dotCount := 0 for _, c := range v { if c == '.' { dotCount++ if dotCount > 3 { return false } } else if !unicode.IsDigit(c) { return false } } return dotCount >= 1 }