forked from DataDog/go-sqllexer
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: -mode encode-marked, and optional MySQL executable-comment lexing #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
8b8313a
feat(sqlprocessor): add -mode encode-marked, keeping quote positions
blotus afea5cb
feat(lexer): optionally lex MySQL executable comment bodies as SQL
blotus 2d6cc32
fix(lexer): treat an executable comment's closing delimiter as a word…
blotus ca3e6f9
Merge remote-tracking branch 'origin/main' into feat/encode-marked
blotus ff8edd9
fix(lexer): WAITFOR/DELAY are keywords, and # comments without a dial…
blotus e78025c
feat(lexer)!: lex MySQL executable comments by default
blotus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "bufio" | ||
| "strings" | ||
| "testing" | ||
| ) | ||
|
|
||
| // The encodings this binary emits are a contract: a model is fitted to one of | ||
| // them, so a change here silently changes what that model sees. These pin the | ||
| // two that "encode" and "encode-marked" produce. | ||
|
|
||
| func TestTokenizeLineTypesOnly(t *testing.T) { | ||
| // Input reaches this function already stripped of quotes by readLine. | ||
| for _, tc := range []struct{ in, want string }{ | ||
| {"anything OR x=x", "IDENT SPACE KEYWORD SPACE IDENT OPERATOR IDENT"}, | ||
| {"SELECT * FROM t", "COMMAND SPACE WILDCARD SPACE KEYWORD SPACE IDENT"}, | ||
| {"", ""}, | ||
| } { | ||
| if got := tokenizeLineTypesOnly(tc.in); got != tc.want { | ||
| t.Errorf("tokenizeLineTypesOnly(%q)\n got: %s\n want: %s", tc.in, got, tc.want) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestTokenizeLineTypesOnlyMarked(t *testing.T) { | ||
| for _, tc := range []struct{ in, want string }{ | ||
| {"anything' OR 'x'='x", "IDENT QUOTE SPACE KEYWORD SPACE QUOTE IDENT QUOTE OPERATOR QUOTE IDENT"}, | ||
| {"O'Brien", "IDENT QUOTE IDENT"}, | ||
| {"'-'", "QUOTE OPERATOR QUOTE"}, | ||
| {"' --", "QUOTE SPACE COMMENT"}, | ||
| {"no quotes here", "IDENT SPACE IDENT SPACE IDENT"}, | ||
| {"'", "QUOTE"}, | ||
| {`"`, "QUOTE"}, | ||
| {"", ""}, | ||
| } { | ||
| if got := tokenizeLineTypesOnlyMarked(tc.in); got != tc.want { | ||
| t.Errorf("tokenizeLineTypesOnlyMarked(%q)\n got: %s\n want: %s", tc.in, got, tc.want) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // TestMarkedSeparatesWhatTheStripCannot is the reason the mode exists: after the | ||
| // strip a quoted tautology and an ordinary phrase are the same sequence, so | ||
| // nothing downstream can tell them apart. | ||
| func TestMarkedSeparatesWhatTheStripCannot(t *testing.T) { | ||
| const attack, benign = "anything' OR 'x'='x", "anything or x=x" | ||
| strip := func(s string) string { | ||
| return tokenizeLineTypesOnly(strings.NewReplacer("'", "", `"`, "").Replace(s)) | ||
| } | ||
| if strip(attack) != strip(benign) { | ||
| t.Fatalf("premise no longer holds: the strip already separates these") | ||
| } | ||
| if tokenizeLineTypesOnlyMarked(attack) == tokenizeLineTypesOnlyMarked(benign) { | ||
| t.Errorf("marked encoding fails to separate them: both %s", | ||
| tokenizeLineTypesOnlyMarked(attack)) | ||
| } | ||
| } | ||
|
|
||
| // TestMarkedNeverLexesAQuote guards the property the strip provides and this | ||
| // mode must not lose: a dangling quote must not swallow the rest of the input. | ||
| func TestMarkedNeverLexesAQuote(t *testing.T) { | ||
| for _, in := range []string{ | ||
| "'; DROP TABLE users; --", "unbalanced ' quote", `a "b`, "admin'--", | ||
| } { | ||
| for _, tok := range strings.Fields(tokenizeLineTypesOnlyMarked(in)) { | ||
| switch tok { | ||
| case "STRING", "INCOMPLETE_STRING", "QUOTED_IDENT": | ||
| t.Errorf("%q produced %s: a quote reached the lexer", in, tok) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestReadLineStripQuotesFlag(t *testing.T) { | ||
| const line = `"1' OR '1'='1"` + "\n" // one JSON-encoded record | ||
| for _, tc := range []struct { | ||
| strip bool | ||
| want string | ||
| }{ | ||
| {true, "1 OR 1=1"}, | ||
| {false, "1' OR '1'='1"}, | ||
| } { | ||
| got, err := readLine(bufio.NewReader(strings.NewReader(line)), tc.strip) | ||
| if err != nil { | ||
| t.Fatalf("readLine(strip=%v): %v", tc.strip, err) | ||
| } | ||
| if got != tc.want { | ||
| t.Errorf("readLine(strip=%v) = %q, want %q", tc.strip, got, tc.want) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // A MySQL executable comment runs on the server, so its body is lexed as SQL | ||
| // and `1/*!50000union select pw from users*/` reaches a model as the statement | ||
| // it executes. -executable-comments=false restores the older encoding, where | ||
| // the whole construct arrived as a single comment token. | ||
| func TestExecutableCommentsFlag(t *testing.T) { | ||
| const in = "1/*!50000union select pw from users*/" | ||
| defer func() { execComments = true }() | ||
|
|
||
| for _, tc := range []struct { | ||
| on bool | ||
| want string | ||
| }{ | ||
| {true, "NUMBER KEYWORD SPACE COMMAND SPACE IDENT SPACE KEYWORD SPACE IDENT"}, | ||
| {false, "NUMBER MULTILINE_COMMENT"}, | ||
| } { | ||
| execComments = tc.on | ||
| if got := tokenizeLineTypesOnly(in); got != tc.want { | ||
| t.Errorf("-executable-comments=%v\n got: %s\n want: %s", tc.on, got, tc.want) | ||
| } | ||
| // The marked encoding lexes each quote-delimited segment separately, so | ||
| // it has to pick the setting up too. | ||
| if got := tokenizeLineTypesOnlyMarked(in); got != tc.want { | ||
| t.Errorf("marked, -executable-comments=%v\n got: %s\n want: %s", tc.on, got, tc.want) | ||
| } | ||
| } | ||
|
|
||
| // A quote ends the segment and the lexer scanning it, so an executable | ||
| // comment opened before a quote does not stay open across it: the tail is | ||
| // lexed on its own and the closing */ falls out as WILDCARD OPERATOR. | ||
| // Quote positions are the point of this encoding, so they win. | ||
| execComments = true | ||
| wantSplit := "NUMBER KEYWORD SPACE COMMAND SPACE QUOTE IDENT WILDCARD OPERATOR" | ||
| if got := tokenizeLineTypesOnlyMarked("1/*!50000union select 'pw*/"); got != wantSplit { | ||
| t.Errorf("marked across a quote\n got: %s\n want: %s", got, wantSplit) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.