0

The task is to implement a function root_relative_path(root : str, path : str) -> str, which calculates the relative path with respect to root, with no intermediate .. go beyond root. e.g., root_relative_path('/abc', '/../def') == '/abc/def'

This question is different from How to calculate relative path in Python? because in this case, root_relative_path(root='/tmp/abc', path='/../def') should return /tmp/abc/def instead of /tmp/def.

lz96
  • 2,816
  • 2
  • 28
  • 46
  • 1
    Instead of pointing out the difference to an other question, you should have asked a detailed question itself. – Klaus D. Oct 26 '18 at 02:31
  • @KlausD. Thank you. I've refined the description of problem. – lz96 Oct 26 '18 at 02:43
  • I understand why you would like to prevent going beyond root, but why silently dropping some '..' instead of throwing an exception? – Francis Colas Oct 26 '18 at 06:54
  • @FrancisColas This function is useful when implementing a HTTP server, where `GET /../../abc` with root=`/tmp/abc` should return files under `/tmp/abc/abc` – lz96 Oct 26 '18 at 18:20

3 Answers3

2
import os.path

def root_relative_path(root : str, path : str) -> str:
    return (os.path.join(root,
        os.path.join(os.sep, os.path.normpath(path)).split(os.sep)[1:])))
Eddy Pronk
  • 6,527
  • 5
  • 33
  • 57
1

I was able to implement your root_relative_path function using a combination of the posixpath and the pathlib modules. The result is

  • Platform independent (as long as the root path corresponds to the current platform)
  • The path can begin with /, ./, or ../
  • And the path will be normalized using all of the techniques covered by the normpath function which includes resolving ..s.

 

from pathlib import Path
import posixpath

def root_relative_path(root : str, path : str) -> str:
    ''' Calculates the relative path with respect to the root. 
        Any ".."s in the relative path will be resolved, 
        but not in a way that would cause the relative path to go beyond the root. '''

    # posixpath.normpath doesn't work as expected if the path doesn't start with a slash, so we make sure it does
    if not path.startswith('/'):
        path = '/' + path

    # The normalization process includes resolving any ".."s
    # we're only using posixpath for the relative portion of the path, the outcome will still be platform independent
    path = posixpath.normpath(path)

    # Remove the beginning slash so that we're not trying to join two absolute paths
    path = path[1:]

    joined = Path(root) / path

    # Per the OPs requirements the result needed to be a string,
    # But if you're allowed to use Pathlib objects, you could just return joined without converting it to a string
    return str(joined)
hostingutilities.com
  • 8,894
  • 3
  • 41
  • 51
0

How about this:

from pathlib import Path

def get_relative_path(path_from: Path, path_to: Path) -> Path:
    """
    Calculate and return a relative path between the `path_from` and
    `path_to` paths. Both paths must be absolute paths!
    """
    if not (path_from.is_absolute() and path_to.is_absolute()):
        raise ValueError('One or both of the passed paths are not absolute.')
    items_from = path_from.parts
    items_to = path_to.parts
    while items_from[0] == items_to[0]:
        items_from = items_from[1:]
        items_to = items_to[1:]
    return Path(*('..' for x in range(1, len(items_from))), *items_to)
karolyi
  • 612
  • 8
  • 14