@@ -2605,3 +2605,215 @@ expand_in_path(
26052605}
26062606
26072607#endif // FEAT_SEARCHPATH
2608+
2609+ /*
2610+ * Converts a file name into a canonical form. It simplifies a file name into
2611+ * its simplest form by stripping out unneeded components, if any. The
2612+ * resulting file name is simplified in place and will either be the same
2613+ * length as that supplied, or shorter.
2614+ */
2615+ void
2616+ simplify_filename (char_u * filename )
2617+ {
2618+ #ifndef AMIGA // Amiga doesn't have "..", it uses "/"
2619+ int components = 0 ;
2620+ char_u * p , * tail , * start ;
2621+ int stripping_disabled = FALSE;
2622+ int relative = TRUE;
2623+
2624+ p = filename ;
2625+ # ifdef BACKSLASH_IN_FILENAME
2626+ if (p [1 ] == ':' ) // skip "x:"
2627+ p += 2 ;
2628+ # endif
2629+
2630+ if (vim_ispathsep (* p ))
2631+ {
2632+ relative = FALSE;
2633+ do
2634+ ++ p ;
2635+ while (vim_ispathsep (* p ));
2636+ }
2637+ start = p ; // remember start after "c:/" or "/" or "///"
2638+
2639+ do
2640+ {
2641+ // At this point "p" is pointing to the char following a single "/"
2642+ // or "p" is at the "start" of the (absolute or relative) path name.
2643+ # ifdef VMS
2644+ // VMS allows device:[path] - don't strip the [ in directory
2645+ if ((* p == '[' || * p == '<' ) && p > filename && p [-1 ] == ':' )
2646+ {
2647+ // :[ or :< composition: vms directory component
2648+ ++ components ;
2649+ p = getnextcomp (p + 1 );
2650+ }
2651+ // allow remote calls as host"user passwd"::device:[path]
2652+ else if (p [0 ] == ':' && p [1 ] == ':' && p > filename && p [-1 ] == '"' )
2653+ {
2654+ // ":: composition: vms host/passwd component
2655+ ++ components ;
2656+ p = getnextcomp (p + 2 );
2657+ }
2658+ else
2659+ # endif
2660+ if (vim_ispathsep (* p ))
2661+ STRMOVE (p , p + 1 ); // remove duplicate "/"
2662+ else if (p [0 ] == '.' && (vim_ispathsep (p [1 ]) || p [1 ] == NUL ))
2663+ {
2664+ if (p == start && relative )
2665+ p += 1 + (p [1 ] != NUL ); // keep single "." or leading "./"
2666+ else
2667+ {
2668+ // Strip "./" or ".///". If we are at the end of the file name
2669+ // and there is no trailing path separator, either strip "/." if
2670+ // we are after "start", or strip "." if we are at the beginning
2671+ // of an absolute path name .
2672+ tail = p + 1 ;
2673+ if (p [1 ] != NUL )
2674+ while (vim_ispathsep (* tail ))
2675+ MB_PTR_ADV (tail );
2676+ else if (p > start )
2677+ -- p ; // strip preceding path separator
2678+ STRMOVE (p , tail );
2679+ }
2680+ }
2681+ else if (p [0 ] == '.' && p [1 ] == '.' &&
2682+ (vim_ispathsep (p [2 ]) || p [2 ] == NUL ))
2683+ {
2684+ // Skip to after ".." or "../" or "..///".
2685+ tail = p + 2 ;
2686+ while (vim_ispathsep (* tail ))
2687+ MB_PTR_ADV (tail );
2688+
2689+ if (components > 0 ) // strip one preceding component
2690+ {
2691+ int do_strip = FALSE;
2692+ char_u saved_char ;
2693+ stat_T st ;
2694+
2695+ /* Don't strip for an erroneous file name. */
2696+ if (!stripping_disabled )
2697+ {
2698+ // If the preceding component does not exist in the file
2699+ // system, we strip it. On Unix, we don't accept a symbolic
2700+ // link that refers to a non-existent file.
2701+ saved_char = p [-1 ];
2702+ p [-1 ] = NUL ;
2703+ # ifdef UNIX
2704+ if (mch_lstat ((char * )filename , & st ) < 0 )
2705+ # else
2706+ if (mch_stat ((char * )filename , & st ) < 0 )
2707+ # endif
2708+ do_strip = TRUE;
2709+ p [-1 ] = saved_char ;
2710+
2711+ -- p ;
2712+ // Skip back to after previous '/'.
2713+ while (p > start && !after_pathsep (start , p ))
2714+ MB_PTR_BACK (start , p );
2715+
2716+ if (!do_strip )
2717+ {
2718+ // If the component exists in the file system, check
2719+ // that stripping it won't change the meaning of the
2720+ // file name. First get information about the
2721+ // unstripped file name. This may fail if the component
2722+ // to strip is not a searchable directory (but a regular
2723+ // file, for instance), since the trailing "/.." cannot
2724+ // be applied then. We don't strip it then since we
2725+ // don't want to replace an erroneous file name by
2726+ // a valid one, and we disable stripping of later
2727+ // components.
2728+ saved_char = * tail ;
2729+ * tail = NUL ;
2730+ if (mch_stat ((char * )filename , & st ) >= 0 )
2731+ do_strip = TRUE;
2732+ else
2733+ stripping_disabled = TRUE;
2734+ * tail = saved_char ;
2735+ # ifdef UNIX
2736+ if (do_strip )
2737+ {
2738+ stat_T new_st ;
2739+
2740+ // On Unix, the check for the unstripped file name
2741+ // above works also for a symbolic link pointing to
2742+ // a searchable directory. But then the parent of
2743+ // the directory pointed to by the link must be the
2744+ // same as the stripped file name. (The latter
2745+ // exists in the file system since it is the
2746+ // component's parent directory.)
2747+ if (p == start && relative )
2748+ (void )mch_stat ("." , & new_st );
2749+ else
2750+ {
2751+ saved_char = * p ;
2752+ * p = NUL ;
2753+ (void )mch_stat ((char * )filename , & new_st );
2754+ * p = saved_char ;
2755+ }
2756+
2757+ if (new_st .st_ino != st .st_ino ||
2758+ new_st .st_dev != st .st_dev )
2759+ {
2760+ do_strip = FALSE;
2761+ // We don't disable stripping of later
2762+ // components since the unstripped path name is
2763+ // still valid.
2764+ }
2765+ }
2766+ # endif
2767+ }
2768+ }
2769+
2770+ if (!do_strip )
2771+ {
2772+ // Skip the ".." or "../" and reset the counter for the
2773+ // components that might be stripped later on.
2774+ p = tail ;
2775+ components = 0 ;
2776+ }
2777+ else
2778+ {
2779+ // Strip previous component. If the result would get empty
2780+ // and there is no trailing path separator, leave a single
2781+ // "." instead. If we are at the end of the file name and
2782+ // there is no trailing path separator and a preceding
2783+ // component is left after stripping, strip its trailing
2784+ // path separator as well.
2785+ if (p == start && relative && tail [-1 ] == '.' )
2786+ {
2787+ * p ++ = '.' ;
2788+ * p = NUL ;
2789+ }
2790+ else
2791+ {
2792+ if (p > start && tail [-1 ] == '.' )
2793+ -- p ;
2794+ STRMOVE (p , tail ); // strip previous component
2795+ }
2796+
2797+ -- components ;
2798+ }
2799+ }
2800+ else if (p == start && !relative ) // leading "/.." or "/../"
2801+ STRMOVE (p , tail ); // strip ".." or "../"
2802+ else
2803+ {
2804+ if (p == start + 2 && p [-2 ] == '.' ) // leading "./../"
2805+ {
2806+ STRMOVE (p - 2 , p ); // strip leading "./"
2807+ tail -= 2 ;
2808+ }
2809+ p = tail ; // skip to char after ".." or "../"
2810+ }
2811+ }
2812+ else
2813+ {
2814+ ++ components ; // simple path component
2815+ p = getnextcomp (p );
2816+ }
2817+ } while (* p != NUL );
2818+ #endif // !AMIGA
2819+ }
0 commit comments