Skip to content

Commit 7a356ec

Browse files
committed
Add method for escaping single-quoted newline chars
1 parent 35f8808 commit 7a356ec

File tree

2 files changed

+62
-5
lines changed

2 files changed

+62
-5
lines changed

lib/graphql/language.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
require "graphql/language/token"
1313
require "graphql/language/visitor"
1414
require "graphql/language/definition_slice"
15+
require "strscan"
1516

1617
module GraphQL
1718
module Language
@@ -33,5 +34,41 @@ def self.serialize(value)
3334
JSON.generate(value, quirks_mode: true)
3435
end
3536
end
37+
38+
# Returns a new string if any single-quoted newlines were escaped.
39+
# Otherwise, returns `query_str` unchanged.
40+
# @return [String]
41+
def self.escape_single_quoted_newlines(query_str)
42+
scanner = StringScanner.new(query_str)
43+
inside_single_quoted_string = false
44+
new_query_str = nil
45+
while !scanner.eos?
46+
if (match = scanner.scan(/(?:\\"|[^"\n\r]|""")+/m)) && new_query_str
47+
new_query_str << match
48+
elsif scanner.scan('"')
49+
new_query_str && (new_query_str << '"')
50+
inside_single_quoted_string = !inside_single_quoted_string
51+
elsif scanner.scan("\n")
52+
if inside_single_quoted_string
53+
new_query_str ||= query_str[0, scanner.pos - 1]
54+
new_query_str << '\\n'
55+
else
56+
new_query_str && (new_query_str << "\n")
57+
end
58+
elsif scanner.scan("\r")
59+
if inside_single_quoted_string
60+
new_query_str ||= query_str[0, scanner.pos - 1]
61+
new_query_str << '\\r'
62+
else
63+
new_query_str && (new_query_str << "\r")
64+
end
65+
elsif scanner.eos?
66+
break
67+
else
68+
raise ArgumentError, "Unmatchable string scanner segment: #{scanner.rest.inspect}"
69+
end
70+
end
71+
new_query_str || query_str
72+
end
3673
end
3774
end

spec/graphql/language/parser_spec.rb

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,35 @@
1616
assert_equal expected_message, err.message
1717
end
1818

19-
it "rejects newlines in single-quoted strings" do
19+
it "rejects newlines in single-quoted strings unless escaped" do
20+
nl_query_string_1 = "{ doStuff(arg: \"
21+
abc\") }"
22+
nl_query_string_2 = "{ doStuff(arg: \"\rabc\") }"
23+
2024
assert_raises(GraphQL::ParseError) {
21-
GraphQL.parse("{ doStuff(arg: \"
22-
abc\") }"
23-
)
25+
GraphQL.parse(nl_query_string_2)
2426
}
2527
assert_raises(GraphQL::ParseError) {
26-
GraphQL.parse("{ doStuff(arg: \"\rabc\") }")
28+
GraphQL.parse(nl_query_string_2)
2729
}
30+
31+
assert GraphQL.parse(GraphQL::Language.escape_single_quoted_newlines(nl_query_string_1))
32+
assert GraphQL.parse(GraphQL::Language.escape_single_quoted_newlines(nl_query_string_2))
33+
end
34+
35+
it "can replace single-quoted newlines" do
36+
replacements = {
37+
"{ a(\"\n abc\n\") }" => '{ a("\\n abc\\n") }',
38+
"{ a(\"\r\n ab\rc\n\") }" => '{ a("\\r\\n ab\\rc\\n") }',
39+
"{ a(\"\n abc\n\") b(\"\n \\\"abc\n\") }" => '{ a("\\n abc\\n") b("\\n \\"abc\\n") }',
40+
# No modification to block strings:
41+
"{ a(\"\"\"\n abc\n\"\"\") }" => "{ a(\"\"\"\n abc\n\"\"\") }",
42+
"{ a(\"\"\"\r\n abc\r\n\"\"\") }" => "{ a(\"\"\"\r\n abc\r\n\"\"\") }",
43+
}
44+
45+
replacements.each_with_index do |(before_str, after_str), idx|
46+
assert_equal after_str, GraphQL::Language.escape_single_quoted_newlines(before_str), "It works for example pair ##{idx + 1} (#{after_str})"
47+
end
2848
end
2949

3050
describe "when there are no selections" do

0 commit comments

Comments
 (0)