pezra-contacts 0.1.0
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.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +47 -0
- data/Rakefile +87 -0
- data/contacts.gemspec +27 -0
- data/lib/config/contacts.yml +10 -0
- data/lib/contacts.rb +46 -0
- data/lib/contacts/flickr.rb +133 -0
- data/lib/contacts/google.rb +308 -0
- data/lib/contacts/version.rb +9 -0
- data/lib/contacts/windows_live.rb +164 -0
- data/lib/contacts/yahoo.rb +236 -0
- data/spec/contact_spec.rb +38 -0
- data/spec/fake_net_http.rb +73 -0
- data/spec/fake_web.rb +279 -0
- data/spec/feeds/contacts.yml +10 -0
- data/spec/feeds/flickr/auth.getFrob.xml +4 -0
- data/spec/feeds/flickr/auth.getToken.xml +5 -0
- data/spec/feeds/google-many.xml +48 -0
- data/spec/feeds/google-single.xml +46 -0
- data/spec/feeds/wl_contacts.xml +29 -0
- data/spec/feeds/yh_contacts.txt +119 -0
- data/spec/feeds/yh_credential.xml +28 -0
- data/spec/flickr/auth_spec.rb +80 -0
- data/spec/gmail/auth_spec.rb +74 -0
- data/spec/gmail/fetching_spec.rb +190 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +83 -0
- data/spec/windows_live/windows_live_spec.rb +30 -0
- data/spec/yahoo/yahoo_spec.rb +77 -0
- data/vendor/windowslivelogin.rb +1151 -0
- metadata +129 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# FakeWeb - Ruby Helper for Faking Web Requests
|
|
2
|
+
# Copyright 2006 Blaine Cook <romeda@gmail.com>.
|
|
3
|
+
#
|
|
4
|
+
# FakeWeb is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 2 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# FakeWeb is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with FakeWeb; if not, write to the Free Software
|
|
16
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
|
|
18
|
+
require 'net/http'
|
|
19
|
+
require 'net/https'
|
|
20
|
+
require 'stringio'
|
|
21
|
+
|
|
22
|
+
module Net #:nodoc:
|
|
23
|
+
|
|
24
|
+
class BufferedIO #:nodoc:
|
|
25
|
+
def initialize( io, debug_output = nil )
|
|
26
|
+
@debug_output = debug_output
|
|
27
|
+
@io = case io
|
|
28
|
+
when Socket, IO: io
|
|
29
|
+
when String
|
|
30
|
+
File.exists?(io) ? File.open(io, "r") : StringIO.new(io)
|
|
31
|
+
end
|
|
32
|
+
raise "Unable to create local socket" unless @io
|
|
33
|
+
connect
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def connect(*args)
|
|
37
|
+
@rbuf = ''
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def rbuf_fill
|
|
41
|
+
@rbuf << @io.sysread(1024)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class HTTP #:nodoc:
|
|
46
|
+
|
|
47
|
+
def HTTP.socket_type #:nodoc:
|
|
48
|
+
FakeWeb::SocketDelegator
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
alias :original_net_http_request :request
|
|
52
|
+
alias :original_net_http_connect :connect
|
|
53
|
+
|
|
54
|
+
def request(req, body = nil, &block)
|
|
55
|
+
prot = use_ssl ? "https" : "http"
|
|
56
|
+
uri = "#{prot}://#{self.address}#{req.path}"
|
|
57
|
+
uri, query_string = uri.split('?')
|
|
58
|
+
query_string = body unless req.method == 'GET'
|
|
59
|
+
|
|
60
|
+
if FakeWeb.registered_uri?(uri)
|
|
61
|
+
@socket = Net::HTTP.socket_type.new
|
|
62
|
+
return FakeWeb.response_for(uri, query_string, req, &block)
|
|
63
|
+
else
|
|
64
|
+
# original_net_http_connect
|
|
65
|
+
# return original_net_http_request(req, body, &block)
|
|
66
|
+
raise "unexpected HTTP #{req.method} to #{uri}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def connect
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/spec/fake_web.rb
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# FakeWeb - Ruby Helper for Faking Web Requests
|
|
2
|
+
# Copyright 2006 Blaine Cook <romeda@gmail.com>.
|
|
3
|
+
#
|
|
4
|
+
# FakeWeb is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 2 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# FakeWeb is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with FakeWeb; if not, write to the Free Software
|
|
16
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
|
|
18
|
+
require 'fake_net_http'
|
|
19
|
+
require 'singleton'
|
|
20
|
+
require 'cgi'
|
|
21
|
+
|
|
22
|
+
module OpenURI #:nodoc: all
|
|
23
|
+
class HTTPError < StandardError; end;
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
module FakeWeb
|
|
27
|
+
|
|
28
|
+
# Resets the FakeWeb Registry. This will force all subsequent web requests to
|
|
29
|
+
# behave as real requests.
|
|
30
|
+
def FakeWeb.clean_registry; FakeWeb::Registry.instance.clean_registry; end
|
|
31
|
+
|
|
32
|
+
# Register +uri+ to be handled according to +options+. +uri+ can be a
|
|
33
|
+
# +String+ or an +URI+ object. +options+ must be a +Hash+ that must contain
|
|
34
|
+
# any one of the following keys:
|
|
35
|
+
#
|
|
36
|
+
# <tt>:string</tt>::
|
|
37
|
+
# Takes a +String+ argument that is returned as the body of the response.
|
|
38
|
+
# FakeWeb.register_uri('http://example.com/', :string => "Hello World!")
|
|
39
|
+
# <tt>:file</tt>::
|
|
40
|
+
# Takes a valid filesystem path to a file that is slurped and returned as
|
|
41
|
+
# the body of the response.
|
|
42
|
+
# FakeWeb.register_uri('http://example.com/', :file => "/tmp/my_response_body.txt")
|
|
43
|
+
# <tt>:response</tt>::
|
|
44
|
+
# Either an <tt>Net::HTTPResponse</tt>, an +IO+ or a +String+.
|
|
45
|
+
#
|
|
46
|
+
# The easier way by far is to pass the <tt>:response</tt> option to
|
|
47
|
+
# +register_uri+ as a +String+ or an (open for reads) +IO+ object which
|
|
48
|
+
# will be used as the complete HTTP response, including headers and body.
|
|
49
|
+
# If the string points to a readable file, this file will be used as the
|
|
50
|
+
# content for the request.
|
|
51
|
+
#
|
|
52
|
+
# To obtain a complete response document, you can use the +curl+ command,
|
|
53
|
+
# like so:
|
|
54
|
+
#
|
|
55
|
+
# curl -i http://www.example.com/ > response_for_www.example.com
|
|
56
|
+
#
|
|
57
|
+
# which can then be used in your test environment like so:
|
|
58
|
+
#
|
|
59
|
+
# FakeWeb.register_uri('http://www.example.com/', :response => 'response_for_www.example.com')
|
|
60
|
+
#
|
|
61
|
+
# See the <tt>Net::HTTPResponse</tt>
|
|
62
|
+
# documentation[http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/classes/Net/HTTPResponse.html]
|
|
63
|
+
# for more information on creating custom response objects.
|
|
64
|
+
#
|
|
65
|
+
# Two optional arguments are also accepted:
|
|
66
|
+
#
|
|
67
|
+
# <tt>:status</tt>::
|
|
68
|
+
# Passing <tt>:status</tt> as a two-value array will set the response code
|
|
69
|
+
# and message. The defaults are <tt>200</tt> and <tt>OK</tt>, respectively.
|
|
70
|
+
# Example:
|
|
71
|
+
# FakeWeb.register_uri('http://www.example.com/', :response => "Go away!", :status => [ 404, "Not Found" ])
|
|
72
|
+
# <tt>:exception</tt>::
|
|
73
|
+
# The argument passed via <tt>:exception</tt> will be raised when the
|
|
74
|
+
# specified URL is requested. Any +Exception+ class is valid. Example:
|
|
75
|
+
# FakeWeb.register_uri('http://www.example.com/', :exception => Net::HTTPError)
|
|
76
|
+
#
|
|
77
|
+
def FakeWeb.register_uri(uri, options); FakeWeb::Registry.instance.register_uri(uri, options); end
|
|
78
|
+
|
|
79
|
+
# Returns the faked Net::HTTPResponse object associated with +uri+.
|
|
80
|
+
def FakeWeb.response_for(uri, query_string, req, &block) #:nodoc: :yields: response
|
|
81
|
+
FakeWeb::Registry.instance.response_for(uri, query_string, req, &block)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Checks for presence of +uri+ in the +FakeWeb+ registry.
|
|
85
|
+
def FakeWeb.registered_uri?(uri); FakeWeb::Registry.instance.registered_uri?(uri); end
|
|
86
|
+
|
|
87
|
+
class Registry #:nodoc:
|
|
88
|
+
include Singleton
|
|
89
|
+
|
|
90
|
+
attr_accessor :uri_map
|
|
91
|
+
|
|
92
|
+
def initialize
|
|
93
|
+
clean_registry
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def clean_registry
|
|
97
|
+
self.uri_map = {}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def register_uri(uri, options)
|
|
101
|
+
uri_map[normalize_uri(uri)] = FakeWeb::Responder.new(uri, options)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def registered_uri?(uri)
|
|
105
|
+
uri_map.has_key?(normalize_uri(uri))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def registered_uri(uri)
|
|
109
|
+
uri = normalize_uri(uri)
|
|
110
|
+
return uri_map[uri] if registered_uri?(uri)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def response_for(uri, query_string, req, &block)
|
|
114
|
+
match = registered_uri(uri)
|
|
115
|
+
compare_methods(match, req)
|
|
116
|
+
compare_queries(match, query_string)
|
|
117
|
+
match.response(req, &block)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def compare_methods(registered, req)
|
|
123
|
+
unless registered.method == req.method
|
|
124
|
+
raise "HTTP method mismatch; expected #{registered.method}, got #{req.method} on #{registered.uri}"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def compare_queries(registered, query_string)
|
|
129
|
+
expected_query = registered.options[:query]
|
|
130
|
+
real_query = parse_query(query_string)
|
|
131
|
+
|
|
132
|
+
eql = if expected_query.respond_to?(:keys) and real_query.respond_to?(:keys)
|
|
133
|
+
(registered.options[:query_partial_match] or expected_query.size == real_query.size) and
|
|
134
|
+
expected_query.all? { |key, value| real_query[key.to_s] == value }
|
|
135
|
+
else
|
|
136
|
+
expected_query == real_query
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
unless eql
|
|
140
|
+
raise "query string mismatch; expected #{expected_query.inspect}, got #{real_query.inspect} on #{registered.uri}"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def parse_query(query_string)
|
|
145
|
+
if query_string
|
|
146
|
+
CGI::parse(query_string).inject({}) do |params, pair|
|
|
147
|
+
params[pair.first] = pair.last.first
|
|
148
|
+
params
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def normalize_uri(uri)
|
|
154
|
+
case uri
|
|
155
|
+
when URI: uri
|
|
156
|
+
else
|
|
157
|
+
uri = 'http://' + uri unless uri.match('^https?://')
|
|
158
|
+
URI.parse(uri)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
module Response #:nodoc:
|
|
164
|
+
def read_body(*args, &block)
|
|
165
|
+
yield @body if block_given?
|
|
166
|
+
@body
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
class Responder #:nodoc:
|
|
171
|
+
|
|
172
|
+
attr_accessor :uri, :options
|
|
173
|
+
|
|
174
|
+
def initialize(uri, options)
|
|
175
|
+
self.uri = uri
|
|
176
|
+
self.options = options
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def method
|
|
180
|
+
options[:method] || 'GET'
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def response(req, &block)
|
|
184
|
+
verify_request(req)
|
|
185
|
+
|
|
186
|
+
if has_baked_response?
|
|
187
|
+
response = baked_response
|
|
188
|
+
else
|
|
189
|
+
code, msg = meta_information
|
|
190
|
+
response = Net::HTTPResponse.send(:response_class, code.to_s).new(uri, code.to_s, msg)
|
|
191
|
+
response.instance_variable_set(:@body, content)
|
|
192
|
+
end
|
|
193
|
+
response.instance_variable_set(:@read, true)
|
|
194
|
+
response.extend FakeWeb::Response
|
|
195
|
+
|
|
196
|
+
optionally_raise(response)
|
|
197
|
+
|
|
198
|
+
yield response if block_given?
|
|
199
|
+
|
|
200
|
+
response
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
private
|
|
204
|
+
|
|
205
|
+
def content
|
|
206
|
+
[ :file, :string ].each do |map_option|
|
|
207
|
+
next unless options.has_key?(map_option)
|
|
208
|
+
return self.send("#{map_option}_response", options[map_option])
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
return ''
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def file_response(path)
|
|
215
|
+
IO.readlines(path).join("\n")
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def string_response(string)
|
|
219
|
+
string
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def baked_response
|
|
223
|
+
resp = case options[:response]
|
|
224
|
+
when Net::HTTPResponse: options[:response]
|
|
225
|
+
when String
|
|
226
|
+
socket = Net::BufferedIO.new(options[:response])
|
|
227
|
+
r = Net::HTTPResponse.read_new(socket)
|
|
228
|
+
r.instance_eval { @header['transfer-encoding'] = nil }
|
|
229
|
+
r.reading_body(socket, true) {}
|
|
230
|
+
r
|
|
231
|
+
else raise StandardError, "Handler unimplemented for response #{options[:response]}"
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def has_baked_response?
|
|
236
|
+
options.has_key?(:response)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def optionally_raise(response)
|
|
240
|
+
return unless options.has_key?(:exception)
|
|
241
|
+
ex_alloc = options[:exception].allocate
|
|
242
|
+
ex_instance = case ex_alloc
|
|
243
|
+
when Net::HTTPError, OpenURI::HTTPError: options[:exception].new('Exception from FakeWeb', response)
|
|
244
|
+
else options[:exception].new
|
|
245
|
+
end
|
|
246
|
+
raise ex_instance
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def meta_information
|
|
250
|
+
if options.has_key?(:status); options[:status]
|
|
251
|
+
else; [ 200, 'OK' ]
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def verify_request(req)
|
|
256
|
+
return unless options.has_key?(:verify)
|
|
257
|
+
options[:verify].call(req)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
class SocketDelegator #:nodoc:
|
|
262
|
+
|
|
263
|
+
def initialize(delegate=nil)
|
|
264
|
+
@delegate = nil
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def method_missing(method, *args, &block)
|
|
268
|
+
return @delegate.send(method, *args, &block) if @delegate
|
|
269
|
+
return self.send("my_#{method}", *args, &block)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def my_closed?
|
|
273
|
+
@closed ||= true
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def my_readuntil(*args)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
windows_live:
|
|
2
|
+
appid: your_app_id
|
|
3
|
+
secret: your_app_secret_key
|
|
4
|
+
security_algorithm: wsignin1.0
|
|
5
|
+
return_url: http://yourserver.com/your_return_url
|
|
6
|
+
policy_url: http://yourserver.com/you_policy_url
|
|
7
|
+
|
|
8
|
+
yahoo:
|
|
9
|
+
appid: i%3DB%26p%3DUw70JGIdHWVRbpqYItcMw--
|
|
10
|
+
secret: a34f389cbd135de4618eed5e23409d34450
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<feed>
|
|
2
|
+
<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gd='http://schemas.google.com/g/2005'>
|
|
3
|
+
<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#contact'/>
|
|
4
|
+
<title>Elizabeth Bennet</title>
|
|
5
|
+
<content>My good friend, Liz. A little quick to judge sometimes, but nice girl.</content>
|
|
6
|
+
<gd:email rel='http://schemas.google.com/g/2005#work' primary='true' address='liz@gmail.com'/>
|
|
7
|
+
<gd:email rel='http://schemas.google.com/g/2005#home' address='liz@example.org'/>
|
|
8
|
+
<gd:phoneNumber rel='http://schemas.google.com/g/2005#work' primary='true'>
|
|
9
|
+
(206)555-1212
|
|
10
|
+
</gd:phoneNumber>
|
|
11
|
+
<gd:phoneNumber rel='http://schemas.google.com/g/2005#home'>
|
|
12
|
+
(206)555-1213
|
|
13
|
+
</gd:phoneNumber>
|
|
14
|
+
<gd:phoneNumber rel='http://schemas.google.com/g/2005#mobile'>
|
|
15
|
+
(206) 555-1212
|
|
16
|
+
</gd:phoneNumber>
|
|
17
|
+
<gd:im rel='http://schemas.google.com/g/2005#home'
|
|
18
|
+
protocol='http://schemas.google.com/g/2005#GOOGLE_TALK'
|
|
19
|
+
address='liz@gmail.com'/>
|
|
20
|
+
<gd:postalAddress rel='http://schemas.google.com/g/2005#work' primary='true'>
|
|
21
|
+
1600 Amphitheatre Pkwy
|
|
22
|
+
Mountain View, CA 94043
|
|
23
|
+
</gd:postalAddress>
|
|
24
|
+
<gd:postalAddress rel='http://schemas.google.com/g/2005#home'>
|
|
25
|
+
800 Main Street
|
|
26
|
+
Mountain View, CA 94041
|
|
27
|
+
</gd:postalAddress>
|
|
28
|
+
<gd:organization>
|
|
29
|
+
<gd:orgName>Google, Inc.</gd:orgName>
|
|
30
|
+
<gd:orgTitle>Tech Writer</gd:orgTitle>
|
|
31
|
+
</gd:organization>
|
|
32
|
+
</entry>
|
|
33
|
+
|
|
34
|
+
<entry>
|
|
35
|
+
<title>Poor Jack</title>
|
|
36
|
+
<content>Poor Jack doesn't have an e-mail address</content>
|
|
37
|
+
</entry>
|
|
38
|
+
|
|
39
|
+
<entry>
|
|
40
|
+
<title>William Paginate</title>
|
|
41
|
+
<gd:email address='will_paginate@googlegroups.com' />
|
|
42
|
+
</entry>
|
|
43
|
+
|
|
44
|
+
<entry>
|
|
45
|
+
<content>This guy doesn't have a name</content>
|
|
46
|
+
<gd:email address='anonymous@example.com' />
|
|
47
|
+
</entry>
|
|
48
|
+
</feed>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<!-- source: http://code.google.com/apis/contacts/developers_guide_protocol.html -->
|
|
2
|
+
<feed xmlns='http://www.w3.org/2005/Atom'
|
|
3
|
+
xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'
|
|
4
|
+
xmlns:gd='http://schemas.google.com/g/2005'>
|
|
5
|
+
<id>http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base</id>
|
|
6
|
+
<updated>2008-03-05T12:36:38.836Z</updated>
|
|
7
|
+
<category scheme='http://schemas.google.com/g/2005#kind'
|
|
8
|
+
term='http://schemas.google.com/contact/2008#contact' />
|
|
9
|
+
<title type='text'>Contacts</title>
|
|
10
|
+
<link rel='http://schemas.google.com/g/2005#feed'
|
|
11
|
+
type='application/atom+xml'
|
|
12
|
+
href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base' />
|
|
13
|
+
<link rel='http://schemas.google.com/g/2005#post'
|
|
14
|
+
type='application/atom+xml'
|
|
15
|
+
href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base' />
|
|
16
|
+
<link rel='self' type='application/atom+xml'
|
|
17
|
+
href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base?max-results=25' />
|
|
18
|
+
<author>
|
|
19
|
+
<name>Elizabeth Bennet</name>
|
|
20
|
+
<email>liz@gmail.com</email>
|
|
21
|
+
</author>
|
|
22
|
+
<generator version='1.0' uri='http://www.google.com/m8/feeds/contacts'>
|
|
23
|
+
Contacts
|
|
24
|
+
</generator>
|
|
25
|
+
<openSearch:totalResults>1</openSearch:totalResults>
|
|
26
|
+
<openSearch:startIndex>1</openSearch:startIndex>
|
|
27
|
+
<openSearch:itemsPerPage>25</openSearch:itemsPerPage>
|
|
28
|
+
<entry>
|
|
29
|
+
<id>
|
|
30
|
+
http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/c9012de
|
|
31
|
+
</id>
|
|
32
|
+
<updated>2008-03-05T12:36:38.835Z</updated>
|
|
33
|
+
<category scheme='http://schemas.google.com/g/2005#kind'
|
|
34
|
+
term='http://schemas.google.com/contact/2008#contact' />
|
|
35
|
+
<title type='text'>Fitzgerald</title>
|
|
36
|
+
<link rel='self' type='application/atom+xml'
|
|
37
|
+
href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/c9012de' />
|
|
38
|
+
<link rel='edit' type='application/atom+xml'
|
|
39
|
+
href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/c9012de/1204720598835000' />
|
|
40
|
+
<gd:phoneNumber rel='http://schemas.google.com/g/2005#home'
|
|
41
|
+
primary='true'>
|
|
42
|
+
456
|
|
43
|
+
</gd:phoneNumber>
|
|
44
|
+
<gd:email label="Personal" rel="http://schemas.google.com/g/2005#home" address="fubar@gmail.com" primary="true" />
|
|
45
|
+
</entry>
|
|
46
|
+
</feed>
|