@@ -171,10 +171,9 @@ bool canonical_path::parent() {
171
171
172
172
canonical_path_result::canonical_path_result () {}
173
173
174
- canonical_path_result::canonical_path_result (std::string &&path)
175
- : path_(std::move(path)) {}
176
-
177
- canonical_path_result::canonical_path_result (const char *path) : path_(path) {}
174
+ canonical_path_result::canonical_path_result (std::string &&path,
175
+ std::size_t existing_path_length)
176
+ : path_(std::move(path)), existing_path_length_(existing_path_length) {}
178
177
179
178
std::string_view canonical_path_result::path () const &noexcept {
180
179
QLJS_ASSERT (this ->ok ());
@@ -206,6 +205,16 @@ std::string &&canonical_path_result::error() && noexcept {
206
205
return std::move (this ->error_ );
207
206
}
208
207
208
+ bool canonical_path_result::have_missing_components () const noexcept {
209
+ QLJS_ASSERT (this ->ok ());
210
+ return this ->existing_path_length_ != this ->path_ ->path_ .size ();
211
+ }
212
+
213
+ void canonical_path_result::drop_missing_components () {
214
+ QLJS_ASSERT (this ->ok ());
215
+ this ->path_ ->path_ .resize (this ->existing_path_length_ );
216
+ }
217
+
209
218
canonical_path_result canonical_path_result::failure (std::string &&error) {
210
219
canonical_path_result result;
211
220
result.error_ = std::move (error);
@@ -224,9 +233,13 @@ class path_canonicalizer {
224
233
}
225
234
#if QLJS_PATHS_WIN32
226
235
// HACK(strager): Convert UTF-16 to UTF-8.
227
- return canonical_path_result (std::filesystem::path (canonical_).string ());
236
+ // TODO(strager): existing_path_length_ is in UTF-16 code units, but it's
237
+ // interpreted as UTF-8 code units! Fix by storing a std::wstring in
238
+ // canonical_path.
239
+ return canonical_path_result (std::filesystem::path (canonical_).string (),
240
+ existing_path_length_);
228
241
#else
229
- return canonical_path_result (std::move (canonical_));
242
+ return canonical_path_result (std::move (canonical_), existing_path_length_ );
230
243
#endif
231
244
}
232
245
@@ -251,13 +264,18 @@ class path_canonicalizer {
251
264
#endif
252
265
canonical_ += preferred_component_separator;
253
266
}
267
+
268
+ if (existing_path_length_ == 0 ) {
269
+ existing_path_length_ = canonical_.size ();
270
+ }
254
271
}
255
272
256
273
private:
257
274
enum class file_type {
258
275
error,
259
276
260
277
directory,
278
+ does_not_exist,
261
279
other,
262
280
symlink,
263
281
};
@@ -358,19 +376,41 @@ class path_canonicalizer {
358
376
if (component == dot) {
359
377
skip_to_next_component ();
360
378
} else if (component == dot_dot) {
361
- parent ();
379
+ if (existing_path_length_ == 0 ) {
380
+ parent ();
381
+ } else {
382
+ QLJS_ASSERT (!canonical_.empty ());
383
+ canonical_ += preferred_component_separator;
384
+ canonical_ += component;
385
+ }
362
386
skip_to_next_component ();
363
387
} else {
388
+ std::size_t canonical_length_without_component = canonical_.size ();
389
+
364
390
canonical_ += preferred_component_separator;
365
391
canonical_ += component;
366
392
need_root_slash_ = false ;
367
393
394
+ if (existing_path_length_ != 0 ) {
395
+ // A parent path did not exist, so this path certainly does not exist.
396
+ // Don't bother checking.
397
+ skip_to_next_component ();
398
+ return ;
399
+ }
400
+
368
401
file_type type = get_file_type (canonical_);
369
402
switch (type) {
370
403
case file_type::error:
371
404
QLJS_ASSERT (!error_.empty ());
372
405
return ;
373
406
407
+ case file_type::does_not_exist:
408
+ if (existing_path_length_ == 0 ) {
409
+ existing_path_length_ = canonical_length_without_component;
410
+ }
411
+ skip_to_next_component ();
412
+ break ;
413
+
374
414
case file_type::directory:
375
415
skip_to_next_component ();
376
416
break ;
@@ -448,10 +488,14 @@ class path_canonicalizer {
448
488
#if defined(_WIN32)
449
489
DWORD attributes = ::GetFileAttributesW (file_path.c_str ());
450
490
if (attributes == INVALID_FILE_ATTRIBUTES) {
491
+ DWORD error = ::GetLastError ();
492
+ if (error == ERROR_FILE_NOT_FOUND) {
493
+ return file_type::does_not_exist;
494
+ }
451
495
error_ = std::string (" failed to canonicalize path " ) +
452
496
string_for_error_message (original_path_) + " : " +
453
497
string_for_error_message (canonical_) + " : " +
454
- windows_error_message (:: GetLastError () );
498
+ windows_error_message (error );
455
499
return file_type::error;
456
500
}
457
501
if (attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
@@ -465,6 +509,9 @@ class path_canonicalizer {
465
509
struct stat s;
466
510
int lstat_rc = ::lstat (file_path.c_str (), &s);
467
511
if (lstat_rc == -1 ) {
512
+ if (errno == ENOENT) {
513
+ return file_type::does_not_exist;
514
+ }
468
515
error_ = std::string (" failed to canonicalize path " ) +
469
516
string_for_error_message (original_path_) + " : " +
470
517
string_for_error_message (canonical_) + " : " +
@@ -540,6 +587,13 @@ class path_canonicalizer {
540
587
path_string canonical_;
541
588
bool need_root_slash_;
542
589
590
+ // During canonicalization, if existing_path_length_ is 0, then we have not
591
+ // found a non-existing path.
592
+ //
593
+ // During canonicalization, if existing_path_length_ is not 0, then we have
594
+ // found a non-existing path. '..' should be preserved.
595
+ std::size_t existing_path_length_ = 0 ;
596
+
543
597
path_string readlink_buffers_[2 ];
544
598
int used_readlink_buffer_ = 0 ; // Index into readlink_buffers_.
545
599
0 commit comments