megar 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.gitignore +19 -0
  2. data/.rspec +1 -0
  3. data/.rvmrc +1 -0
  4. data/.travis.yml +11 -0
  5. data/CHANGELOG +5 -0
  6. data/Gemfile +4 -0
  7. data/Guardfile +11 -0
  8. data/LICENSE +22 -0
  9. data/README.rdoc +218 -0
  10. data/Rakefile +33 -0
  11. data/bin/megar +16 -0
  12. data/lib/extensions/math.rb +13 -0
  13. data/lib/js_ref_impl/_README +9 -0
  14. data/lib/js_ref_impl/base64_1.js +83 -0
  15. data/lib/js_ref_impl/crypto_5.js +1795 -0
  16. data/lib/js_ref_impl/download_8.js +867 -0
  17. data/lib/js_ref_impl/hex_1.js +76 -0
  18. data/lib/js_ref_impl/index_9.js +666 -0
  19. data/lib/js_ref_impl/js.manifest +115 -0
  20. data/lib/js_ref_impl/rsa_1.js +456 -0
  21. data/lib/js_ref_impl/sjcl_1.js +1 -0
  22. data/lib/js_ref_impl/upload_10.js +691 -0
  23. data/lib/megar.rb +11 -0
  24. data/lib/megar/catalog.rb +5 -0
  25. data/lib/megar/catalog/catalog_item.rb +90 -0
  26. data/lib/megar/catalog/file.rb +14 -0
  27. data/lib/megar/catalog/files.rb +13 -0
  28. data/lib/megar/catalog/folder.rb +20 -0
  29. data/lib/megar/catalog/folders.rb +28 -0
  30. data/lib/megar/connection.rb +84 -0
  31. data/lib/megar/crypto.rb +399 -0
  32. data/lib/megar/exception.rb +55 -0
  33. data/lib/megar/session.rb +157 -0
  34. data/lib/megar/shell.rb +87 -0
  35. data/lib/megar/version.rb +3 -0
  36. data/megar.gemspec +30 -0
  37. data/spec/fixtures/crypto_expectations/sample_user.json +109 -0
  38. data/spec/spec_helper.rb +24 -0
  39. data/spec/support/crypto_expectations_helper.rb +44 -0
  40. data/spec/support/mocks_helper.rb +22 -0
  41. data/spec/unit/catalog/file_spec.rb +31 -0
  42. data/spec/unit/catalog/files_spec.rb +26 -0
  43. data/spec/unit/catalog/folder_spec.rb +28 -0
  44. data/spec/unit/catalog/folders_spec.rb +49 -0
  45. data/spec/unit/connection_spec.rb +50 -0
  46. data/spec/unit/crypto_spec.rb +476 -0
  47. data/spec/unit/exception_spec.rb +35 -0
  48. data/spec/unit/extensions/math_spec.rb +21 -0
  49. data/spec/unit/session_spec.rb +146 -0
  50. data/spec/unit/shell_spec.rb +18 -0
  51. metadata +238 -0
