diff env/lib/python3.9/site-packages/boto/cloudfront/distribution.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/env/lib/python3.9/site-packages/boto/cloudfront/distribution.py	Mon Mar 22 18:12:50 2021 +0000
@@ -0,0 +1,757 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# 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, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing 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 MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR 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.
+
+import uuid
+import base64
+import time
+from boto.compat import six, json
+from boto.cloudfront.identity import OriginAccessIdentity
+from boto.cloudfront.object import Object, StreamingObject
+from boto.cloudfront.signers import ActiveTrustedSigners, TrustedSigners
+from boto.cloudfront.logging import LoggingInfo
+from boto.cloudfront.origin import S3Origin, CustomOrigin
+from boto.s3.acl import ACL
+
+class DistributionConfig(object):
+
+    def __init__(self, connection=None, origin=None, enabled=False,
+                 caller_reference='', cnames=None, comment='',
+                 trusted_signers=None, default_root_object=None,
+                 logging=None):
+        """
+        :param origin: Origin information to associate with the
+                       distribution.  If your distribution will use
+                       an Amazon S3 origin, then this should be an
+                       S3Origin object. If your distribution will use
+                       a custom origin (non Amazon S3), then this
+                       should be a CustomOrigin object.
+        :type origin: :class:`boto.cloudfront.origin.S3Origin` or
+                      :class:`boto.cloudfront.origin.CustomOrigin`
+
+        :param enabled: Whether the distribution is enabled to accept
+                        end user requests for content.
+        :type enabled: bool
+
+        :param caller_reference: A unique number that ensures the
+                                 request can't be replayed.  If no
+                                 caller_reference is provided, boto
+                                 will generate a type 4 UUID for use
+                                 as the caller reference.
+        :type enabled: str
+
+        :param cnames: A CNAME alias you want to associate with this
+                       distribution. You can have up to 10 CNAME aliases
+                       per distribution.
+        :type enabled: array of str
+
+        :param comment: Any comments you want to include about the
+                        distribution.
+        :type comment: str
+
+        :param trusted_signers: Specifies any AWS accounts you want to
+                                permit to create signed URLs for private
+                                content. If you want the distribution to
+                                use signed URLs, this should contain a
+                                TrustedSigners object; if you want the
+                                distribution to use basic URLs, leave
+                                this None.
+        :type trusted_signers: :class`boto.cloudfront.signers.TrustedSigners`
+
+        :param default_root_object: Designates a default root object.
+                                    Only include a DefaultRootObject value
+                                    if you are going to assign a default
+                                    root object for the distribution.
+        :type comment: str
+
+        :param logging: Controls whether access logs are written for the
+                        distribution. If you want to turn on access logs,
+                        this should contain a LoggingInfo object; otherwise
+                        it should contain None.
+        :type logging: :class`boto.cloudfront.logging.LoggingInfo`
+
+        """
+        self.connection = connection
+        self.origin = origin
+        self.enabled = enabled
+        if caller_reference:
+            self.caller_reference = caller_reference
+        else:
+            self.caller_reference = str(uuid.uuid4())
+        self.cnames = []
+        if cnames:
+            self.cnames = cnames
+        self.comment = comment
+        self.trusted_signers = trusted_signers
+        self.logging = logging
+        self.default_root_object = default_root_object
+
+    def __repr__(self):
+        return "DistributionConfig:%s" % self.origin
+
+    def to_xml(self):
+        s = '<?xml version="1.0" encoding="UTF-8"?>\n'
+        s += '<DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2010-07-15/">\n'
+        if self.origin:
+            s += self.origin.to_xml()
+        s += '  <CallerReference>%s</CallerReference>\n' % self.caller_reference
+        for cname in self.cnames:
+            s += '  <CNAME>%s</CNAME>\n' % cname
+        if self.comment:
+            s += '  <Comment>%s</Comment>\n' % self.comment
+        s += '  <Enabled>'
+        if self.enabled:
+            s += 'true'
+        else:
+            s += 'false'
+        s += '</Enabled>\n'
+        if self.trusted_signers:
+            s += '<TrustedSigners>\n'
+            for signer in self.trusted_signers:
+                if signer == 'Self':
+                    s += '  <Self></Self>\n'
+                else:
+                    s += '  <AwsAccountNumber>%s</AwsAccountNumber>\n' % signer
+            s += '</TrustedSigners>\n'
+        if self.logging:
+            s += '<Logging>\n'
+            s += '  <Bucket>%s</Bucket>\n' % self.logging.bucket
+            s += '  <Prefix>%s</Prefix>\n' % self.logging.prefix
+            s += '</Logging>\n'
+        if self.default_root_object:
+            dro = self.default_root_object
+            s += '<DefaultRootObject>%s</DefaultRootObject>\n' % dro
+        s += '</DistributionConfig>\n'
+        return s
+
+    def startElement(self, name, attrs, connection):
+        if name == 'TrustedSigners':
+            self.trusted_signers = TrustedSigners()
+            return self.trusted_signers
+        elif name == 'Logging':
+            self.logging = LoggingInfo()
+            return self.logging
+        elif name == 'S3Origin':
+            self.origin = S3Origin()
+            return self.origin
+        elif name == 'CustomOrigin':
+            self.origin = CustomOrigin()
+            return self.origin
+        else:
+            return None
+
+    def endElement(self, name, value, connection):
+        if name == 'CNAME':
+            self.cnames.append(value)
+        elif name == 'Comment':
+            self.comment = value
+        elif name == 'Enabled':
+            if value.lower() == 'true':
+                self.enabled = True
+            else:
+                self.enabled = False
+        elif name == 'CallerReference':
+            self.caller_reference = value
+        elif name == 'DefaultRootObject':
+            self.default_root_object = value
+        else:
+            setattr(self, name, value)
+
+class StreamingDistributionConfig(DistributionConfig):
+
+    def __init__(self, connection=None, origin='', enabled=False,
+                 caller_reference='', cnames=None, comment='',
+                 trusted_signers=None, logging=None):
+        super(StreamingDistributionConfig, self).__init__(connection=connection,
+                                    origin=origin, enabled=enabled,
+                                    caller_reference=caller_reference,
+                                    cnames=cnames, comment=comment,
+                                    trusted_signers=trusted_signers,
+                                    logging=logging)
+    def to_xml(self):
+        s = '<?xml version="1.0" encoding="UTF-8"?>\n'
+        s += '<StreamingDistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2010-07-15/">\n'
+        if self.origin:
+            s += self.origin.to_xml()
+        s += '  <CallerReference>%s</CallerReference>\n' % self.caller_reference
+        for cname in self.cnames:
+            s += '  <CNAME>%s</CNAME>\n' % cname
+        if self.comment:
+            s += '  <Comment>%s</Comment>\n' % self.comment
+        s += '  <Enabled>'
+        if self.enabled:
+            s += 'true'
+        else:
+            s += 'false'
+        s += '</Enabled>\n'
+        if self.trusted_signers:
+            s += '<TrustedSigners>\n'
+            for signer in self.trusted_signers:
+                if signer == 'Self':
+                    s += '  <Self/>\n'
+                else:
+                    s += '  <AwsAccountNumber>%s</AwsAccountNumber>\n' % signer
+            s += '</TrustedSigners>\n'
+        if self.logging:
+            s += '<Logging>\n'
+            s += '  <Bucket>%s</Bucket>\n' % self.logging.bucket
+            s += '  <Prefix>%s</Prefix>\n' % self.logging.prefix
+            s += '</Logging>\n'
+        s += '</StreamingDistributionConfig>\n'
+        return s
+
+class DistributionSummary(object):
+
+    def __init__(self, connection=None, domain_name='', id='',
+                 last_modified_time=None, status='', origin=None,
+                 cname='', comment='', enabled=False):
+        self.connection = connection
+        self.domain_name = domain_name
+        self.id = id
+        self.last_modified_time = last_modified_time
+        self.status = status
+        self.origin = origin
+        self.enabled = enabled
+        self.cnames = []
+        if cname:
+            self.cnames.append(cname)
+        self.comment = comment
+        self.trusted_signers = None
+        self.etag = None
+        self.streaming = False
+
+    def __repr__(self):
+        return "DistributionSummary:%s" % self.domain_name
+
+    def startElement(self, name, attrs, connection):
+        if name == 'TrustedSigners':
+            self.trusted_signers = TrustedSigners()
+            return self.trusted_signers
+        elif name == 'S3Origin':
+            self.origin = S3Origin()
+            return self.origin
+        elif name == 'CustomOrigin':
+            self.origin = CustomOrigin()
+            return self.origin
+        return None
+
+    def endElement(self, name, value, connection):
+        if name == 'Id':
+            self.id = value
+        elif name == 'Status':
+            self.status = value
+        elif name == 'LastModifiedTime':
+            self.last_modified_time = value
+        elif name == 'DomainName':
+            self.domain_name = value
+        elif name == 'Origin':
+            self.origin = value
+        elif name == 'CNAME':
+            self.cnames.append(value)
+        elif name == 'Comment':
+            self.comment = value
+        elif name == 'Enabled':
+            if value.lower() == 'true':
+                self.enabled = True
+            else:
+                self.enabled = False
+        elif name == 'StreamingDistributionSummary':
+            self.streaming = True
+        else:
+            setattr(self, name, value)
+
+    def get_distribution(self):
+        return self.connection.get_distribution_info(self.id)
+
+class StreamingDistributionSummary(DistributionSummary):
+
+    def get_distribution(self):
+        return self.connection.get_streaming_distribution_info(self.id)
+
+class Distribution(object):
+
+    def __init__(self, connection=None, config=None, domain_name='',
+                 id='', last_modified_time=None, status=''):
+        self.connection = connection
+        self.config = config
+        self.domain_name = domain_name
+        self.id = id
+        self.last_modified_time = last_modified_time
+        self.status = status
+        self.in_progress_invalidation_batches = 0
+        self.active_signers = None
+        self.etag = None
+        self._bucket = None
+        self._object_class = Object
+
+    def __repr__(self):
+        return "Distribution:%s" % self.domain_name
+
+    def startElement(self, name, attrs, connection):
+        if name == 'DistributionConfig':
+            self.config = DistributionConfig()
+            return self.config
+        elif name == 'ActiveTrustedSigners':
+            self.active_signers = ActiveTrustedSigners()
+            return self.active_signers
+        else:
+            return None
+
+    def endElement(self, name, value, connection):
+        if name == 'Id':
+            self.id = value
+        elif name == 'LastModifiedTime':
+            self.last_modified_time = value
+        elif name == 'Status':
+            self.status = value
+        elif name == 'InProgressInvalidationBatches':
+            self.in_progress_invalidation_batches = int(value)
+        elif name == 'DomainName':
+            self.domain_name = value
+        else:
+            setattr(self, name, value)
+
+    def update(self, enabled=None, cnames=None, comment=None):
+        """
+        Update the configuration of the Distribution.  The only values
+        of the DistributionConfig that can be directly updated are:
+
+         * CNAMES
+         * Comment
+         * Whether the Distribution is enabled or not
+
+        Any changes to the ``trusted_signers`` or ``origin`` properties of
+        this distribution's current config object will also be included in
+        the update. Therefore, to set the origin access identity for this
+        distribution, set ``Distribution.config.origin.origin_access_identity``
+        before calling this update method.
+
+        :type enabled: bool
+        :param enabled: Whether the Distribution is active or not.
+
+        :type cnames: list of str
+        :param cnames: The DNS CNAME's associated with this
+                        Distribution.  Maximum of 10 values.
+
+        :type comment: str or unicode
+        :param comment: The comment associated with the Distribution.
+
+        """
+        new_config = DistributionConfig(self.connection, self.config.origin,
+                                        self.config.enabled, self.config.caller_reference,
+                                        self.config.cnames, self.config.comment,
+                                        self.config.trusted_signers,
+                                        self.config.default_root_object)
+        if enabled is not None:
+            new_config.enabled = enabled
+        if cnames is not None:
+            new_config.cnames = cnames
+        if comment is not None:
+            new_config.comment = comment
+        self.etag = self.connection.set_distribution_config(self.id, self.etag, new_config)
+        self.config = new_config
+        self._object_class = Object
+
+    def enable(self):
+        """
+        Activate the Distribution.  A convenience wrapper around
+        the update method.
+        """
+        self.update(enabled=True)
+
+    def disable(self):
+        """
+        Deactivate the Distribution.  A convenience wrapper around
+        the update method.
+        """
+        self.update(enabled=False)
+
+    def delete(self):
+        """
+        Delete this CloudFront Distribution.  The content
+        associated with the Distribution is not deleted from
+        the underlying Origin bucket in S3.
+        """
+        self.connection.delete_distribution(self.id, self.etag)
+
+    def _get_bucket(self):
+        if isinstance(self.config.origin, S3Origin):
+            if not self._bucket:
+                bucket_dns_name = self.config.origin.dns_name
+                bucket_name = bucket_dns_name.replace('.s3.amazonaws.com', '')
+                from boto.s3.connection import S3Connection
+                s3 = S3Connection(self.connection.aws_access_key_id,
+                                  self.connection.aws_secret_access_key,
+                                  proxy=self.connection.proxy,
+                                  proxy_port=self.connection.proxy_port,
+                                  proxy_user=self.connection.proxy_user,
+                                  proxy_pass=self.connection.proxy_pass)
+                self._bucket = s3.get_bucket(bucket_name)
+                self._bucket.distribution = self
+                self._bucket.set_key_class(self._object_class)
+            return self._bucket
+        else:
+            raise NotImplementedError('Unable to get_objects on CustomOrigin')
+
+    def get_objects(self):
+        """
+        Return a list of all content objects in this distribution.
+
+        :rtype: list of :class:`boto.cloudfront.object.Object`
+        :return: The content objects
+        """
+        bucket = self._get_bucket()
+        objs = []
+        for key in bucket:
+            objs.append(key)
+        return objs
+
+    def set_permissions(self, object, replace=False):
+        """
+        Sets the S3 ACL grants for the given object to the appropriate
+        value based on the type of Distribution.  If the Distribution
+        is serving private content the ACL will be set to include the
+        Origin Access Identity associated with the Distribution.  If
+        the Distribution is serving public content the content will
+        be set up with "public-read".
+
+        :type object: :class:`boto.cloudfront.object.Object`
+        :param enabled: The Object whose ACL is being set
+
+        :type replace: bool
+        :param replace: If False, the Origin Access Identity will be
+                        appended to the existing ACL for the object.
+                        If True, the ACL for the object will be
+                        completely replaced with one that grants
+                        READ permission to the Origin Access Identity.
+
+        """
+        if isinstance(self.config.origin, S3Origin):
+            if self.config.origin.origin_access_identity:
+                id = self.config.origin.origin_access_identity.split('/')[-1]
+                oai = self.connection.get_origin_access_identity_info(id)
+                policy = object.get_acl()
+                if replace:
+                    policy.acl = ACL()
+                policy.acl.add_user_grant('READ', oai.s3_user_id)
+                object.set_acl(policy)
+            else:
+                object.set_canned_acl('public-read')
+
+    def set_permissions_all(self, replace=False):
+        """
+        Sets the S3 ACL grants for all objects in the Distribution
+        to the appropriate value based on the type of Distribution.
+
+        :type replace: bool
+        :param replace: If False, the Origin Access Identity will be
+                        appended to the existing ACL for the object.
+                        If True, the ACL for the object will be
+                        completely replaced with one that grants
+                        READ permission to the Origin Access Identity.
+
+        """
+        bucket = self._get_bucket()
+        for key in bucket:
+            self.set_permissions(key, replace)
+
+    def add_object(self, name, content, headers=None, replace=True):
+        """
+        Adds a new content object to the Distribution.  The content
+        for the object will be copied to a new Key in the S3 Bucket
+        and the permissions will be set appropriately for the type
+        of Distribution.
+
+        :type name: str or unicode
+        :param name: The name or key of the new object.
+
+        :type content: file-like object
+        :param content: A file-like object that contains the content
+                        for the new object.
+
+        :type headers: dict
+        :param headers: A dictionary containing additional headers
+                        you would like associated with the new
+                        object in S3.
+
+        :rtype: :class:`boto.cloudfront.object.Object`
+        :return: The newly created object.
+        """
+        if self.config.origin.origin_access_identity:
+            policy = 'private'
+        else:
+            policy = 'public-read'
+        bucket = self._get_bucket()
+        object = bucket.new_key(name)
+        object.set_contents_from_file(content, headers=headers, policy=policy)
+        if self.config.origin.origin_access_identity:
+            self.set_permissions(object, replace)
+        return object
+
+    def create_signed_url(self, url, keypair_id,
+                          expire_time=None, valid_after_time=None,
+                          ip_address=None, policy_url=None,
+                          private_key_file=None, private_key_string=None):
+        """
+        Creates a signed CloudFront URL that is only valid within the specified
+        parameters.
+
+        :type url: str
+        :param url: The URL of the protected object.
+
+        :type keypair_id: str
+        :param keypair_id: The keypair ID of the Amazon KeyPair used to sign
+            theURL.  This ID MUST correspond to the private key
+            specified with private_key_file or private_key_string.
+
+        :type expire_time: int
+        :param expire_time: The expiry time of the URL. If provided, the URL
+            will expire after the time has passed. If not provided the URL will
+            never expire. Format is a unix epoch.
+            Use int(time.time() + duration_in_sec).
+
+        :type valid_after_time: int
+        :param valid_after_time: If provided, the URL will not be valid until
+            after valid_after_time. Format is a unix epoch.
+            Use int(time.time() + secs_until_valid).
+
+        :type ip_address: str
+        :param ip_address: If provided, only allows access from the specified
+            IP address.  Use '192.168.0.10' for a single IP or
+            use '192.168.0.0/24' CIDR notation for a subnet.
+
+        :type policy_url: str
+        :param policy_url: If provided, allows the signature to contain
+            wildcard globs in the URL.  For example, you could
+            provide: 'http://example.com/media/\*' and the policy
+            and signature would allow access to all contents of
+            the media subdirectory. If not specified, only
+            allow access to the exact url provided in 'url'.
+
+        :type private_key_file: str or file object.
+        :param private_key_file: If provided, contains the filename of the
+            private key file used for signing or an open
+            file object containing the private key
+            contents.  Only one of private_key_file or
+            private_key_string can be provided.
+
+        :type private_key_string: str
+        :param private_key_string: If provided, contains the private key string
+            used for signing. Only one of private_key_file or
+            private_key_string can be provided.
+
+        :rtype: str
+        :return: The signed URL.
+        """
+        # Get the required parameters
+        params = self._create_signing_params(
+                     url=url, keypair_id=keypair_id, expire_time=expire_time,
+                     valid_after_time=valid_after_time, ip_address=ip_address,
+                     policy_url=policy_url, private_key_file=private_key_file,
+                     private_key_string=private_key_string)
+
+        #combine these into a full url
+        if "?" in url:
+            sep = "&"
+        else:
+            sep = "?"
+        signed_url_params = []
+        for key in ["Expires", "Policy", "Signature", "Key-Pair-Id"]:
+            if key in params:
+                param = "%s=%s" % (key, params[key])
+                signed_url_params.append(param)
+        signed_url = url + sep + "&".join(signed_url_params)
+        return signed_url
+
+    def _create_signing_params(self, url, keypair_id,
+                          expire_time=None, valid_after_time=None,
+                          ip_address=None, policy_url=None,
+                          private_key_file=None, private_key_string=None):
+        """
+        Creates the required URL parameters for a signed URL.
+        """
+        params = {}
+        # Check if we can use a canned policy
+        if expire_time and not valid_after_time and not ip_address and not policy_url:
+            # we manually construct this policy string to ensure formatting
+            # matches signature
+            policy = self._canned_policy(url, expire_time)
+            params["Expires"] = str(expire_time)
+        else:
+            # If no policy_url is specified, default to the full url.
+            if policy_url is None:
+                policy_url = url
+            # Can't use canned policy
+            policy = self._custom_policy(policy_url, expires=expire_time,
+                                         valid_after=valid_after_time,
+                                         ip_address=ip_address)
+
+            encoded_policy = self._url_base64_encode(policy)
+            params["Policy"] = encoded_policy
+        #sign the policy
+        signature = self._sign_string(policy, private_key_file, private_key_string)
+        #now base64 encode the signature (URL safe as well)
+        encoded_signature = self._url_base64_encode(signature)
+        params["Signature"] = encoded_signature
+        params["Key-Pair-Id"] = keypair_id
+        return params
+
+    @staticmethod
+    def _canned_policy(resource, expires):
+        """
+        Creates a canned policy string.
+        """
+        policy = ('{"Statement":[{"Resource":"%(resource)s",'
+                  '"Condition":{"DateLessThan":{"AWS:EpochTime":'
+                  '%(expires)s}}}]}' % locals())
+        return policy
+
+    @staticmethod
+    def _custom_policy(resource, expires=None, valid_after=None, ip_address=None):
+        """
+        Creates a custom policy string based on the supplied parameters.
+        """
+        condition = {}
+        # SEE: http://docs.amazonwebservices.com/AmazonCloudFront/latest/DeveloperGuide/RestrictingAccessPrivateContent.html#CustomPolicy
+        # The 'DateLessThan' property is required.
+        if not expires:
+            # Defaults to ONE day
+            expires = int(time.time()) + 86400
+        condition["DateLessThan"] = {"AWS:EpochTime": expires}
+        if valid_after:
+            condition["DateGreaterThan"] = {"AWS:EpochTime": valid_after}
+        if ip_address:
+            if '/' not in ip_address:
+                ip_address += "/32"
+            condition["IpAddress"] = {"AWS:SourceIp": ip_address}
+        policy = {"Statement": [{
+                     "Resource": resource,
+                     "Condition": condition}]}
+        return json.dumps(policy, separators=(",", ":"))
+
+    @staticmethod
+    def _sign_string(message, private_key_file=None, private_key_string=None):
+        """
+        Signs a string for use with Amazon CloudFront.
+        Requires the rsa library be installed.
+        """
+        try:
+            import rsa
+        except ImportError:
+            raise NotImplementedError("Boto depends on the python rsa "
+                                      "library to generate signed URLs for "
+                                      "CloudFront")
+        # Make sure only one of private_key_file and private_key_string is set
+        if private_key_file and private_key_string:
+            raise ValueError("Only specify the private_key_file or the private_key_string not both")
+        if not private_key_file and not private_key_string:
+            raise ValueError("You must specify one of private_key_file or private_key_string")
+        # If private_key_file is a file name, open it and read it
+        if private_key_string is None:
+            if isinstance(private_key_file, six.string_types):
+                with open(private_key_file, 'r') as file_handle:
+                    private_key_string = file_handle.read()
+            # Otherwise, treat it like a file
+            else:
+                private_key_string = private_key_file.read()
+
+        # Sign it!
+        private_key = rsa.PrivateKey.load_pkcs1(private_key_string)
+        signature = rsa.sign(str(message), private_key, 'SHA-1')
+        return signature
+
+    @staticmethod
+    def _url_base64_encode(msg):
+        """
+        Base64 encodes a string using the URL-safe characters specified by
+        Amazon.
+        """
+        msg_base64 = base64.b64encode(msg)
+        msg_base64 = msg_base64.replace('+', '-')
+        msg_base64 = msg_base64.replace('=', '_')
+        msg_base64 = msg_base64.replace('/', '~')
+        return msg_base64
+
+class StreamingDistribution(Distribution):
+
+    def __init__(self, connection=None, config=None, domain_name='',
+                 id='', last_modified_time=None, status=''):
+        super(StreamingDistribution, self).__init__(connection, config,
+                              domain_name, id, last_modified_time, status)
+        self._object_class = StreamingObject
+
+    def startElement(self, name, attrs, connection):
+        if name == 'StreamingDistributionConfig':
+            self.config = StreamingDistributionConfig()
+            return self.config
+        else:
+            return super(StreamingDistribution, self).startElement(name, attrs,
+                connection)
+
+    def update(self, enabled=None, cnames=None, comment=None):
+        """
+        Update the configuration of the StreamingDistribution.  The only values
+        of the StreamingDistributionConfig that can be directly updated are:
+
+         * CNAMES
+         * Comment
+         * Whether the Distribution is enabled or not
+
+        Any changes to the ``trusted_signers`` or ``origin`` properties of
+        this distribution's current config object will also be included in
+        the update. Therefore, to set the origin access identity for this
+        distribution, set
+        ``StreamingDistribution.config.origin.origin_access_identity``
+        before calling this update method.
+
+        :type enabled: bool
+        :param enabled: Whether the StreamingDistribution is active or not.
+
+        :type cnames: list of str
+        :param cnames: The DNS CNAME's associated with this
+                        Distribution.  Maximum of 10 values.
+
+        :type comment: str or unicode
+        :param comment: The comment associated with the Distribution.
+
+        """
+        new_config = StreamingDistributionConfig(self.connection,
+                                                 self.config.origin,
+                                                 self.config.enabled,
+                                                 self.config.caller_reference,
+                                                 self.config.cnames,
+                                                 self.config.comment,
+                                                 self.config.trusted_signers)
+        if enabled is not None:
+            new_config.enabled = enabled
+        if cnames is not None:
+            new_config.cnames = cnames
+        if comment is not None:
+            new_config.comment = comment
+        self.etag = self.connection.set_streaming_distribution_config(self.id,
+                                                                      self.etag,
+                                                                      new_config)
+        self.config = new_config
+        self._object_class = StreamingObject
+
+    def delete(self):
+        self.connection.delete_streaming_distribution(self.id, self.etag)
+
+