class Analyzer::Php::Laminas

Defined in:

analyzer/analyzers/php/laminas.cr

Constant Summary

CONSTRAINT_METACHARACTERS = "\\^$.|?*+()[]{}"

Regex metacharacters. A Laminas constraint is a validation pattern ('itemId' => '[0-9]+'), so it only doubles as a value when it matches exactly one string — i.e. when it contains none of these.

HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]
NAMESPACE_MARKER_RE = /Laminas\\|Zend\\|Mezzio\\/

Precompiled once at load: the three namespace markers used to be three separate String#includes? scans of the whole file ORed together. Crystal's String#includes? is measurably slower than a single precompiled Regex#matches? call, and this runs on every .php file fed into the analyzer during a project-wide scan.

PARAM_PATTERNS = [{/->getQueryParams\s*\(\s*\)\s*\[\s*['"]([^'"]+)['"]\s*\]/, "query"}, {/->getParsedBody\s*\(\s*\)\s*\[\s*['"]([^'"]+)['"]\s*\]/, "form"}, {/->getUploadedFiles\s*\(\s*\)\s*\[\s*['"]([^'"]+)['"]\s*\]/, "form"}, {/->getHeaderLine\s*\(\s*['"]([^'"]+)['"]\s*\)/, "header"}, {/->getHeader\s*\(\s*['"]([^'"]+)['"]\s*\)/, "header"}, {/->getCookieParams\s*\(\s*\)\s*\[\s*['"]([^'"]+)['"]\s*\]/, "cookie"}]
SPLIT_ARGS_RULES = Noir::TopLevelSplit::Rules.new(nest: (Noir::TopLevelSplit::Nest::Paren | Noir::TopLevelSplit::Nest::Bracket) | Noir::TopLevelSplit::Nest::Brace, quotes: "\"'", escape: Noir::TopLevelSplit::Escape::InQuotes, strip: false, empties: Noir::TopLevelSplit::Empties::DropTrailing, per_kind: false, clamp: true)

Rules::SHARED_DEPTH_RAW with a trailing empty dropped, then a strip applied by this method rather than by the splitter.

The order matters and is not cosmetic. The body this replaces stripped each part as it pushed it, but decided whether to emit the tail from the RAW slice (if start < size), so f(a, ) kept a final "" while f(a,) did not. Stripping inside the splitter would test the stripped tail and drop both. Splitting raw and stripping afterwards reproduces the original exactly.

The old scan walked bytes to dodge String#[](Int); the shared splitter is a single forward each_char pass, so it is O(n) on any input without needing the byte detour. Every delimiter, quote and bracket involved is ASCII and UTF-8 continuation bytes are all

= 0x80, so the two scans see the same split points.

Class Method Summary

Instance Method Summary

Class methods inherited from class Analyzer::Php::PhpEngine

test_path?(relative_path : String) : Bool test_path?

Instance methods inherited from class FileScanEngine

analyze analyze, analyze_file(path : String) : Array(Endpoint) analyze_file

Instance methods inherited from class Analyzer

analyze analyze, base_path : String base_path, base_paths : Array(String) base_paths, base_relative_path(path : String) : String base_relative_path, callees_needed? : Bool callees_needed?, content_matches?(content : String, markers : Regex) : Bool content_matches?, http_header_name(name : String) : String | Nil http_header_name, line_number_for_index(content : String, char_index : Int32) : Int32 line_number_for_index, logger : NoirLogger logger, parallel_analyze(files : Array(String), &block : String -> Nil) parallel_analyze, read_file_content(path : String) : String read_file_content, result : Array(Endpoint) result, tech : String tech, unique_params(params : Array(Param)) : Array(Param) unique_params, url : String url, web_root_path(path : String, markers : Array(String)) : String web_root_path

Constructor methods inherited from class Analyzer

new(options : Hash(String, YAML::Any)) new

Macros inherited from class Analyzer

analyzer_for(tech) analyzer_for

Instance methods inherited from module FileHelper

all_files : Array(String) all_files, get_files_by_basename(basename : String) : Array(String) get_files_by_basename, get_files_by_extension(extension : String) : Array(String) get_files_by_extension, get_files_by_extensions(extensions : Array(String)) : Array(String) get_files_by_extensions, get_files_by_prefix(prefix : String) : Array(String) get_files_by_prefix, get_files_by_prefix_and_extension(prefix : String, extension : String) : Array(String) get_files_by_prefix_and_extension, get_files_by_relative_path(relative_path : String, root : String = "") : Array(String) get_files_by_relative_path, get_public_dir_files(base_path : String, folder : String) : Array(String) get_public_dir_files, get_public_files(base_path : String, anchors : Array(String) = ["shard.yml", "Gemfile"]) : Array(String) get_public_files, walked_path(expanded : String) : String walked_path

Class Method Detail

def self.tech_name : String #

[View source]

Instance Method Detail

def analyze_file(path : String) : Array(Endpoint) #

[View source]
def tech : String #

Instance-side view of the same declaration. The per-file rescues live on this base class, which has no way to name the analyzer that is running inside them, so a skipped file could not be attributed to a tech. Deriving it from analyzer_for keeps the name written exactly once.


[View source]