# cleanname.rb # # Copyright (c) 2003 Masahiro Sakai # # This program is free software. # You can distribute/modify this program under the same terms of ruby. # # # Usage # ----- # # File.cleanname takes a filename and by lexical processing only # returns the shortest string that names the same (possibly # hypothetical) file. It eliminates multiple and trailing slashes, and # it lexically interprets . and .. directory components in the name. # # Bibliography # ------------ # # [1] Rob Pike, ``Lexical File Names in Plan 9 or Getting Dot-Dot Right''. # Proceedings of 2000 USENIX Annual Technical Conference # def File.cleanname(path) path = path.dup # 1. reduce multile slashes to a single slash path.gsub!(%r!/+!, '/') # 2. Eliminate . path name elements (the current directory). path.gsub!(%r!(\A|/)\.(\Z|/)!, '\1') # 3. Eliminate .. path name elements (the parent directory) and # the non-. non-.., element that precedes them. re = %r!(\A|/)(?:[^/.]|(?:\.\.[^/])|(?:\.[^/.]))[^/]*/\.\.(\Z|/)! while path.gsub!(re, '\1') end # 4. Eliminate .. elements that begin a rooted # path, that is, replace /.. by / at the beginning # of a path. # # 5. Leave intact .. elements that begin a nonrooted path. path.sub!(%r!\A(?:/\.\.)+!, '/') path.sub!(%r!/\Z!, '') unless path == '/' # remove trailing slash path.empty? ? '.' : path end if __FILE__ == $0 def test(src, expected) dest = File.cleanname(src) if dest == expected puts "pass" else puts "fail: File.cleanname(#{src.dump})=#{dest.dump} is not equal to #{expected.dump}" end end def test_fix(src) test(src, src) end test('./', '.') test('../', '..') test('./foo', 'foo') test('foo/../bar', 'bar') test('../foo/.././bar/../.', '..') test_fix('../.htaccess') test_fix('../../foo/bar') test_fix('..') test_fix('.') test_fix('/') end