ucenter 0.0.3 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 455a44d1c4adabc58b1e3427ab519d340edae2d2
4
- data.tar.gz: 44f6c5b7e7f0b6716fc66ec6680f70ba7173c140
3
+ metadata.gz: 39fb7334c69206eda4dc53352bc1761faa573f8d
4
+ data.tar.gz: 59c136c1cd60937283cd40c59ad738be9976ed33
5
5
  SHA512:
6
- metadata.gz: f53e7f4caa4ef1d23c28446d83e7d1e4296329d2fd30ac33f803843518a186431459c5037609dced43ea82f8922e72ee744d9f3c52a66c918f7467f6f6a28d1d
7
- data.tar.gz: ff434fc772fc413ebb1a114f0212d5a80b92179a27d5d3abebc4e6dfbddd85e966182a184647a73383110c9dffd54a023cfe42afe1912271d6275b8366a697a7
6
+ metadata.gz: 5ab39a314f899cdaf779ba25b52dda6b9ad7b71b5e56aacbc95179c60c6cda71774b20c12325cb6a31e7329a1d673a4f200f34e6924eb741b64581cc21da16b7
7
+ data.tar.gz: 35d08c5f26b5c8b27b25fdcbd4cfe2f074a1a198a045a1de15a3ae6cf0427275a1ba7d694b0e25d66d256b9b6bfa699f821841e03f04e8abef21480029cba900
@@ -1,7 +1,14 @@
1
1
  require 'ucenter/config'
2
- require 'ucenter/client'
2
+
3
3
  require 'ucenter/interface/base'
4
4
  require 'ucenter/interface/authcode'
5
5
  require 'ucenter/interface/user'
6
- require 'ucenter/interface/xml'
7
- require 'ucenter/interface/utility'
6
+ require 'ucenter/interface/app'
7
+
8
+ require 'ucenter/tools/base'
9
+ require 'ucenter/tools/url'
10
+ require 'ucenter/tools/xml'
11
+ require 'ucenter/tools/php'
12
+
13
+ require 'ucenter/client'
14
+ require 'ucenter/utility'
@@ -1,5 +1,9 @@
1
1
  module Ucenter
2
2
  class Client
3
+ def app
4
+ @app ||= Ucenter::Interface::App.new(self)
5
+ end
6
+
3
7
  def authcode
4
8
  @authcode ||= Ucenter::Interface::Authcode.new(self)
5
9
  end
@@ -7,14 +11,5 @@ module Ucenter
7
11
  def user
8
12
  @user ||= Ucenter::Interface::User.new(self)
9
13
  end
10
-
11
- def utility
12
- @utility ||= Ucenter::Interface::Utility.new(self)
13
- end
14
-
15
- def xml
16
- @xml ||= Ucenter::Interface::Xml.new(self)
17
- end
18
-
19
14
  end
20
15
  end
