blob: 205cecd188199eec31781c139ef0b19d8224fff5 [file] [log] [blame]
Shawn Pearce47fd6562016-05-28 14:15:15 -07001// Copyright (C) 2016 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.gitiles.doc;
16
17import com.google.common.base.CharMatcher;
Shawn Pearce47fd6562016-05-28 14:15:15 -070018import javax.annotation.Nullable;
19
20class PathResolver {
21 /**
22 * Resolve a path within the repository.
23 *
Dave Borowitz40255d52016-08-19 16:16:22 -040024 * @param file path of the Markdown file in the repository that is making the reference. May be
25 * null.
26 * @param target destination within the repository. If {@code target} starts with {@code '/'},
27 * {@code file} may be null and {@code target} is evaluated as from the root directory of the
28 * repository.
29 * @return resolved form of {@code target} within the repository. Null if {@code target} is not
30 * valid from {@code file}. Does not begin with {@code '/'}, even if {@code target} does.
Shawn Pearce47fd6562016-05-28 14:15:15 -070031 */
32 @Nullable
33 static String resolve(@Nullable String file, String target) {
34 if (target.startsWith("/")) {
35 return trimLeadingSlash(target);
36 } else if (file == null) {
37 return null;
38 }
39
40 String dir = trimLastComponent(trimLeadingSlash(file));
41 while (!target.isEmpty()) {
42 if (target.startsWith("../") || target.equals("..")) {
43 if (dir.isEmpty()) {
44 return null;
45 }
46 dir = trimLastComponent(dir);
47 target = target.equals("..") ? "" : target.substring(3);
48 } else if (target.startsWith("./")) {
49 target = target.substring(2);
50 } else if (target.equals(".")) {
51 target = "";
52 } else {
53 break;
54 }
55 }
56 return trimLeadingSlash(dir + '/' + target);
57 }
58
59 private static String trimLeadingSlash(String s) {
60 return CharMatcher.is('/').trimLeadingFrom(s);
61 }
62
63 private static String trimLastComponent(String path) {
64 int slash = path.lastIndexOf('/');
65 return slash < 0 ? "" : path.substring(0, slash);
66 }
67
Shawn Pearcec68ad0b2016-05-28 16:52:47 -070068 static String relative(@Nullable String requestUri, String dest) {
69 if (requestUri != null) {
70 // base is the path the browser will use for relative URLs.
71 String base = requestUri;
72 if (!base.endsWith("/")) {
73 int slash = base.lastIndexOf('/');
74 if (slash < 0) {
75 return dest;
76 }
77 base = base.substring(0, slash + 1);
78 }
79 if (dest.startsWith(base)) {
80 return dest.substring(base.length());
81 }
82 }
83 return dest;
84 }
85
Shawn Pearce47fd6562016-05-28 14:15:15 -070086 private PathResolver() {}
87}