3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
21 """Stub version of the XMPP API, writes messages to logs."""
34 from google
.appengine
.api
import apiproxy_stub
35 from google
.appengine
.api
import app_identity
36 from google
.appengine
.api
.xmpp
import xmpp_service_pb
37 from google
.appengine
.runtime
import apiproxy_errors
41 INVALID_JID_CHARACTERS
= ',;()[]'
44 class XmppServiceStub(apiproxy_stub
.APIProxyStub
):
45 """Python only xmpp service stub.
47 This stub does not use an XMPP network. It prints messages to the console
48 instead of sending any stanzas.
53 def __init__(self
, log
=logging
.info
, service_name
='xmpp'):
57 log: A logger, used for dependency injection.
58 service_name: Service name expected for all calls.
60 super(XmppServiceStub
, self
).__init
__(service_name
)
63 def _Dynamic_GetPresence(self
, request
, response
):
64 """Implementation of XmppService::GetPresence.
66 Returns online if the first character of the JID comes before 'm' in the
67 alphabet, otherwise returns offline.
70 request: A PresenceRequest.
71 response: A PresenceResponse.
73 self
._GetFrom
(request
.from_jid())
74 self
._FillInPresenceResponse
(request
.jid(), response
)
76 def _Dynamic_BulkGetPresence(self
, request
, response
):
77 self
._GetFrom
(request
.from_jid())
78 for jid
in request
.jid_list():
79 subresponse
= response
.add_presence_response()
80 self
._FillInPresenceResponse
(jid
, subresponse
)
82 def _FillInPresenceResponse(self
, jid
, response
):
83 """Arbitrarily fill in a presence response or subresponse."""
84 response
.set_is_available(jid
[0] < 'm')
85 response
.set_valid(self
._ValidateJid
(jid
))
86 response
.set_presence(1)
88 def _Dynamic_SendMessage(self
, request
, response
):
89 """Implementation of XmppService::SendMessage.
92 request: An XmppMessageRequest.
93 response: An XmppMessageResponse .
95 from_jid
= self
._GetFrom
(request
.from_jid())
97 log_message
.append('Sending an XMPP Message:')
98 log_message
.append(' From:')
99 log_message
.append(' ' + from_jid
)
100 log_message
.append(' Body:')
101 log_message
.append(' ' + request
.body())
102 log_message
.append(' Type:')
103 log_message
.append(' ' + request
.type())
104 log_message
.append(' Raw Xml:')
105 log_message
.append(' ' + str(request
.raw_xml()))
106 log_message
.append(' To JIDs:')
107 for jid
in request
.jid_list():
108 log_message
.append(' ' + jid
)
109 self
.log('\n'.join(log_message
))
111 for jid
in request
.jid_list():
112 if self
._ValidateJid
(jid
):
113 response
.add_status(xmpp_service_pb
.XmppMessageResponse
.NO_ERROR
)
115 response
.add_status(xmpp_service_pb
.XmppMessageResponse
.INVALID_JID
)
117 def _Dynamic_SendInvite(self
, request
, response
):
118 """Implementation of XmppService::SendInvite.
121 request: An XmppInviteRequest.
122 response: An XmppInviteResponse .
124 from_jid
= self
._GetFrom
(request
.from_jid())
125 self
._ParseJid
(request
.jid())
127 log_message
.append('Sending an XMPP Invite:')
128 log_message
.append(' From:')
129 log_message
.append(' ' + from_jid
)
130 log_message
.append(' To: ' + request
.jid())
131 self
.log('\n'.join(log_message
))
133 def _Dynamic_SendPresence(self
, request
, response
):
134 """Implementation of XmppService::SendPresence.
137 request: An XmppSendPresenceRequest.
138 response: An XmppSendPresenceResponse .
140 from_jid
= self
._GetFrom
(request
.from_jid())
142 log_message
.append('Sending an XMPP Presence:')
143 log_message
.append(' From:')
144 log_message
.append(' ' + from_jid
)
145 log_message
.append(' To: ' + request
.jid())
147 log_message
.append(' Type: ' + request
.type())
149 log_message
.append(' Show: ' + request
.show())
151 log_message
.append(' Status: ' + request
.status())
152 self
.log('\n'.join(log_message
))
154 def _ParseJid(self
, jid
):
155 """Parse the given JID.
157 Also tests that the given jid:
159 * Contains one and only one @.
160 * Has one or zero resources.
162 * Does not contain any invalid characters.
165 jid: The JID to validate
168 A tuple (node, domain, resource) representing the JID.
171 apiproxy_errors.ApplicationError if the requested JID is invalid using the
172 criteria listed above.
174 if set(jid
).intersection(INVALID_JID_CHARACTERS
):
175 self
.log('Invalid JID: characters "%s" not supported. JID: %s',
176 INVALID_JID_CHARACTERS
,
178 raise apiproxy_errors
.ApplicationError(
179 xmpp_service_pb
.XmppServiceError
.INVALID_JID
)
181 node
, domain
, resource
= ('', '', '')
184 self
.log('Invalid JID: No \'@\' character found. JID: %s', jid
)
185 raise apiproxy_errors
.ApplicationError(
186 xmpp_service_pb
.XmppServiceError
.INVALID_JID
)
190 self
.log('Invalid JID: No node. JID: %s', jid
)
191 raise apiproxy_errors
.ApplicationError(
192 xmpp_service_pb
.XmppServiceError
.INVALID_JID
)
195 if rest
.find('@') > -1:
196 self
.log('Invalid JID: Second \'@\' character found. JID: %s',
198 raise apiproxy_errors
.ApplicationError(
199 xmpp_service_pb
.XmppServiceError
.INVALID_JID
)
201 slash
= rest
.find('/')
206 domain
= rest
[:slash
]
207 resource
= rest
[slash
+1:]
209 if resource
.find('/') > -1:
210 self
.log('Invalid JID: Second \'/\' character found. JID: %s',
212 raise apiproxy_errors
.ApplicationError(
213 xmpp_service_pb
.XmppServiceError
.INVALID_JID
)
215 return node
, domain
, resource
217 def _ValidateJid(self
, jid
):
218 """Validate the given JID using self._ParseJid."""
222 except apiproxy_errors
.ApplicationError
:
225 def _GetFrom(self
, requested
):
226 """Validates that the from JID is valid.
228 The JID uses the display-app-id for all apps to simulate a common case
229 in production (alias === display-app-id).
232 requested: The requested from JID.
235 string, The from JID.
238 apiproxy_errors.ApplicationError if the requested JID is invalid.
241 full_appid
= os
.environ
.get('APPLICATION_ID')
242 partition
, _
, display_app_id
= (
243 app_identity
.app_identity
._ParseFullAppId
(full_appid
))
244 if requested
== None or requested
== '':
245 return display_app_id
+ '@appspot.com/bot'
247 node
, domain
, resource
= self
._ParseJid
(requested
)
249 if domain
== 'appspot.com' and node
== display_app_id
:
250 return node
+ '@' + domain
+ '/' + resource
251 elif domain
== display_app_id
+ '.appspotchat.com':
252 return node
+ '@' + domain
+ '/' + resource
254 self
.log('Invalid From JID: Must be appid@appspot.com[/resource] or '
255 'node@appid.appspotchat.com[/resource]. JID: %s', requested
)
256 raise apiproxy_errors
.ApplicationError(
257 xmpp_service_pb
.XmppServiceError
.INVALID_JID
)
259 def _Dynamic_CreateChannel(self
, request
, response
):
260 """Implementation of XmppService::CreateChannel.
263 request: A CreateChannelRequest.
264 response: A CreateChannelResponse.
267 log_message
.append('Sending a Create Channel:')
268 log_message
.append(' Client ID:')
269 log_message
.append(' ' + request
.application_key())
270 if request
.duration_minutes():
271 log_message
.append(' Duration minutes: ' +
272 str(request
.duration_minutes()))
273 self
.log('\n'.join(log_message
))
275 def _Dynamic_SendChannelMessage(self
, request
, response
):
276 """Implementation of XmppService::SendChannelMessage.
279 request: A SendMessageRequest.
280 response: A SendMessageRequest.
283 log_message
.append('Sending a Channel Message:')
284 log_message
.append(' Client ID:')
285 log_message
.append(' ' + request
.application_key())
286 log_message
.append(' Message:')
287 log_message
.append(' ' + str(request
.message()))
288 self
.log('\n'.join(log_message
))