Skip to content

Commit

Permalink
Fixed #4278: String#underscore with digit (#4280)
Browse files Browse the repository at this point in the history
* Treat digit as downcase character. 
* Add missing test case: digit between upcase letters
* Rename OpenSSL constants for String#underscore changes
  • Loading branch information
makenowjust authored and Martin Verzilli committed Jun 2, 2017
1 parent 6b3bde1 commit da48f82
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 30 deletions.
20 changes: 10 additions & 10 deletions spec/std/openssl/ssl/context_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ describe OpenSSL::SSL::Context do
context = OpenSSL::SSL::Context::Client.new

(context.options & OpenSSL::SSL::Options::ALL).should eq(OpenSSL::SSL::Options::ALL)
(context.options & OpenSSL::SSL::Options::NO_SSLV2).should eq(OpenSSL::SSL::Options::NO_SSLV2)
(context.options & OpenSSL::SSL::Options::NO_SSLV3).should eq(OpenSSL::SSL::Options::NO_SSLV3)
(context.options & OpenSSL::SSL::Options::NO_SSL_V2).should eq(OpenSSL::SSL::Options::NO_SSL_V2)
(context.options & OpenSSL::SSL::Options::NO_SSL_V3).should eq(OpenSSL::SSL::Options::NO_SSL_V3)
(context.options & OpenSSL::SSL::Options::NO_SESSION_RESUMPTION_ON_RENEGOTIATION).should eq(OpenSSL::SSL::Options::NO_SESSION_RESUMPTION_ON_RENEGOTIATION)
(context.options & OpenSSL::SSL::Options::SINGLE_ECDH_USE).should eq(OpenSSL::SSL::Options::SINGLE_ECDH_USE)
(context.options & OpenSSL::SSL::Options::SINGLE_DH_USE).should eq(OpenSSL::SSL::Options::SINGLE_DH_USE)
Expand All @@ -22,8 +22,8 @@ describe OpenSSL::SSL::Context do
context = OpenSSL::SSL::Context::Server.new

(context.options & OpenSSL::SSL::Options::ALL).should eq(OpenSSL::SSL::Options::ALL)
(context.options & OpenSSL::SSL::Options::NO_SSLV2).should eq(OpenSSL::SSL::Options::NO_SSLV2)
(context.options & OpenSSL::SSL::Options::NO_SSLV3).should eq(OpenSSL::SSL::Options::NO_SSLV3)
(context.options & OpenSSL::SSL::Options::NO_SSL_V2).should eq(OpenSSL::SSL::Options::NO_SSL_V2)
(context.options & OpenSSL::SSL::Options::NO_SSL_V3).should eq(OpenSSL::SSL::Options::NO_SSL_V3)
(context.options & OpenSSL::SSL::Options::NO_SESSION_RESUMPTION_ON_RENEGOTIATION).should eq(OpenSSL::SSL::Options::NO_SESSION_RESUMPTION_ON_RENEGOTIATION)
(context.options & OpenSSL::SSL::Options::SINGLE_ECDH_USE).should eq(OpenSSL::SSL::Options::SINGLE_ECDH_USE)
(context.options & OpenSSL::SSL::Options::SINGLE_DH_USE).should eq(OpenSSL::SSL::Options::SINGLE_DH_USE)
Expand Down Expand Up @@ -96,22 +96,22 @@ describe OpenSSL::SSL::Context do
context.add_options(OpenSSL::SSL::Options::ALL)
.should eq(default_options | OpenSSL::SSL::Options::ALL)

context.add_options(OpenSSL::SSL::Options.flags(NO_SSLV2, NO_SSLV3))
.should eq(OpenSSL::SSL::Options.flags(ALL, NO_SSLV2, NO_SSLV3))
context.add_options(OpenSSL::SSL::Options.flags(NO_SSL_V2, NO_SSL_V3))
.should eq(OpenSSL::SSL::Options.flags(ALL, NO_SSL_V2, NO_SSL_V3))
end

it "removes options" do
context = OpenSSL::SSL::Context::Client.insecure
default_options = context.options
context.add_options(OpenSSL::SSL::Options.flags(NO_TLSV1, NO_SSLV2))
context.remove_options(OpenSSL::SSL::Options::NO_TLSV1).should eq(default_options | OpenSSL::SSL::Options::NO_SSLV2)
context.add_options(OpenSSL::SSL::Options.flags(NO_TLS_V1, NO_SSL_V2))
context.remove_options(OpenSSL::SSL::Options::NO_TLS_V1).should eq(default_options | OpenSSL::SSL::Options::NO_SSL_V2)
end

