1# -*- coding: utf-8 -*-
2
3## Amazon S3 manager - Exceptions library
4## Author: Michal Ludvig <michal@logix.cz>
5##         http://www.logix.cz/michal
6## License: GPL Version 2
7## Copyright: TGRMN Software and contributors
8
9from __future__ import absolute_import
10
11from logging import debug, error
12import sys
13import S3.BaseUtils
14import S3.Utils
15from . import ExitCodes
16
17if sys.version_info >= (3, 0):
18    PY3 = True
19    # In python 3, unicode -> str, and str -> bytes
20    unicode = str
21else:
22    PY3 = False
23
24## External exceptions
25
26from ssl import SSLError as S3SSLError
27
28try:
29    from ssl import CertificateError as S3SSLCertificateError
30except ImportError:
31    class S3SSLCertificateError(Exception):
32        pass
33
34
35try:
36    from xml.etree.ElementTree import ParseError as XmlParseError
37except ImportError:
38    # ParseError was only added in python2.7, before ET was raising ExpatError
39    from xml.parsers.expat import ExpatError as XmlParseError
40
41
42## s3cmd exceptions
43
44class S3Exception(Exception):
45    def __init__(self, message=""):
46        self.message = S3.Utils.unicodise(message)
47
48    def __str__(self):
49        ## Don't return self.message directly because
50        ## __unicode__() method could be overridden in subclasses!
51        if PY3:
52            return self.__unicode__()
53        else:
54            return S3.Utils.deunicodise(self.__unicode__())
55
56    def __unicode__(self):
57        return self.message
58
59    ## (Base)Exception.message has been deprecated in Python 2.6
60    def _get_message(self):
61        return self._message
62
63    def _set_message(self, message):
64        self._message = message
65    message = property(_get_message, _set_message)
66
67
68class S3Error (S3Exception):
69    def __init__(self, response):
70        self.status = response["status"]
71        self.reason = response["reason"]
72        self.info = {
73            "Code": "",
74            "Message": "",
75            "Resource": ""
76        }
77        debug("S3Error: %s (%s)" % (self.status, self.reason))
78        if "headers" in response:
79            for header in response["headers"]:
80                debug("HttpHeader: %s: %s" % (header, response["headers"][header]))
81        if "data" in response and response["data"]:
82            try:
83                tree = S3.BaseUtils.getTreeFromXml(response["data"])
84            except XmlParseError:
85                debug("Not an XML response")
86            else:
87                try:
88                    self.info.update(self.parse_error_xml(tree))
89                except Exception as e:
90                    error("Error parsing xml: %s.  ErrorXML: %s" % (e, response["data"]))
91
92        self.code = self.info["Code"]
93        self.message = self.info["Message"]
94        self.resource = self.info["Resource"]
95
96    def __unicode__(self):
97        retval = u"%d " % (self.status)
98        retval += (u"(%s)" % ("Code" in self.info and self.info["Code"] or self.reason))
99        error_msg = self.info.get("Message")
100        if error_msg:
101            retval += (u": %s" % error_msg)
102        return retval
103
104    def get_error_code(self):
105        if self.status in [301, 307]:
106            return ExitCodes.EX_SERVERMOVED
107        elif self.status in [400, 405, 411, 416, 417, 501, 504]:
108            return ExitCodes.EX_SERVERERROR
109        elif self.status == 403:
110            return ExitCodes.EX_ACCESSDENIED
111        elif self.status == 404:
112            return ExitCodes.EX_NOTFOUND
113        elif self.status == 409:
114            return ExitCodes.EX_CONFLICT
115        elif self.status == 412:
116            return ExitCodes.EX_PRECONDITION
117        elif self.status == 500:
118            return ExitCodes.EX_SOFTWARE
119        elif self.status in [429, 503]:
120            return ExitCodes.EX_SERVICE
121        else:
122            return ExitCodes.EX_SOFTWARE
123
124    @staticmethod
125    def parse_error_xml(tree):
126        info = {}
127        error_node = tree
128        if not error_node.tag == "Error":
129            error_node = tree.find(".//Error")
130        if error_node is not None:
131            for child in error_node:
132                if child.text != "":
133                    debug("ErrorXML: " + child.tag + ": " + repr(child.text))
134                    info[child.tag] = child.text
135        else:
136            raise S3ResponseError("Malformed error XML returned from remote server.")
137        return info
138
139
140class CloudFrontError(S3Error):
141    pass
142
143class S3UploadError(S3Exception):
144    pass
145
146class S3DownloadError(S3Exception):
147    pass
148
149class S3RequestError(S3Exception):
150    pass
151
152class S3ResponseError(S3Exception):
153    pass
154
155class InvalidFileError(S3Exception):
156    pass
157
158class ParameterError(S3Exception):
159    pass
160
161# vim:et:ts=4:sts=4:ai
162