blob: 8ebfff8c911d7f976e12d3730b55504b3d9a4273 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Stub version of the XMPP API, writes messages to logs."""
import logging
import os
from google.appengine.api import apiproxy_stub
from google.appengine.api import app_identity
from google.appengine.api.xmpp import xmpp_service_pb
from google.appengine.runtime import apiproxy_errors
INVALID_JID_CHARACTERS = ',;()[]'
class XmppServiceStub(apiproxy_stub.APIProxyStub):
"""Python only xmpp service stub.
This stub does not use an XMPP network. It prints messages to the console
instead of sending any stanzas.
"""
THREADSAFE = True
def __init__(self, log=logging.info, service_name='xmpp'):
"""Initializer.
Args:
log: A logger, used for dependency injection.
service_name: Service name expected for all calls.
"""
super(XmppServiceStub, self).__init__(service_name)
self.log = log
def _Dynamic_GetPresence(self, request, response):
"""Implementation of XmppService::GetPresence.
Returns online if the first character of the JID comes before 'm' in the
alphabet, otherwise returns offline.
Args:
request: A PresenceRequest.
response: A PresenceResponse.
"""
self._GetFrom(request.from_jid())
self._FillInPresenceResponse(request.jid(), response)
def _Dynamic_BulkGetPresence(self, request, response):
self._GetFrom(request.from_jid())
for jid in request.jid_list():
subresponse = response.add_presence_response()
self._FillInPresenceResponse(jid, subresponse)
def _FillInPresenceResponse(self, jid, response):
"""Arbitrarily fill in a presence response or subresponse."""
response.set_is_available(jid[0] < 'm')
response.set_valid(self._ValidateJid(jid))
response.set_presence(1)
def _Dynamic_SendMessage(self, request, response):
"""Implementation of XmppService::SendMessage.
Args:
request: An XmppMessageRequest.
response: An XmppMessageResponse .
"""
from_jid = self._GetFrom(request.from_jid())
log_message = []
log_message.append('Sending an XMPP Message:')
log_message.append(' From:')
log_message.append(' ' + from_jid)
log_message.append(' Body:')
log_message.append(' ' + request.body())
log_message.append(' Type:')
log_message.append(' ' + request.type())
log_message.append(' Raw Xml:')
log_message.append(' ' + str(request.raw_xml()))
log_message.append(' To JIDs:')
for jid in request.jid_list():
log_message.append(' ' + jid)
self.log('\n'.join(log_message))
for jid in request.jid_list():
if self._ValidateJid(jid):
response.add_status(xmpp_service_pb.XmppMessageResponse.NO_ERROR)
else:
response.add_status(xmpp_service_pb.XmppMessageResponse.INVALID_JID)
def _Dynamic_SendInvite(self, request, response):
"""Implementation of XmppService::SendInvite.
Args:
request: An XmppInviteRequest.
response: An XmppInviteResponse .
"""
from_jid = self._GetFrom(request.from_jid())
self._ParseJid(request.jid())
log_message = []
log_message.append('Sending an XMPP Invite:')
log_message.append(' From:')
log_message.append(' ' + from_jid)
log_message.append(' To: ' + request.jid())
self.log('\n'.join(log_message))
def _Dynamic_SendPresence(self, request, response):
"""Implementation of XmppService::SendPresence.
Args:
request: An XmppSendPresenceRequest.
response: An XmppSendPresenceResponse .
"""
from_jid = self._GetFrom(request.from_jid())
log_message = []
log_message.append('Sending an XMPP Presence:')
log_message.append(' From:')
log_message.append(' ' + from_jid)
log_message.append(' To: ' + request.jid())
if request.type():
log_message.append(' Type: ' + request.type())
if request.show():
log_message.append(' Show: ' + request.show())
if request.status():
log_message.append(' Status: ' + request.status())
self.log('\n'.join(log_message))
def _ParseJid(self, jid):
"""Parse the given JID.
Also tests that the given jid:
* Contains one and only one @.
* Has one or zero resources.
* Has a node.
* Does not contain any invalid characters.
Args:
jid: The JID to validate
Returns:
A tuple (node, domain, resource) representing the JID.
Raises:
apiproxy_errors.ApplicationError if the requested JID is invalid using the
criteria listed above.
"""
if set(jid).intersection(INVALID_JID_CHARACTERS):
self.log('Invalid JID: characters "%s" not supported. JID: %s',
INVALID_JID_CHARACTERS,
jid)
raise apiproxy_errors.ApplicationError(
xmpp_service_pb.XmppServiceError.INVALID_JID)
node, domain, resource = ('', '', '')
at = jid.find('@')
if at == -1:
self.log('Invalid JID: No \'@\' character found. JID: %s', jid)
raise apiproxy_errors.ApplicationError(
xmpp_service_pb.XmppServiceError.INVALID_JID)
node = jid[:at]
if not node:
self.log('Invalid JID: No node. JID: %s', jid)
raise apiproxy_errors.ApplicationError(
xmpp_service_pb.XmppServiceError.INVALID_JID)
rest = jid[at+1:]
if rest.find('@') > -1:
self.log('Invalid JID: Second \'@\' character found. JID: %s',
jid)
raise apiproxy_errors.ApplicationError(
xmpp_service_pb.XmppServiceError.INVALID_JID)
slash = rest.find('/')
if slash == -1:
domain = rest
resource = 'bot'
else:
domain = rest[:slash]
resource = rest[slash+1:]
if resource.find('/') > -1:
self.log('Invalid JID: Second \'/\' character found. JID: %s',
jid)
raise apiproxy_errors.ApplicationError(
xmpp_service_pb.XmppServiceError.INVALID_JID)
return node, domain, resource
def _ValidateJid(self, jid):
"""Validate the given JID using self._ParseJid."""
try:
self._ParseJid(jid)
return True
except apiproxy_errors.ApplicationError:
return False
def _GetFrom(self, requested):
"""Validates that the from JID is valid.
The JID uses the display-app-id for all apps to simulate a common case
in production (alias === display-app-id).
Args:
requested: The requested from JID.
Returns:
string, The from JID.
Raises:
apiproxy_errors.ApplicationError if the requested JID is invalid.
"""
full_appid = os.environ.get('APPLICATION_ID')
partition, _, display_app_id = (
app_identity.app_identity._ParseFullAppId(full_appid))
if requested == None or requested == '':
return display_app_id + '@appspot.com/bot'
node, domain, resource = self._ParseJid(requested)
if domain == 'appspot.com' and node == display_app_id:
return node + '@' + domain + '/' + resource
elif domain == display_app_id + '.appspotchat.com':
return node + '@' + domain + '/' + resource
self.log('Invalid From JID: Must be appid@appspot.com[/resource] or '
'node@appid.appspotchat.com[/resource]. JID: %s', requested)
raise apiproxy_errors.ApplicationError(
xmpp_service_pb.XmppServiceError.INVALID_JID)
def _Dynamic_CreateChannel(self, request, response):
"""Implementation of XmppService::CreateChannel.
Args:
request: A CreateChannelRequest.
response: A CreateChannelResponse.
"""
log_message = []
log_message.append('Sending a Create Channel:')
log_message.append(' Client ID:')
log_message.append(' ' + request.application_key())
if request.duration_minutes():
log_message.append(' Duration minutes: ' +
str(request.duration_minutes()))
self.log('\n'.join(log_message))
def _Dynamic_SendChannelMessage(self, request, response):
"""Implementation of XmppService::SendChannelMessage.
Args:
request: A SendMessageRequest.
response: A SendMessageRequest.
"""
log_message = []
log_message.append('Sending a Channel Message:')
log_message.append(' Client ID:')
log_message.append(' ' + request.application_key())
log_message.append(' Message:')
log_message.append(' ' + str(request.message()))
self.log('\n'.join(log_message))