# Copyright (c) 2011 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 hmac
import unittest
from hashlib import sha1
from StringIO import StringIO
from time import time

from swift.common.swob import Request, Response
from swift.common.middleware import tempauth, formpost
from swift.common.utils import split_path


class FakeApp(object):

    def __init__(self, status_headers_body_iter=None,
                 check_no_query_string=True):
        self.status_headers_body_iter = status_headers_body_iter
        if not self.status_headers_body_iter:
            self.status_headers_body_iter = iter([('404 Not Found', {
                'x-test-header-one-a': 'value1',
                'x-test-header-two-a': 'value2',
                'x-test-header-two-b': 'value3'}, '')])
        self.requests = []
        self.check_no_query_string = check_no_query_string

    def __call__(self, env, start_response):
        if self.check_no_query_string and env.get('QUERY_STRING'):
            raise Exception('Query string %s should have been discarded!' %
                            env['QUERY_STRING'])
        body = ''
        while True:
            chunk = env['wsgi.input'].read()
            if not chunk:
                break
            body += chunk
        env['wsgi.input'] = StringIO(body)
        self.requests.append(Request.blank('', environ=env))
        if env.get('swift.authorize_override') and \
                env.get('REMOTE_USER') != '.wsgi.pre_authed':
            raise Exception(
                'Invalid REMOTE_USER %r with swift.authorize_override' % (
                    env.get('REMOTE_USER'),))
        if 'swift.authorize' in env:
            resp = env['swift.authorize'](self.requests[-1])
            if resp:
                return resp(env, start_response)
        status, headers, body = self.status_headers_body_iter.next()
        return Response(status=status, headers=headers,
                        body=body)(env, start_response)


class TestParseAttrs(unittest.TestCase):

    def test_basic_content_type(self):
        name, attrs = formpost._parse_attrs('text/plain')
        self.assertEquals(name, 'text/plain')
        self.assertEquals(attrs, {})

    def test_content_type_with_charset(self):
        name, attrs = formpost._parse_attrs('text/plain; charset=UTF8')
        self.assertEquals(name, 'text/plain')
        self.assertEquals(attrs, {'charset': 'UTF8'})

    def test_content_disposition(self):
        name, attrs = formpost._parse_attrs(
            'form-data; name="somefile"; filename="test.html"')
        self.assertEquals(name, 'form-data')
        self.assertEquals(attrs, {'name': 'somefile', 'filename': 'test.html'})


class TestIterRequests(unittest.TestCase):

    def test_bad_start(self):
        it = formpost._iter_requests(StringIO('blah'), 'unique')
        exc = None
        try:
            it.next()
        except formpost.FormInvalid as err:
            exc = err
        self.assertEquals(str(exc), 'invalid starting boundary')

    def test_empty(self):
        it = formpost._iter_requests(StringIO('--unique'), 'unique')
        fp = it.next()
        self.assertEquals(fp.read(), '')
        exc = None
        try:
            it.next()
        except StopIteration as err:
            exc = err
        self.assertTrue(exc is not None)

    def test_basic(self):
        it = formpost._iter_requests(
            StringIO('--unique\r\nabcdefg\r\n--unique--'), 'unique')
        fp = it.next()
        self.assertEquals(fp.read(), 'abcdefg')
        exc = None
        try:
            it.next()
        except StopIteration as err:
            exc = err
        self.assertTrue(exc is not None)

    def test_basic2(self):
        it = formpost._iter_requests(
            StringIO('--unique\r\nabcdefg\r\n--unique\r\nhijkl\r\n--unique--'),
            'unique')
        fp = it.next()
        self.assertEquals(fp.read(), 'abcdefg')
        fp = it.next()
        self.assertEquals(fp.read(), 'hijkl')
        exc = None
        try:
            it.next()
        except StopIteration as err:
            exc = err
        self.assertTrue(exc is not None)

    def test_tiny_reads(self):
        it = formpost._iter_requests(
            StringIO('--unique\r\nabcdefg\r\n--unique\r\nhijkl\r\n--unique--'),
            'unique')
        fp = it.next()
        self.assertEquals(fp.read(2), 'ab')
        self.assertEquals(fp.read(2), 'cd')
        self.assertEquals(fp.read(2), 'ef')
        self.assertEquals(fp.read(2), 'g')
        self.assertEquals(fp.read(2), '')
        fp = it.next()
        self.assertEquals(fp.read(), 'hijkl')
        exc = None
        try:
            it.next()
        except StopIteration as err:
            exc = err
        self.assertTrue(exc is not None)

    def test_big_reads(self):
        it = formpost._iter_requests(
            StringIO('--unique\r\nabcdefg\r\n--unique\r\nhijkl\r\n--unique--'),
            'unique')
        fp = it.next()
        self.assertEquals(fp.read(65536), 'abcdefg')
        self.assertEquals(fp.read(), '')
        fp = it.next()
        self.assertEquals(fp.read(), 'hijkl')
        exc = None
        try:
            it.next()
        except StopIteration as err:
            exc = err
        self.assertTrue(exc is not None)

    def test_broken_mid_stream(self):
        # We go ahead and accept whatever is sent instead of rejecting the
        # whole request, in case the partial form is still useful.
        it = formpost._iter_requests(
            StringIO('--unique\r\nabc'), 'unique')
        fp = it.next()
        self.assertEquals(fp.read(), 'abc')
        exc = None
        try:
            it.next()
        except StopIteration as err:
            exc = err
        self.assertTrue(exc is not None)

    def test_readline(self):
        it = formpost._iter_requests(
            StringIO('--unique\r\nab\r\ncd\ref\ng\r\n--unique\r\nhi\r\n\r\n'
                     'jkl\r\n\r\n--unique--'), 'unique')
        fp = it.next()
        self.assertEquals(fp.readline(), 'ab\r\n')
        self.assertEquals(fp.readline(), 'cd\ref\ng')
        self.assertEquals(fp.readline(), '')
        fp = it.next()
        self.assertEquals(fp.readline(), 'hi\r\n')
        self.assertEquals(fp.readline(), '\r\n')
        self.assertEquals(fp.readline(), 'jkl\r\n')
        exc = None
        try:
            it.next()
        except StopIteration as err:
            exc = err
        self.assertTrue(exc is not None)

    def test_readline_with_tiny_chunks(self):
        orig_read_chunk_size = formpost.READ_CHUNK_SIZE
        try:
            formpost.READ_CHUNK_SIZE = 2
            it = formpost._iter_requests(
                StringIO('--unique\r\nab\r\ncd\ref\ng\r\n--unique\r\nhi\r\n'
                         '\r\njkl\r\n\r\n--unique--'), 'unique')
            fp = it.next()
            self.assertEquals(fp.readline(), 'ab\r\n')
            self.assertEquals(fp.readline(), 'cd\ref\ng')
            self.assertEquals(fp.readline(), '')
            fp = it.next()
            self.assertEquals(fp.readline(), 'hi\r\n')
            self.assertEquals(fp.readline(), '\r\n')
            self.assertEquals(fp.readline(), 'jkl\r\n')
            exc = None
            try:
                it.next()
            except StopIteration as err:
                exc = err
            self.assertTrue(exc is not None)
        finally:
            formpost.READ_CHUNK_SIZE = orig_read_chunk_size


