From 2a731344f2c760b78d5144f92ab73890320ce37a Mon Sep 17 00:00:00 2001 From: Lucas Bajolet Date: Mon, 14 Sep 2015 11:13:24 -0400 Subject: [PATCH] lib: Update sha1 and base64 to use Bytes instead of String Signed-off-by: Lucas Bajolet --- contrib/opportunity/src/opportunity_controller.nit | 1 - contrib/opportunity/src/opportunity_model.nit | 2 +- examples/rosettacode/sha_1.nit | 2 +- lib/base64.nit | 149 ++++++++++++-------- lib/core/bytes.nit | 26 +++- lib/sha1.nit | 61 +++----- lib/websocket/websocket.nit | 2 +- 7 files changed, 137 insertions(+), 106 deletions(-) diff --git a/contrib/opportunity/src/opportunity_controller.nit b/contrib/opportunity/src/opportunity_controller.nit index 868dd9e..26a4ef6 100644 --- a/contrib/opportunity/src/opportunity_controller.nit +++ b/contrib/opportunity/src/opportunity_controller.nit @@ -16,7 +16,6 @@ module opportunity_controller import nitcorn -import sha1 import templates import opportunity_model diff --git a/contrib/opportunity/src/opportunity_model.nit b/contrib/opportunity/src/opportunity_model.nit index ef7654d..cc72f23 100644 --- a/contrib/opportunity/src/opportunity_model.nit +++ b/contrib/opportunity/src/opportunity_model.nit @@ -247,7 +247,7 @@ class Meetup redef fun commit(db) do if id == "" then var time = get_time - var tmpid = (name + date + place + time.to_s).sha1_to_s + var tmpid = (name + date + place + time.to_s).sha1.hexdigest if not db.execute("INSERT INTO meetups (id, name, date, place, answer_mode) VALUES({tmpid.to_sql_string}, {name.html_escape.to_sql_string}, {date.html_escape.to_sql_string}, {place.html_escape.to_sql_string}, {answer_mode});") then print "Error recording entry Meetup {self}" print db.error or else "Null error" diff --git a/examples/rosettacode/sha_1.nit b/examples/rosettacode/sha_1.nit index 2eaf471..8776d48 100644 --- a/examples/rosettacode/sha_1.nit +++ b/examples/rosettacode/sha_1.nit @@ -9,4 +9,4 @@ module sha_1 import sha1 -print "Rosetta Code".sha1_to_s +print "Rosetta Code".sha1.hexdigest diff --git a/lib/base64.nit b/lib/base64.nit index 5f1c920..4eae395 100644 --- a/lib/base64.nit +++ b/lib/base64.nit @@ -17,88 +17,76 @@ # Offers the base 64 encoding and decoding algorithms module base64 -redef class String - +redef class NativeString # Alphabet used by the base64 algorithm - private fun base64_chars : String + private fun base64_chars : SequenceRead[Byte] do - return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".bytes end + + # Reversed alphabet for base64 private fun inverted_base64_chars : HashMap[Byte, Byte] do var inv_base64_chars = new HashMap[Byte, Byte] - for k in [0..base64_chars.bytelen[ do - inv_base64_chars[base64_chars.bytes[k]] = k.to_b + var l = base64_chars.length + for k in [0 .. l[ do + inv_base64_chars[base64_chars[k]] = k.to_b end return inv_base64_chars end - # Encodes the receiver string to base64. + # Encodes `self` to base64. + # # By default, uses "=" for padding. - fun encode_base64 : String do return encode_base64_custom_padding('='.ascii.to_b) - - # Encodes the receiver string to base64 using a custom padding character. # - # If using the default padding character `=`, see `encode_base64`. - fun encode_base64_custom_padding(padding : Byte) : String - do - var base64_bytes = once base64_chars.bytes - var length = bytelen - + # assert "string".encode_base64 == "c3RyaW5n" + private fun encode_base64(length: Int, padding: nullable Byte): Bytes do + var base64_bytes = once base64_chars + if padding == null then padding = '='.ascii.to_b var steps = length / 3 var bytes_in_last_step = length % 3 var result_length = steps * 4 if bytes_in_last_step > 0 then result_length += 4 - var result = new NativeString(result_length + 1) - var bytes = self.bytes - result[result_length] = 0u8 - - var mask_6bit = 0b0011_1111 + var result = new Bytes.with_capacity(result_length) + var in_off = 0 for s in [0 .. steps[ do - var e = 0 - for ss in [0 .. 3[ do - e += bytes[s * 3 + ss].to_i << ((2 - ss) * 8) - end - for ss in [0..4[ do - result[s * 4 + 3 - ss] = base64_bytes[(e >> (ss * 6)) & mask_6bit] - end + var ind = ((self[in_off] & 0b1111_1100u8) >> 2).to_i + result.add base64_bytes[ind] + ind = ((self[in_off] & 0b0000_0011u8) << 4).to_i | ((self[in_off + 1] & 0b1111_0000u8) >> 4).to_i + result.add base64_bytes[ind] + ind = ((self[in_off + 1] & 0b0000_1111u8) << 2).to_i | ((self[in_off + 2] & 0b1100_0000u8) >> 6).to_i + result.add base64_bytes[ind] + ind = (self[in_off + 2] & 0b0011_1111u8).to_i + result.add base64_bytes[ind] + in_off += 3 end - - var out_off = result_length - 4 - var in_off = length - bytes_in_last_step if bytes_in_last_step == 1 then - result[out_off] = base64_bytes[((bytes[in_off] & 0b1111_1100u8) >> 2).to_i] - result[out_off + 1] = base64_bytes[((bytes[in_off] & 0b0000_0011u8) << 4).to_i] - out_off += 2 + result.add base64_bytes[((self[in_off] & 0b1111_1100u8) >> 2).to_i] + result.add base64_bytes[((self[in_off] & 0b0000_0011u8) << 4).to_i] else if bytes_in_last_step == 2 then - result[out_off] = base64_bytes[((bytes[in_off] & 0b1111_1100u8) >> 2).to_i] - result[out_off + 1] = base64_bytes[(((bytes[in_off] & 0b0000_0011u8) << 4) | ((bytes[in_off + 1] & 0b1111_0000u8) >> 4)).to_i] - result[out_off + 2] = base64_bytes[((bytes[in_off + 1] & 0b0000_1111u8) << 2).to_i] - out_off += 3 - end - if bytes_in_last_step > 0 then - for i in [out_off .. result_length[ do result[i] = padding + result.add base64_bytes[((self[in_off] & 0b1111_1100u8) >> 2).to_i] + result.add base64_bytes[(((self[in_off] & 0b0000_0011u8) << 4) | ((self[in_off + 1] & 0b1111_0000u8) >> 4)).to_i] + result.add base64_bytes[((self[in_off + 1] & 0b0000_1111u8) << 2).to_i] end + var rempad = if bytes_in_last_step > 0 then 3 - bytes_in_last_step else 0 + for i in [0 .. rempad[ do result.add padding - return result.to_s_with_length(result_length) + return result end - # Decodes the receiver string from base64. - # By default, uses "=" for padding. - fun decode_base64 : String do return decode_base64_custom_padding('='.ascii.to_b) - - # Decodes the receiver string to base64 using a custom padding character. + # Decodes `self` from base64 # - # If using the default padding character `=`, see `decode_base64`. - fun decode_base64_custom_padding(padding : Byte) : String - do + # assert "c3RyaW5n".decode_base64 == "string" + # + # REQUIRE: `length % 4 == 0` + private fun decode_base64(length: Int, padding: nullable Byte): Bytes do + if padding == null then padding = '='.ascii.to_b var inv = once inverted_base64_chars - var length = bytelen - if length == 0 then return "" + if length == 0 then return new Bytes.empty assert length % 4 == 0 else print "base64::decode_base64 only supports strings of length multiple of 4" - var bytes = self.bytes + var bytes = self var steps = length / 4 var result_length = steps * 3 @@ -113,17 +101,16 @@ redef class String if padding_len == 1 then result_length -= 1 if padding_len == 2 then result_length -= 2 - var result = new NativeString(result_length + 1) - result[result_length] = 0u8 + var result = new Bytes.with_capacity(result_length + 1) for s in [0 .. steps[ do var c0 = inv[bytes[s * 4]] var c1 = inv[bytes[s * 4 + 1]] var c2 = inv[bytes[s * 4 + 2]] var c3 = inv[bytes[s * 4 + 3]] - result[s * 3] = ((c0 & 0b0011_1111u8) << 2) | ((c1 & 0b0011_0000u8) >> 4) - result[s * 3 + 1] = ((c1 & 0b0000_1111u8) << 4) | ((c2 & 0b0011_1100u8) >> 2) - result[s * 3 + 2] = ((c2 & 0b0000_0011u8) << 6) | (c3 & 0b0011_1111u8) + result.add (((c0 & 0b0011_1111u8) << 2) | ((c1 & 0b0011_0000u8) >> 4)) + result.add (((c1 & 0b0000_1111u8) << 4) | ((c2 & 0b0011_1100u8) >> 2)) + result.add (((c2 & 0b0000_0011u8) << 6) | (c3 & 0b0011_1111u8)) end var last_start = steps * 4 @@ -131,14 +118,52 @@ redef class String var c0 = inv[bytes[last_start]] var c1 = inv[bytes[last_start + 1]] var c2 = inv[bytes[last_start + 2]] - result[result_length - 2] = ((c0 & 0b0011_1111u8) << 2) | ((c1 & 0b0011_0000u8) >> 4) - result[result_length - 1] = ((c1 & 0b0000_1111u8) << 4) | ((c2 & 0b0011_1100u8) >> 2) + result.add (((c0 & 0b0011_1111u8) << 2) | ((c1 & 0b0011_0000u8) >> 4)) + result.add (((c1 & 0b0000_1111u8) << 4) | ((c2 & 0b0011_1100u8) >> 2)) else if padding_len == 2 then var c0 = inv[bytes[last_start]] var c1 = inv[bytes[last_start + 1]] - result[result_length - 1] = ((c0 & 0b0011_1111u8) << 2) | ((c1 & 0b0011_0000u8) >> 4) + result.add (((c0 & 0b0011_1111u8) << 2) | ((c1 & 0b0011_0000u8) >> 4)) end - return result.to_s_with_length(result_length) + return result + end +end + +redef class Bytes + + # Encodes the receiver string to base64 using a custom padding character. + # + # If using the default padding character `=`, see `encode_base64`. + fun encode_base64(padding: nullable Byte): Bytes + do + return items.encode_base64(length, padding) + end + + # Decodes the receiver string to base64 using a custom padding character. + # + # Default padding character `=` + fun decode_base64(padding : nullable Byte) : Bytes + do + return items.decode_base64(length, padding) + end +end + +redef class String + + # Encodes the receiver string to base64 using a custom padding character. + # + # If using the default padding character `=`, see `encode_base64`. + fun encode_base64(padding: nullable Byte): String + do + return to_cstring.encode_base64(bytelen, padding).to_s + end + + # Decodes the receiver string to base64 using a custom padding character. + # + # Default padding character `=` + fun decode_base64(padding : nullable Byte) : String + do + return to_cstring.decode_base64(bytelen, padding).to_s end end diff --git a/lib/core/bytes.nit b/lib/core/bytes.nit index 332efc2..d589646 100644 --- a/lib/core/bytes.nit +++ b/lib/core/bytes.nit @@ -19,6 +19,16 @@ import kernel import collection::array intrude import text::flat +redef class Byte + # Write self as a string into `ns` at position `pos` + private fun add_digest_at(ns: NativeString, pos: Int) do + var tmp = (0xF0u8 & self) >> 4 + ns[pos] = if tmp >= 0x0Au8 then tmp + 0x37u8 else tmp + 0x30u8 + tmp = 0x0Fu8 & self + ns[pos + 1] = if tmp >= 0x0Au8 then tmp + 0x37u8 else tmp + 0x30u8 + end +end + # A buffer containing Byte-manipulation facilities # # Uses Copy-On-Write when persisted @@ -26,7 +36,7 @@ class Bytes super AbstractArray[Byte] # A NativeString being a char*, it can be used as underlying representation here. - private var items: NativeString + var items: NativeString # Number of bytes in the array redef var length @@ -63,6 +73,20 @@ class Bytes return items[i] end + # Returns self as a hexadecimal digest + fun hexdigest: String do + var elen = length * 2 + var ns = new NativeString(elen) + var i = 0 + var oi = 0 + while i < length do + self[i].add_digest_at(ns, oi) + i += 1 + oi += 2 + end + return new FlatString.full(ns, elen, 0, elen - 1, elen) + end + # var b = new Bytes.with_capacity(1) # b[0] = 101u8 # assert b.to_s == "e" diff --git a/lib/sha1.nit b/lib/sha1.nit index 8a5acc9..2bd8fbe 100644 --- a/lib/sha1.nit +++ b/lib/sha1.nit @@ -1,7 +1,5 @@ # This file is part of NIT (http://www.nitlanguage.org). # -# Copyright 2014 Lucas Bajolet -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -219,22 +217,12 @@ in "C Header" `{ } `} -redef class String - - # Computes the SHA1 of the receiver - # - # Returns a digest of 20 bytes as a String, - # note that all the characters are not necessarily ASCII. - # If you want the hex string version of the digest, use - # sha1_to_s. - # - # import base64 - # assert "The quick brown fox jumps over the lazy dog".sha1.encode_base64 == "L9ThxnotKPzthJ7hu3bnORuT6xI=" - fun sha1: String import String.to_cstring, String.length, NativeString.to_s_with_length `{ +redef class NativeString + private fun sha1_intern(len: Int): NativeString `{ sha1nfo s; sha1_init(&s); - sha1_write(&s, String_to_cstring(self), String_length(self)); + sha1_write(&s, self, len); uint8_t* digest = sha1_result(&s); char* digested = malloc(21); @@ -243,35 +231,30 @@ redef class String digested[20] = '\0'; - return NativeString_to_s_with_length(digested, 20); + return digested; `} +end + +redef class String + + # Computes the SHA1 of the receiver + # + # Returns a digest of 20 bytes as a NativeString, + # note that all the characters are not necessarily ASCII. + # If you want the hex string version of the digest, use + # sha1_hexdigest. + # + # import base64 + # assert "The quick brown fox jumps over the lazy dog".sha1 == [0x2Fu8, 0xD4u8, 0xE1u8, 0xC6u8, 0x7Au8, 0x2Du8, 0x28u8, 0xFCu8, 0xEDu8, 0x84u8, 0x9Eu8, 0xE1u8, 0xBBu8, 0x76u8, 0xE7u8, 0x39u8, 0x1Bu8, 0x93u8, 0xEBu8, 0x12u8] + fun sha1: Bytes do + return new Bytes(to_cstring.sha1_intern(bytelen), 20, 20) + end # Computes the SHA1 of the receiver. # # Returns a 40 char String containing the Hexadecimal # Digest in its Char form. # - # assert "The quick brown fox jumps over the lazy dog".sha1_to_s == "2FD4E1C67A2D28FCED849EE1BB76E7391B93EB12" - fun sha1_to_s: String import String.to_cstring, String.length, NativeString.to_s_with_length `{ - sha1nfo s; - - sha1_init(&s); - sha1_write(&s, String_to_cstring(self), String_length(self)); - uint8_t* digest = sha1_result(&s); - - char* ret_str = malloc(41); - char* hexmap = "0123456789ABCDEF"; - - int i; - for(i=0;i<20;i++){ - uint8_t q = digest[i]; - ret_str[i*2] = hexmap[q >> 4]; - ret_str[(i*2)+1] = hexmap[q & 0x0F]; - } - ret_str[40] = '\0'; - - return NativeString_to_s_with_length(ret_str, 40); - `} - + # assert "The quick brown fox jumps over the lazy dog".sha1_hexdigest == "2FD4E1C67A2D28FCED849EE1BB76E7391B93EB12" + fun sha1_hexdigest: String do return sha1.hexdigest end - diff --git a/lib/websocket/websocket.nit b/lib/websocket/websocket.nit index c3fdaad..2a3c32f 100644 --- a/lib/websocket/websocket.nit +++ b/lib/websocket/websocket.nit @@ -114,7 +114,7 @@ class WebsocketConnection resp_map["Connection:"] = "Upgrade" var key = heads["Sec-WebSocket-Key"] key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" - key = key.sha1.encode_base64 + key = key.sha1.encode_base64.to_s resp_map["Sec-WebSocket-Accept:"] = key var resp = resp_map.join("\r\n", " ") resp += "\r\n\r\n" -- 1.7.9.5