@@ -0,0 +1,55 @@
1
+ module Megar
2
+
3
+ # A general Megar exception
4
+ class Error < StandardError; end
5
+
6
+ class MegaRequestError < Error
7
+
8
+ # Initialise with +error_code+ returned from Mega
9
+ def initialize(error_code)
10
+ msg = case error_code
11
+ when -1
12
+ "EINTERNAL (-1): An internal error has occurred. Please submit a bug report, detailing the exact circumstances in which this error occurred."
13
+ when -2
14
+ "EARGS (-2): You have passed invalid arguments to this command."
15
+ when -3
16
+ "EAGAIN (-3) (always at the request level): A temporary congestion or server malfunction prevented your request from being processed. No data was altered. Retry. Retries must be spaced with exponential backoff."
17
+ when -4
18
+ "ERATELIMIT (-4): You have exceeded your command weight per time quota. Please wait a few seconds, then try again (this should never happen in sane real-life applications)."
19
+ when -5
20
+ "EFAILED (-5): The upload failed. Please restart it from scratch."
21
+ when -6
22
+ "ETOOMANY (-6): Too many concurrent IP addresses are accessing this upload target URL."
23
+ when -7
24
+ "ERANGE (-7): The upload file packet is out of range or not starting and ending on a chunk boundary."
25
+ when -8
26
+ "EEXPIRED (-8): The upload target URL you are trying to access has expired. Please request a fresh one."
27
+ when -9
28
+ "ENOENT (-9): Object (typically, node or user) not found"
29
+ when -10
30
+ "ECIRCULAR (-10): Circular linkage attempted"
31
+ when -11
32
+ "EACCESS (-11): Access violation (e.g., trying to write to a read-only share)"
33
+ when -12
34
+ "EEXIST (-12): Trying to create an object that already exists"
35
+ when -13
36
+ "EINCOMPLETE (-13): Trying to access an incomplete resource"
37
+ when -14
38
+ "EKEY (-14): A decryption operation failed (never returned by the API)"
39
+ when -15
40
+ "ESID (-15): Invalid or expired user session, please relogin"
41
+ when -16
42
+ "EBLOCKED (-16): User blocked"
43
+ when -17
44
+ "EOVERQUOTA (-17): Request over quota"
45
+ when -18
46
+ "ETEMPUNAVAIL (-18): Resource temporarily not available, please try again later"
47
+ else
48
+ "UNDEFINED Mega error #{error_code}"
49
+ end
50
+ super(msg)
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,157 @@
1
+ class Megar::Session
2
+
3
+ include Megar::Crypto
4
+ include Megar::Connection
5
+
6
+ attr_accessor :options
7
+ attr_accessor :email
8
+ attr_accessor :password
9
+ attr_accessor :master_key
10
+ attr_accessor :rsa_private_key # binary string
11
+ attr_accessor :decomposed_rsa_private_key # 4 part array of a32
12
+
13
+ # Start a new session, given +options+ hash.
14
+ #
15
+ # Required +options+ parameters:
16
+ # email: 'your email address' -- email for authentication
17
+ # password: 'your password' -- password for authentication
18
+ #
19
+ # Optional +options+ parameters:
20
+ # api_endpoint: 'url' -- talk to an alternative API endpoint
21
+ # autoconnect: true/false -- performs immediate login if true (default)
22
+ #
23
+ def initialize(options={})
24
+ default_options = { autoconnect: true }
25
+ @options = default_options.merge(options.symbolize_keys)
26
+ self.api_endpoint = @options[:api_endpoint] if @options[:api_endpoint]
27
+ connect! if @options[:autoconnect]
28
+ end
29
+
30
+ # Returns authenticated/connected status
31
+ def connected?
32
+ !sid.nil?
33
+ end
34
+
35
+ # Returns a pretty representation of the session object
36
+ def to_s
37
+ if connected?
38
+ "#{self.class.name}: connected as #{email}"
39
+ else
40
+ "#{self.class.name}: not connected"
41
+ end
42
+ end
43
+
44
+ # Command: perform login session challenge/response.
45
+ # Establishes a user session based on the response to a cryptographic challenge.
46
+ #
47
+ def connect!
48
+ handle_login_challenge_response(get_login_response)
49
+ end
50
+
51
+ # Returns the user email (convenience method)
52
+ def email
53
+ @email ||= options[:email]
54
+ end
55
+
56
+ # Returns the user password (convenience method)
57
+ def password
58
+ @password ||= options[:password]
59
+ end
60
+
61
+ # Returns the rsa_private_key base64-encoded
62
+ def rsa_private_key_b64
63
+ base64urlencode(rsa_private_key)
64
+ end
65
+
66
+ # Returns the folder collection
67
+ def folders
68
+ refresh_files! if @folders.nil?
69
+ @folders
70
+ end
71
+
72
+ # Returns the files collection
73
+ def files
74
+ refresh_files! if @files.nil?
75
+ @files
76
+ end
77
+
78
+ def refresh_files!
79
+ handle_files_response(get_files_response)
80
+ end
81
+
82
+ def reset_files!
83
+ @folders = Megar::Folders.new
84
+ @files = Megar::Files.new
85
+ end
86
+
87
+ protected
88
+
89
+ # Command: enforces guard condition requiring authenticated connection to proceed
90
+ def ensure_connected!
91
+ raise "Not connected" unless connected?
92
+ end
93
+
94
+ def get_login_response
95
+ api_request({'a' => 'us', 'user' => email, 'uh' => uh})
96
+ end
97
+
98
+ # Command: decrypt/decode the login +response_data+ received from Mega
99
+ #
100
+ # Javascript reference implementation: function api_getsid2(res,ctx)
101
+ #
102
+ def handle_login_challenge_response(response_data)
103
+ if k = response_data['k']
104
+ enc_master_key = base64_to_a32(k)
105
+ self.master_key = decrypt_key(enc_master_key, password_key)
106
+ end
107
+ if privk = response_data['privk']
108
+ self.rsa_private_key = decrypt_base64_to_str(privk, master_key)
109
+ self.decomposed_rsa_private_key = decompose_rsa_private_key(rsa_private_key)
110
+ if csid = response_data['csid']
111
+ self.sid = decrypt_session_id(csid,decomposed_rsa_private_key)
112
+ end
113
+ end
114
+ end
115
+
116
+ def get_files_response
117
+ ensure_connected!
118
+ api_request({'a' => 'f', 'c' => 1})
119
+ end
120
+
121
+ # Command: decrypt/decode the login +response_data+ received from Mega
122
+ #
123
+ def handle_files_response(response_data)
124
+ reset_files!
125
+ response_data['f'].each do |f|
126
+ item_attributes = {id: f['h'], payload: f.dup, type: f['t'] }
127
+ case f['t']
128
+ when 0 # File
129
+ item_attributes[:key] = k = decrypt_file_key(f)
130
+ item_attributes[:attributes] = decrypt_file_attributes(f,k)
131
+ files.add(item_attributes)
132
+ when 1 # Folder
133
+ item_attributes[:key] = k = decrypt_file_key(f)
134
+ item_attributes[:attributes] = decrypt_file_attributes(f,k)
135
+ folders.add(item_attributes)
136
+ when 2,3,4 # Root, Inbox, Trash Bin
137
+ folders.add(item_attributes)
138
+ end
139
+ end
140
+ true
141
+ end
142
+
143
+ # Returns the encoded user password key
144
+ def password_key
145
+ prepare_key_pw(password)
146
+ end
147
+
148
+ # Returns the calculated uh parameter based on email and password
149
+ #
150
+ # Javascript reference implementation: function stringhash(s,aes)
151
+ #
152
+ def uh
153
+ stringhash(email.downcase, password_key)
154
+ end
155
+
156
+
157
+ end
@@ -0,0 +1,87 @@
1
+ require 'uri'
2
+
3
+ # class that groks the megar command line options and invokes the required task
4
+ class Megar::Shell
5
+
6
+ # holds the parsed options
7
+ attr_reader :options
8
+
9
+ # holds the remaining command line arguments
10
+ attr_reader :args
11
+
12
+ # initializes the shell with command line argments:
13
+ #
14
+ # +options+ is expected to be the hash structure as provided by GetOptions.new(..)
15
+ #
16
+ # +args+ is the remaining command line arguments
17
+ #
18
+ def initialize(options,args)
19
+ @options = (options||{}).each{|k,v| {k => v} }
20
+ @args = args
21
+ end
22
+
23
+ # Command: execute the megar task according to the options provided on initialisation
24
+ def run
25
+ if email && password
26
+ $stderr.puts "Connecting to mega as #{email}.."
27
+ raise "Failed to connect!" unless session.connected?
28
+ case args.first
29
+ when /ls/i
30
+ session.files.each do |file|
31
+ puts file
32
+ end
33
+ else
34
+ $stderr.puts "Connected!"
35
+ end
36
+ else
37
+ usage
38
+ end
39
+ end
40
+
41
+ # defines the valid command line options
42
+ OPTIONS = %w(help verbose email=s password=s)
43
+
44
+ class << self
45
+
46
+ # prints usage/help information
47
+ def usage
48
+ $stderr.puts <<-EOS
49
+
50
+ Megar v#{Megar::VERSION}
51
+ ===================================
52
+
53
+ Usage:
54
+ megar [options] [commands]
55
+
56
+ Options:
57
+ -h | --help : shows command help
58
+ -v | --verbose : run with verbose
59
+ -e= | --email=value : email address for login
60
+ -p= | --password=value : password for login
61
+
62
+ Commands:
63
+ (none) : will perform a basic connection test only
64
+ ls : returns a full file listing
65
+
66
+ Examples:
67
+ megar --email=my@mail.com --password=MyPassword ls
68
+ megar -e my@mail.com -p MyPassword ls
69
+
70
+ EOS
71
+ end
72
+ end
73
+
74
+ # prints usage/help information
75
+ def usage
76
+ self.class.usage
77
+ end
78
+
79
+ def session
80
+ @session ||= Megar::Session.new(email: email, password: password)
81
+ end
82
+
83
+ # Option shortcuts
84
+ def email ; options[:email] ; end
85
+ def password ; options[:password] ; end
86
+
87
+ end
@@ -0,0 +1,3 @@
1
+ module Megar
2
+ VERSION = "0.0.1"
3
+ end
data/megar.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'megar/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "megar"
8
+ gem.version = Megar::VERSION
9
+ gem.authors = ["Paul Gallagher"]
10
+ gem.email = ["gallagher.paul@gmail.com"]
11
+ gem.description = %q{Megaargh! A Ruby wrapper for the Mega API (mega.co.nz)}
12
+ gem.summary = %q{A Ruby wrapper and command-line tool for the Mega API (mega.co.nz)}
13
+ gem.homepage = "https://github.com/tardate/megar"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_runtime_dependency(%q<getoptions>, [">= 0.3"])
21
+ gem.add_runtime_dependency(%q<activesupport>, [">= 3.0.3"])
22
+
23
+ gem.add_development_dependency(%q<bundler>, ["> 1.1.0"])
24
+ gem.add_development_dependency(%q<rake>, ["~> 0.9.2.2"])
25
+ gem.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
26
+ gem.add_development_dependency(%q<rdoc>, ["~> 3.11"])
27
+ gem.add_development_dependency(%q<guard-rspec>, ["~> 1.2.0"])
28
+ gem.add_development_dependency(%q<rb-fsevent>, ["~> 0.9.1"])
29
+
30
+ end
@@ -0,0 +1,109 @@
1
+ {
2
+ "email": "megartest@gmail.com",
3
+ "email_mixed_case": "Megartest@gmail.com",
4
+ "password": "15kMW4GiHVWFmqJJ",
5
+ "autoconnect": false,
6
+ "login_response_data": {
7
+ "csid": "CAA4by3fJFnderfxuUgwXDhQJkaxKTREaVIA4QuyKmE2CnVDP2w5pU-tftPcHvfdCoV9Gi-rTKsB11rBcc1yK1I8IVOzAwST9yUwSmZI-9Euq-FEUOCsnHYLDlykTOSisU9OU0GausJZbCCHrSLI_Q49Ndc-BBQc361W-DkZUl8ZgKCl3qleBP5aepJbR-bSTR5bJ-IxRmdf2jYUE0dz3lLMGSC4XjGBXgydFlGNgJqURpik9FrW1gPFyKESRNfxudteoSWO-f2bgD-4eEek4kHAaZZQW2K5Yb2GBXCOVQ0_7jpcRd9-gwhUHpTJETNxtEPxMEBtOtAhEm1hxXSxWZgr",
8
+ "privk": "4Iurk4vQ0BlLEEvEyaRUX8QnYzJFIedm0RVKe2pLh4wTzBBtgmafnszD_dP3Y40P-DSNLEGfWghL-0_BIW4XP8tfGNFkMg7mnYgQYC91ccz58BbhGEDf7-j97dstckf3OSSsT9D9H-cocXzyt1m231w9d7YLX23c3KAGz3vxdnl66F4jsFGO9AdIyb1KTEEt7NHkljtbz3WgWlk6W6wBawuEbJvLPHPx2L16i0iacbbkQi8ZF4pmddswMa9yWOSXwUribGDbdgx_jDGjBoKJUJI-7Aa-aOaRPEoVUTft2R2lNAVErvYwD_GIsIhQiCcJWul-KqPhI3vquvzUHd2rHHvKyTLyvXKyIDeVdBGXTaqFA2ni9xsjUNMOX14ftBZ4_fgDce0jvrZzYRs5IuOMt3K9gKMpBpUWtnlaBYbnYdL2zJuFlrM77PlcYab2uLLqlSe4hh-FxMAc_p_8tDAoU4dPgpgOpU3GRR8i7oszu_MKibzNpRBeAX1UtwKwQepjPnipu9aQlPy9OtgB-5KmGi34EcMKBPg8BN6vHRYg0Za_lk_TtTQ4WbkllAqR3Fla5-RBjzB7f3dWae4ES3VX4f-dI-UYrrKDQkqaS0R_xLaMPNG6_GQMDM7L5Fsa8I3hFjdgfs9rV4Xf_uIFrhUjm25EuT5JCdobNmwBi9VU5nCbk0LQJdrXDNYV3fmD-Y_p_KV8x_-opRnXUV03SCopOD6DhitsFtpv065QXcG1SNDu0mM-kgDh9E-QddtQQtXLLPuKFyX5o1ts08EqXTxHsZo5tIsevbExZoz9hxSRrr-V5swEDcJXoV78_k6sR3iniZ_m77JVo9QU572av2MvNhyu5YeTXyc0m6Me7cPGiO0",
9
+ "k": "GmSWkhwjjn38w8OHkD_S-A"
10
+ },
11
+ "master_key": [
12
+ 384287193,
13
+ 302859698,
14
+ 554881366,
15
+ 530403344
16
+ ],
17
+ "expected_uh": "mBdCzS2yFgo",
18
+ "sid": "yfMNTyTPzUKOuWYWRO-qhXpBOEN6Q2Y5dFp3JAKn0WNBqLBEzgbaMaILNg",
19
+ "rsa_private_key_b64": "BACOQ7VCjwkiwYCKhqRRvPTMBaQ5Yx2_sszj6ILrxrTBOJ2bu-9LsOSvaGz0HpnM2lXpJg3h6QWJdlQBOhwvfeafHg7rcEaaDuUWONifv5ABpQgeoAij6cgARMvFiOdjarMvu954Eduup8PHcgfjsBSu76qpu3UqkZCpBpByAYU1oQQA1i5oxuA9i7qsXxhJRsBeceI7hZSnNYAQzsWfFD0kDW8GsJBuK-CST6oNNp0z7MQ5Qof6DZ8x9wt5ZJR43HFmD2VtTQUZLvIeemC4URuR74UWn25CETyzQdIhdNSEibaCRvi0sNwb-ikCZVxxo4-O7ZBQPw0egrQOJW06HlCQUykH_00EIoiBRcbJLS6W3nJ8KhddDf_4glZYPz6fBpqu1jIdIBGOGQf4Ttn_DmdAgkLxWF7AF6k59D45J9kxfANRJIBGSWUsI1bQBlrwFtois4O14uQkQ40Jh_d5BCrLPe599zrliGH1SsECx_xYGWcWY_GPsPa99d_St56BB9oUtDW3A1clEx2uVS8OWqRPgVimu6MXs_TEEnjWGRFofHpl_8qGeAoPbNkXsTMQb6uUK4NCYq3UzVfreByEnOqxuQcu2jlF4Ey0UH9w_KjkRyT8xu--kyZAbktYaw4EZmGUxZIlkZLk_OAGaW8KWPZhdGxjfi8K7WeZK2HtLe4At48DC_ED_20pNtkTCrB6-FQlMj6a4O2C1sE6pSdrIPofcK-HpaefOXqYKWpRM-Wnp_qHnuJgEtgodQ-pw74nXkOsVFZoJzVGCepZKoa2xhevAu3CfXMfR97LIski17PPwWV9n7yQ7onSKDmJP-q4mO7PG7LICE22WkgJkk_T1W4UkDTkCxZE_t64F6JOryg",
20
+ "decomposed_rsa_private_key": [
21
+ 99901518447147034909201847419097315968297147437151469508360346463543063455874100243913856315732601845631262435262260706834732781343251117295631282035669718956849603873760427510697419513879951677293141811802298231979984193675329310799949319780256427743430868153430092107435691279045586737323088097814348379553,
22
+ 150403214039042969422234539598081448727095065760827625117291927993312435808658801596490175435022631344068300286691567969167599002686861320953369584858579175145292571827741537678953851066650767421880724569960757205263834584408256907364362335186143689470541020576121430290451811639884653381769242114589063140137,
23
+ 9722388475302835659981770144245473802727189238482784140772185098454692776534140965636906536278050956583642123384956999145748370323609525303581225997179041811165866787665739142915159339213277652697292051951101524711140179706280769338283140995188236373535772131640760223532777113050011892350517809489041748221890761908227035155427120665309649346176680716768795083108884918500667128372337390742180547015864208380672823661707652326881334341297176353315311938916714461230279773342086788397843164325182504213999351119600183937196301188144973390771200970801617167449547771853642668679963596445734355177856606187012189522929,
24
+ 76655456251690686310715773467748134538235257990884898225079665817629234781830040448768066408365824785604130747777597240274232562744683362773774949177985094198657737800269908016421544875604264806165580741547472576851314301645952126968540259498362204011872412045782028541860398434400028332528808362023863391812
25
+ ],
26
+ "files_response_data": {
27
+ "f": [
28
+ {
29
+ "h": "ewZ1GAqT",
30
+ "p": "",
31
+ "u": "zA8CzCf9tZw",
32
+ "t": 4,
33
+ "a": "",
34
+ "k": "",
35
+ "ts": 1361694312
36
+ },
37
+ {
38
+ "h": "qtBjmBTR",
39
+ "p": "",
40
+ "u": "zA8CzCf9tZw",
41
+ "t": 3,
42
+ "a": "",
43
+ "k": "",
44
+ "ts": 1361694312
45
+ },
46
+ {
47
+ "h": "ywJVFBKB",
48
+ "p": "",
49
+ "u": "zA8CzCf9tZw",
50
+ "t": 2,
51
+ "a": "",
52
+ "k": "",
53
+ "ts": 1361694312
54
+ },
55
+ {
56
+ "h": "jtwkAQaK",
57
+ "p": "ywJVFBKB",
58
+ "u": "zA8CzCf9tZw",
59
+ "t": 1,
60
+ "a": "US0wKXcni_p8dnqRvhR_Otafji3ioNJ5IsgSHB5zhOw",
61
+ "k": "zA8CzCf9tZw:4LMrqo0U-VwPGhhXV2fjQw",
62
+ "ts": 1361696928
63
+ },
64
+ {
65
+ "h": "atY1xRAb",
66
+ "p": "ywJVFBKB",
67
+ "u": "zA8CzCf9tZw",
68
+ "t": 1,
69
+ "a": "XOtY9s3EU2DypiRM2I74SS79lZaaQYQcmO4-Lekk0rw",
70
+ "k": "zA8CzCf9tZw:56VbHKn14ww22T-Xuwyjvg",
71
+ "ts": 1361696936
72
+ },
73
+ {
74
+ "h": "74ZTXbyR",
75
+ "p": "ywJVFBKB",
76
+ "u": "zA8CzCf9tZw",
77
+ "t": 0,
78
+ "a": "m9IIApZejMhZnsRy63HvhrkYh_AalPjfRYzsMpkq5-Q",
79
+ "k": "zA8CzCf9tZw:fJjggPPQGf_5MzqCuvyUGxuxVnDJLo6qBGAOwJAkOm4",
80
+ "s": 39,
81
+ "ts": 1361697187
82
+ },
83
+ {
84
+ "h": "L0xHwayA",
85
+ "p": "ywJVFBKB",
86
+ "u": "zA8CzCf9tZw",
87
+ "t": 0,
88
+ "a": "n4CazRegf4aLA4BNrdoEsqRLGLQ244NjJUJi53Zz-J4",
89
+ "k": "zA8CzCf9tZw:Iz6fsJ_qIgmS4uC4eirPnfaZOcLOoQA2t_5wTtfqp7Y",
90
+ "s": 137080,
91
+ "ts": 1361697194
92
+ }
93
+ ],
94
+ "ok": [
95
+
96
+ ],
97
+ "s": [
98
+
99
+ ],
100
+ "u": [
101
+ {
102
+ "u": "zA8CzCf9tZw",
103
+ "c": 2,
104
+ "m": "megartest@gmail.com"
105
+ }
106
+ ],
107
+ "sn": "pQ-WDyYPBHU"
108
+ }
109
+ }