fernet 1.5 → 1.6

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -5,7 +5,5 @@ rvm:
5
5
  - "1.9.2"
6
6
  - rbx-19mode
7
7
  - "1.8.7"
8
- - jruby-18mode
9
- - jruby-19mode
10
8
 
11
9
  script: bundle exec rspec -b spec
data/Gemfile CHANGED
@@ -1,8 +1,4 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- platforms :jruby do
4
- gem 'jruby-openssl', '~> 0.7.7'
5
- end
6
-
7
3
  # Specify your gem's dependencies in fernet.gemspec
8
4
  gemspec
data/fernet.gemspec CHANGED
@@ -15,5 +15,7 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Fernet::VERSION
17
17
 
18
+ gem.add_dependency "yajl-ruby"
19
+
18
20
  gem.add_development_dependency "rspec"
19
21
  end
@@ -1,4 +1,5 @@
1
1
  require 'base64'
2
+ require 'yajl'
2
3
  require 'openssl'
3
4
  require 'date'
4
5
 
@@ -15,13 +16,13 @@ module Fernet
15
16
 
16
17
  def generate
17
18
  yield self if block_given?
18
- data.merge!(:issued_at => DateTime.now.to_s)
19
+ data.merge!(:issued_at => DateTime.now)
19
20
 
20
21
  if encrypt?
21
22
  iv = encrypt_data!
22
23
  @payload = "#{base64(data)}|#{base64(iv)}"
23
24
  else
24
- @payload = base64(Fernet::OkJson.encode(stringify_hash_keys(data)))
25
+ @payload = base64(Yajl::Encoder.encode(data))
25
26
  end
26
27
 
27
28
  mac = OpenSSL::HMAC.hexdigest('sha256', payload, signing_key)
@@ -46,7 +47,7 @@ module Fernet
46
47
  iv = cipher.random_iv
47
48
  cipher.iv = iv
48
49
  cipher.key = encryption_key
49
- @data = cipher.update(Fernet::OkJson.encode(stringify_hash_keys(data))) + cipher.final
50
+ @data = cipher.update(Yajl::Encoder.encode(data)) + cipher.final
50
51
  iv
51
52
  end
52
53
 
@@ -66,11 +67,5 @@ module Fernet
66
67
  @encrypt
67
68
  end
68
69
 
69
- def stringify_hash_keys(hash)
70
- hash.inject({}) do |result, (k, v)|
71
- result[k.to_s] = v
72
- result
73
- end
74
- end
75
70
  end
76
71
  end
@@ -1,4 +1,5 @@
1
1
  require 'base64'
2
+ require 'yajl'
2
3
  require 'openssl'
3
4
  require 'date'
4
5
 
@@ -43,12 +44,12 @@ module Fernet
43
44
  parts = @token.split('|')
44
45
  if decrypt?
45
46
  encrypted_data, iv, @received_signature = *parts
46
- @data = Fernet::OkJson.decode(decrypt!(encrypted_data, Base64.urlsafe_decode64(iv)))
47
+ @data = Yajl::Parser.parse(decrypt!(encrypted_data, Base64.urlsafe_decode64(iv)))
47
48
  signing_blob = "#{encrypted_data}|#{iv}"
48
49
  else
49
50
  encoded_data, @received_signature = *parts
50
51
  signing_blob = encoded_data
51
- @data = Fernet::OkJson.decode(Base64.urlsafe_decode64(encoded_data))
52
+ @data = Yajl::Parser.parse(Base64.urlsafe_decode64(encoded_data))
52
53
  end
53
54
  @regenerated_mac = OpenSSL::HMAC.hexdigest('sha256', signing_blob, signing_key)
54
55
  end
@@ -1,3 +1,3 @@
1
1
  module Fernet
2
- VERSION = "1.5"
2
+ VERSION = "1.6"
3
3
  end
data/lib/fernet.rb CHANGED
@@ -3,7 +3,6 @@ require 'fernet/generator'
3
3
  require 'fernet/verifier'
4
4
  require 'fernet/secret'
5
5
  require 'fernet/configuration'
6
- require 'fernet/okjson'
7
6
 