it "returns options" do
context = OpenSSL::SSL::Context::Client.insecure
default_options = context.options
context.add_options(OpenSSL::SSL::Options.flags(ALL, NO_SSLV2))
context.options.should eq(default_options | OpenSSL::SSL::Options.flags(ALL, NO_SSLV2))
context.add_options(OpenSSL::SSL::Options.flags(ALL, NO_SSL_V2))
context.options.should eq(default_options | OpenSSL::SSL::Options.flags(ALL, NO_SSL_V2))
end

it "adds modes" do
Expand Down
4 changes: 4 additions & 0 deletions spec/std/string_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1672,6 +1672,10 @@ describe "String" do
"C_".underscore.should eq("c_")
"HTTP".underscore.should eq("http")
"HTTP_CLIENT".underscore.should eq("http_client")
"CSS3".underscore.should eq("css3")
"HTTP1.1".underscore.should eq("http1.1")
"3.14IsPi".underscore.should eq("3.14_is_pi")
"I2C".underscore.should eq("i2_c")
end

it "does camelcase" do
Expand Down
16 changes: 8 additions & 8 deletions src/openssl/lib_ssl.cr
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ lib LibSSL
CIPHER_SERVER_PREFERENCE = 0x00400000
TLS_ROLLBACK_BUG = 0x00800000

NO_SSLV3 = 0x02000000
NO_TLSV1 = 0x04000000
NO_TLSV1_2 = 0x08000000
NO_TLSV1_1 = 0x10000000
NO_SSL_V3 = 0x02000000
NO_TLS_V1 = 0x04000000
NO_TLS_V1_2 = 0x08000000
NO_TLS_V1_1 = 0x10000000

NETSCAPE_CA_DN_BUG = 0x20000000
NETSCAPE_DEMO_CIPHER_CHANGE_BUG = 0x40000000
Expand All @@ -97,23 +97,23 @@ lib LibSSL
NETSCAPE_CHALLENGE_BUG = 0x00000000
NETSCAPE_REUSE_CIPHER_CHANGE_BUG = 0x00000000
SSLREF2_REUSE_CERT_TYPE_BUG = 0x00000000
MICROSOFT_BIG_SSLV3_BUFFER = 0x00000000
MICROSOFT_BIG_SSL_V3_BUFFER = 0x00000000
SSLEAY_080_CLIENT_DH_BUG = 0x00000000
TLS_D5_BUG = 0x00000000
TLS_BLOCK_PADDING_BUG = 0x00000000
NO_SSLV2 = 0x00000000
NO_SSL_V2 = 0x00000000
SINGLE_ECDH_USE = 0x00000000
SINGLE_DH_USE = 0x00000000
{% else %}
MICROSOFT_SESS_ID_BUG = 0x00000001
NETSCAPE_CHALLENGE_BUG = 0x00000002
NETSCAPE_REUSE_CIPHER_CHANGE_BUG = 0x00000008
SSLREF2_REUSE_CERT_TYPE_BUG = 0x00000010
MICROSOFT_BIG_SSLV3_BUFFER = 0x00000020
MICROSOFT_BIG_SSL_V3_BUFFER = 0x00000020
SSLEAY_080_CLIENT_DH_BUG = 0x00000080
TLS_D5_BUG = 0x00000100
TLS_BLOCK_PADDING_BUG = 0x00000200
NO_SSLV2 = 0x01000000
NO_SSL_V2 = 0x01000000
SINGLE_ECDH_USE = 0x00080000
SINGLE_DH_USE = 0x00100000
{% end %}
Expand Down
16 changes: 8 additions & 8 deletions src/openssl/ssl/context.cr
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ abstract class OpenSSL::SSL::Context
# require "openssl"
#
# context = OpenSSL::SSL::Context::Client.new
# context.add_options(OpenSSL::SSL::Options::NO_SSLV2 | OpenSSL::SSL::Options::NO_SSLV3)
# context.add_options(OpenSSL::SSL::Options::NO_SSL_V2 | OpenSSL::SSL::Options::NO_SSL_V3)
# ```
def initialize(method : LibSSL::SSLMethod = Context.default_method)
super(method)
Expand Down Expand Up @@ -123,7 +123,7 @@ abstract class OpenSSL::SSL::Context
#
# ```
# context = OpenSSL::SSL::Context::Server.new
# context.add_options(OpenSSL::SSL::Options::NO_SSLV2 | OpenSSL::SSL::Options::NO_SSLV3)
# context.add_options(OpenSSL::SSL::Options::NO_SSL_V2 | OpenSSL::SSL::Options::NO_SSL_V3)
# ```
def initialize(method : LibSSL::SSLMethod = Context.default_method)
super(method)
Expand Down Expand Up @@ -151,8 +151,8 @@ abstract class OpenSSL::SSL::Context

