# Copyright (c) 2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
from six.moves.urllib.parse import quote, unquote
import tarfile
from xml.sax import saxutils
from time import time
from eventlet import sleep
import zlib
from swift.common.swob import Request, HTTPBadGateway, \
    HTTPCreated, HTTPBadRequest, HTTPNotFound, HTTPUnauthorized, HTTPOk, \
    HTTPPreconditionFailed, HTTPRequestEntityTooLarge, HTTPNotAcceptable, \
    HTTPLengthRequired, HTTPException, HTTPServerError, wsgify
from swift.common.utils import get_logger, register_swift_info
from swift.common import constraints
from swift.common.http import HTTP_UNAUTHORIZED, HTTP_NOT_FOUND, HTTP_CONFLICT


class CreateContainerError(Exception):
    def __init__(self, msg, status_int, status):
        self.status_int = status_int
        self.status = status
        super(CreateContainerError, self).__init__(msg)


ACCEPTABLE_FORMATS = ['text/plain', 'application/json', 'application/xml',
                      'text/xml']


def get_response_body(data_format, data_dict, error_list):
    """
    Returns a properly formatted response body according to format. Handles
    json and xml, otherwise will return text/plain. Note: xml response does not
    include xml declaration.
    :params data_format: resulting format
    :params data_dict: generated data about results.
    :params error_list: list of quoted filenames that failed
    """
    if data_format == 'application/json':
        data_dict['Errors'] = error_list
        return json.dumps(data_dict)
    if data_format and data_format.endswith('/xml'):
        output = '<delete>\n'
        for key in sorted(data_dict):
            xml_key = key.replace(' ', '_').lower()
            output += '<%s>%s</%s>\n' % (xml_key, data_dict[key], xml_key)
        output += '<errors>\n'
        output += '\n'.join(
            ['<object>'
             '<name>%s</name><status>%s</status>'
             '</object>' % (saxutils.escape(name), status) for
             name, status in error_list])
        output += '</errors>\n</delete>\n'
        return output

    output = ''
    for key in sorted(data_dict):
        output += '%s: %s\n' % (key, data_dict[key])
    output += 'Errors:\n'
    output += '\n'.join(
        ['%s, %s' % (name, status)
         for name, status in error_list])
    return output


def pax_key_to_swift_header(pax_key):
    if (pax_key == u"SCHILY.xattr.user.mime_type" or
            pax_key == u"LIBARCHIVE.xattr.user.mime_type"):
        return "Content-Type"
    elif pax_key.startswith(u"SCHILY.xattr.user.meta."):
        useful_part = pax_key[len(u"SCHILY.xattr.user.meta."):]
        return "X-Object-Meta-" + useful_part.encode("utf-8")
    elif pax_key.startswith(u"LIBARCHIVE.xattr.user.meta."):
        useful_part = pax_key[len(u"LIBARCHIVE.xattr.user.meta."):]
        return "X-Object-Meta-" + useful_part.encode("utf-8")
    else:
        # You can get things like atime/mtime/ctime or filesystem ACLs in
        # pax headers; those aren't really user metadata. The same goes for
        # other, non-user metadata.
        return None


class Bulk(object):
    """
    Middleware that will do many operations on a single request.

    Extract Archive:

    Expand tar files into a swift account. Request must be a PUT with the
    query parameter ?extract-archive=format specifying the format of archive
    file. Accepted formats are tar, tar.gz, and tar.bz2.

    For a PUT to the following url:

    /v1/AUTH_Account/$UPLOAD_PATH?extract-archive=tar.gz

    UPLOAD_PATH is where the files will be expanded to. UPLOAD_PATH can be a
    container, a pseudo-directory within a container, or an empty string. The
    destination of a file in the archive will be built as follows:

    /v1/AUTH_Account/$UPLOAD_PATH/$FILE_PATH

    Where FILE_PATH is the file name from the listing in the tar file.

    If the UPLOAD_PATH is an empty string, containers will be auto created
    accordingly and files in the tar that would not map to any container (files
    in the base directory) will be ignored.

    Only regular files will be uploaded. Empty directories, symlinks, etc will
    not be uploaded.

    Content Type:

    If the content-type header is set in the extract-archive call, Swift will
    assign that content-type to all the underlying files. The bulk middleware
    will extract the archive file and send the internal files using PUT
    operations using the same headers from the original request
    (e.g. auth-tokens, content-Type, etc.). Notice that any middleware call
    that follows the bulk middleware does not know if this was a bulk request
    or if these were individual requests sent by the user.

    In order to make Swift detect the content-type for the files based on the
    file extension, the content-type in the extract-archive call should not be
    set. Alternatively, it is possible to explicitly tell swift to detect the
    content type using this header:

    X-Detect-Content-Type:true

    For example:

    curl -X PUT http://127.0.0.1/v1/AUTH_acc/cont/$?extract-archive=tar -T
    backup.tar -H "Content-Type: application/x-tar" -H "X-Auth-Token: xxx"
    -H "X-Detect-Content-Type:true"

    Assigning Metadata:

    The tar file format (1) allows for UTF-8 key/value pairs to be associated
    with each file in an archive. If a file has extended attributes, then tar
    will store those as key/value pairs. The bulk middleware can read those
    extended attributes and convert them to Swift object metadata. Attributes
    starting with "user.meta" are converted to object metadata, and
    "user.mime_type" is converted to Content-Type.

    For example:

    setfattr -n user.mime_type -v "application/python-setup" setup.py
    setfattr -n user.meta.lunch -v "burger and fries" setup.py
    setfattr -n user.meta.dinner -v "baked ziti" setup.py
    setfattr -n user.stuff -v "whee" setup.py

    Will get translated to headers:

    Content-Type: application/python-setup
    X-Object-Meta-Lunch: burger and fries
    X-Object-Meta-Dinner: baked ziti

    The bulk middleware  will handle xattrs stored by both GNU and BSD tar (2).
    Only xattrs user.mime_type and user.meta.* are processed. Other attributes
    are ignored.

    Notes:

    (1) The POSIX 1003.1-2001 (pax) format. The default format on GNU tar
    1.27.1 or later.

    (2) Even with pa
x-format tarballs, different encoders store xattrs slightly
    differently; for example, GNU tar stores the xattr "user.userattribute" as
    pax header "SCHILY.xattr.user.userattribute", while BS
D tar (which uses
    libarchive) stores it as "LIBARCHIVE.xattr.user.userattribute".

    Response:

    The response from bulk operations functions differently from other swift
    responses. This is because a short request body sent from the client could
    result in many operations on the proxy server and precautions need to be
    made to prevent the request from timing out due to lack of activity. To
    this end, the client will always receive a 200 OK response, regardless of
    the actual success of the call.  The body of the response must be parsed to
    determine the actual success of the operation. In addition to this the
    client may receive zero or more whitespace characters prepended to the
    actual response body while the proxy server is completing the request.

    The format of the response body defaults to text/plain but can be either
    json or xml depending on the Accept header. Acceptable formats are
    text/plain, application/json, application/xml, and text/xml. An example
    body is as follows:

    {"Response Status"