Update python stuff to latest
Probably definitely break linux
This commit is contained in:
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
## $LicenseInfo:firstyear=2011&license=viewerlgpl$
|
||||
## Second Life Viewer Source Code
|
||||
## Copyright (C) 2011, Linden Research, Inc.
|
||||
##
|
||||
## This library is free software; you can redistribute it and/or
|
||||
## modify it under the terms of the GNU Lesser General Public
|
||||
## License as published by the Free Software Foundation;
|
||||
## version 2.1 of the License only.
|
||||
##
|
||||
## This library is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
## Lesser General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU Lesser General Public
|
||||
## License along with this library; if not, write to the Free Software
|
||||
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
##
|
||||
## Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
## $/LicenseInfo$
|
||||
|
||||
import warnings
|
||||
|
||||
warnings.warn("indra.ipc.httputil has been deprecated; use eventlet.httpc instead", DeprecationWarning, 2)
|
||||
|
||||
from eventlet.httpc import *
|
||||
|
||||
|
||||
makeConnection = make_connection
|
||||
@@ -1,100 +0,0 @@
|
||||
"""\
|
||||
@file llsdhttp.py
|
||||
@brief Functions to ease moving llsd over http
|
||||
|
||||
$LicenseInfo:firstyear=2006&license=mit$
|
||||
|
||||
Copyright (c) 2006-2009, Linden Research, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
$/LicenseInfo$
|
||||
"""
|
||||
|
||||
import os.path
|
||||
import os
|
||||
import urlparse
|
||||
|
||||
from indra.base import llsd
|
||||
|
||||
from eventlet import httpc
|
||||
|
||||
suite = httpc.HttpSuite(llsd.format_xml, llsd.parse, 'application/llsd+xml')
|
||||
delete = suite.delete
|
||||
delete_ = suite.delete_
|
||||
get = suite.get
|
||||
get_ = suite.get_
|
||||
head = suite.head
|
||||
head_ = suite.head_
|
||||
post = suite.post
|
||||
post_ = suite.post_
|
||||
put = suite.put
|
||||
put_ = suite.put_
|
||||
request = suite.request
|
||||
request_ = suite.request_
|
||||
|
||||
# import every httpc error exception into our namespace for convenience
|
||||
for x in httpc.status_to_error_map.itervalues():
|
||||
globals()[x.__name__] = x
|
||||
ConnectionError = httpc.ConnectionError
|
||||
Retriable = httpc.Retriable
|
||||
|
||||
for x in (httpc.ConnectionError,):
|
||||
globals()[x.__name__] = x
|
||||
|
||||
|
||||
def postFile(url, filename):
|
||||
f = open(filename)
|
||||
body = f.read()
|
||||
f.close()
|
||||
llsd_body = llsd.parse(body)
|
||||
return post_(url, llsd_body)
|
||||
|
||||
|
||||
# deprecated in favor of get_
|
||||
def getStatus(url, use_proxy=False):
|
||||
status, _headers, _body = get_(url, use_proxy=use_proxy)
|
||||
return status
|
||||
|
||||
# deprecated in favor of put_
|
||||
def putStatus(url, data):
|
||||
status, _headers, _body = put_(url, data)
|
||||
return status
|
||||
|
||||
# deprecated in favor of delete_
|
||||
def deleteStatus(url):
|
||||
status, _headers, _body = delete_(url)
|
||||
return status
|
||||
|
||||
# deprecated in favor of post_
|
||||
def postStatus(url, data):
|
||||
status, _headers, _body = post_(url, data)
|
||||
return status
|
||||
|
||||
|
||||
def postFileStatus(url, filename):
|
||||
status, _headers, body = postFile(url, filename)
|
||||
return status, body
|
||||
|
||||
|
||||
def getFromSimulator(path, use_proxy=False):
|
||||
return get('http://' + simulatorHostAndPort + path, use_proxy=use_proxy)
|
||||
|
||||
|
||||
def postToSimulator(path, data=None):
|
||||
return post('http://' + simulatorHostAndPort + path, data)
|
||||
@@ -1,81 +0,0 @@
|
||||
"""\
|
||||
@file mysql_pool.py
|
||||
@brief Thin wrapper around eventlet.db_pool that chooses MySQLdb and Tpool.
|
||||
|
||||
$LicenseInfo:firstyear=2007&license=mit$
|
||||
|
||||
Copyright (c) 2007-2009, Linden Research, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
$/LicenseInfo$
|
||||
"""
|
||||
|
||||
import MySQLdb
|
||||
from eventlet import db_pool
|
||||
|
||||
class DatabaseConnector(db_pool.DatabaseConnector):
|
||||
def __init__(self, credentials, *args, **kwargs):
|
||||
super(DatabaseConnector, self).__init__(MySQLdb, credentials,
|
||||
conn_pool=db_pool.ConnectionPool,
|
||||
*args, **kwargs)
|
||||
|
||||
# get is extended relative to eventlet.db_pool to accept a port argument
|
||||
def get(self, host, dbname, port=3306):
|
||||
key = (host, dbname, port)
|
||||
if key not in self._databases:
|
||||
new_kwargs = self._kwargs.copy()
|
||||
new_kwargs['db'] = dbname
|
||||
new_kwargs['host'] = host
|
||||
new_kwargs['port'] = port
|
||||
new_kwargs.update(self.credentials_for(host))
|
||||
dbpool = ConnectionPool(*self._args, **new_kwargs)
|
||||
self._databases[key] = dbpool
|
||||
|
||||
return self._databases[key]
|
||||
|
||||
class ConnectionPool(db_pool.TpooledConnectionPool):
|
||||
"""A pool which gives out saranwrapped MySQLdb connections from a pool
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ConnectionPool, self).__init__(MySQLdb, *args, **kwargs)
|
||||
|
||||
def get(self):
|
||||
conn = super(ConnectionPool, self).get()
|
||||
# annotate the connection object with the details on the
|
||||
# connection; this is used elsewhere to check that you haven't
|
||||
# suddenly changed databases in midstream while making a
|
||||
# series of queries on a connection.
|
||||
arg_names = ['host','user','passwd','db','port','unix_socket','conv','connect_timeout',
|
||||
'compress', 'named_pipe', 'init_command', 'read_default_file', 'read_default_group',
|
||||
'cursorclass', 'use_unicode', 'charset', 'sql_mode', 'client_flag', 'ssl',
|
||||
'local_infile']
|
||||
# you could have constructed this connectionpool with a mix of
|
||||
# keyword and non-keyword arguments, but we want to annotate
|
||||
# the connection object with a dict so it's easy to check
|
||||
# against so here we are converting the list of non-keyword
|
||||
# arguments (in self._args) into a dict of keyword arguments,
|
||||
# and merging that with the actual keyword arguments
|
||||
# (self._kwargs). The arg_names variable lists the
|
||||
# constructor arguments for MySQLdb Connection objects.
|
||||
converted_kwargs = dict([ (arg_names[i], arg) for i, arg in enumerate(self._args) ])
|
||||
converted_kwargs.update(self._kwargs)
|
||||
conn.connection_parameters = converted_kwargs
|
||||
return conn
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
"""\
|
||||
@file russ.py
|
||||
@brief Recursive URL Substitution Syntax helpers
|
||||
@author Phoenix
|
||||
|
||||
Many details on how this should work is available on the wiki:
|
||||
https://wiki.secondlife.com/wiki/Recursive_URL_Substitution_Syntax
|
||||
|
||||
Adding features to this should be reflected in that page in the
|
||||
implementations section.
|
||||
|
||||
$LicenseInfo:firstyear=2007&license=mit$
|
||||
|
||||
Copyright (c) 2007-2009, Linden Research, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
$/LicenseInfo$
|
||||
"""
|
||||
|
||||
import urllib
|
||||
from indra.ipc import llsdhttp
|
||||
|
||||
class UnbalancedBraces(Exception):
|
||||
pass
|
||||
|
||||
class UnknownDirective(Exception):
|
||||
pass
|
||||
|
||||
class BadDirective(Exception):
|
||||
pass
|
||||
|
||||
def format_value_for_path(value):
|
||||
if type(value) in [list, tuple]:
|
||||
# *NOTE: treat lists as unquoted path components so that the quoting
|
||||
# doesn't get out-of-hand. This is a workaround for the fact that
|
||||
# russ always quotes, even if the data it's given is already quoted,
|
||||
# and it's not safe to simply unquote a path directly, so if we want
|
||||
# russ to substitute urls parts inside other url parts we always
|
||||
# have to do so via lists of unquoted path components.
|
||||
return '/'.join([urllib.quote(str(item)) for item in value])
|
||||
else:
|
||||
return urllib.quote(str(value))
|
||||
|
||||
def format(format_str, context):
|
||||
"""@brief Format format string according to rules for RUSS.
|
||||
@see https://osiris.lindenlab.com/mediawiki/index.php/Recursive_URL_Substitution_Syntax
|
||||
@param format_str The input string to format.
|
||||
@param context A map used for string substitutions.
|
||||
@return Returns the formatted string. If no match, the braces remain intact.
|
||||
"""
|
||||
while True:
|
||||
#print "format_str:", format_str
|
||||
all_matches = _find_sub_matches(format_str)
|
||||
if not all_matches:
|
||||
break
|
||||
substitutions = 0
|
||||
while True:
|
||||
matches = all_matches.pop()
|
||||
# we work from right to left to make sure we do not
|
||||
# invalidate positions earlier in format_str
|
||||
matches.reverse()
|
||||
for pos in matches:
|
||||
# Use index since _find_sub_matches should have raised
|
||||
# an exception, and failure to find now is an exception.
|
||||
end = format_str.index('}', pos)
|
||||
#print "directive:", format_str[pos+1:pos+5]
|
||||
if format_str[pos + 1] == '$':
|
||||
value = context[format_str[pos + 2:end]]
|
||||
if value is not None:
|
||||
value = format_value_for_path(value)
|
||||
elif format_str[pos + 1] == '%':
|
||||
value = _build_query_string(
|
||||
context.get(format_str[pos + 2:end]))
|
||||
elif format_str[pos+1:pos+5] == 'http' or format_str[pos+1:pos+5] == 'file':
|
||||
value = _fetch_url_directive(format_str[pos + 1:end])
|
||||
else:
|
||||
raise UnknownDirective, format_str[pos:end + 1]
|
||||
if value is not None:
|
||||
format_str = format_str[:pos]+str(value)+format_str[end+1:]
|
||||
substitutions += 1
|
||||
|
||||
# If there were any substitutions at this depth, re-parse
|
||||
# since this may have revealed new things to substitute
|
||||
if substitutions:
|
||||
break
|
||||
if not all_matches:
|
||||
break
|
||||
|
||||
# If there were no substitutions at all, and we have exhausted
|
||||
# the possible matches, bail.
|
||||
if not substitutions:
|
||||
break
|
||||
return format_str
|
||||
|
||||
def _find_sub_matches(format_str):
|
||||
"""@brief Find all of the substitution matches.
|
||||
@param format_str the RUSS conformant format string.
|
||||
@return Returns an array of depths of arrays of positional matches in input.
|
||||
"""
|
||||
depth = 0
|
||||
matches = []
|
||||
for pos in range(len(format_str)):
|
||||
if format_str[pos] == '{':
|
||||
depth += 1
|
||||
if not len(matches) == depth:
|
||||
matches.append([])
|
||||
matches[depth - 1].append(pos)
|
||||
continue
|
||||
if format_str[pos] == '}':
|
||||
depth -= 1
|
||||
continue
|
||||
if not depth == 0:
|
||||
raise UnbalancedBraces, format_str
|
||||
return matches
|
||||
|
||||
def _build_query_string(query_dict):
|
||||
"""\
|
||||
@breif given a dict, return a query string. utility wrapper for urllib.
|
||||
@param query_dict input query dict
|
||||
@returns Returns an urlencoded query string including leading '?'.
|
||||
"""
|
||||
if query_dict:
|
||||
keys = query_dict.keys()
|
||||
keys.sort()
|
||||
def stringize(value):
|
||||
if type(value) in (str,unicode):
|
||||
return value
|
||||
else:
|
||||
return str(value)
|
||||
query_list = [urllib.quote(str(key)) + '=' + urllib.quote(stringize(query_dict[key])) for key in keys]
|
||||
return '?' + '&'.join(query_list)
|
||||
else:
|
||||
return ''
|
||||
|
||||
def _fetch_url_directive(directive):
|
||||
"*FIX: This only supports GET"
|
||||
commands = directive.split('|')
|
||||
resource = llsdhttp.get(commands[0])
|
||||
if len(commands) == 3:
|
||||
resource = _walk_resource(resource, commands[2])
|
||||
return resource
|
||||
|
||||
def _walk_resource(resource, path):
|
||||
path = path.split('/')
|
||||
for child in path:
|
||||
if not child:
|
||||
continue
|
||||
resource = resource[child]
|
||||
return resource
|
||||
@@ -1,134 +0,0 @@
|
||||
"""\
|
||||
@file servicebuilder.py
|
||||
@author Phoenix
|
||||
@brief Class which will generate service urls.
|
||||
|
||||
$LicenseInfo:firstyear=2007&license=mit$
|
||||
|
||||
Copyright (c) 2007-2009, Linden Research, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
$/LicenseInfo$
|
||||
"""
|
||||
|
||||
from indra.base import config
|
||||
from indra.ipc import llsdhttp
|
||||
from indra.ipc import russ
|
||||
|
||||
# *NOTE: agent presence relies on this variable existing and being current, it is a huge hack
|
||||
services_config = {}
|
||||
try:
|
||||
services_config = llsdhttp.get(config.get('services-config'))
|
||||
except:
|
||||
pass
|
||||
|
||||
_g_builder = None
|
||||
def _builder():
|
||||
global _g_builder
|
||||
if _g_builder is None:
|
||||
_g_builder = ServiceBuilder()
|
||||
return _g_builder
|
||||
|
||||
def build(name, context={}, **kwargs):
|
||||
""" Convenience method for using a global, singleton, service builder. Pass arguments either via a dict or via python keyword arguments, or both!
|
||||
|
||||
Example use:
|
||||
> context = {'channel':'Second Life Release', 'version':'1.18.2.0'}
|
||||
> servicebuilder.build('version-manager-version', context)
|
||||
'http://int.util.vaak.lindenlab.com/channel/Second%20Life%20Release/1.18.2.0'
|
||||
> servicebuilder.build('version-manager-version', channel='Second Life Release', version='1.18.2.0')
|
||||
'http://int.util.vaak.lindenlab.com/channel/Second%20Life%20Release/1.18.2.0'
|
||||
> servicebuilder.build('version-manager-version', context, version='1.18.1.2')
|
||||
'http://int.util.vaak.lindenlab.com/channel/Second%20Life%20Release/1.18.1.2'
|
||||
"""
|
||||
global _g_builder
|
||||
if _g_builder is None:
|
||||
_g_builder = ServiceBuilder()
|
||||
return _g_builder.buildServiceURL(name, context, **kwargs)
|
||||
|
||||
def build_path(name, context={}, **kwargs):
|
||||
context = context.copy() # shouldn't modify the caller's dictionary
|
||||
context.update(kwargs)
|
||||
return _builder().buildPath(name, context)
|
||||
|
||||
class ServiceBuilder(object):
|
||||
def __init__(self, services_definition = services_config):
|
||||
"""\
|
||||
@brief
|
||||
@brief Create a ServiceBuilder.
|
||||
@param services_definition Complete services definition, services.xml.
|
||||
"""
|
||||
# no need to keep a copy of the services section of the
|
||||
# complete services definition, but it doesn't hurt much.
|
||||
self.services = services_definition['services']
|
||||
self.builders = {}
|
||||
for service in self.services:
|
||||
service_builder = service.get('service-builder')
|
||||
if not service_builder:
|
||||
continue
|
||||
if isinstance(service_builder, dict):
|
||||
# We will be constructing several builders
|
||||
for name, builder in service_builder.iteritems():
|
||||
full_builder_name = service['name'] + '-' + name
|
||||
self.builders[full_builder_name] = builder
|
||||
else:
|
||||
self.builders[service['name']] = service_builder
|
||||
|
||||
def buildPath(self, name, context):
|
||||
"""\
|
||||
@brief given the environment on construction, return a service path.
|
||||
@param name The name of the service.
|
||||
@param context A dict of name value lookups for the service.
|
||||
@returns Returns the
|
||||
"""
|
||||
return russ.format(self.builders[name], context)
|
||||
|
||||
def buildServiceURL(self, name, context={}, **kwargs):
|
||||
"""\
|
||||
@brief given the environment on construction, return a service URL.
|
||||
@param name The name of the service.
|
||||
@param context A dict of name value lookups for the service.
|
||||
@param kwargs Any keyword arguments are treated as members of the
|
||||
context, this allows you to be all 31337 by writing shit like:
|
||||
servicebuilder.build('name', param=value)
|
||||
@returns Returns the
|
||||
"""
|
||||
context = context.copy() # shouldn't modify the caller's dictionary
|
||||
context.update(kwargs)
|
||||
base_url = config.get('services-base-url')
|
||||
svc_path = russ.format(self.builders[name], context)
|
||||
return base_url + svc_path
|
||||
|
||||
|
||||
def on_in(query_name, host_key, schema_key):
|
||||
"""\
|
||||
@brief Constructs an on/in snippet (for running named queries)
|
||||
from a schema name and two keys referencing values stored in
|
||||
indra.xml.
|
||||
|
||||
@param query_name Name of the query.
|
||||
@param host_key Logical name of destination host. Will be
|
||||
looked up in indra.xml.
|
||||
@param schema_key Logical name of destination schema. Will
|
||||
be looked up in indra.xml.
|
||||
"""
|
||||
return "on/config:%s/in/config:%s/%s" % (host_key.strip('/'),
|
||||
schema_key.strip('/'),
|
||||
query_name.lstrip('/'))
|
||||
|
||||
@@ -1,468 +0,0 @@
|
||||
"""\
|
||||
@file siesta.py
|
||||
@brief A tiny llsd based RESTful web services framework
|
||||
|
||||
$LicenseInfo:firstyear=2008&license=mit$
|
||||
|
||||
Copyright (c) 2008, Linden Research, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
$/LicenseInfo$
|
||||
"""
|
||||
|
||||
from indra.base import config
|
||||
from indra.base import llsd
|
||||
from webob import exc
|
||||
import webob
|
||||
import re, socket
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
try:
|
||||
import cjson
|
||||
json_decode = cjson.decode
|
||||
json_encode = cjson.encode
|
||||
JsonDecodeError = cjson.DecodeError
|
||||
JsonEncodeError = cjson.EncodeError
|
||||
except ImportError:
|
||||
import simplejson
|
||||
json_decode = simplejson.loads
|
||||
json_encode = simplejson.dumps
|
||||
JsonDecodeError = ValueError
|
||||
JsonEncodeError = TypeError
|
||||
|
||||
|
||||
llsd_parsers = {
|
||||
'application/json': json_decode,
|
||||
llsd.BINARY_MIME_TYPE: llsd.parse_binary,
|
||||
'application/llsd+notation': llsd.parse_notation,
|
||||
llsd.XML_MIME_TYPE: llsd.parse_xml,
|
||||
'application/xml': llsd.parse_xml,
|
||||
}
|
||||
|
||||
|
||||
def mime_type(content_type):
|
||||
'''Given a Content-Type header, return only the MIME type.'''
|
||||
|
||||
return content_type.split(';', 1)[0].strip().lower()
|
||||
|
||||
class BodyLLSD(object):
|
||||
'''Give a webob Request or Response an llsd based "content" property.
|
||||
|
||||
Getting the content property parses the body, and caches the result.
|
||||
|
||||
Setting the content property formats a payload, and the body property
|
||||
is set.'''
|
||||
|
||||
def _llsd__get(self):
|
||||
'''Get, set, or delete the LLSD value stored in this object.'''
|
||||
|
||||
try:
|
||||
return self._llsd
|
||||
except AttributeError:
|
||||
if not self.body:
|
||||
raise AttributeError('No llsd attribute has been set')
|
||||
else:
|
||||
mtype = mime_type(self.content_type)
|
||||
try:
|
||||
parser = llsd_parsers[mtype]
|
||||
except KeyError:
|
||||
raise exc.HTTPUnsupportedMediaType(
|
||||
'Content type %s not supported' % mtype).exception
|
||||
try:
|
||||
self._llsd = parser(self.body)
|
||||
except (llsd.LLSDParseError, JsonDecodeError, TypeError), err:
|
||||
raise exc.HTTPBadRequest(
|
||||
'Could not parse body: %r' % err.args).exception
|
||||
return self._llsd
|
||||
|
||||
def _llsd__set(self, val):
|
||||
req = getattr(self, 'request', None)
|
||||
if req is not None:
|
||||
formatter, ctype = formatter_for_request(req)
|
||||
self.content_type = ctype
|
||||
else:
|
||||
formatter, ctype = formatter_for_mime_type(
|
||||
mime_type(self.content_type))
|
||||
self.body = formatter(val)
|
||||
|
||||
def _llsd__del(self):
|
||||
if hasattr(self, '_llsd'):
|
||||
del self._llsd
|
||||
|
||||
content = property(_llsd__get, _llsd__set, _llsd__del)
|
||||
|
||||
|
||||
class Response(webob.Response, BodyLLSD):
|
||||
'''Response class with LLSD support.
|
||||
|
||||
A sensible default content type is used.
|
||||
|
||||
Setting the llsd property also sets the body. Getting the llsd
|
||||
property parses the body if necessary.
|
||||
|
||||
If you set the body property directly, the llsd property will be
|
||||
deleted.'''
|
||||
|
||||
default_content_type = 'application/llsd+xml'
|
||||
|
||||
def _body__set(self, body):
|
||||
if hasattr(self, '_llsd'):
|
||||
del self._llsd
|
||||
super(Response, self)._body__set(body)
|
||||
|
||||
def cache_forever(self):
|
||||
self.cache_expires(86400 * 365)
|
||||
|
||||
body = property(webob.Response._body__get, _body__set,
|
||||
webob.Response._body__del,
|
||||
webob.Response._body__get.__doc__)
|
||||
|
||||
|
||||
class Request(webob.Request, BodyLLSD):
|
||||
'''Request class with LLSD support.
|
||||
|
||||
Sensible content type and accept headers are used by default.
|
||||
|
||||
Setting the content property also sets the body. Getting the content
|
||||
property parses the body if necessary.
|
||||
|
||||
If you set the body property directly, the content property will be
|
||||
deleted.'''
|
||||
|
||||
default_content_type = 'application/llsd+xml'
|
||||
default_accept = ('application/llsd+xml; q=0.5, '
|
||||
'application/llsd+notation; q=0.3, '
|
||||
'application/llsd+binary; q=0.2, '
|
||||
'application/xml; q=0.1, '
|
||||
'application/json; q=0.0')
|
||||
|
||||
def __init__(self, environ=None, *args, **kwargs):
|
||||
if environ is None:
|
||||
environ = {}
|
||||
else:
|
||||
environ = environ.copy()
|
||||
if 'CONTENT_TYPE' not in environ:
|
||||
environ['CONTENT_TYPE'] = self.default_content_type
|
||||
if 'HTTP_ACCEPT' not in environ:
|
||||
environ['HTTP_ACCEPT'] = self.default_accept
|
||||
super(Request, self).__init__(environ, *args, **kwargs)
|
||||
|
||||
def _body__set(self, body):
|
||||
if hasattr(self, '_llsd'):
|
||||
del self._llsd
|
||||
super(Request, self)._body__set(body)
|
||||
|
||||
def path_urljoin(self, *parts):
|
||||
return '/'.join([path_url.rstrip('/')] + list(parts))
|
||||
|
||||
body = property(webob.Request._body__get, _body__set,
|
||||
webob.Request._body__del, webob.Request._body__get.__doc__)
|
||||
|
||||
def create_response(self, content=None, status='200 OK',
|
||||
conditional_response=webob.NoDefault):
|
||||
resp = self.ResponseClass(status=status, request=self,
|
||||
conditional_response=conditional_response)
|
||||
resp.content = content
|
||||
return resp
|
||||
|
||||
def curl(self):
|
||||
'''Create and fill out a pycurl easy object from this request.'''
|
||||
|
||||
import pycurl
|
||||
c = pycurl.Curl()
|
||||
c.setopt(pycurl.URL, self.url())
|
||||
if self.headers:
|
||||
c.setopt(pycurl.HTTPHEADER,
|
||||
['%s: %s' % (k, self.headers[k]) for k in self.headers])
|
||||
c.setopt(pycurl.FOLLOWLOCATION, True)
|
||||
c.setopt(pycurl.AUTOREFERER, True)
|
||||
c.setopt(pycurl.MAXREDIRS, 16)
|
||||
c.setopt(pycurl.NOSIGNAL, True)
|
||||
c.setopt(pycurl.READFUNCTION, self.body_file.read)
|
||||
c.setopt(pycurl.SSL_VERIFYHOST, 2)
|
||||
|
||||
if self.method == 'POST':
|
||||
c.setopt(pycurl.POST, True)
|
||||
post301 = getattr(pycurl, 'POST301', None)
|
||||
if post301 is not None:
|
||||
# Added in libcurl 7.17.1.
|
||||
c.setopt(post301, True)
|
||||
elif self.method == 'PUT':
|
||||
c.setopt(pycurl.PUT, True)
|
||||
elif self.method != 'GET':
|
||||
c.setopt(pycurl.CUSTOMREQUEST, self.method)
|
||||
return c
|
||||
|
||||
Request.ResponseClass = Response
|
||||
Response.RequestClass = Request
|
||||
|
||||
|
||||
llsd_formatters = {
|
||||
'application/json': json_encode,
|
||||
'application/llsd+binary': llsd.format_binary,
|
||||
'application/llsd+notation': llsd.format_notation,
|
||||
'application/llsd+xml': llsd.format_xml,
|
||||
'application/xml': llsd.format_xml,
|
||||
}
|
||||
|
||||
formatter_qualities = (
|
||||
('application/llsd+xml', 1.0),
|
||||
('application/llsd+notation', 0.5),
|
||||
('application/llsd+binary', 0.4),
|
||||
('application/xml', 0.3),
|
||||
('application/json', 0.2),
|
||||
)
|
||||
|
||||
def formatter_for_mime_type(mime_type):
|
||||
'''Return a formatter that encodes to the given MIME type.
|
||||
|
||||
The result is a pair of function and MIME type.'''
|
||||
try:
|
||||
return llsd_formatters[mime_type], mime_type
|
||||
except KeyError:
|
||||
raise exc.HTTPInternalServerError(
|
||||
'Could not use MIME type %r to format response' %
|
||||
mime_type).exception
|
||||
|
||||
|
||||
def formatter_for_request(req):
|
||||
'''Return a formatter that encodes to the preferred type of the client.
|
||||
|
||||
The result is a pair of function and actual MIME type.'''
|
||||
ctype = req.accept.best_match(formatter_qualities)
|
||||
try:
|
||||
return llsd_formatters[ctype], ctype
|
||||
except KeyError:
|
||||
raise exc.HTTPNotAcceptable().exception
|
||||
|
||||
|
||||
def wsgi_adapter(func, environ, start_response):
|
||||
'''Adapt a Siesta callable to act as a WSGI application.'''
|
||||
# Process the request as appropriate.
|
||||
try:
|
||||
req = Request(environ)
|
||||
#print req.urlvars
|
||||
resp = func(req, **req.urlvars)
|
||||
if not isinstance(resp, webob.Response):
|
||||
try:
|
||||
formatter, ctype = formatter_for_request(req)
|
||||
resp = req.ResponseClass(formatter(resp), content_type=ctype)
|
||||
resp._llsd = resp
|
||||
except (JsonEncodeError, TypeError), err:
|
||||
resp = exc.HTTPInternalServerError(
|
||||
detail='Could not format response')
|
||||
except exc.HTTPException, e:
|
||||
resp = e
|
||||
except socket.error, e:
|
||||
resp = exc.HTTPInternalServerError(detail=e.args[1])
|
||||
return resp(environ, start_response)
|
||||
|
||||
|
||||
def llsd_callable(func):
|
||||
'''Turn a callable into a Siesta application.'''
|
||||
|
||||
def replacement(environ, start_response):
|
||||
return wsgi_adapter(func, environ, start_response)
|
||||
|
||||
return replacement
|
||||
|
||||
|
||||
def llsd_method(http_method, func):
|
||||
def replacement(environ, start_response):
|
||||
if environ['REQUEST_METHOD'] == http_method:
|
||||
return wsgi_adapter(func, environ, start_response)
|
||||
return exc.HTTPMethodNotAllowed()(environ, start_response)
|
||||
|
||||
return replacement
|
||||
|
||||
|
||||
http11_methods = 'OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT'.split()
|
||||
http11_methods.sort()
|
||||
|
||||
def llsd_class(cls):
|
||||
'''Turn a class into a Siesta application.
|
||||
|
||||
A new instance is created for each request. A HTTP method FOO is
|
||||
turned into a call to the handle_foo method of the instance.'''
|
||||
|
||||
def foo(req, **kwargs):
|
||||
instance = cls()
|
||||
method = req.method.lower()
|
||||
try:
|
||||
handler = getattr(instance, 'handle_' + method)
|
||||
except AttributeError:
|
||||
allowed = [m for m in http11_methods
|
||||
if hasattr(instance, 'handle_' + m.lower())]
|
||||
raise exc.HTTPMethodNotAllowed(
|
||||
headers={'Allow': ', '.join(allowed)}).exception
|
||||
#print "kwargs: ", kwargs
|
||||
return handler(req, **kwargs)
|
||||
|
||||
def replacement(environ, start_response):
|
||||
return wsgi_adapter(foo, environ, start_response)
|
||||
|
||||
return replacement
|
||||
|
||||
|
||||
def curl(reqs):
|
||||
import pycurl
|
||||
|
||||
m = pycurl.CurlMulti()
|
||||
curls = [r.curl() for r in reqs]
|
||||
io = {}
|
||||
for c in curls:
|
||||
fp = StringIO()
|
||||
hdr = StringIO()
|
||||
c.setopt(pycurl.WRITEFUNCTION, fp.write)
|
||||
c.setopt(pycurl.HEADERFUNCTION, hdr.write)
|
||||
io[id(c)] = fp, hdr
|
||||
m.handles = curls
|
||||
try:
|
||||
while True:
|
||||
ret, num_handles = m.perform()
|
||||
if ret != pycurl.E_CALL_MULTI_PERFORM:
|
||||
break
|
||||
finally:
|
||||
m.close()
|
||||
|
||||
for req, c in zip(reqs, curls):
|
||||
fp, hdr = io[id(c)]
|
||||
hdr.seek(0)
|
||||
status = hdr.readline().rstrip()
|
||||
headers = []
|
||||
name, values = None, None
|
||||
|
||||
# XXX We don't currently handle bogus header data.
|
||||
|
||||
for line in hdr.readlines():
|
||||
if not line[0].isspace():
|
||||
if name:
|
||||
headers.append((name, ' '.join(values)))
|
||||
name, value = line.strip().split(':', 1)
|
||||
value = [value]
|
||||
else:
|
||||
values.append(line.strip())
|
||||
if name:
|
||||
headers.append((name, ' '.join(values)))
|
||||
|
||||
resp = c.ResponseClass(fp.getvalue(), status, headers, request=req)
|
||||
|
||||
|
||||
route_re = re.compile(r'''
|
||||
\{ # exact character "{"
|
||||
(\w*) # "config" or variable (restricted to a-z, 0-9, _)
|
||||
(?:([:~])([^}]+))? # optional :type or ~regex part
|
||||
\} # exact character "}"
|
||||
''', re.VERBOSE)
|
||||
|
||||
predefined_regexps = {
|
||||
'uuid': r'[a-f0-9][a-f0-9-]{31,35}',
|
||||
'int': r'\d+',
|
||||
'host': r'[a-z0-9][a-z0-9\-\.]*',
|
||||
}
|
||||
|
||||
def compile_route(route):
|
||||
fp = StringIO()
|
||||
last_pos = 0
|
||||
for match in route_re.finditer(route):
|
||||
#print "matches: ", match.groups()
|
||||
fp.write(re.escape(route[last_pos:match.start()]))
|
||||
var_name = match.group(1)
|
||||
sep = match.group(2)
|
||||
expr = match.group(3)
|
||||
if var_name == 'config':
|
||||
expr = re.escape(str(config.get(var_name)))
|
||||
else:
|
||||
if expr:
|
||||
if sep == ':':
|
||||
expr = predefined_regexps[expr]
|
||||
# otherwise, treat what follows '~' as a regexp
|
||||
else:
|
||||
expr = '[^/]+'
|
||||
if var_name != '':
|
||||
expr = '(?P<%s>%s)' % (var_name, expr)
|
||||
else:
|
||||
expr = '(%s)' % (expr,)
|
||||
fp.write(expr)
|
||||
last_pos = match.end()
|
||||
fp.write(re.escape(route[last_pos:]))
|
||||
compiled_route = '^%s$' % fp.getvalue()
|
||||
#print route, "->", compiled_route
|
||||
return compiled_route
|
||||
|
||||
class Router(object):
|
||||
'''WSGI routing class. Parses a URL and hands off a request to
|
||||
some other WSGI application. If no suitable application is found,
|
||||
responds with a 404.'''
|
||||
|
||||
def __init__(self):
|
||||
self._new_routes = []
|
||||
self._routes = []
|
||||
self._paths = []
|
||||
|
||||
def add(self, route, app, methods=None):
|
||||
self._new_routes.append((route, app, methods))
|
||||
|
||||
def _create_routes(self):
|
||||
for route, app, methods in self._new_routes:
|
||||
self._paths.append(route)
|
||||
self._routes.append(
|
||||
(re.compile(compile_route(route)),
|
||||
app,
|
||||
methods and dict.fromkeys(methods)))
|
||||
self._new_routes = []
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
# load up the config from the config file. Only needs to be
|
||||
# done once per interpreter. This is the entry point of all
|
||||
# siesta applications, so this is where we trap it.
|
||||
_conf = config.get_config()
|
||||
if _conf is None:
|
||||
import os.path
|
||||
fname = os.path.join(
|
||||
environ.get('ll.config_dir', '/local/linden/etc'),
|
||||
'indra.xml')
|
||||
config.load(fname)
|
||||
|
||||
# proceed with handling the request
|
||||
self._create_routes()
|
||||
path_info = environ['PATH_INFO']
|
||||
request_method = environ['REQUEST_METHOD']
|
||||
allowed = []
|
||||
for regex, app, methods in self._routes:
|
||||
m = regex.match(path_info)
|
||||
if m:
|
||||
#print "groupdict:",m.groupdict()
|
||||
if not methods or request_method in methods:
|
||||
environ['paste.urlvars'] = m.groupdict()
|
||||
return app(environ, start_response)
|
||||
else:
|
||||
allowed += methods
|
||||
if allowed:
|
||||
allowed = dict.fromkeys(allows).keys()
|
||||
allowed.sort()
|
||||
resp = exc.HTTPMethodNotAllowed(
|
||||
headers={'Allow': ', '.join(allowed)})
|
||||
else:
|
||||
resp = exc.HTTPNotFound()
|
||||
return resp(environ, start_response)
|
||||
@@ -1,235 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
## $LicenseInfo:firstyear=2011&license=viewerlgpl$
|
||||
## Second Life Viewer Source Code
|
||||
## Copyright (C) 2011, Linden Research, Inc.
|
||||
##
|
||||
## This library is free software; you can redistribute it and/or
|
||||
## modify it under the terms of the GNU Lesser General Public
|
||||
## License as published by the Free Software Foundation;
|
||||
## version 2.1 of the License only.
|
||||
##
|
||||
## This library is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
## Lesser General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU Lesser General Public
|
||||
## License along with this library; if not, write to the Free Software
|
||||
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
##
|
||||
## Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
## $/LicenseInfo$
|
||||
from indra.base import llsd, lluuid
|
||||
from indra.ipc import siesta
|
||||
import datetime, math, unittest
|
||||
from webob import exc
|
||||
|
||||
|
||||
class ClassApp(object):
|
||||
def handle_get(self, req):
|
||||
pass
|
||||
|
||||
def handle_post(self, req):
|
||||
return req.llsd
|
||||
|
||||
|
||||
def callable_app(req):
|
||||
if req.method == 'UNDERPANTS':
|
||||
raise exc.HTTPMethodNotAllowed()
|
||||
elif req.method == 'GET':
|
||||
return None
|
||||
return req.llsd
|
||||
|
||||
|
||||
class TestBase:
|
||||
def test_basic_get(self):
|
||||
req = siesta.Request.blank('/')
|
||||
self.assertEquals(req.get_response(self.server).body,
|
||||
llsd.format_xml(None))
|
||||
|
||||
def test_bad_method(self):
|
||||
req = siesta.Request.blank('/')
|
||||
req.environ['REQUEST_METHOD'] = 'UNDERPANTS'
|
||||
self.assertEquals(req.get_response(self.server).status_int,
|
||||
exc.HTTPMethodNotAllowed.code)
|
||||
|
||||
json_safe = {
|
||||
'none': None,
|
||||
'bool_true': True,
|
||||
'bool_false': False,
|
||||
'int_zero': 0,
|
||||
'int_max': 2147483647,
|
||||
'int_min': -2147483648,
|
||||
'long_zero': 0,
|
||||
'long_max': 2147483647L,
|
||||
'long_min': -2147483648L,
|
||||
'float_zero': 0,
|
||||
'float': math.pi,
|
||||
'float_huge': 3.14159265358979323846e299,
|
||||
'str_empty': '',
|
||||
'str': 'foo',
|
||||
u'unic\u1e51de_empty': u'',
|
||||
u'unic\u1e51de': u'\u1e4exx\u10480',
|
||||
}
|
||||
json_safe['array'] = json_safe.values()
|
||||
json_safe['tuple'] = tuple(json_safe.values())
|
||||
json_safe['dict'] = json_safe.copy()
|
||||
|
||||
json_unsafe = {
|
||||
'uuid_empty': lluuid.UUID(),
|
||||
'uuid_full': lluuid.UUID('dc61ab0530200d7554d23510559102c1a98aab1b'),
|
||||
'binary_empty': llsd.binary(),
|
||||
'binary': llsd.binary('f\0\xff'),
|
||||
'uri_empty': llsd.uri(),
|
||||
'uri': llsd.uri('http://www.secondlife.com/'),
|
||||
'datetime_empty': datetime.datetime(1970,1,1),
|
||||
'datetime': datetime.datetime(1999,9,9,9,9,9),
|
||||
}
|
||||
json_unsafe.update(json_safe)
|
||||
json_unsafe['array'] = json_unsafe.values()
|
||||
json_unsafe['tuple'] = tuple(json_unsafe.values())
|
||||
json_unsafe['dict'] = json_unsafe.copy()
|
||||
json_unsafe['iter'] = iter(json_unsafe.values())
|
||||
|
||||
def _test_client_content_type_good(self, content_type, ll):
|
||||
def run(ll):
|
||||
req = siesta.Request.blank('/')
|
||||
req.environ['REQUEST_METHOD'] = 'POST'
|
||||
req.content_type = content_type
|
||||
req.llsd = ll
|
||||
req.accept = content_type
|
||||
resp = req.get_response(self.server)
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
return req, resp
|
||||
|
||||
if False and isinstance(ll, dict):
|
||||
def fixup(v):
|
||||
if isinstance(v, float):
|
||||
return '%.5f' % v
|
||||
if isinstance(v, long):
|
||||
return int(v)
|
||||
if isinstance(v, (llsd.binary, llsd.uri)):
|
||||
return v
|
||||
if isinstance(v, (tuple, list)):
|
||||
return [fixup(i) for i in v]
|
||||
if isinstance(v, dict):
|
||||
return dict([(k, fixup(i)) for k, i in v.iteritems()])
|
||||
return v
|
||||
for k, v in ll.iteritems():
|
||||
l = [k, v]
|
||||
req, resp = run(l)
|
||||
self.assertEquals(fixup(resp.llsd), fixup(l))
|
||||
|
||||
run(ll)
|
||||
|
||||
def test_client_content_type_json_good(self):
|
||||
self._test_client_content_type_good('application/json', self.json_safe)
|
||||
|
||||
def test_client_content_type_llsd_xml_good(self):
|
||||
self._test_client_content_type_good('application/llsd+xml',
|
||||
self.json_unsafe)
|
||||
|
||||
def test_client_content_type_llsd_notation_good(self):
|
||||
self._test_client_content_type_good('application/llsd+notation',
|
||||
self.json_unsafe)
|
||||
|
||||
def test_client_content_type_llsd_binary_good(self):
|
||||
self._test_client_content_type_good('application/llsd+binary',
|
||||
self.json_unsafe)
|
||||
|
||||
def test_client_content_type_xml_good(self):
|
||||
self._test_client_content_type_good('application/xml',
|
||||
self.json_unsafe)
|
||||
|
||||
def _test_client_content_type_bad(self, content_type):
|
||||
req = siesta.Request.blank('/')
|
||||
req.environ['REQUEST_METHOD'] = 'POST'
|
||||
req.body = '\0invalid nonsense under all encodings'
|
||||
req.content_type = content_type
|
||||
self.assertEquals(req.get_response(self.server).status_int,
|
||||
exc.HTTPBadRequest.code)
|
||||
|
||||
def test_client_content_type_json_bad(self):
|
||||
self._test_client_content_type_bad('application/json')
|
||||
|
||||
def test_client_content_type_llsd_xml_bad(self):
|
||||
self._test_client_content_type_bad('application/llsd+xml')
|
||||
|
||||
def test_client_content_type_llsd_notation_bad(self):
|
||||
self._test_client_content_type_bad('application/llsd+notation')
|
||||
|
||||
def test_client_content_type_llsd_binary_bad(self):
|
||||
self._test_client_content_type_bad('application/llsd+binary')
|
||||
|
||||
def test_client_content_type_xml_bad(self):
|
||||
self._test_client_content_type_bad('application/xml')
|
||||
|
||||
def test_client_content_type_bad(self):
|
||||
req = siesta.Request.blank('/')
|
||||
req.environ['REQUEST_METHOD'] = 'POST'
|
||||
req.body = 'XXX'
|
||||
req.content_type = 'application/nonsense'
|
||||
self.assertEquals(req.get_response(self.server).status_int,
|
||||
exc.HTTPUnsupportedMediaType.code)
|
||||
|
||||
def test_request_default_content_type(self):
|
||||
req = siesta.Request.blank('/')
|
||||
self.assertEquals(req.content_type, req.default_content_type)
|
||||
|
||||
def test_request_default_accept(self):
|
||||
req = siesta.Request.blank('/')
|
||||
from webob import acceptparse
|
||||
self.assertEquals(str(req.accept).replace(' ', ''),
|
||||
req.default_accept.replace(' ', ''))
|
||||
|
||||
def test_request_llsd_auto_body(self):
|
||||
req = siesta.Request.blank('/')
|
||||
req.llsd = {'a': 2}
|
||||
self.assertEquals(req.body, '<?xml version="1.0" ?><llsd><map>'
|
||||
'<key>a</key><integer>2</integer></map></llsd>')
|
||||
|
||||
def test_request_llsd_mod_body_changes_llsd(self):
|
||||
req = siesta.Request.blank('/')
|
||||
req.llsd = {'a': 2}
|
||||
req.body = '<?xml version="1.0" ?><llsd><integer>1337</integer></llsd>'
|
||||
self.assertEquals(req.llsd, 1337)
|
||||
|
||||
def test_request_bad_llsd_fails(self):
|
||||
def crashme(ctype):
|
||||
def boom():
|
||||
class foo(object): pass
|
||||
req = siesta.Request.blank('/')
|
||||
req.content_type = ctype
|
||||
req.llsd = foo()
|
||||
for mime_type in siesta.llsd_parsers:
|
||||
self.assertRaises(TypeError, crashme(mime_type))
|
||||
|
||||
|
||||
class ClassServer(TestBase, unittest.TestCase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
unittest.TestCase.__init__(self, *args, **kwargs)
|
||||
self.server = siesta.llsd_class(ClassApp)
|
||||
|
||||
|
||||
class CallableServer(TestBase, unittest.TestCase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
unittest.TestCase.__init__(self, *args, **kwargs)
|
||||
self.server = siesta.llsd_callable(callable_app)
|
||||
|
||||
|
||||
class RouterServer(unittest.TestCase):
|
||||
def test_router(self):
|
||||
def foo(req, quux):
|
||||
print quux
|
||||
|
||||
r = siesta.Router()
|
||||
r.add('/foo/{quux:int}', siesta.llsd_callable(foo), methods=['GET'])
|
||||
req = siesta.Request.blank('/foo/33')
|
||||
req.get_response(r)
|
||||
|
||||
req = siesta.Request.blank('/foo/bar')
|
||||
self.assertEquals(req.get_response(r).status_int,
|
||||
exc.HTTPNotFound.code)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,597 +0,0 @@
|
||||
"""
|
||||
@file webdav.py
|
||||
@brief Classes to make manipulation of a webdav store easier.
|
||||
|
||||
$LicenseInfo:firstyear=2007&license=mit$
|
||||
|
||||
Copyright (c) 2007-2009, Linden Research, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
$/LicenseInfo$
|
||||
"""
|
||||
|
||||
import sys, os, httplib, urlparse
|
||||
import socket, time
|
||||
import xml.dom.minidom
|
||||
import syslog
|
||||
# import signal
|
||||
|
||||
__revision__ = '0'
|
||||
|
||||
dav_debug = False
|
||||
|
||||
|
||||
# def urlsafe_b64decode (enc):
|
||||
# return base64.decodestring (enc.replace ('_', '/').replace ('-', '+'))
|
||||
|
||||
# def urlsafe_b64encode (str):
|
||||
# return base64.encodestring (str).replace ('+', '-').replace ('/', '_')
|
||||
|
||||
|
||||
class DAVError (Exception):
|
||||
""" Base class for exceptions in this module. """
|
||||
def __init__ (self, status=0, message='', body='', details=''):
|
||||
self.status = status
|
||||
self.message = message
|
||||
self.body = body
|
||||
self.details = details
|
||||
Exception.__init__ (self, '%d:%s:%s%s' % (self.status, self.message,
|
||||
self.body, self.details))
|
||||
|
||||
def print_to_stderr (self):
|
||||
""" print_to_stderr docstring """
|
||||
print >> sys.stderr, str (self.status) + ' ' + self.message
|
||||
print >> sys.stderr, str (self.details)
|
||||
|
||||
|
||||
class Timeout (Exception):
|
||||
""" Timeout docstring """
|
||||
def __init__ (self, arg=''):
|
||||
Exception.__init__ (self, arg)
|
||||
|
||||
|
||||
def alarm_handler (signum, frame):
|
||||
""" alarm_handler docstring """
|
||||
raise Timeout ('caught alarm')
|
||||
|
||||
|
||||
class WebDAV:
|
||||
""" WebDAV docstring """
|
||||
def __init__ (self, url, proxy=None, retries_before_fail=6):
|
||||
self.init_url = url
|
||||
self.init_proxy = proxy
|
||||
self.retries_before_fail = retries_before_fail
|
||||
url_parsed = urlparse.urlsplit (url)
|
||||
|
||||
self.top_path = url_parsed[ 2 ]
|
||||
# make sure top_path has a trailing /
|
||||
if self.top_path == None or self.top_path == '':
|
||||
self.top_path = '/'
|
||||
elif len (self.top_path) > 1 and self.top_path[-1:] != '/':
|
||||
self.top_path += '/'
|
||||
|
||||
if dav_debug:
|
||||
syslog.syslog ('new WebDAV %s : %s' % (str (url), str (proxy)))
|
||||
|
||||
if proxy:
|
||||
proxy_parsed = urlparse.urlsplit (proxy)
|
||||
self.host_header = url_parsed[ 1 ]
|
||||
host_and_port = proxy_parsed[ 1 ].split (':')
|
||||
self.host = host_and_port[ 0 ]
|
||||
if len (host_and_port) > 1:
|
||||
self.port = int(host_and_port[ 1 ])
|
||||
else:
|
||||
self.port = 80
|
||||
else: # no proxy
|
||||
host_and_port = url_parsed[ 1 ].split (':')
|
||||
self.host_header = None
|
||||
self.host = host_and_port[ 0 ]
|
||||
if len (host_and_port) > 1:
|
||||
self.port = int(host_and_port[ 1 ])
|
||||
else:
|
||||
self.port = 80
|
||||
|
||||
self.connection = False
|
||||
self.connect ()
|
||||
|
||||
|
||||
def log (self, msg, depth=0):
|
||||
""" log docstring """
|
||||
if dav_debug and depth == 0:
|
||||
host = str (self.init_url)
|
||||
if host == 'http://int.tuco.lindenlab.com:80/asset/':
|
||||
host = 'tuco'
|
||||
if host == 'http://harriet.lindenlab.com/asset-keep/':
|
||||
host = 'harriet/asset-keep'
|
||||
if host == 'http://harriet.lindenlab.com/asset-flag/':
|
||||
host = 'harriet/asset-flag'
|
||||
if host == 'http://harriet.lindenlab.com/asset/':
|
||||
host = 'harriet/asset'
|
||||
if host == 'http://ozzy.lindenlab.com/asset/':
|
||||
host = 'ozzy/asset'
|
||||
if host == 'http://station11.lindenlab.com:12041/:':
|
||||
host = 'station11:12041'
|
||||
proxy = str (self.init_proxy)
|
||||
if proxy == 'None':
|
||||
proxy = ''
|
||||
if proxy == 'http://int.tuco.lindenlab.com:3128/':
|
||||
proxy = 'tuco'
|
||||
syslog.syslog ('WebDAV (%s:%s) %s' % (host, proxy, str (msg)))
|
||||
|
||||
|
||||
def connect (self):
|
||||
""" connect docstring """
|
||||
self.log ('connect')
|
||||
self.connection = httplib.HTTPConnection (self.host, self.port)
|
||||
|
||||
def __err (self, response, details):
|
||||
""" __err docstring """
|
||||
raise DAVError (response.status, response.reason, response.read (),
|
||||
str (self.init_url) + ':' + \
|
||||
str (self.init_proxy) + ':' + str (details))
|
||||
|
||||
def request (self, method, path, body=None, headers=None,
|
||||
read_all=True, body_hook = None, recurse=0, allow_cache=True):
|
||||
""" request docstring """
|
||||
# self.log ('request %s %s' % (method, path))
|
||||
if headers == None:
|
||||
headers = {}
|
||||
if not allow_cache:
|
||||
headers['Pragma'] = 'no-cache'
|
||||
headers['cache-control'] = 'no-cache'
|
||||
try:
|
||||
if method.lower () != 'purge':
|
||||
if path.startswith ('/'):
|
||||
path = path[1:]
|
||||
if self.host_header: # use proxy
|
||||
headers[ 'host' ] = self.host_header
|
||||
fullpath = 'http://%s%s%s' % (self.host_header,
|
||||
self.top_path, path)
|
||||
else: # no proxy
|
||||
fullpath = self.top_path + path
|
||||
else:
|
||||
fullpath = path
|
||||
|
||||
self.connection.request (method, fullpath, body, headers)
|
||||
if body_hook:
|
||||
body_hook ()
|
||||
|
||||
# signal.signal (signal.SIGALRM, alarm_handler)
|
||||
# try:
|
||||
# signal.alarm (120)
|
||||
# signal.alarm (0)
|
||||
# except Timeout, e:
|
||||
# if recurse < 6:
|
||||
# return self.retry_request (method, path, body, headers,
|
||||
# read_all, body_hook, recurse)
|
||||
# else:
|
||||
# raise DAVError (0, 'timeout', self.host,
|
||||
# (method, path, body, headers, recurse))
|
||||
|
||||
response = self.connection.getresponse ()
|
||||
|
||||
if read_all:
|
||||
while len (response.read (1024)) > 0:
|
||||
pass
|
||||
if (response.status == 500 or \
|
||||
response.status == 503 or \
|
||||
response.status == 403) and \
|
||||
recurse < self.retries_before_fail:
|
||||
return self.retry_request (method, path, body, headers,
|
||||
read_all, body_hook, recurse)
|
||||
return response
|
||||
except (httplib.ResponseNotReady,
|
||||
httplib.BadStatusLine,
|
||||
socket.error):
|
||||
# if the server hangs up on us (keepalive off, broken pipe),
|
||||
# we need to reconnect and try again.
|
||||
if recurse < self.retries_before_fail:
|
||||
return self.retry_request (method, path, body, headers,
|
||||
read_all, body_hook, recurse)
|
||||
raise DAVError (0, 'reconnect failed', self.host,
|
||||
(method, path, body, headers, recurse))
|
||||
|
||||
|
||||
def retry_request (self, method, path, body, headers,
|
||||
read_all, body_hook, recurse):
|
||||
""" retry_request docstring """
|
||||
time.sleep (10.0 * recurse)
|
||||
self.connect ()
|
||||
return self.request (method, path, body, headers,
|
||||
read_all, body_hook, recurse+1)
|
||||
|
||||
|
||||
|
||||
def propfind (self, path, body=None, depth=1):
|
||||
""" propfind docstring """
|
||||
# self.log ('propfind %s' % path)
|
||||
headers = {'Content-Type':'text/xml; charset="utf-8"',
|
||||
'Depth':str(depth)}
|
||||
response = self.request ('PROPFIND', path, body, headers, False)
|
||||
if response.status == 207:
|
||||
return response # Multi-Status
|
||||
self.__err (response, ('PROPFIND', path, body, headers, 0))
|
||||
|
||||
|
||||
def purge (self, path):
|
||||
""" issue a squid purge command """
|
||||
headers = {'Accept':'*/*'}
|
||||
response = self.request ('PURGE', path, None, headers)
|
||||
if response.status == 200 or response.status == 404:
|
||||
# 200 if it was purge, 404 if it wasn't there.
|
||||
return response
|
||||
self.__err (response, ('PURGE', path, None, headers))
|
||||
|
||||
|
||||
def get_file_size (self, path):
|
||||
"""
|
||||
Use propfind to ask a webdav server what the size of
|
||||
a file is. If used on a directory (collection) return 0
|
||||
"""
|
||||
self.log ('get_file_size %s' % path)
|
||||
# "getcontentlength" property
|
||||
# 8.1.1 Example - Retrieving Named Properties
|
||||
# http://docs.python.org/lib/module-xml.dom.html
|
||||
nsurl = 'http://apache.org/dav/props/'
|
||||
doc = xml.dom.minidom.Document ()
|
||||
propfind_element = doc.createElementNS (nsurl, "D:propfind")
|
||||
propfind_element.setAttributeNS (nsurl, 'xmlns:D', 'DAV:')
|
||||
doc.appendChild (propfind_element)
|
||||
prop_element = doc.createElementNS (nsurl, "D:prop")
|
||||
propfind_element.appendChild (prop_element)
|
||||
con_len_element = doc.createElementNS (nsurl, "D:getcontentlength")
|
||||
prop_element.appendChild (con_len_element)
|
||||
|
||||
response = self.propfind (path, doc.toxml ())
|
||||
doc.unlink ()
|
||||
|
||||
resp_doc = xml.dom.minidom.parseString (response.read ())
|
||||
cln = resp_doc.getElementsByTagNameNS ('DAV:','getcontentlength')[ 0 ]
|
||||
try:
|
||||
content_length = int (cln.childNodes[ 0 ].nodeValue)
|
||||
except IndexError:
|
||||
return 0
|
||||
resp_doc.unlink ()
|
||||
return content_length
|
||||
|
||||
|
||||
def file_exists (self, path):
|
||||
"""
|
||||
do an http head on the given file. return True if it succeeds
|
||||
"""
|
||||
self.log ('file_exists %s' % path)
|
||||
expect_gzip = path.endswith ('.gz')
|
||||
response = self.request ('HEAD', path)
|
||||
got_gzip = response.getheader ('Content-Encoding', '').strip ()
|
||||
if got_gzip.lower () == 'x-gzip' and expect_gzip == False:
|
||||
# the asset server fakes us out if we ask for the non-gzipped
|
||||
# version of an asset, but the server has the gzipped version.
|
||||
return False
|
||||
return response.status == 200
|
||||
|
||||
|
||||
def mkdir (self, path):
|
||||
""" mkdir docstring """
|
||||
self.log ('mkdir %s' % path)
|
||||
headers = {}
|
||||
response = self.request ('MKCOL', path, None, headers)
|
||||
if response.status == 201:
|
||||
return # success
|
||||
if response.status == 405:
|
||||
return # directory already existed?
|
||||
self.__err (response, ('MKCOL', path, None, headers, 0))
|
||||
|
||||
|
||||
def delete (self, path):
|
||||
""" delete docstring """
|
||||
self.log ('delete %s' % path)
|
||||
headers = {'Depth':'infinity'} # collections require infinity
|
||||
response = self.request ('DELETE', path, None, headers)
|
||||
if response.status == 204:
|
||||
return # no content
|
||||
if response.status == 404:
|
||||
return # hmm
|
||||
self.__err (response, ('DELETE', path, None, headers, 0))
|
||||
|
||||
|
||||
def list_directory (self, path, dir_filter=None, allow_cache=True,
|
||||
minimum_cache_time=False):
|
||||
"""
|
||||
Request an http directory listing and parse the filenames out of lines
|
||||
like: '<LI><A HREF="X"> X</A>'. If a filter function is provided,
|
||||
only return filenames that the filter returns True for.
|
||||
|
||||
This is sort of grody, but it seems faster than other ways of getting
|
||||
this information from an isilon.
|
||||
"""
|
||||
self.log ('list_directory %s' % path)
|
||||
|
||||
def try_match (lline, before, after):
|
||||
""" try_match docstring """
|
||||
try:
|
||||
blen = len (before)
|
||||
asset_start_index = lline.index (before)
|
||||
asset_end_index = lline.index (after, asset_start_index + blen)
|
||||
asset = line[ asset_start_index + blen : asset_end_index ]
|
||||
|
||||
if not dir_filter or dir_filter (asset):
|
||||
return [ asset ]
|
||||
return []
|
||||
except ValueError:
|
||||
return []
|
||||
|
||||
if len (path) > 0 and path[-1:] != '/':
|
||||
path += '/'
|
||||
|
||||
response = self.request ('GET', path, None, {}, False,
|
||||
allow_cache=allow_cache)
|
||||
|
||||
if allow_cache and minimum_cache_time: # XXX
|
||||
print response.getheader ('Date')
|
||||
# s = "2005-12-06T12:13:14"
|
||||
# from datetime import datetime
|
||||
# from time import strptime
|
||||
# datetime(*strptime(s, "%Y-%m-%dT%H:%M:%S")[0:6])
|
||||
# datetime.datetime(2005, 12, 6, 12, 13, 14)
|
||||
|
||||
if response.status != 200:
|
||||
self.__err (response, ('GET', path, None, {}, 0))
|
||||
assets = []
|
||||
for line in response.read ().split ('\n'):
|
||||
lline = line.lower ()
|
||||
if lline.find ("parent directory") == -1:
|
||||
# isilon file
|
||||
assets += try_match (lline, '<li><a href="', '"> ')
|
||||
# apache dir
|
||||
assets += try_match (lline, 'alt="[dir]"> <a href="', '/">')
|
||||
# apache file
|
||||
assets += try_match (lline, 'alt="[ ]"> <a href="', '">')
|
||||
return assets
|
||||
|
||||
|
||||
def __tmp_filename (self, path_and_file):
|
||||
""" __tmp_filename docstring """
|
||||
head, tail = os.path.split (path_and_file)
|
||||
if head != '':
|
||||
return head + '/.' + tail + '.' + str (os.getpid ())
|
||||
else:
|
||||
return head + '.' + tail + '.' + str (os.getpid ())
|
||||
|
||||
|
||||
def __put__ (self, filesize, body_hook, remotefile):
|
||||
""" __put__ docstring """
|
||||
headers = {'Content-Length' : str (filesize)}
|
||||
remotefile_tmp = self.__tmp_filename (remotefile)
|
||||
response = self.request ('PUT', remotefile_tmp, None,
|
||||
headers, True, body_hook)
|
||||
if not response.status in (201, 204): # created, no content
|
||||
self.__err (response, ('PUT', remotefile, None, headers, 0))
|
||||
if filesize != self.get_file_size (remotefile_tmp):
|
||||
try:
|
||||
self.delete (remotefile_tmp)
|
||||
except:
|
||||
pass
|
||||
raise DAVError (0, 'tmp upload error', remotefile_tmp)
|
||||
# move the file to its final location
|
||||
try:
|
||||
self.rename (remotefile_tmp, remotefile)
|
||||
except DAVError, exc:
|
||||
if exc.status == 403: # try to clean up the tmp file
|
||||
try:
|
||||
self.delete (remotefile_tmp)
|
||||
except:
|
||||
pass
|
||||
raise
|
||||
if filesize != self.get_file_size (remotefile):
|
||||
raise DAVError (0, 'file upload error', str (remotefile_tmp))
|
||||
|
||||
|
||||
def put_string (self, strng, remotefile):
|
||||
""" put_string docstring """
|
||||
self.log ('put_string %d -> %s' % (len (strng), remotefile))
|
||||
filesize = len (strng)
|
||||
def body_hook ():
|
||||
""" body_hook docstring """
|
||||
self.connection.send (strng)
|
||||
self.__put__ (filesize, body_hook, remotefile)
|
||||
|
||||
|
||||
def put_file (self, localfile, remotefile):
|
||||
"""
|
||||
Send a local file to a remote webdav store. First, upload to
|
||||
a temporary filename. Next make sure the file is the size we
|
||||
expected. Next, move the file to its final location. Next,
|
||||
check the file size at the final location.
|
||||
"""
|
||||
self.log ('put_file %s -> %s' % (localfile, remotefile))
|
||||
filesize = os.path.getsize (localfile)
|
||||
def body_hook ():
|
||||
""" body_hook docstring """
|
||||
handle = open (localfile)
|
||||
while True:
|
||||
data = handle.read (1300)
|
||||
if len (data) == 0:
|
||||
break
|
||||
self.connection.send (data)
|
||||
handle.close ()
|
||||
self.__put__ (filesize, body_hook, remotefile)
|
||||
|
||||
|
||||
def create_empty_file (self, remotefile):
|
||||
""" create an empty file """
|
||||
self.log ('touch_file %s' % (remotefile))
|
||||
headers = {'Content-Length' : '0'}
|
||||
response = self.request ('PUT', remotefile, None, headers)
|
||||
if not response.status in (201, 204): # created, no content
|
||||
self.__err (response, ('PUT', remotefile, None, headers, 0))
|
||||
if self.get_file_size (remotefile) != 0:
|
||||
raise DAVError (0, 'file upload error', str (remotefile))
|
||||
|
||||
|
||||
def __get_file_setup (self, remotefile, check_size=True):
|
||||
""" __get_file_setup docstring """
|
||||
if check_size:
|
||||
remotesize = self.get_file_size (remotefile)
|
||||
response = self.request ('GET', remotefile, None, {}, False)
|
||||
if response.status != 200:
|
||||
self.__err (response, ('GET', remotefile, None, {}, 0))
|
||||
try:
|
||||
content_length = int (response.getheader ("Content-Length"))
|
||||
except TypeError:
|
||||
content_length = None
|
||||
if check_size:
|
||||
if content_length != remotesize:
|
||||
raise DAVError (0, 'file DL size error', remotefile)
|
||||
return (response, content_length)
|
||||
|
||||
|
||||
def __get_file_read (self, writehandle, response, content_length):
|
||||
""" __get_file_read docstring """
|
||||
if content_length != None:
|
||||
so_far_length = 0
|
||||
while so_far_length < content_length:
|
||||
data = response.read (content_length - so_far_length)
|
||||
if len (data) == 0:
|
||||
raise DAVError (0, 'short file download')
|
||||
so_far_length += len (data)
|
||||
writehandle.write (data)
|
||||
while len (response.read ()) > 0:
|
||||
pass
|
||||
else:
|
||||
while True:
|
||||
data = response.read ()
|
||||
if (len (data) < 1):
|
||||
break
|
||||
writehandle.write (data)
|
||||
|
||||
|
||||
def get_file (self, remotefile, localfile, check_size=True):
|
||||
"""
|
||||
Get a remote file from a webdav server. Download to a local
|
||||
tmp file, then move into place. Sanity check file sizes as
|
||||
we go.
|
||||
"""
|
||||
self.log ('get_file %s -> %s' % (remotefile, localfile))
|
||||
(response, content_length) = \
|
||||
self.__get_file_setup (remotefile, check_size)
|
||||
localfile_tmp = self.__tmp_filename (localfile)
|
||||
handle = open (localfile_tmp, 'w')
|
||||
self.__get_file_read (handle, response, content_length)
|
||||
handle.close ()
|
||||
if check_size:
|
||||
if content_length != os.path.getsize (localfile_tmp):
|
||||
raise DAVError (0, 'file DL size error',
|
||||
remotefile+','+localfile)
|
||||
os.rename (localfile_tmp, localfile)
|
||||
|
||||
|
||||
def get_file_as_string (self, remotefile, check_size=True):
|
||||
"""
|
||||
download a file from a webdav server and return it as a string.
|
||||
"""
|
||||
self.log ('get_file_as_string %s' % remotefile)
|
||||
(response, content_length) = \
|
||||
self.__get_file_setup (remotefile, check_size)
|
||||
# (tmp_handle, tmp_filename) = tempfile.mkstemp ()
|
||||
tmp_handle = os.tmpfile ()
|
||||
self.__get_file_read (tmp_handle, response, content_length)
|
||||
tmp_handle.seek (0)
|
||||
ret = tmp_handle.read ()
|
||||
tmp_handle.close ()
|
||||
# os.unlink (tmp_filename)
|
||||
return ret
|
||||
|
||||
|
||||
def get_post_as_string (self, remotefile, body):
|
||||
"""
|
||||
Do an http POST, send body, get response and return it.
|
||||
"""
|
||||
self.log ('get_post_as_string %s' % remotefile)
|
||||
# headers = {'Content-Type':'application/x-www-form-urlencoded'}
|
||||
headers = {'Content-Type':'text/xml; charset="utf-8"'}
|
||||
# b64body = urlsafe_b64encode (asset_url)
|
||||
response = self.request ('POST', remotefile, body, headers, False)
|
||||
if response.status != 200:
|
||||
self.__err (response, ('POST', remotefile, body, headers, 0))
|
||||
try:
|
||||
content_length = int (response.getheader ('Content-Length'))
|
||||
except TypeError:
|
||||
content_length = None
|
||||
tmp_handle = os.tmpfile ()
|
||||
self.__get_file_read (tmp_handle, response, content_length)
|
||||
tmp_handle.seek (0)
|
||||
ret = tmp_handle.read ()
|
||||
tmp_handle.close ()
|
||||
return ret
|
||||
|
||||
|
||||
def __destination_command (self, verb, remotesrc, dstdav, remotedst):
|
||||
"""
|
||||
self and dstdav should point to the same http server.
|
||||
"""
|
||||
if len (remotedst) > 0 and remotedst[ 0 ] == '/':
|
||||
remotedst = remotedst[1:]
|
||||
headers = {'Destination': 'http://%s:%d%s%s' % (dstdav.host,
|
||||
dstdav.port,
|
||||
dstdav.top_path,
|
||||
remotedst)}
|
||||
response = self.request (verb, remotesrc, None, headers)
|
||||
if response.status == 201:
|
||||
return # created
|
||||
if response.status == 204:
|
||||
return # no content
|
||||
self.__err (response, (verb, remotesrc, None, headers, 0))
|
||||
|
||||
|
||||
def rename (self, remotesrc, remotedst):
|
||||
""" rename a file on a webdav server """
|
||||
self.log ('rename %s -> %s' % (remotesrc, remotedst))
|
||||
self.__destination_command ('MOVE', remotesrc, self, remotedst)
|
||||
def xrename (self, remotesrc, dstdav, remotedst):
|
||||
""" rename a file on a webdav server """
|
||||
self.log ('xrename %s -> %s' % (remotesrc, remotedst))
|
||||
self.__destination_command ('MOVE', remotesrc, dstdav, remotedst)
|
||||
|
||||
|
||||
def copy (self, remotesrc, remotedst):
|
||||
""" copy a file on a webdav server """
|
||||
self.log ('copy %s -> %s' % (remotesrc, remotedst))
|
||||
self.__destination_command ('COPY', remotesrc, self, remotedst)
|
||||
def xcopy (self, remotesrc, dstdav, remotedst):
|
||||
""" copy a file on a webdav server """
|
||||
self.log ('xcopy %s -> %s' % (remotesrc, remotedst))
|
||||
self.__destination_command ('COPY', remotesrc, dstdav, remotedst)
|
||||
|
||||
|
||||
def put_string (data, url):
|
||||
"""
|
||||
upload string s to a url
|
||||
"""
|
||||
url_parsed = urlparse.urlsplit (url)
|
||||
dav = WebDAV ('%s://%s/' % (url_parsed[ 0 ], url_parsed[ 1 ]))
|
||||
dav.put_string (data, url_parsed[ 2 ])
|
||||
|
||||
|
||||
def get_string (url, check_size=True):
|
||||
"""
|
||||
return the contents of a url as a string
|
||||
"""
|
||||
url_parsed = urlparse.urlsplit (url)
|
||||
dav = WebDAV ('%s://%s/' % (url_parsed[ 0 ], url_parsed[ 1 ]))
|
||||
return dav.get_file_as_string (url_parsed[ 2 ], check_size)
|
||||
@@ -1,273 +0,0 @@
|
||||
"""\
|
||||
@file xml_rpc.py
|
||||
@brief An implementation of a parser/generator for the XML-RPC xml format.
|
||||
|
||||
$LicenseInfo:firstyear=2006&license=mit$
|
||||
|
||||
Copyright (c) 2006-2009, Linden Research, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
$/LicenseInfo$
|
||||
"""
|
||||
|
||||
|
||||
from greenlet import greenlet
|
||||
|
||||
from mulib import mu
|
||||
|
||||
from xml.sax import handler
|
||||
from xml.sax import parseString
|
||||
|
||||
|
||||
# States
|
||||
class Expected(object):
|
||||
def __init__(self, tag):
|
||||
self.tag = tag
|
||||
|
||||
def __getattr__(self, name):
|
||||
return type(self)(name)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r)' % (
|
||||
type(self).__name__, self.tag)
|
||||
|
||||
|
||||
class START(Expected):
|
||||
pass
|
||||
|
||||
|
||||
class END(Expected):
|
||||
pass
|
||||
|
||||
|
||||
class STR(object):
|
||||
tag = ''
|
||||
|
||||
|
||||
START = START('')
|
||||
END = END('')
|
||||
|
||||
|
||||
class Malformed(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class XMLParser(handler.ContentHandler):
|
||||
def __init__(self, state_machine, next_states):
|
||||
handler.ContentHandler.__init__(self)
|
||||
self.state_machine = state_machine
|
||||
if not isinstance(next_states, tuple):
|
||||
next_states = (next_states, )
|
||||
self.next_states = next_states
|
||||
self._character_buffer = ''
|
||||
|
||||
def assertState(self, state, name, *rest):
|
||||
if not isinstance(self.next_states, tuple):
|
||||
self.next_states = (self.next_states, )
|
||||
for next in self.next_states:
|
||||
if type(state) == type(next):
|
||||
if next.tag and next.tag != name:
|
||||
raise Malformed(
|
||||
"Expected %s, got %s %s %s" % (
|
||||
next, state, name, rest))
|
||||
break
|
||||
else:
|
||||
raise Malformed(
|
||||
"Expected %s, got %s %s %s" % (
|
||||
self.next_states, state, name, rest))
|
||||
|
||||
def startElement(self, name, attrs):
|
||||
self.assertState(START, name.lower(), attrs)
|
||||
self.next_states = self.state_machine.switch(START, (name.lower(), dict(attrs)))
|
||||
|
||||
def endElement(self, name):
|
||||
if self._character_buffer.strip():
|
||||
characters = self._character_buffer.strip()
|
||||
self._character_buffer = ''
|
||||
self.assertState(STR, characters)
|
||||
self.next_states = self.state_machine.switch(characters)
|
||||
self.assertState(END, name.lower())
|
||||
self.next_states = self.state_machine.switch(END, name.lower())
|
||||
|
||||
def error(self, exc):
|
||||
self.bozo = 1
|
||||
self.exc = exc
|
||||
|
||||
def fatalError(self, exc):
|
||||
self.error(exc)
|
||||
raise exc
|
||||
|
||||
def characters(self, characters):
|
||||
self._character_buffer += characters
|
||||
|
||||
|
||||
def parse(what):
|
||||
child = greenlet(xml_rpc)
|
||||
me = greenlet.getcurrent()
|
||||
startup_states = child.switch(me)
|
||||
parser = XMLParser(child, startup_states)
|
||||
try:
|
||||
parseString(what, parser)
|
||||
except Malformed:
|
||||
print what
|
||||
raise
|
||||
return child.switch()
|
||||
|
||||
|
||||
def xml_rpc(yielder):
|
||||
yielder.switch(START.methodcall)
|
||||
yielder.switch(START.methodname)
|
||||
methodName = yielder.switch(STR)
|
||||
yielder.switch(END.methodname)
|
||||
|
||||
yielder.switch(START.params)
|
||||
|
||||
root = None
|
||||
params = []
|
||||
while True:
|
||||
state, _ = yielder.switch(START.param, END.params)
|
||||
if state == END:
|
||||
break
|
||||
|
||||
yielder.switch(START.value)
|
||||
|
||||
params.append(
|
||||
handle(yielder))
|
||||
|
||||
yielder.switch(END.value)
|
||||
yielder.switch(END.param)
|
||||
|
||||
yielder.switch(END.methodcall)
|
||||
## Resume parse
|
||||
yielder.switch()
|
||||
## Return result to parse
|
||||
return methodName.strip(), params
|
||||
|
||||
|
||||
def handle(yielder):
|
||||
_, (tag, attrs) = yielder.switch(START)
|
||||
if tag in ['int', 'i4']:
|
||||
result = int(yielder.switch(STR))
|
||||
elif tag == 'boolean':
|
||||
result = bool(int(yielder.switch(STR)))
|
||||
elif tag == 'string':
|
||||
result = yielder.switch(STR)
|
||||
elif tag == 'double':
|
||||
result = float(yielder.switch(STR))
|
||||
elif tag == 'datetime.iso8601':
|
||||
result = yielder.switch(STR)
|
||||
elif tag == 'base64':
|
||||
result = base64.b64decode(yielder.switch(STR))
|
||||
elif tag == 'struct':
|
||||
result = {}
|
||||
while True:
|
||||
state, _ = yielder.switch(START.member, END.struct)
|
||||
if state == END:
|
||||
break
|
||||
|
||||
yielder.switch(START.name)
|
||||
key = yielder.switch(STR)
|
||||
yielder.switch(END.name)
|
||||
|
||||
yielder.switch(START.value)
|
||||
result[key] = handle(yielder)
|
||||
yielder.switch(END.value)
|
||||
|
||||
yielder.switch(END.member)
|
||||
## We already handled </struct> above, don't want to handle it below
|
||||
return result
|
||||
elif tag == 'array':
|
||||
result = []
|
||||
yielder.switch(START.data)
|
||||
while True:
|
||||
state, _ = yielder.switch(START.value, END.data)
|
||||
if state == END:
|
||||
break
|
||||
|
||||
result.append(handle(yielder))
|
||||
|
||||
yielder.switch(END.value)
|
||||
|
||||
yielder.switch(getattr(END, tag))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
VALUE = mu.tag_factory('value')
|
||||
BOOLEAN = mu.tag_factory('boolean')
|
||||
INT = mu.tag_factory('int')
|
||||
STRUCT = mu.tag_factory('struct')
|
||||
MEMBER = mu.tag_factory('member')
|
||||
NAME = mu.tag_factory('name')
|
||||
ARRAY = mu.tag_factory('array')
|
||||
DATA = mu.tag_factory('data')
|
||||
STRING = mu.tag_factory('string')
|
||||
DOUBLE = mu.tag_factory('double')
|
||||
METHODRESPONSE = mu.tag_factory('methodResponse')
|
||||
PARAMS = mu.tag_factory('params')
|
||||
PARAM = mu.tag_factory('param')
|
||||
|
||||
mu.inline_elements['string'] = True
|
||||
mu.inline_elements['boolean'] = True
|
||||
mu.inline_elements['name'] = True
|
||||
|
||||
|
||||
def _generate(something):
|
||||
if isinstance(something, dict):
|
||||
result = STRUCT()
|
||||
for key, value in something.items():
|
||||
result[
|
||||
MEMBER[
|
||||
NAME[key], _generate(value)]]
|
||||
return VALUE[result]
|
||||
elif isinstance(something, list):
|
||||
result = DATA()
|
||||
for item in something:
|
||||
result[_generate(item)]
|
||||
return VALUE[ARRAY[[result]]]
|
||||
elif isinstance(something, basestring):
|
||||
return VALUE[STRING[something]]
|
||||
elif isinstance(something, bool):
|
||||
if something:
|
||||
return VALUE[BOOLEAN['1']]
|
||||
return VALUE[BOOLEAN['0']]
|
||||
elif isinstance(something, int):
|
||||
return VALUE[INT[something]]
|
||||
elif isinstance(something, float):
|
||||
return VALUE[DOUBLE[something]]
|
||||
|
||||
def generate(*args):
|
||||
params = PARAMS()
|
||||
for arg in args:
|
||||
params[PARAM[_generate(arg)]]
|
||||
return METHODRESPONSE[params]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print parse("""<?xml version="1.0"?> <methodCall> <methodName>examples.getStateName</methodName> <params> <param> <value><i4>41</i4></value> </param> </params> </methodCall>
|
||||
""")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user