Is ruby controller params escaped? - ruby

I have a controller with PUT method:
class UtilsController < ActionController::API
def update_user_password
email = params[:email]
password = params[:password]
new_password = params[:new_password]
puts("'#{password}'")
end
end
and use openapi/javascript and Postman to send password nRP63P#$
and in console it logs nRP63P\#$
Is params of controller escaped, and how to get real value? URI.unescape?
Reference is welcome.
Thank you.

Please compare output with p and puts
password = 'nRP63P#$'
p password # will print "nRP63P\#$"
puts password # will print nRP63P#$

You can unescape value with tools of Rack
I think you were looking exactly for this Rack::Utils#unescape method.
So in your case it will be
unescaped_password = Rack::Utils.unescape(params[:password])
Keep in mind that, ruby output by default escapes some special chars # is one of them, cause it is indicator of comment, in order to evaluate further code ruby escapes it. So to verify yourself you need to write it to STDOUT or to a file.
puts unescaped_password
File.open('test.txt', 'w') { |f| f.write(unescaped_password)
and then inspect your result.
Do not use Kernel#pp or Kernel#p or String#inspect cause all of them will print values with special chars escaped by ruby itself and can mislead you.

Related

"RuntimeError: string modified" when I try gsub! incoming string inside each

My method gets strings with some urls. Urls can have ascii only symbols or any another:
"тест non-latin url http://url.cоm and test only-latin url https://url.com"
I want to parse my incoming strings, and then change all matches in the original string as follows:
"тест non-url <b>[NON-LATIN LETTERS: http://url.cоm]</b> and test only-latin url https://url.com"
and pass it to the next method.
I used each with gsub!:
def validate_url(string)
validate_url = string.gsub(/((http|https):\/\/.*?)(\s|$|\n|\Z|\t|\r|<.)/)
validate_url.each do |link|
if link.ascii_only?
string.gsub!(link, "#{link}")
else
string.gsub!(link, "<b>[NON-LATIN LETTERS: #{link}]</b>")
end
end
end
I see RuntimeError: string modified error and don't understand why and how to fix that.
For whatever reason that string refuses to be modified. While we haven't yet figured out why, here's how you can sidestep the issue: don't modify the string in-place, return a new string.
def validate_url(string)
string.gsub(/((http|https):\/\/.*?)(\s|$|\n|\Z|\t|\r|<.)/) do |link|
if link.ascii_only?
"#{link}"
else
"<b>[NON-LATIN LETTERS: #{link}]</b>"
end
end
end
Then
clean = validate_url(string)

Swap part of a string in Ruby

What's the easiest way in Ruby to interchange a part of a string with another value. Let's say that I have an email, and I want to check it on two domains, but I don't know which one I'll get as an input. The app I'm building should work with #gmail.com and #googlemail.com domains.
Example:
swap_string 'user#gmail.com' # >>user#googlemail.com
swap_string 'user#googlemail.com' # >>user#gmail.com
If you're looking to substitute a part of a string with something else, gsub works quite well.
Link to Gsub docs
It lets you match a part of a string with regex, and then substitute just that part with another string. Naturally, in place of regex, you can just use a specific string.
Example:
"user#gmail.com".gsub(/#gmail/, '#googlemail')
is equal to
user#googlemail.com
In my example I used #gmail and #googlemail instead of just gmail and googlemail. The reason for this is to make sure it's not an account with gmail in the name. It's unlikely, but could happen.
Don't match the .com either, as that can change depending on where the user's email is.
Assuming googlemail.com and gmail.com are the only two possibilities, you can use sub to replace a pattern with given replacement:
def swap_string(str)
if str =~ /gmail.com$/
str.sub("gmail.com","googlemail.com")
else
str.sub("googlemail.com","gmail.com")
end
end
swap_string 'user#gmail.com'
# => "user#googlemail.com"
swap_string 'user#googlemail.com'
# => "user#gmail.com"
You can try with Ruby gsub :
eg:
"user#gmail.com".gsub("gmail.com","googlemail.com");
As per your need of passing a string parameter in a function this should do:
def swap_mails(str)
if str =~ /gmail.com$/
str.sub('gmail.com','googlemail.com');
else
str.sub('googlemail.com','gmail.com');
end
end
swap_mails "vgmail#gmail.com" //vgmail#googlemail.com
swap_mails "vgmail#googlemail.com" ////vgmail#gmail.com
My addition :
def swap_domain str
str[/.+#/] + [ 'gmail.com', 'googlemail.com' ].detect do |d|
d != str.split('#')[1]
end
end
swap_domain 'user#gmail.com'
#=> user#googlemail.com
swap_domain 'user#googlemail.com'
#=> user#gmail.com
And this is bad code, imo.
String has a neat trick up it's sleeve in the form of String#[]:
def swap_string(string, lookups = {})
string.tap do |s|
lookups.each { |find, replace| s[find] = replace and break if s[find] }
end
end
# Example Usage
lookups = {"googlemail.com"=>"gmail.com", "gmail.com"=>"googlemail.com"}
swap_string("user#gmail.com", lookups) # => user#googlemail.com
swap_string("user#googlemail.com", lookups) # => user#gmail.com
Allowing lookups to be passed to your method makes it more reusable but you could just as easily have that hash inside of the method itself.

Copy a file with the variables substituted

I have a file containing substituted variables (#{...}) and I would like to copy it into another file, with the variables substituted by their values.
Here's what I have
file = File.open(#batch_file_name, "w+")
script=File.open("/runBatch.script","r")
script.each do |line|
file.puts(line)
end
But this is apparently not the right way to do that. Any suggestion ?
Instead of #{...} in your file use ERB files.
No, this isn't the right way to do it. You can't expect Ruby to magically interpret any #{} it encounters anywhere in your data as variable interpolation. This would (amongst other terrible side effects) yield massive security problems everywhere.
If you want to interpolate data into a string you'll need to eval it, which has its own security risks:
str = 'The value of x is #{x}'
puts str # The value of x is #{x}
x = "123"
puts eval "\"#{str}\"" # Thje value of x is 123
It's not clear which variables you're trying to interpolate into your data. This is almost certainly the wrong way to go about doing whatever it is your doing.
Ok say you have a file named tmp.file that has the following text:
This is #{foobar}!
Then you can easily do the following:
str = ""
File.open("tmp.file", "r") do |f|
str = f.read
end
abc = "Sparta"
puts eval('"' + str + '"')
And your result would be This is Sparta!
But as already suggested you should go with a real template solution like ERB. Then you would use your files like views in Rails. Instead of This is #{foobar}. you would have This is <%= foobar %>.

Is there a way to decode q-encoded strings in Ruby?

I'm working with mails, and names and subjects sometimes come q-encoded, like this:
=?UTF-8?Q?J=2E_Pablo_Fern=C3=A1ndez?=
Is there a way to decode them in Ruby? It seems TMail should take care of it, but it's not doing it.
I use this to parse email subjects:
You could try the following:
str = "=?UTF-8?Q?J=2E_Pablo_Fern=C3=A1ndez?="
if m = /=\?([A-Za-z0-9\-]+)\?(B|Q)\?([!->#-~]+)\?=/i.match(str)
case m[2]
when "B" # Base64 encoded
decoded = Base64.decode64(m[3])
when "Q" # Q encoded
decoded = m[3].unpack("M").first.gsub('_',' ')
else
p "Could not find keyword!!!"
end
Iconv.conv('utf-8',m[1],decoded) # to convert to utf-8
end
Ruby includes a method of decoding Quoted-Printable strings:
puts "Pablo_Fern=C3=A1ndez".unpack "M"
# => Pablo_Fernández
But this doesn't seem to work on your entire string (including the =?UTF-8?Q? part at the beginning. Maybe you can work it out from there, though.
This is a pretty old question but TMail::Unquoter (or its new incarnation Mail::Encodings) does the job as well.
TMail::Unquoter.unquote_and_convert_to(str, 'utf-8' )
or
Mail::Encodings.unquote_and_convert_to( str, 'utf-8' )
Decoding on a line-per-line basis:
line.unpack("M")
Convert STDIN or file provided input of encoded strings into a decoded output:
if ARGV[0]
lines = File.read(ARGV[0]).lines
else
lines = STDIN.each_line.to_a
end
puts lines.map { |c| c.unpack("M") }.join
This might help anyone wanting to test an email. delivery.html_part is normally encoded, but can be decoded to a straight HTML body using .decoded.
test "email test" do
UserMailer.confirm_email(user).deliver_now
assert_equal 1, ActionMailer::Base.deliveries.size
delivery = ActionMailer::Base.deliveries.last
assert_equal "Please confirm your email", delivery.subject
assert delivery.html_part.decoded =~ /Click the link below to confirm your email/ # DECODING HERE
end
The most efficient and up to date solution it seems to use the value_decode method of the Mail gem.
> Mail::Encodings.value_decode("=?UTF-8?Q?Greg_of_Google?=")
=> "Greg of Google"
https://www.rubydoc.info/github/mikel/mail/Mail/Encodings#value_decode-class_method
Below is Ruby code you can cut-and-paste, if inclined. It will run tests if executed directly with ruby, ruby ./copy-pasted.rb. As done in the code, I use this module as a refinement to the String core class.
A few remarks on the solution:
Other solutions perform .gsub('_', ' ') on the unpacked string. However, I do not believe this is correct, and can result in an incorrect decoding depending on the charsets. RFC2047 Section 4.2 (2) indicates "_ always represents hexidecimal 20", so it seems correct to first substitute =20 for _ then rely on the unpack result. (This also makes the implementation more elegant.) This is also discussed in an answer to a related question.
To be more instructive, I have written the regular expression in free-spacing mode to allow comments (I find this generally helpful for complex regular expressions). If you adjust the regular expression, take note that free-spacing mode changes the matching of white-space, which must then be done escaped or as a character class (as in the code). I've also added the regular expression on regex101, so you can read an explanation of the named capture groups, lazy quantifiers, etc. and experiment yourself.
The regular expression will absorb space ( ; but not TAB or newline) between multiple Q-encoded phrases in a single string, as shown with string test_4. This is because RFC2047 Section 5 (1) indicates that multiple Q encoded phrases must be separated from each other by linear white-space. Depending on your use-case, absorbing the white-space may not be desired.
The regular expression code named capture permits unexpected quoted printable codes (other than [bBqQ] so that a match will occur and the code can raise an error. This helps me to detect unexpected values when processing text. Change the regular expression named capture for code to [bBqQ] if you do not want this behaviour. (There will be no match and the original string will be returned.)
It makes use of the global Regexp.last_match as a convenience in the gsub block. You may need to take care if using this in multi-threaded code, I have not given this any consideration.
Additional references and reading:
https://en.wikipedia.org/wiki/Quoted-printable
https://en.wikipedia.org/wiki/MIME#Encoded-Word
require "minitest/autorun"
module QuotedPrintableDecode
class UnhandledCodeError < StandardError
def initialize(code)
super("Unhandled quoted printable code: '#{code}'.")
end
end
##qp_text_regex = %r{
=\? # Opening literal: `=?`
(?<charset>[^\?]+) # Character set, e.g. "Windows-1252" in `=?Windows-1252?`
\? # Literal: `?`
(?<code>[a-zA-Z]) # Encoding, e.g. "Q" in `?Q?` (`B`ase64); [BbQq] expected, others raise
\? # Literal: `?`
(?<text>[^\?]+?) # Encoded text, lazy (non-greedy) matched, e.g. "Foo_bar" in `?Foo_bar?`
\?= # Closing literal: `?=`
(?:[ ]+(?==\?))? # Optional separating linear whitespace if another Q-encode follows
}x # Free-spacing mode to allow above comments, also changes whitespace match
refine String do
def decode_q_p(to: "UTF-8")
self.gsub(##qp_text_regex) do
code, from, text = Regexp.last_match.values_at(:code, :charset, :text)
q_p_charset_to_charset(code, text, from, to)
end
end
private
def q_p_charset_to_charset(code, text, from, to)
case code
when "q", "Q"
text.gsub("_", "=20").unpack("M")
when "b", "B"
text.unpack("m")
else
raise UnhandledCodeError.new(code)
end.first.encode(to, from)
end
end
end
class TestQPDecode < Minitest::Test
using QuotedPrintableDecode
def test_decode_single_utf_8_phrase
encoded = "=?UTF-8?Q?J=2E_Pablo_Fern=C3=A1ndez?="
assert_equal encoded.decode_q_p, "J. Pablo Fernández"
end
def test_decoding_preserves_space_between_unencoded_phrase
encoded = "=?utf-8?Q?Alfred_Sanford?= <me#example.com>"
assert_equal encoded.decode_q_p, "Alfred Sanford <me#example.com>"
end
def test_decodinge_multiple_adjacent_phrases_absorbs_separating_whitespace
encoded = "=?Windows-1252?Q?Foo_-_D?= =?Windows-1252?Q?ocument_World=9617=96520;_Recor?= =?Windows-1252?Q?d_People_to_C?= =?Windows-1252?Q?anada's_History?="
assert_equal encoded.decode_q_p, "Foo - Document World–17–520; Record People to Canada's History"
end
def test_decoding_string_without_encoded_phrases_preserves_original
encoded = "Contains no QP phrases"
assert_equal encoded.decode_q_p, encoded
end
def test_unhandled_code_raises
klass = QuotedPrintableDecode::UnhandledCodeError
message = "Unhandled quoted printable code: 'Z'."
encoded = "=?utf-8?Z?Unhandled code Z?="
raised_error = assert_raises(klass) { encoded.decode_q_p }
assert_equal message, raised_error.message
end
end

Sinatra does not support multiple lines?

For the following code, why does only "World" gets printed
get '/' do
"Hello"
"World"
end
This has nothing to do with sinatra itself. It just uses the return value of the block and in ruby the return value is the last evaluated expression, which in your case is "World". This might work for you:
get '/' do
r = "Hello"
r += "World"
end
In this case you add as many string values to r as you want and the last expression would return the complete string "HelloWorld".
Correct me if I'm wrong, but I do believe in plain ruby, the last line evaluated is what gets returned.
Tomas correctly answered your question, but one way to do what I think you're meaning to do (output multiple lines), you could use:
get '/' do
output =<<EOS
Hello
World
EOS
output
end
You could use a line break char to separate lines..
get '/' do
"Hello\nWorld"
end
Don't confuse your controller with your view.
What you're probably looking for is this:
get '/' do
haml :hello_world
end
And then in views/hello_world.haml:
Hello
World
I agree with Matt.
If you want you can use that method with one file too.
get '/' do
erb :hello_world
end
__END__
##hello_world
hello
world
I just use puts inside my controller to get some debug printed to STDOUT.

Resources