@@ -0,0 +1,16 @@
1
+ module Ucenter
2
+ module Interface
3
+ class App < Base
4
+ def get_apps(col = '*', where = '')
5
+ col = db_client.escape(col)
6
+ where = db_client.escape(where)
7
+ data = db_client.query("SELECT #{col} FROM #{Ucenter::Config.uc_dbtablepre}applications #{where!="" ? " WHERE #{where} " : ""}").to_a
8
+ data.each_with_index do |item,index|
9
+ item['extra'] = Ucenter::Tools::PHP.unserialize(item['extra'])
10
+ data[index] = item
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -46,6 +46,56 @@ module Ucenter
46
46
  end
47
47
  end
48
48
 
49
+ def encode string
50
+ operation = 'ENCODE'
51
+ expiry = 0
52
+ ckey_length = 4
53
+ key = md5(Ucenter::Config.uc_key)
54
+ keya = md5(key[0, 16])
55
+ keyb = md5(key[16, 16])
56
+ keyc = ckey_length > 0 ? (operation == 'DECODE' ? string[0, ckey_length] : (md5(microtime()))[-ckey_length..-1]) : ''
57
+ cryptkey = keya + md5(keya+keyc)
58
+ key_length = cryptkey.size
59
+ string = operation == 'DECODE' ? base64_url_decode(string[ckey_length..-1]) : sprintf('%010d', expiry>0 ? expiry + Time.now.to_i : 0)+ (md5(string+keyb))[0, 16] + string
60
+ string_ords = ords(string)
61
+ string_length = string_ords.size
62
+ result = ''
63
+ box = (0..255).to_a
64
+
65
+ rndkey = []
66
+
67
+ 0.upto(255) do |i|
68
+ rndkey[i] = (cryptkey[i % key_length]).ord
69
+ end
70
+
71
+ j = i = 0
72
+ while i < 256 do
73
+ j = (j + box[i] + rndkey[i]) % 256
74
+ box[i], box[j] = box[j], box[i]
75
+ i +=1
76
+ end
77
+
78
+ a = j = i = 0
79
+ while i < string_length
80
+ a = (a + 1) % 256
81
+ j = (j + box[a]) % 256
82
+ box[a], box[j] = box[j], box[a]
83
+ result += (string_ords[i] ^ (box[(box[a] + box[j]) % 256])).chr
84
+ i +=1
85
+ end
86
+
87
+ if operation == 'DECODE' then
88
+ if ( result[0,10] == '0'*10 || (result[0, 10]).to_i - Time.now.to_i > 0 ) and
89
+ result[10, 16] == (md5(result[26..-1] + keyb))[0, 16] then
90
+ return result[26..-1]
91
+ else
92
+ return ''
93
+ end
94
+ else
95
+ keyc + (Base64.encode64(result)).gsub(/=/, '')
96
+ end
97
+ end
98
+
49
99
  def microtime
50
100
  epoch_mirco = Time.now.to_f
51
101
  epoch_full = Time.now.to_i
@@ -62,6 +112,10 @@ module Ucenter
62
112
  Base64.decode64 str2
63
113
  end
64
114
 
115
+ def ords(s)
116
+ s.bytes.to_a
117
+ end
118
+
65
119
  end
66
120
  end
67
121
  end
@@ -3,6 +3,7 @@ module Ucenter
3
3
  class User < Base
4
4
 
5
5
  def get_user(username, is_uid = FALSE)
6
+ username = db_client.escape(username) unless username.nil?
6
7
  if is_uid == 1
7
8
  data = get_user_by_uid(username)
8
9
  elsif is_uid == 2
@@ -17,7 +18,7 @@ module Ucenter
17
18
 
18
19
  def check_login(username, password, is_uid = FALSE)
19
20
  user = get_user(username, is_uid)
20
- if user['username'].nil?
21
+ if user.nil? or user['username'].nil?
21
22
  return [-1,nil]
22
23
  elsif user['password'] != md5("#{md5(password)}#{user['salt']}")
23
24
  return [-2,nil]
@@ -25,6 +26,45 @@ module Ucenter
25
26
  [user['uid'],user]
26
27
  end
27
28
 
29
+ def check_username(username)
30
+ len = username.bytesize
31
+ return FALSE if len > 15 or len < 3 or !( /\s+|^c:\\con\\con|\[%,\*\"\s\<\>\&\]|\xA1\xA1|\xAC\xA3|^Guest|^\xD3\xCE\xBF\xCD|\xB9\x43\xAB\xC8/is =~ username).nil?
32
+ TRUE
33
+ end
34
+
35
+ def check_email_format(email)
36
+ email.bytesize > 6 and !(/^[\w\-\.]+@[\w\-\.]+(\.\w+)+$/ =~ email).nil?
37
+ end
38
+
39
+ def check_email_access(email)
40
+ # TODO: 验证email权限
41
+ TRUE
42
+ end
43
+
44
+ def check_email_exist(email, username = '')
45
+ username = db_client.escape(username)
46
+ email = db_client.escape(email)
47
+ sql_add = username != '' ? "AND username<>'#{username}'" : '';
48
+ db_client.query("SELECT email FROM #{Ucenter::Config.uc_dbtablepre}members WHERE email='#{email}' #{sql_add}").to_a[0]
49
+ end
50
+
51
+ def check_mergeuser(username)
52
+ username = db_client.escape(username)
53
+ db_client.query("SELECT count(*) FROM #{Ucenter::Config.uc_dbtablepre}mergemembers WHERE appid='#{Ucenter::Config.app_id}' AND username='#{username}'").to_a[0]
54
+ end
55
+
56
+ def add_user
57
+ # TODO: 注册
58
+ end
59
+
60
+ def edit_user
61
+ # TODO: 修改
62
+ end
63
+
64
+ def delete_user
65
+ # TODO: 删除
66
+ end
67
+
28
68
  protected
29
69
  def get_user_by_username(username)
30
70
  db_client.query("SELECT * FROM #{Ucenter::Config.uc_dbtablepre}members WHERE username='#{username}'").to_a[0]
@@ -0,0 +1,18 @@
1
+ module Ucenter
2
+ module Tools
3
+ # The Base class of API
4
+ class Base
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ def get(path, opts={}, &block)
10
+ request(:get, path, opts, &block)
11
+ end
12
+
13
+ def post(path, opts={}, &block)
14
+ request(:post, path, opts, &block)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,317 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright (c) 2003-2009 Thomas Hurst <tom@hur.st>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ # SOFTWARE.
21
+
22
+ # PHP serialize() and unserialize() workalikes
23
+ #
24
+ # Release History:
25
+ # 1.0.0 - 2003-06-02 - First release.
26
+ # 1.0.1 - 2003-06-16 - Minor bugfixes.
27
+ # 1.0.2 - 2004-09-17 - Switch all {}'s to explicit Hash.new's.
28
+ # 1.1.0 - 2009-04-01 - Pass assoc to recursive calls (thanks to Edward Speyer).
29
+ # - Serialize Symbol like String.
30
+ # - Add testsuite.
31
+ # - Instantiate auto-generated Structs properly (thanks
32
+ # to Philip Hallstrom).
33
+ # - Unserialize arrays properly in assoc mode.
34
+ # - Add PHP session support (thanks to TJ Vanderpoel).
35
+ # - Release as tarball and gem.
36
+ #
37
+ # See http://www.php.net/serialize and http://www.php.net/unserialize for
38
+ # details on the PHP side of all this.
39
+ module Ucenter
40
+ module Tools
41
+ # string = PHP.serialize(mixed var[, bool assoc])
42
+ #
43
+ # Returns a string representing the argument in a form PHP.unserialize
44
+ # and PHP's unserialize() should both be able to load.
45
+ #
46
+ # Array, Hash, Fixnum, Float, True/FalseClass, NilClass, String and Struct
47
+ # are supported; as are objects which support the to_assoc method, which
48
+ # returns an array of the form [['attr_name', 'value']..]. Anything else
49
+ # will raise a TypeError.
50
+ #
51
+ # If 'assoc' is specified, Array's who's first element is a two value
52
+ # array will be assumed to be an associative array, and will be serialized
53
+ # as a PHP associative array rather than a multidimensional array.
54
+ class PHP
55
+ def self.serialize(var, assoc = false) # {{{
56
+ s = ''
57
+ case var
58
+ when Array
59
+ s << "a:#{var.size}:{"
60
+ if assoc and var.first.is_a?(Array) and var.first.size == 2
61
+ var.each { |k,v|
62
+ s << self.serialize(k, assoc) << self.serialize(v, assoc)
63
+ }
64
+ else
65
+ var.each_with_index { |v,i|
66
+ s << "i:#{i};#{self.serialize(v, assoc)}"
67
+ }
68
+ end
69
+
70
+ s << '}'
71
+
72
+ when Hash
73
+ s << "a:#{var.size}:{"
74
+ var.each do |k,v|
75
+ s << "#{self.serialize(k, assoc)}#{self.serialize(v, assoc)}"
76
+ end
77
+ s << '}'
78
+
79
+ when Struct
80
+ # encode as Object with same name
81
+ s << "O:#{var.class.to_s.length}:\"#{var.class.to_s.downcase}\":#{var.members.length}:{"
82
+ var.members.each do |member|
83
+ s << "#{self.serialize(member, assoc)}#{self.serialize(var[member], assoc)}"
84
+ end
85
+ s << '}'
86
+
87
+ when String, Symbol
88
+ s << "s:#{var.to_s.length}:\"#{var.to_s}\";"
89
+
90
+ when Fixnum # PHP doesn't have bignums
91
+ s << "i:#{var};"
92
+
93
+ when Float
94
+ s << "d:#{var};"
95
+
96
+ when NilClass
97
+ s << 'N;'
98
+
99
+ when FalseClass, TrueClass
100
+ s << "b:#{var ? 1 :0};"
101
+
102
+ else
103
+ if var.respond_to?(:to_assoc)
104
+ v = var.to_assoc
105
+ # encode as Object with same name
106
+ s << "O:#{var.class.to_s.length}:\"#{var.class.to_s.downcase}\":#{v.length}:{"
107
+ v.each do |k,v|
108
+ s << "#{self.serialize(k.to_s, assoc)}#{self.serialize(v, assoc)}"
109
+ end
110
+ s << '}'
111
+ else
112
+ raise TypeError, "Unable to serialize type #{var.class}"
113
+ end
114
+ end
115
+
116
+ s
117
+ end # }}}
118
+
119
+ # string = PHP.serialize_session(mixed var[, bool assoc])
120
+ #
121
+ # Like PHP.serialize, but only accepts a Hash or associative Array as the root
122
+ # type. The results are returned in PHP session format.
123
+ def self.serialize_session(var, assoc = false) # {{{
124
+ s = ''
125
+ case var
126
+ when Hash
127
+ var.each do |key,value|
128
+ if key.to_s =~ /\|/
129
+ raise IndexError, "Top level names may not contain pipes"
130
+ end
131
+ s << "#{key}|#{self.serialize(value, assoc)}"
132
+ end
133
+ when Array
134
+ var.each do |x|
135
+ case x
136
+ when Array
137
+ if x.size == 2
138
+ s << "#{x[0]}|#{self.serialize(x[1])}"
139
+ else
140
+ raise TypeError, "Array is not associative"
141
+ end
142
+ end
143
+ end
144
+ else
145
+ raise TypeError, "Unable to serialize sessions with top level types other than Hash and associative Array"
146
+ end
147
+ s
148
+ end # }}}
149
+
150
+ # mixed = PHP.unserialize(string serialized, [hash classmap, [bool assoc]])
151
+ #
152
+ # Returns an object containing the reconstituted data from serialized.
153
+ #
154
+ # If a PHP array (associative; like an ordered hash) is encountered, it
155
+ # scans the keys; if they're all incrementing integers counting from 0,
156
+ # it's unserialized as an Array, otherwise it's unserialized as a Hash.
157
+ # Note: this will lose ordering. To avoid this, specify assoc=true,
158
+ # and it will be unserialized as an associative array: [[key,value],...]
159
+ #
160
+ # If a serialized object is encountered, the hash 'classmap' is searched for
161
+ # the class name (as a symbol). Since PHP classnames are not case-preserving,
162
+ # this *must* be a .capitalize()d representation. The value is expected
163
+ # to be the class itself; i.e. something you could call .new on.
164
+ #
165
+ # If it's not found in 'classmap', the current constant namespace is searched,
166
+ # and failing that, a new Struct(classname) is generated, with the arguments
167
+ # for .new specified in the same order PHP provided; since PHP uses hashes
168
+ # to represent attributes, this should be the same order they're specified
169
+ # in PHP, but this is untested.
170
+ #
171
+ # each serialized attribute is sent to the new object using the respective
172
+ # {attribute}=() method; you'll get a NameError if the method doesn't exist.
173
+ #
174
+ # Array, Hash, Fixnum, Float, True/FalseClass, NilClass and String should
175
+ # be returned identically (i.e. foo == PHP.unserialize(PHP.serialize(foo))
176
+ # for these types); Struct should be too, provided it's in the namespace
177
+ # Module.const_get within unserialize() can see, or you gave it the same
178
+ # name in the Struct.new(<structname>), otherwise you should provide it in
179
+ # classmap.
180
+ #
181
+ # Note: StringIO is required for unserialize(); it's loaded as needed
182
+ def self.unserialize(string, classmap = nil, assoc = false) # {{{
183
+ if classmap == true or classmap == false
184
+ assoc = classmap
185
+ classmap = {}
186
+ end
187
+ classmap ||= {}
188
+
189
+ require 'stringio'
190
+ string = StringIO.new(string)
191
+ def string.read_until(char)
192
+ val = ''
193
+ while (c = self.read(1)) != char
194
+ val << c
195
+ end
196
+ val
197
+ end
198
+
199
+ if string.string =~ /^(\w+)\|/ # session_name|serialized_data
200
+ ret = Hash.new
201
+ loop do
202
+ if string.string[string.pos, 32] =~ /^(\w+)\|/
203
+ string.pos += $&.size
204
+ ret[$1] = self.do_unserialize(string, classmap, assoc)
205
+ else
206
+ break
207
+ end
208
+ end
209
+ ret
210
+ else
211
+ self.do_unserialize(string, classmap, assoc)
212
+ end
213
+ end
214
+
215
+ private
216
+ def self.do_unserialize(string, classmap, assoc)
217
+ val = nil
218
+ # determine a type
219
+ type = string.read(2)[0,1]
220
+ case type
221
+ when 'a' # associative array, a:length:{[index][value]...}
222
+ count = string.read_until('{').to_i
223
+ val = vals = Array.new
224
+ count.times do |i|
225
+ vals << [do_unserialize(string, classmap, assoc), do_unserialize(string, classmap, assoc)]
226
+ end
227
+ string.read(1) # skip the ending }
228
+
229
+ # now, we have an associative array, let's clean it up a bit...
230
+ # arrays have all numeric indexes, in order; otherwise we assume a hash
231
+ array = true
232
+ i = 0
233
+ vals.each do |key,value|
234
+ if key != i # wrong index -> assume hash
235
+ array = false
236
+ break
237
+ end
238
+ i += 1
239
+ end
240
+
241
+ if array
242
+ vals.collect! do |key,value|
243
+ value
244
+ end
245
+ else
246
+ if assoc
247
+ val = vals.map {|v| v }
248
+ else
249
+ val = Hash.new
250
+ vals.each do |key,value|
251
+ val[key] = value
252
+ end
253
+ end
254
+ end
255
+
256
+ when 'O' # object, O:length:"class":length:{[attribute][value]...}
257
+ # class name (lowercase in PHP, grr)
258
+ len = string.read_until(':').to_i + 3 # quotes, seperator
259
+ klass = string.read(len)[1...-2].capitalize.intern # read it, kill useless quotes
260
+
261
+ # read the attributes
262
+ attrs = []
263
+ len = string.read_until('{').to_i
264
+
265
+ len.times do
266
+ attr = (do_unserialize(string, classmap, assoc))
267
+ attrs << [attr.intern, (attr << '=').intern, do_unserialize(string, classmap, assoc)]
268
+ end
269
+ string.read(1)
270
+
271
+ val = nil
272
+ # See if we need to map to a particular object
273
+ if classmap.has_key?(klass)
274
+ val = classmap[klass].new
275
+ elsif Struct.const_defined?(klass) # Nope; see if there's a Struct
276
+ classmap[klass] = val = Struct.const_get(klass)
277
+ val = val.new
278
+ else # Nope; see if there's a Constant
279
+ begin
280
+ classmap[klass] = val = Module.const_get(klass)
281
+
282
+ val = val.new
283
+ rescue NameError # Nope; make a new Struct
284
+ classmap[klass] = Struct.new(klass.to_s, *attrs.collect { |v| v[0].to_s })
285
+ val = val.new
286
+ end
287
+ end
288
+
289
+ attrs.each do |attr,attrassign,v|
290
+ val.__send__(attrassign, v)
291
+ end
292
+
293
+ when 's' # string, s:length:"data";
294
+ len = string.read_until(':').to_i + 3 # quotes, separator
295
+ val = string.read(len)[1...-2] # read it, kill useless quotes
296
+
297
+ when 'i' # integer, i:123
298
+ val = string.read_until(';').to_i
299
+
300
+ when 'd' # double (float), d:1.23
301
+ val = string.read_until(';').to_f
302
+
303
+ when 'N' # NULL, N;
304
+ val = nil
305
+
306
+ when 'b' # bool, b:0 or 1
307
+ val = (string.read(2)[0] == ?1 ? true : false)
308
+
309
+ else
310
+ raise TypeError, "Unable to unserialize type '#{type}'"
311
+ end
312
+
313
+ val
314
+ end # }}}
315
+ end
316
+ end
317
+ end
@@ -1,6 +1,6 @@
1
1
  module Ucenter
2
- module Interface
3
- class Utility < Base
2
+ module Tools
3
+ class Url < Base
4
4
  def explain_query(query_params)
5
5
  query_params = '' if query_params === {}
6
6
  # systemname=cets&responsible=sj ==> {"systemname"=>"cets","responsible"=>"sj"}
@@ -1,5 +1,5 @@
1
1
  module Ucenter
2
- module Interface
2
+ module Tools
3
3
  class Xml < Base
4
4
  def parse body
5
5
  reader = Nokogiri::XML::Reader(body)
@@ -0,0 +1,12 @@
1
+ module Ucenter
2
+ class Utility
3
+ def url
4
+ @url ||= Ucenter::Tools::Url.new(self)
5
+ end
6
+
7
+ def xml
8
+ @xml ||= Ucenter::Tools::Xml.new(self)
9
+ end
10
+
11
+ end
12
+ end
@@ -1,3 +1,3 @@
1
1
  module Ucenter
2
- VERSION = "0.0.3"
2
+ VERSION = "0.1.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ucenter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willin Wang
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-27 00:00:00.000000000 Z
11
+ date: 2014-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,11 +80,15 @@ files:
80
80
  - lib/ucenter.rb
81
81
  - lib/ucenter/client.rb
82
82
  - lib/ucenter/config.rb
83
+ - lib/ucenter/interface/app.rb
83
84
  - lib/ucenter/interface/authcode.rb
84
85
  - lib/ucenter/interface/base.rb
85
86
  - lib/ucenter/interface/user.rb
86
- - lib/ucenter/interface/utility.rb
87
- - lib/ucenter/interface/xml.rb
87
+ - lib/ucenter/tools/base.rb
88
+ - lib/ucenter/tools/php.rb
89
+ - lib/ucenter/tools/url.rb
90
+ - lib/ucenter/tools/xml.rb
91
+ - lib/ucenter/utility.rb
88
92
  - lib/ucenter/version.rb
89
93
  - ucenter.gemspec
90
94
  homepage: https://github.com/willin/ucenter