class TestCappedFileLikeObject(unittest.TestCase):

    def test_whole(self):
        self.assertEquals(
            formpost._CappedFileLikeObject(StringIO('abc'), 10).read(), 'abc')

    def test_exceeded(self):
        exc = None
        try:
            formpost._CappedFileLikeObject(StringIO('abc'), 2).read()
        except EOFError as err:
            exc = err
        self.assertEquals(str(exc), 'max_file_size exceeded')

    def test_whole_readline(self):
        fp = formpost._CappedFileLikeObject(StringIO('abc\ndef'), 10)
        self.assertEquals(fp.readline(), 'abc\n')
        self.assertEquals(fp.readline(), 'def')
        self.assertEquals(fp.readline(), '')

    def test_exceeded_readline(self):
        fp = formpost._CappedFileLikeObject(StringIO('abc\ndef'), 5)
        self.assertEquals(fp.readline(), 'abc\n')
        exc = None
        try:
            self.assertEquals(fp.readline(), 'def')
        except EOFError as err:
            exc = err
        self.assertEquals(str(exc), 'max_file_size exceeded')

    def test_read_sized(self):
        fp = formpost._CappedFileLikeObject(StringIO('abcdefg'), 10)
        self.assertEquals(fp.read(2), 'ab')
        self.assertEquals(fp.read(2), 'cd')
        self.assertEquals(fp.read(2), 'ef')
        self.assertEquals(fp.read(2), 'g')
        self.assertEquals(fp.read(2), '')


class TestFormPost(unittest.TestCase):

    def setUp(self):
        self.app = FakeApp()
        self.auth = tempauth.filter_factory({})(self.app)
        self.formpost = formpost.filter_factory({})(self.auth)

    def _make_request(self, path, tempurl_keys=(), **kwargs):
        req = Request.blank(path, **kwargs)

        # Fake out the caching layer so that get_account_info() finds its
        # data. Include something that isn't tempurl keys to prove we skip it.
        meta = {'user-job-title': 'Personal Trainer',
                'user-real-name': 'Jim Shortz'}
        for idx, key in enumerate(tempurl_keys):
            meta_name = 'temp-url-key' + (("-%d" % (idx + 1) if idx else ""))
            if key:
                meta[meta_name] = key

        _junk, account, _junk, _junk = split_path(path, 2, 4)
        req.environ['swift.account/' + account] = self._fake_cache_env(
            account, tempurl_keys)
        return req

    def _fake_cache_env(self, account, tempurl_keys=()):
        # Fake out the caching layer so that get_account_info() finds its
        # data. Include something that isn't tempurl keys to prove we skip it.
        meta = {'user-job-title': 'Personal Trainer',
                'user-real-name': 'Jim Shortz'}
        for idx, key in enumerate(tempurl_keys):
            meta_name = 'temp-url-key' + ("-%d" % (idx + 1) if idx else "")
            if key:
                meta[meta_name] = key

        return {'status': 204,
                'container_count': '0',
                'total_object_count': '0',
                'bytes': '0',
                'meta': meta}

    def _make_sig_env_body(self, path, redirect, max_file_size, max_file_count,
                           expires, key, user_agent=True):
        sig = hmac.new(
            key,
            '%s\n%s\n%s\n%s\n%s' % (
                path, redirect, max_file_size, max_file_count, expires),