App Engine Python SDK version 1.8.1
[gae.git] / python / google / appengine / api / xmpp / xmpp_service_stub.py
blob8ebfff8c911d7f976e12d3730b55504b3d9a4273
1 #!/usr/bin/env python
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."""
31 import logging
32 import os
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.
49 """
51 THREADSAFE = True
53 def __init__(self, log=logging.info, service_name='xmpp'):
54 """Initializer.
56 Args:
57 log: A logger, used for dependency injection.
58 service_name: Service name expected for all calls.
59 """
60 super(XmppServiceStub, self).__init__(service_name)
61 self.log = log
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.
69 Args:
70 request: A PresenceRequest.
71 response: A PresenceResponse.
72 """
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.
91 Args:
92 request: An XmppMessageRequest.
93 response: An XmppMessageResponse .
94 """
95 from_jid = self._GetFrom(request.from_jid())
96 log_message = []
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)
114 else:
115 response.add_status(xmpp_service_pb.XmppMessageResponse.INVALID_JID)
117 def _Dynamic_SendInvite(self, request, response):
118 """Implementation of XmppService::SendInvite.
120 Args:
121 request: An XmppInviteRequest.
122 response: An XmppInviteResponse .
124 from_jid = self._GetFrom(request.from_jid())
125 self._ParseJid(request.jid())
126 log_message = []
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.
136 Args:
137 request: An XmppSendPresenceRequest.
138 response: An XmppSendPresenceResponse .
140 from_jid = self._GetFrom(request.from_jid())
141 log_message = []
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())
146 if request.type():
147 log_message.append(' Type: ' + request.type())
148 if request.show():
149 log_message.append(' Show: ' + request.show())
150 if request.status():
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.
161 * Has a node.
162 * Does not contain any invalid characters.
164 Args:
165 jid: The JID to validate
167 Returns:
168 A tuple (node, domain, resource) representing the JID.
170 Raises:
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,
177 jid)
178 raise apiproxy_errors.ApplicationError(
179 xmpp_service_pb.XmppServiceError.INVALID_JID)
181 node, domain, resource = ('', '', '')
182 at = jid.find('@')
183 if at == -1:
184 self.log('Invalid JID: No \'@\' character found. JID: %s', jid)
185 raise apiproxy_errors.ApplicationError(
186 xmpp_service_pb.XmppServiceError.INVALID_JID)
188 node = jid[:at]
189 if not node:
190 self.log('Invalid JID: No node. JID: %s', jid)
191 raise apiproxy_errors.ApplicationError(
192 xmpp_service_pb.XmppServiceError.INVALID_JID)
194 rest = jid[at+1:]
195 if rest.find('@') > -1:
196 self.log('Invalid JID: Second \'@\' character found. JID: %s',
197 jid)
198 raise apiproxy_errors.ApplicationError(
199 xmpp_service_pb.XmppServiceError.INVALID_JID)
201 slash = rest.find('/')
202 if slash == -1:
203 domain = rest
204 resource = 'bot'
205 else:
206 domain = rest[:slash]
207 resource = rest[slash+1:]
209 if resource.find('/') > -1:
210 self.log('Invalid JID: Second \'/\' character found. JID: %s',
211 jid)
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."""
219 try:
220 self._ParseJid(jid)
221 return True
222 except apiproxy_errors.ApplicationError:
223 return False
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).
231 Args:
232 requested: The requested from JID.
234 Returns:
235 string, The from JID.
237 Raises:
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.
262 Args:
263 request: A CreateChannelRequest.
264 response: A CreateChannelResponse.
266 log_message = []
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.
278 Args:
279 request: A SendMessageRequest.
280 response: A SendMessageRequest.
282 log_message = []
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))