Пример #1
0
def path_join(base, path):
    """Joins 'base' and 'path' ('path' is interpreted as a relative path).

    This method is like os.path.join(), but 'path' is interpreted relatively.
    E.g., os.path.join('/a/b', '/c') yields '/c', but this function yields
    '/a/b/c'.

    Args:
        base: The base path.
        path: The path to append to base; this is treated as a relative path.

    Returns:
        The path obtaining by appending 'path' to 'base'.
    """
    if os.path.isabs(path):
        # Remove drive letter (if we are on Windows).
        unused_drive, path_no_drive = os.path.splitdrive(path)
        # Remove leading path separator.
        path = path_no_drive[1:]
    return AbstractFileSystem.normpath(os.path.join(base, path))
Пример #2
0
def get_all_courses(rules_text=None):
    """Reads all course rewrite rule definitions from environment variable."""
    # Normalize text definition.
    if not rules_text:
        rules_text = GCB_COURSES_CONFIG.value
    rules_text = rules_text.replace(',', '\n')

    # Use cached value if exists.
    cached = ApplicationContext.ALL_COURSE_CONTEXTS_CACHE.get(rules_text)
    if cached:
        return cached

    # Compute the list of contexts.
    rules = rules_text.split('\n')
    slugs = {}
    namespaces = {}
    all_contexts = []
    for rule in rules:
        rule = rule.strip()
        if not rule or rule.startswith('#'):
            continue
        parts = rule.split(':')

        # validate length
        if len(parts) < 3:
            raise Exception('Expected rule definition of the form '
                            ' \'type:slug:folder[:ns]\', got %s: ' % rule)

        # validate type
        if parts[0] != SITE_TYPE_COURSE:
            raise Exception('Expected \'%s\', found: \'%s\'.'
                            % (SITE_TYPE_COURSE, parts[0]))
        site_type = parts[0]

        # validate slug
        slug = parts[1]
        slug_parts = urlparse.urlparse(slug)
        if slug != slug_parts[2]:
            raise Exception(
                'Bad rule: \'%s\'. '
                'Course URL prefix \'%s\' must be a simple URL fragment.' % (
                    rule, slug))
        if slug in slugs:
            raise Exception(
                'Bad rule: \'%s\'. '
                'Course URL prefix \'%s\' is already defined.' % (rule, slug))
        slugs[slug] = True

        # validate folder name
        if parts[2]:
            folder = parts[2]
            # pylint: disable-msg=g-long-lambda
            create_fs = lambda unused_ns: LocalReadOnlyFileSystem(
                logical_home_folder=folder)
        else:
            folder = '/'
            # pylint: disable-msg=g-long-lambda
            create_fs = lambda ns: DatastoreBackedFileSystem(
                ns=ns,
                logical_home_folder=appengine_config.BUNDLE_ROOT,
                inherits_from=LocalReadOnlyFileSystem(logical_home_folder='/'),
                inheritable_folders=GCB_INHERITABLE_FOLDER_NAMES)

        # validate or derive namespace
        namespace = appengine_config.DEFAULT_NAMESPACE_NAME
        if len(parts) == 4:
            namespace = parts[3]
        else:
            if folder and folder != '/':
                namespace = '%s%s' % (GCB_BASE_COURSE_NAMESPACE,
                                      folder.replace('/', '-'))
        try:
            namespace_manager.validate_namespace(namespace)
        except Exception as e:
            raise Exception(
                'Error validating namespace "%s" in rule "%s"; %s.' % (
                    namespace, rule, e))

        if namespace in namespaces:
            raise Exception(
                'Bad rule \'%s\'. '
                'Namespace \'%s\' is already defined.' % (rule, namespace))
        namespaces[namespace] = True

        all_contexts.append(ApplicationContext(
            site_type, slug, folder, namespace,
            AbstractFileSystem(create_fs(namespace)),
            raw=rule))

    _validate_appcontext_list(all_contexts)

    # Cache result to avoid re-parsing over and over.
    ApplicationContext.ALL_COURSE_CONTEXTS_CACHE = {rules_text: all_contexts}

    return all_contexts
Пример #3
0
def test_url_to_handler_mapping_for_course_type():
    """Tests mapping of a URL to a handler for course type."""

    # setup rules
    setup_courses('course:/a/b:/c/d, course:/e/f:/g/h')

    # setup helper classes
    class FakeHandler0(object):
        def __init__(self):
            self.app_context = None

    class FakeHandler1(object):
        def __init__(self):
            self.app_context = None

    class FakeHandler2(zipserve.ZipHandler):
        def __init__(self):
            self.app_context = None

    class FakeHandler3(zipserve.ZipHandler):
        def __init__(self):
            self.app_context = None

    class FakeHandler4(zipserve.ZipHandler):
        def __init__(self):
            self.app_context = None

    # Setup handler.
    handler0 = FakeHandler0
    handler1 = FakeHandler1
    handler2 = FakeHandler2
    urls = [('/', handler0), ('/foo', handler1), ('/bar', handler2)]
    ApplicationRequestHandler.bind(urls)

    # Test proper handler mappings.
    assert_handled('/a/b', FakeHandler0)
    assert_handled('/a/b/', FakeHandler0)
    assert_handled('/a/b/foo', FakeHandler1)
    assert_handled('/a/b/bar', FakeHandler2)

    # Test partial path match.
    assert_handled('/a/b/foo/bee', None)
    assert_handled('/a/b/bar/bee', FakeHandler2)

    # Test assets mapping.
    handler = assert_handled('/a/b/assets/img/foo.png', AssetHandler)
    assert AbstractFileSystem.normpath(
        handler.app_context.get_template_home()).endswith(
            AbstractFileSystem.normpath('/c/d/views'))

    # This is allowed as we don't go out of /assets/...
    handler = assert_handled(
        '/a/b/assets/foo/../models/models.py', AssetHandler)
    assert AbstractFileSystem.normpath(handler.filename).endswith(
        AbstractFileSystem.normpath('/c/d/assets/models/models.py'))

    # This is not allowed as we do go out of /assets/...
    assert_handled('/a/b/assets/foo/../../models/models.py', None)

    # Test negative cases
    assert_handled('/foo', None)
    assert_handled('/baz', None)

    # Site 'views' and 'data' are not accessible
    assert_handled('/a/b/view/base.html', None)
    assert_handled('/a/b/data/units.csv', None)

    # Default mapping
    reset_courses()
    handler3 = FakeHandler3
    handler4 = FakeHandler4
    urls = [
        ('/', handler0),
        ('/foo', handler1),
        ('/bar', handler2),
        ('/zip', handler3),
        ('/zip/a/b', handler4)]
    ApplicationRequestHandler.bind(urls)

    # Positive cases
    assert_handled('/', FakeHandler0)
    assert_handled('/foo', FakeHandler1)
    assert_handled('/bar', FakeHandler2)
    handler = assert_handled('/assets/js/main.js', AssetHandler)
    assert AbstractFileSystem.normpath(
        handler.app_context.get_template_home()).endswith(
            AbstractFileSystem.normpath('/views'))

    # Partial URL matching cases test that the most specific match is found.
    assert_handled('/zip', FakeHandler3)
    assert_handled('/zip/a', FakeHandler3)
    assert_handled('/zip/a/b', FakeHandler4)
    assert_handled('/zip/a/b/c', FakeHandler4)

    # Negative cases
    assert_handled('/baz', None)
    assert_handled('/favicon.ico', None)
    assert_handled('/e/f/index.html', None)
    assert_handled('/foo/foo.css', None)

    # Clean up.
    ApplicationRequestHandler.bind([])