1 // Copyright 2012 Google Inc. All Rights Reserved.
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
15 #include "includes_normalize.h"
16
17 #include "string_piece.h"
18 #include "string_piece_util.h"
19 #include "util.h"
20
21 #include <algorithm>
22 #include <iterator>
23 #include <sstream>
24
25 #include <windows.h>
26
27 namespace {
28
InternalGetFullPathName(const StringPiece & file_name,char * buffer,size_t buffer_length,string * err)29 bool InternalGetFullPathName(const StringPiece& file_name, char* buffer,
30 size_t buffer_length, string *err) {
31 DWORD result_size = GetFullPathNameA(file_name.AsString().c_str(),
32 buffer_length, buffer, NULL);
33 if (result_size == 0) {
34 *err = "GetFullPathNameA(" + file_name.AsString() + "): " +
35 GetLastErrorString();
36 return false;
37 } else if (result_size > buffer_length) {
38 *err = "path too long";
39 return false;
40 }
41 return true;
42 }
43
IsPathSeparator(char c)44 bool IsPathSeparator(char c) {
45 return c == '/' || c == '\\';
46 }
47
48 // Return true if paths a and b are on the same windows drive.
49 // Return false if this funcation cannot check
50 // whether or not on the same windows drive.
SameDriveFast(StringPiece a,StringPiece b)51 bool SameDriveFast(StringPiece a, StringPiece b) {
52 if (a.size() < 3 || b.size() < 3) {
53 return false;
54 }
55
56 if (!islatinalpha(a[0]) || !islatinalpha(b[0])) {
57 return false;
58 }
59
60 if (ToLowerASCII(a[0]) != ToLowerASCII(b[0])) {
61 return false;
62 }
63
64 if (a[1] != ':' || b[1] != ':') {
65 return false;
66 }
67
68 return IsPathSeparator(a[2]) && IsPathSeparator(b[2]);
69 }
70
71 // Return true if paths a and b are on the same Windows drive.
SameDrive(StringPiece a,StringPiece b,string * err)72 bool SameDrive(StringPiece a, StringPiece b, string* err) {
73 if (SameDriveFast(a, b)) {
74 return true;
75 }
76
77 char a_absolute[_MAX_PATH];
78 char b_absolute[_MAX_PATH];
79 if (!InternalGetFullPathName(a, a_absolute, sizeof(a_absolute), err)) {
80 return false;
81 }
82 if (!InternalGetFullPathName(b, b_absolute, sizeof(b_absolute), err)) {
83 return false;
84 }
85 char a_drive[_MAX_DIR];
86 char b_drive[_MAX_DIR];
87 _splitpath(a_absolute, a_drive, NULL, NULL, NULL);
88 _splitpath(b_absolute, b_drive, NULL, NULL, NULL);
89 return _stricmp(a_drive, b_drive) == 0;
90 }
91
92 // Check path |s| is FullPath style returned by GetFullPathName.
93 // This ignores difference of path separator.
94 // This is used not to call very slow GetFullPathName API.
IsFullPathName(StringPiece s)95 bool IsFullPathName(StringPiece s) {
96 if (s.size() < 3 ||
97 !islatinalpha(s[0]) ||
98 s[1] != ':' ||
99 !IsPathSeparator(s[2])) {
100 return false;
101 }
102
103 // Check "." or ".." is contained in path.
104 for (size_t i = 2; i < s.size(); ++i) {
105 if (!IsPathSeparator(s[i])) {
106 continue;
107 }
108
109 // Check ".".
110 if (i + 1 < s.size() && s[i+1] == '.' &&
111 (i + 2 >= s.size() || IsPathSeparator(s[i+2]))) {
112 return false;
113 }
114
115 // Check "..".
116 if (i + 2 < s.size() && s[i+1] == '.' && s[i+2] == '.' &&
117 (i + 3 >= s.size() || IsPathSeparator(s[i+3]))) {
118 return false;
119 }
120 }
121
122 return true;
123 }
124
125 } // anonymous namespace
126
IncludesNormalize(const string & relative_to)127 IncludesNormalize::IncludesNormalize(const string& relative_to) {
128 string err;
129 relative_to_ = AbsPath(relative_to, &err);
130 if (!err.empty()) {
131 Fatal("Initializing IncludesNormalize(): %s", err.c_str());
132 }
133 split_relative_to_ = SplitStringPiece(relative_to_, '/');
134 }
135
AbsPath(StringPiece s,string * err)136 string IncludesNormalize::AbsPath(StringPiece s, string* err) {
137 if (IsFullPathName(s)) {
138 string result = s.AsString();
139 for (size_t i = 0; i < result.size(); ++i) {
140 if (result[i] == '\\') {
141 result[i] = '/';
142 }
143 }
144 return result;
145 }
146
147 char result[_MAX_PATH];
148 if (!InternalGetFullPathName(s, result, sizeof(result), err)) {
149 return "";
150 }
151 for (char* c = result; *c; ++c)
152 if (*c == '\\')
153 *c = '/';
154 return result;
155 }
156
Relativize(StringPiece path,const vector<StringPiece> & start_list,string * err)157 string IncludesNormalize::Relativize(
158 StringPiece path, const vector<StringPiece>& start_list, string* err) {
159 string abs_path = AbsPath(path, err);
160 if (!err->empty())
161 return "";
162 vector<StringPiece> path_list = SplitStringPiece(abs_path, '/');
163 int i;
164 for (i = 0; i < static_cast<int>(min(start_list.size(), path_list.size()));
165 ++i) {
166 if (!EqualsCaseInsensitiveASCII(start_list[i], path_list[i])) {
167 break;
168 }
169 }
170
171 vector<StringPiece> rel_list;
172 rel_list.reserve(start_list.size() - i + path_list.size() - i);
173 for (int j = 0; j < static_cast<int>(start_list.size() - i); ++j)
174 rel_list.push_back("..");
175 for (int j = i; j < static_cast<int>(path_list.size()); ++j)
176 rel_list.push_back(path_list[j]);
177 if (rel_list.size() == 0)
178 return ".";
179 return JoinStringPiece(rel_list, '/');
180 }
181
Normalize(const string & input,string * result,string * err) const182 bool IncludesNormalize::Normalize(const string& input,
183 string* result, string* err) const {
184 char copy[_MAX_PATH + 1];
185 size_t len = input.size();
186 if (len > _MAX_PATH) {
187 *err = "path too long";
188 return false;
189 }
190 strncpy(copy, input.c_str(), input.size() + 1);
191 uint64_t slash_bits;
192 if (!CanonicalizePath(copy, &len, &slash_bits, err))
193 return false;
194 StringPiece partially_fixed(copy, len);
195 string abs_input = AbsPath(partially_fixed, err);
196 if (!err->empty())
197 return false;
198
199 if (!SameDrive(abs_input, relative_to_, err)) {
200 if (!err->empty())
201 return false;
202 *result = partially_fixed.AsString();
203 return true;
204 }
205 *result = Relativize(abs_input, split_relative_to_, err);
206 if (!err->empty())
207 return false;
208 return true;
209 }
210