8
7
  if RUBY_VERSION == '1.8.7'
9
8
  require 'shim/base64'
metadata CHANGED
@@ -1,34 +1,38 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fernet
3
3
  version: !ruby/object:Gem::Version
4
- prerelease:
5
- version: '1.5'
4
+ version: '1.6'
5
+ prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Harold Giménez
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-08 00:00:00.000000000 Z
12
+ date: 2012-11-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: rspec
16
- version_requirements: !ruby/object:Gem::Requirement
15
+ name: yajl-ruby
16
+ requirement: &70306497693420 !ruby/object:Gem::Requirement
17
+ none: false
17
18
  requirements:
18
19
  - - ! '>='
19
20
  - !ruby/object:Gem::Version
20
- version: !binary |-
21
- MA==
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70306497693420
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &70306497692980 !ruby/object:Gem::Requirement
22
28
  none: false
23
- requirement: !ruby/object:Gem::Requirement
24
29
  requirements:
25
30
  - - ! '>='
26
31
  - !ruby/object:Gem::Version
27
- version: !binary |-
28
- MA==
29
- none: false
30
- prerelease: false
32
+ version: '0'
31
33
  type: :development
34
+ prerelease: false
35
+ version_requirements: *70306497692980
32
36
  description: Delicious HMAC Digest(if) authentication and encryption
33
37
  email:
34
38
  - harold.gimenez@gmail.com
@@ -47,7 +51,6 @@ files:
47
51
  - lib/fernet.rb
48
52
  - lib/fernet/configuration.rb
49
53
  - lib/fernet/generator.rb
50
- - lib/fernet/okjson.rb
51
54
  - lib/fernet/secret.rb
52
55
  - lib/fernet/verifier.rb
53
56
  - lib/fernet/version.rb
@@ -56,28 +59,26 @@ files:
56
59
  - spec/spec_helper.rb
57
60
  homepage: ''
58
61
  licenses: []
59
- post_install_message:
62
+ post_install_message:
60
63
  rdoc_options: []
61
64
  require_paths:
62
65
  - lib
63
66
  required_ruby_version: !ruby/object:Gem::Requirement
67
+ none: false
64
68
  requirements:
65
69
  - - ! '>='
66
70
  - !ruby/object:Gem::Version
67
- version: !binary |-
68
- MA==
69
- none: false
71
+ version: '0'
70
72
  required_rubygems_version: !ruby/object:Gem::Requirement
73
+ none: false
71
74
  requirements:
72
75
  - - ! '>='
73
76
  - !ruby/object:Gem::Version
74
- version: !binary |-
75
- MA==
76
- none: false
77
+ version: '0'
77
78
  requirements: []
78
- rubyforge_project:
79
- rubygems_version: 1.8.24
80
- signing_key:
79
+ rubyforge_project:
80
+ rubygems_version: 1.8.10
81
+ signing_key:
81
82
  specification_version: 3
82
83
  summary: Easily generate and verify AES encrypted HMAC based authentication tokens
83
84
  test_files:
data/lib/fernet/okjson.rb DELETED
@@ -1,596 +0,0 @@
1
- # encoding: UTF-8
2
- #
3
- # Copyright 2011, 2012 Keith Rarick
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- # THE SOFTWARE.
22
-
23
- # See https://github.com/kr/okjson for updates.
24
-
25
- require 'stringio'
26
-
27
- # Some parts adapted from
28
- # http://golang.org/src/pkg/json/decode.go and
29
- # http://golang.org/src/pkg/utf8/utf8.go
30
- module Fernet::OkJson
31
- extend self
32
-
33
-
34
- # Decodes a json document in string s and
35
- # returns the corresponding ruby value.
36
- # String s must be valid UTF-8. If you have
37
- # a string in some other encoding, convert
38
- # it first.
39
- #
40
- # String values in the resulting structure
41
- # will be UTF-8.
42
- def decode(s)
43
- ts = lex(s)
44
- v, ts = textparse(ts)
45
- if ts.length > 0
46
- raise Error, 'trailing garbage'
47
- end
48
- v
49
- end
50
-
51
-
52
- # Parses a "json text" in the sense of RFC 4627.
53
- # Returns the parsed value and any trailing tokens.
54
- # Note: this is almost the same as valparse,
55
- # except that it does not accept atomic values.
56
- def textparse(ts)
57
- if ts.length < 0
58
- raise Error, 'empty'
59
- end
60
-
61
- typ, _, val = ts[0]
62
- case typ
63
- when '{' then objparse(ts)
64
- when '[' then arrparse(ts)
65
- else
66
- raise Error, "unexpected #{val.inspect}"
67
- end
68
- end
69
-
70
-
71
- # Parses a "value" in the sense of RFC 4627.
72
- # Returns the parsed value and any trailing tokens.
73
- def valparse(ts)
74
- if ts.length < 0
75
- raise Error, 'empty'
76
- end
77
-
78
- typ, _, val = ts[0]
79
- case typ
80
- when '{' then objparse(ts)
81
- when '[' then arrparse(ts)
82
- when :val,:str then [val, ts[1..-1]]
83
- else
84
- raise Error, "unexpected #{val.inspect}"
85
- end
86
- end
87
-
88
-
89
- # Parses an "object" in the sense of RFC 4627.
90
- # Returns the parsed value and any trailing tokens.
91
- def objparse(ts)
92
- ts = eat('{', ts)
93
- obj = {}
94
-
95
- if ts[0][0] == '}'
96
- return obj, ts[1..-1]
97
- end
98
-
99
- k, v, ts = pairparse(ts)
100
- obj[k] = v
101
-
102
- if ts[0][0] == '}'
103
- return obj, ts[1..-1]
104
- end
105
-
106
- loop do
107
- ts = eat(',', ts)
108
-
109
- k, v, ts = pairparse(ts)
110
- obj[k] = v
111
-
112
- if ts[0][0] == '}'
113
- return obj, ts[1..-1]
114
- end
115
- end
116
- end
117
-
118
-
119
- # Parses a "member" in the sense of RFC 4627.
120
- # Returns the parsed values and any trailing tokens.
121
- def pairparse(ts)
122
- (typ, _, k), ts = ts[0], ts[1..-1]
123
- if typ != :str
124
- raise Error, "unexpected #{k.inspect}"
125
- end
126
- ts = eat(':', ts)
127
- v, ts = valparse(ts)
128
- [k, v, ts]
129
- end
130
-
131
-
132
- # Parses an "array" in the sense of RFC 4627.
133
- # Returns the parsed value and any trailing tokens.
134
- def arrparse(ts)
135
- ts = eat('[', ts)
136
- arr = []
137
-
138
- if ts[0][0] == ']'
139
- return arr, ts[1..-1]
140
- end
141
-
142
- v, ts = valparse(ts)
143
- arr << v
144
-
145
- if ts[0][0] == ']'
146
- return arr, ts[1..-1]
147
- end
148
-
149
- loop do
150
- ts = eat(',', ts)
151
-
152
- v, ts = valparse(ts)
153
- arr << v
154
-
155
- if ts[0][0] == ']'
156
- return arr, ts[1..-1]
157
- end
158
- end
159
- end
160
-
161
-
162
- def eat(typ, ts)
163
- if ts[0][0] != typ
164
- raise Error, "expected #{typ} (got #{ts[0].inspect})"
165
- end
166
- ts[1..-1]
167
- end
168
-
169
-
170
- # Scans s and returns a list of json tokens,
171
- # excluding white space (as defined in RFC 4627).
172
- def lex(s)
173
- ts = []
174
- while s.length > 0
175
- typ, lexeme, val = tok(s)
176
- if typ == nil
177
- raise Error, "invalid character at #{s[0,10].inspect}"
178
- end
179
- if typ != :space
180
- ts << [typ, lexeme, val]
181
- end
182
- s = s[lexeme.length..-1]
183
- end
184
- ts
185
- end
186
-
187
-
188
- # Scans the first token in s and
189
- # returns a 3-element list, or nil
190
- # if s does not begin with a valid token.
191
- #
192
- # The first list element is one of
193
- # '{', '}', ':', ',', '[', ']',
194
- # :val, :str, and :space.
195
- #
196
- # The second element is the lexeme.
197
- #
198
- # The third element is the value of the
199
- # token for :val and :str, otherwise
200
- # it is the lexeme.
201
- def tok(s)
202
- case s[0]
203
- when ?{ then ['{', s[0,1], s[0,1]]
204
- when ?} then ['}', s[0,1], s[0,1]]
205
- when ?: then [':', s[0,1], s[0,1]]
206
- when ?, then [',', s[0,1], s[0,1]]
207
- when ?[ then ['[', s[0,1], s[0,1]]
208
- when ?] then [']', s[0,1], s[0,1]]
209
- when ?n then nulltok(s)
210
- when ?t then truetok(s)
211
- when ?f then falsetok(s)
212
- when ?" then strtok(s)
213
- when Spc then [:space, s[0,1], s[0,1]]
214
- when ?\t then [:space, s[0,1], s[0,1]]
215
- when ?\n then [:space, s[0,1], s[0,1]]
216
- when ?\r then [:space, s[0,1], s[0,1]]
217
- else numtok(s)
218
- end
219
- end
220
-
221
-
222
- def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end
223
- def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end
224
- def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end
225
-
226
-
227
- def numtok(s)
228
- m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
229
- if m && m.begin(0) == 0
230
- if m[3] && !m[2]
231
- [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
232
- elsif m[2]
233
- [:val, m[0], Float(m[0])]
234
- else
235
- [:val, m[0], Integer(m[0])]
236
- end
237
- else
238
- []
239
- end
240
- end
241
-
242
-
243
- def strtok(s)
244
- m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
245
- if ! m
246
- raise Error, "invalid string literal at #{abbrev(s)}"
247
- end
248
- [:str, m[0], unquote(m[0])]
249
- end
250
-
251
-
252
- def abbrev(s)
253
- t = s[0,10]
254
- p = t['`']
255
- t = t[0,p] if p
256
- t = t + '...' if t.length < s.length
257
- '`' + t + '`'
258
- end
259
-
260
-
261
- # Converts a quoted json string literal q into a UTF-8-encoded string.
262
- # The rules are different than for Ruby, so we cannot use eval.
263
- # Unquote will raise an error if q contains control characters.
264
- def unquote(q)
265
- q = q[1...-1]
266
- a = q.dup # allocate a big enough string
267
- rubydoesenc = false
268
- # In ruby >= 1.9, a[w] is a codepoint, not a byte.
269
- if a.class.method_defined?(:force_encoding)
270
- a.force_encoding('UTF-8')
271
- rubydoesenc = true
272
- end
273
- r, w = 0, 0
274
- while r < q.length
275
- c = q[r]
276
- case true
277
- when c == ?\\
278
- r += 1
279
- if r >= q.length
280
- raise Error, "string literal ends with a \"\\\": \"#{q}\""
281
- end
282
-
283
- case q[r]
284
- when ?",?\\,?/,?'
285
- a[w] = q[r]
286
- r += 1
287
- w += 1
288
- when ?b,?f,?n,?r,?t
289
- a[w] = Unesc[q[r]]
290
- r += 1
291
- w += 1
292
- when ?u
293
- r += 1
294
- uchar = begin
295
- hexdec4(q[r,4])
296
- rescue RuntimeError => e
297
- raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
298
- end
299
- r += 4
300
- if surrogate? uchar
301
- if q.length >= r+6
302
- uchar1 = hexdec4(q[r+2,4])
303
- uchar = subst(uchar, uchar1)
304
- if uchar != Ucharerr
305
- # A valid pair; consume.
306
- r += 6
307
- end
308
- end
309
- end
310
- if rubydoesenc
311
- a[w] = '' << uchar
312
- w += 1
313
- else
314
- w += ucharenc(a, w, uchar)
315
- end
316
- else
317
- raise Error, "invalid escape char #{q[r]} in \"#{q}\""
318
- end
319
- when c == ?", c < Spc
320
- raise Error, "invalid character in string literal \"#{q}\""
321
- else
322
- # Copy anything else byte-for-byte.
323
- # Valid UTF-8 will remain valid UTF-8.
324
- # Invalid UTF-8 will remain invalid UTF-8.
325
- # In ruby >= 1.9, c is a codepoint, not a byte,
326
- # in which case this is still what we want.
327
- a[w] = c
328
- r += 1
329
- w += 1
330
- end
331
- end
332
- a[0,w]
333
- end
334
-
335
-
336
- # Encodes unicode character u as UTF-8
337
- # bytes in string a at position i.
338
- # Returns the number of bytes written.
339
- def ucharenc(a, i, u)
340
- case true
341
- when u <= Uchar1max
342
- a[i] = (u & 0xff).chr
343
- 1
344
- when u <= Uchar2max
345
- a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
346
- a[i+1] = (Utagx | (u&Umaskx)).chr
347
- 2
348
- when u <= Uchar3max
349
- a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
350
- a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
351
- a[i+2] = (Utagx | (u&Umaskx)).chr
352
- 3
353
- else
354
- a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
355
- a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
356
- a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
357
- a[i+3] = (Utagx | (u&Umaskx)).chr
358
- 4
359
- end
360
- end
361
-
362
-
363
- def hexdec4(s)
364
- if s.length != 4
365
- raise Error, 'short'
366
- end
367
- (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
368
- end
369
-
370
-
371
- def subst(u1, u2)
372
- if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3
373
- return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself
374
- end
375
- return Ucharerr
376
- end
377
-
378
-
379
- def surrogate?(u)
380
- Usurr1 <= u && u < Usurr3
381
- end
382
-
383
-
384
- def nibble(c)
385
- case true
386
- when ?0 <= c && c <= ?9 then c.ord - ?0.ord
387
- when ?a <= c && c <= ?z then c.ord - ?a.ord + 10
388
- when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
389
- else
390
- raise Error, "invalid hex code #{c}"
391
- end
392
- end
393
-
394
-
395
- # Encodes x into a json text. It may contain only
396
- # Array, Hash, String, Numeric, true, false, nil.
397
- # (Note, this list excludes Symbol.)
398
- # X itself must be an Array or a Hash.
399
- # No other value can be encoded, and an error will
400
- # be raised if x contains any other value, such as
401
- # Nan, Infinity, Symbol, and Proc, or if a Hash key
402
- # is not a String.
403
- # Strings contained in x must be valid UTF-8.
404
- def encode(x)
405
- case x
406
- when Hash then objenc(x)
407
- when Array then arrenc(x)
408
- else
409
- raise Error, 'root value must be an Array or a Hash'
410
- end
411
- end
412
-
413
-
414
- def valenc(x)
415
- case x
416
- when Hash then objenc(x)
417
- when Array then arrenc(x)
418
- when String then strenc(x)
419
- when Numeric then numenc(x)
420
- when true then "true"
421
- when false then "false"
422
- when nil then "null"
423
- else
424
- raise Error, "cannot encode #{x.class}: #{x.inspect}"
425
- end
426
- end
427
-
428
-
429
- def objenc(x)
430
- '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
431
- end
432
-
433
-
434
- def arrenc(a)
435
- '[' + a.map{|x| valenc(x)}.join(',') + ']'
436
- end
437
-
438
-
439
- def keyenc(k)
440
- case k
441
- when String then strenc(k)
442
- else
443
- raise Error, "Hash key is not a string: #{k.inspect}"
444
- end
445
- end
446
-
447
-
448
- def strenc(s)
449
- t = StringIO.new
450
- t.putc(?")
451
- r = 0
452
-
453
- # In ruby >= 1.9, s[r] is a codepoint, not a byte.
454
- rubydoesenc = s.class.method_defined?(:encoding)
455
-
456
- while r < s.length
457
- case s[r]
458
- when ?" then t.print('\\"')
459
- when ?\\ then t.print('\\\\')
460
- when ?\b then t.print('\\b')
461
- when ?\f then t.print('\\f')
462
- when ?\n then t.print('\\n')
463
- when ?\r then t.print('\\r')
464
- when ?\t then t.print('\\t')
465
- else
466
- c = s[r]
467
- case true
468
- when rubydoesenc
469
- begin
470
- c.ord # will raise an error if c is invalid UTF-8
471
- t.write(c)
472
- rescue
473
- t.write(Ustrerr)
474
- end
475
- when Spc <= c && c <= ?~
476
- t.putc(c)
477
- else
478
- n = ucharcopy(t, s, r) # ensure valid UTF-8 output
479
- r += n - 1 # r is incremented below
480
- end
481
- end
482
- r += 1
483
- end
484
- t.putc(?")
485
- t.string
486
- end
487
-
488
-
489
- def numenc(x)
490
- if ((x.nan? || x.infinite?) rescue false)
491
- raise Error, "Numeric cannot be represented: #{x}"
492
- end
493
- "#{x}"
494
- end
495
-
496
-
497
- # Copies the valid UTF-8 bytes of a single character
498
- # from string s at position i to I/O object t, and
499
- # returns the number of bytes copied.
500
- # If no valid UTF-8 char exists at position i,
501
- # ucharcopy writes Ustrerr and returns 1.
502
- def ucharcopy(t, s, i)
503
- n = s.length - i
504
- raise Utf8Error if n < 1
505
-
506
- c0 = s[i].ord
507
-
508
- # 1-byte, 7-bit sequence?
509
- if c0 < Utagx
510
- t.putc(c0)
511
- return 1
512
- end
513
-
514
- raise Utf8Error if c0 < Utag2 # unexpected continuation byte?
515
-
516
- raise Utf8Error if n < 2 # need continuation byte
517
- c1 = s[i+1].ord
518
- raise Utf8Error if c1 < Utagx || Utag2 <= c1
519
-
520
- # 2-byte, 11-bit sequence?
521
- if c0 < Utag3
522
- raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max
523
- t.putc(c0)
524
- t.putc(c1)
525
- return 2
526
- end
527
-
528
- # need second continuation byte
529
- raise Utf8Error if n < 3
530
-
531
- c2 = s[i+2].ord
532
- raise Utf8Error if c2 < Utagx || Utag2 <= c2
533
-
534
- # 3-byte, 16-bit sequence?
535
- if c0 < Utag4
536
- u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx)
537
- raise Utf8Error if u <= Uchar2max
538
- t.putc(c0)
539
- t.putc(c1)
540
- t.putc(c2)
541
- return 3
542
- end
543
-
544
- # need third continuation byte
545
- raise Utf8Error if n < 4
546
- c3 = s[i+3].ord
547
- raise Utf8Error if c3 < Utagx || Utag2 <= c3
548
-
549
- # 4-byte, 21-bit sequence?
550
- if c0 < Utag5
551
- u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx)
552
- raise Utf8Error if u <= Uchar3max
553
- t.putc(c0)
554
- t.putc(c1)
555
- t.putc(c2)
556
- t.putc(c3)
557
- return 4
558
- end
559
-
560
- raise Utf8Error
561
- rescue Utf8Error
562
- t.write(Ustrerr)
563
- return 1
564
- end
565
-
566
-
567
- class Utf8Error < ::StandardError
568
- end
569
-
570
-
571
- class Error < ::StandardError
572
- end
573
-
574
-
575
- Utagx = 0x80 # 1000 0000
576
- Utag2 = 0xc0 # 1100 0000
577
- Utag3 = 0xe0 # 1110 0000
578
- Utag4 = 0xf0 # 1111 0000
579
- Utag5 = 0xF8 # 1111 1000
580
- Umaskx = 0x3f # 0011 1111
581
- Umask2 = 0x1f # 0001 1111
582
- Umask3 = 0x0f # 0000 1111
583
- Umask4 = 0x07 # 0000 0111
584
- Uchar1max = (1<<7) - 1
585
- Uchar2max = (1<<11) - 1
586
- Uchar3max = (1<<16) - 1
587
- Ucharerr = 0xFFFD # unicode "replacement char"
588
- Ustrerr = "\xef\xbf\xbd" # unicode "replacement char"
589
- Usurrself = 0x10000
590
- Usurr1 = 0xd800
591
- Usurr2 = 0xdc00
592
- Usurr3 = 0xe000
593
-
594
- Spc = ' '[0]
595
- Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t}
596
- end