@@ -1454,28 +1454,45 @@ restore_entry(struct archive_write_disk *a)
14541454 en = create_filesystem_object (a );
14551455 } else if (en == EEXIST ) {
14561456 mode_t st_mode ;
1457+ mode_t lst_mode ;
1458+ BY_HANDLE_FILE_INFORMATION lst ;
14571459 /*
14581460 * We know something is in the way, but we don't know what;
14591461 * we need to find out before we go any further.
14601462 */
14611463 int r = 0 ;
1464+ int dirlnk = 0 ;
1465+
14621466 /*
14631467 * The SECURE_SYMLINK logic has already removed a
14641468 * symlink to a dir if the client wants that. So
14651469 * follow the symlink if we're creating a dir.
1466- */
1467- if (S_ISDIR (a -> mode ))
1468- r = file_information (a , a -> name , & a -> st , & st_mode , 0 );
1469- /*
14701470 * If it's not a dir (or it's a broken symlink),
14711471 * then don't follow it.
1472+ *
1473+ * Windows distinguishes file and directory symlinks.
1474+ * A file symlink may erroneously point to a directory
1475+ * and a directory symlink to a file. Windows does not follow
1476+ * such symlinks. We always need both source and target
1477+ * information.
14721478 */
1473- if (r != 0 || !S_ISDIR (a -> mode ))
1474- r = file_information (a , a -> name , & a -> st , & st_mode , 1 );
1479+ r = file_information (a , a -> name , & lst , & lst_mode , 1 );
14751480 if (r != 0 ) {
14761481 archive_set_error (& a -> archive , errno ,
14771482 "Can't stat existing object" );
14781483 return (ARCHIVE_FAILED );
1484+ } else if (S_ISLNK (lst_mode )) {
1485+ if (lst .dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
1486+ dirlnk = 1 ;
1487+ /* In case of a symlink we need target information */
1488+ r = file_information (a , a -> name , & a -> st , & st_mode , 0 );
1489+ if (r != 0 ) {
1490+ a -> st = lst ;
1491+ st_mode = lst_mode ;
1492+ }
1493+ } else {
1494+ a -> st = lst ;
1495+ st_mode = lst_mode ;
14791496 }
14801497
14811498 /*
@@ -1499,8 +1516,15 @@ restore_entry(struct archive_write_disk *a)
14991516 }
15001517
15011518 if (!S_ISDIR (st_mode )) {
1502- /* A non-dir is in the way, unlink it. */
1503- if (disk_unlink (a -> name ) != 0 ) {
1519+ /* Edge case: a directory symlink pointing to a file */
1520+ if (dirlnk ) {
1521+ if (disk_rmdir (a -> name ) != 0 ) {
1522+ archive_set_error (& a -> archive , errno ,
1523+ "Can't unlink directory symlink" );
1524+ return (ARCHIVE_FAILED );
1525+ }
1526+ } else if (disk_unlink (a -> name ) != 0 ) {
1527+ /* A non-dir is in the way, unlink it. */
15041528 archive_set_error (& a -> archive , errno ,
15051529 "Can't unlink already-existing object" );
15061530 return (ARCHIVE_FAILED );
@@ -1927,6 +1951,9 @@ check_symlinks(struct archive_write_disk *a)
19271951 p = a -> path_safe .s ;
19281952 while ((* pn != '\0' ) && (* p == * pn ))
19291953 ++ p , ++ pn ;
1954+ /* Skip leading backslashes */
1955+ while (* pn == '\\' )
1956+ ++ pn ;
19301957 c = pn [0 ];
19311958 /* Keep going until we've checked the entire name. */
19321959 while (pn [0 ] != '\0' && (pn [0 ] != '\\' || pn [1 ] != '\0' )) {
@@ -1944,8 +1971,8 @@ check_symlinks(struct archive_write_disk *a)
19441971 } else if (S_ISLNK (st_mode )) {
19451972 if (c == '\0' ) {
19461973 /*
1947- * Last element is symlink; remove it
1948- * so we can overwrite it with the
1974+ * Last element is a file or directory symlink.
1975+ * Remove it so we can overwrite it with the
19491976 * item being extracted.
19501977 */
19511978 if (st .dwFileAttributes &
@@ -1978,7 +2005,13 @@ check_symlinks(struct archive_write_disk *a)
19782005 return (0 );
19792006 } else if (a -> flags & ARCHIVE_EXTRACT_UNLINK ) {
19802007 /* User asked us to remove problems. */
1981- if (disk_unlink (a -> name ) != 0 ) {
2008+ if (st .dwFileAttributes &
2009+ FILE_ATTRIBUTE_DIRECTORY ) {
2010+ r = disk_rmdir (a -> name );
2011+ } else {
2012+ r = disk_unlink (a -> name );
2013+ }
2014+ if (r != 0 ) {
19822015 archive_set_error (& a -> archive , 0 ,
19832016 "Cannot remove intervening "
19842017 "symlink %ls" , a -> name );
@@ -1994,6 +2027,8 @@ check_symlinks(struct archive_write_disk *a)
19942027 return (ARCHIVE_FAILED );
19952028 }
19962029 }
2030+ pn [0 ] = c ;
2031+ pn ++ ;
19972032 }
19982033 pn [0 ] = c ;
19992034 /* We've checked and/or cleaned the whole path, so remember it. */
0 commit comments