import sys
import cherrypy
from cherrypy._cpcompat import ntou
from cherrypy._cptree import Application
from cherrypy.test import helper

script_names = ["", "/foo", "/users/fred/blog", "/corp/blog"]


class ObjectMappingTest(helper.CPWebCase):

    def setup_server():
        class Root:

            @cherrypy.expose
            def index(self, name="world"):
                return name

            @cherrypy.expose
            def foobar(self):
                return "bar"

            @cherrypy.expose
            def default(self, *params, **kwargs):
                return "default:" + repr(params)

            @cherrypy.expose
            def other(self):
                return "other"

            @cherrypy.expose
            def extra(self, *p):
                return repr(p)

            @cherrypy.expose
            def redirect(self):
                raise cherrypy.HTTPRedirect('dir1/', 302)

            def notExposed(self):
                return "not exposed"

            @cherrypy.expose
            def confvalue(self):
                return cherrypy.request.config.get("user")

            @cherrypy.expose
            def redirect_via_url(self, path):
                raise cherrypy.HTTPRedirect(cherrypy.url(path))

            @cherrypy.expose
            def translate_html(self):
                return "OK"

        @cherrypy.expose
        def mapped_func(self, ID=None):
            return "ID is %s" % ID
        setattr(Root, "Von B\xfclow", mapped_func)

        class Exposing:

            @cherrypy.expose
            def base(self):
                return "expose works!"
            cherrypy.expose(base, "1")
            cherrypy.expose(base, "2")

        class ExposingNewStyle(object):

            @cherrypy.expose
            def base(self):
                return "expose works!"
            cherrypy.expose(base, "1")
            cherrypy.expose(base, "2")

        class Dir1:

            @cherrypy.expose
            def index(self):
                return "index for dir1"

            @cherrypy.expose
            @cherrypy.config(**{'tools.trailing_slash.extra': True})
            def myMethod(self):
                return "myMethod from dir1, path_info is:" + repr(
                    cherrypy.request.path_info)

            @cherrypy.expose
            def default(self, *params):
                return "default for dir1, param is:" + repr(params)

        class Dir2:

            @cherrypy.expose
            def index(self):
                return "index for dir2, path is:" + cherrypy.request.path_info

            @cherrypy.expose
            def script_name(self):
                return cherrypy.tree.script_name()

            @cherrypy.expose
            def cherrypy_url(self):
                return cherrypy.url("/extra")

            @cherrypy.expose
            def posparam(self, *vpath):
                return "/".join(vpath)

        class Dir3:

            def default(self):
                return "default for dir3, not exposed"

        class Dir4:

            def index(self):
                return "index for dir4, not exposed"

        class DefNoIndex:

            @cherrypy.expose
            def default(self, *args):
                raise cherrypy.HTTPRedirect("contact")

        # MethodDispatcher code
        @cherrypy.expose
        class ByMethod:

            def __init__(self, *things):
                self.things = list(things)

            def GET(self):
                return repr(self.things)

            def POST(self, thing):
                self.things.append(thing)

        class Collection:
            default = ByMethod('a', 'bit')

        Root.exposing = Exposing()
        Root.exposingnew = ExposingNewStyle()
        Root.dir1 = Dir1()
        Root.dir1.dir2 = Dir2()
        Root.dir1.dir2.dir3 = Dir3()
        Root.dir1.dir2.dir3.dir4 = Dir4()
        Root.defnoindex = DefNoIndex()
        Root.bymethod = ByMethod('another')
        Root.collection = Collection()

        d = cherrypy.dispatch.MethodDispatcher()
        for url in script_names:
            conf = {'/': {'user': (url or "/").split("/")[-2]},
                    '/bymethod': {'request.dispatch': d},
                    '/collection': {'request.dispatch': d},
                    }
            cherrypy.tree.mount(Root(), url, conf)

        class Isolated:

            @cherrypy.expose
            def index(self):
                return "made it!"

        cherrypy.tree.mount(Isolated(), "/isolated")

        @cherrypy.expose
        class AnotherApp:

            def GET(self):
                return "milk"

        cherrypy.tree.mount(AnotherApp(), "/app",
                            {'/': {'request.dispatch': d}})
    setup_server = staticmethod(setup_server)

    def testObjectMapping(self):
        for url in script_names:
            prefix = self.script_name = url

            self.getPage('/')
            self.assertBody('world')

            self.getPage("/dir1/myMethod")
            self.assertBody(
                "myMethod from dir1, path_info is:'/dir1/myMethod'")

            self.getPage("/this/method/does/not/exist")
            self.assertBody(
                "default:('this', 'method', 'does', 'not', 'exist')")

            self.getPage("/extra/too/much")
            self.assertBody("('too', 'much')")

            self.getPage("/other")
            self.assertBody('other')

            self.getPage("/notExposed")
            self.assertBody("default:('notExposed',)")

            self.getPage("/dir1/dir2/")
            self.assertBody('index for dir2, path is:/dir1/dir2/')

            # Test omitted trailing slash (should be redirected by default).
            self.getPage("/dir1/dir2")
            self.assertStatus(301)
            self.assertHeader('Location', '%s/dir1/dir2/' % self.base())

            # Test extra trailing slash (should be redirected if configured).
            self.getPage("/dir1/myMethod/")
            self.assertStatus(301)
            self.assertHeader('Location', '%s/dir1/myMethod' % self.base())

            # Test that default method must be exposed in order to match.
            self.getPage("/dir1/dir2/dir3/dir4/index")
            self.assertBody(
                "default for dir1, param is:('dir2', 'dir3', 'dir4', 'index')")

            # Test *vpath when default() is defined but not index()
            # This also tests HTTPRedirect with default.
            self.getPage("/defnoindex")
            self.assertStatus((302, 303))
            self.assertHeader('Location', '%s/contact' % self.base())
            self.getPage("/defnoindex/")
            self.assertStatus((302, 303))
            self.assertHeader('Location', '%s/defnoindex/contact' %
                              self.base())
            self.getPage("/defnoindex/page")
            self.assertStatus((302, 303))
            self.assertHeader('Location', '%s/defnoindex/contact' %
                              self.base())

            self.getPage("/redirect")
            self.assertStatus('302 Found')
            self.assertHeader('Location', '%s/dir1/' % self.base())

            if not getattr(cherrypy.server, "using_apache", False):
                # Test that we can use URL's which aren't all valid Python
                # identifiers
                # This should also test the %XX-unquoting of URL's.
                self.getPage("/Von%20B%fclow?ID=14")
                self.assertBody("ID is 14")

                # Test that %2F in the path doesn't get unquoted too early;
                # that is, it should not be used to separate path components.
                # See ticket #393.
                self.getPage("/page%2Fname")
                self.assertBody("default:('page/name',)")

            self.getPage("/dir1/dir2/script_name")
            self.assertBody(url)
            self.getPage("/dir1/dir2/cherrypy_url")
            self.assertBody("%s/extra" % self.base())

            # Test that configs don't overwrite each other from diferent apps
            self.getPage("/confvalue")
            self.assertBody((url or "/").split("/")[-2])

        self.script_name = ""

        # Test absoluteURI's in the Request-Line
        self.getPage('http://%s:%s/' % (self.interface(), self.PORT))
        self.assertBody('world')

        self.getPage('http://%s:%s/abs/?service=http://192.168.0.1/x/y/z' %
                     (self.interface(), self.PORT))
        self.assertBody("default:('abs',)")

        self.getPage('/rel/?service=http://192.168.120.121:8000/x/y/z')
        self.assertBody("default:('rel',)")

        # Test that the "isolated" app doesn't leak url's into the root app.
        # If it did leak, Root.default() would answer with
        #   "default:('isolated', 'doesnt', 'exist')".
        self.getPage("/isolated/")
        self.assertStatus("200 OK")
        self.assertBody("made it!")
        self.getPage("/isolated/doesnt/exist")
        self.assertStatus("404 Not Found")

        # Make sure /foobar maps to Root.foobar and not to the app
        # mounted at /foo. See
        # https://github.com/cherrypy/cherrypy/issues/573
        self.getPage("/foobar")
        self.assertBody("bar")

    def test_translate(self):
        self.getPage("/translate_html")
        self.assertStatus("200 OK")
        self.assertBody("OK")

        self.getPage("/translate.html")
        self.assertStatus("200 OK")
        self.assertBody("OK")

        self.getPage("/translate-html")
        self.assertStatus("200 OK")
        self.assertBody("OK")

    def test_redir_using_url(self):
        for url in script_names:
            prefix = self.script_name = url

            # Test the absolute path to the parent (leading slash)
            self.getPage('/redirect_via_url?path=./')
            self.assertStatus(('302 Found', '303 See Other'))
            self.assertHeader('Location', '%s/' % self.base())

            # Test the relative path to the parent (no leading slash)
            self.getPage('/redirect_via_url?path=./')
            self.assertStatus(('302 Found', '303 See Other'))
            self.assertHeader('Location', '%s/' % self.base())

            # Test the absolute path to the parent (leading slash)
            self.getPage('/redirect_via_url/?path=./')
            self.assertStatus(('302 Found', '303 See Other'))
            self.assertHeader('Location', '%s/' % self.base())

            # Test the relative path to the parent (no leading slash)
            self.getPage('/redirect_via_url/?path=./')
            self.assertStatus(('302 Found', '303 See Other'))
            self.assertHeader('Location', '%s/' % self.base())

    def testPositionalParams(self):
        self.getPage("/dir1/dir2/posparam/18/24/hut/hike")
        self.assertBody("18/24/hut/hike")

        # intermediate index methods should not receive posparams;
        # only the "final" index method should do so.
        self.getPage("/dir1/dir2/5/3/sir")
        self.assertBody("default for dir1, param is:('dir2', '5', '3', 'sir')")

        # test that extra positional args raises an 404 Not Found
        # See https://github.com/cherrypy/cherrypy/issues/733.
        self.getPage("/dir1/dir2/script_name/extra/stuff")
        self.assertStatus(404)

    def testExpose(self):
        # Test the cherrypy.expose function/decorator
        self.getPage("/exposing/base")
        self.assertBody("expose works!")

        self.getPage("/exposing/1")
        self.assertBody("expose works!")

        self.getPage("/exposing/2")
        self.assertBody("expose works!")

        self.getPage("/exposingnew/base")
        self.assertBody("expose works!")

        self.getPage("/exposingnew/1")
        self.assertBody("expose works!")

        self.getPage("/exposingnew/2")
        self.assertBody("expose works!")

    def testMethodDispatch(self):
        self.getPage("/bymethod")
        self.assertBody("['another']")
        self.assertHeader('Allow', 'GET, HEAD, POST')

        self.getPage("/bymethod", method="HEAD")
        self.assertBody("")
        self.assertHeader('Allow', 'GET, HEAD, POST')

        self.getPage("/bymethod", method="POST", body="thing=one")
        self.assertBody("")
        self.assertHeader('Allow', 'GET, HEAD, POST')

        self.getPage("/bymethod")
        self.assertBody(repr(['another', ntou('one')]))
        self.assertHeader('Allow', 'GET, HEAD, POST')

        self.getPage("/bymethod", method="PUT")
        self.assertErrorPage(405)
        self.assertHeader('Allow', 'GET, HEAD, POST')

        # Test default with posparams
        self.getPage("/collection/silly", method="POST")
        self.getPage("/collection", method="GET")
        self.assertBody("['a', 'bit', 'silly']")

        # Test custom dispatcher set on app root (see #737).
        self.getPage("/app")
        self.assertBody("milk")

    def testTreeMounting(self):
        class Root(object):

            @cherrypy.expose
            def hello(self):
                return "Hello world!"

        # When mounting an application instance,
        # we can't specify a different script name in the call to mount.
        a = Application(Root(), '/somewhere')
        self.assertRaises(ValueError, cherrypy.tree.mount, a, '/somewhereelse')

        # When mounting an application instance...
        a = Application(Root(), '/somewhere')
        # ...we MUST allow in identical script name in the call to mount...
        cherrypy.tree.mount(a, '/somewhere')
        self.getPage('/somewhere/hello')
        self.assertStatus(200)
        # ...and MUST allow a missing script_name.
        del cherrypy.tree.apps['/somewhere']
        cherrypy.tree.mount(a)
        self.getPage('/somewhere/hello')
        self.assertStatus(200)

        # In addition, we MUST be able to create an Application using
        # script_name == None for access to the wsgi_environ.
        a = Application(Root(), script_name=None)
        # However, this does not apply to tree.mount
        self.assertRaises(TypeError, cherrypy.tree.mount, a, None)

    def testKeywords(self):
        if sys.version_info < (3,):
            return self.skip("skipped (Python 3 only)")
        exec("""class Root(object):
    @cherrypy.expose
    def hello(self, *, name='world'):
        return 'Hello %s!' % name
cherrypy.tree.mount(Application(Root(), '/keywords'))""")

        self.getPage('/keywords/hello')
        self.assertStatus(200)
        self.getPage('/keywords/hello/extra')
        self.assertStatus(404)