add_options(OpenSSL::SSL::Options.flags(
ALL,
NO_SSLV2,
NO_SSLV3,
NO_SSL_V2,
NO_SSL_V3,
NO_SESSION_RESUMPTION_ON_RENEGOTIATION,
SINGLE_ECDH_USE,
SINGLE_DH_USE
Expand Down Expand Up @@ -265,9 +265,9 @@ abstract class OpenSSL::SSL::Context
# Example:
# ```
# context.add_options(
# OpenSSL::SSL::Options::ALL | # various workarounds
# OpenSSL::SSL::Options::NO_SSLV2 | # disable overly deprecated SSLv2
# OpenSSL::SSL::Options::NO_SSLV3 # disable deprecated SSLv3
# OpenSSL::SSL::Options::ALL | # various workarounds
# OpenSSL::SSL::Options::NO_SSL_V2 | # disable overly deprecated SSLv2
# OpenSSL::SSL::Options::NO_SSL_V3 # disable deprecated SSLv3
# )
# ```
def add_options(options : OpenSSL::SSL::Options)
Expand All @@ -283,7 +283,7 @@ abstract class OpenSSL::SSL::Context
#
# Example:
# ```
# context.remove_options(OpenSSL::SSL::Options::NO_SSLV3)
# context.remove_options(OpenSSL::SSL::Options::NO_SSL_V3)
# ```
def remove_options(options : OpenSSL::SSL::Options)
opts = {% if LibSSL::OPENSSL_110 %}
Expand Down
19 changes: 15 additions & 4 deletions src/string.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3278,27 +3278,37 @@ class String
# "DoesWhatItSaysOnTheTin".underscore # => "does_what_it_says_on_the_tin"
# "PartyInTheUSA".underscore # => "party_in_the_usa"
# "HTTP_CLIENT".underscore # => "http_client"
# "3.14IsPi".underscore # => "3.14_is_pi"
# ```
def underscore
first = true
last_is_downcase = false
last_is_upcase = false
last_is_digit = false
mem = nil

String.build(bytesize + 10) do |str|
each_char do |char|
downcase = 'a' <= char <= 'z'
digit = '0' <= char <= '9'
downcase = 'a' <= char <= 'z' || digit
upcase = 'A' <= char <= 'Z'

if first
str << char.downcase
elsif last_is_downcase && upcase
if mem
# This is the case of A1Bcd, we need to put 'mem' (not to need to convert as downcase
# ^
# because 'mem' is digit surely) before putting this char as downcase.
str << mem
mem = nil
end
# This is the case of AbcDe, we need to put an underscore before the 'D'
# ^
str << '_'
str << char.downcase
elsif last_is_upcase && upcase
# This is the case of 1) ABCde, 2) ABCDe or 3) ABC_de:if the next char is upcase (case 1) we need
elsif (last_is_upcase || last_is_digit) && (upcase || digit)
# This is the case of 1) A1Bcd, 2) A1BCd or 3) A1B_cd:if the next char is upcase (case 1) we need
# ^ ^ ^
# 1) we need to append this char as downcase
# 2) we need to append an underscore and then the char as downcase, so we save this char
Expand All @@ -3313,7 +3323,7 @@ class String
if mem
if char == '_'
# case 3
else
elsif last_is_upcase && downcase
# case 1
str << '_'
end
Expand All @@ -3326,6 +3336,7 @@ class String

last_is_downcase = downcase
last_is_upcase = upcase
last_is_digit = digit
first = false
end

Expand Down

0 comments on commit da48f82

Please sign in to comment.