मैं फ़ाइल नाम के रूप में उपयोग करने के लिए जावा में सुरक्षित रूप से एक स्ट्रिंग कैसे एन्कोड कर सकता हूं?


117

मैं एक बाहरी प्रक्रिया से एक स्ट्रिंग प्राप्त कर रहा हूं। मैं उस स्ट्रिंग का उपयोग फ़ाइल नाम बनाने के लिए करना चाहता हूं, और फिर उस फ़ाइल पर लिखूं। यहाँ ऐसा करने के लिए मेरा कोड स्निपेट है:

    String s = ... // comes from external source
    File currentFile = new File(System.getProperty("user.home"), s);
    PrintWriter currentWriter = new PrintWriter(currentFile);

यदि किसी अमान्य वर्ण में, जैसे कि '' / '' यूनिक्स-आधारित OS में है, तो एक java.io.ile.FileNotFoundException (सही तरीके से) फेंकी गई है।

मैं स्ट्रिंग को सुरक्षित रूप से कैसे एन्कोड कर सकता हूं ताकि इसे फ़ाइल नाम के रूप में उपयोग किया जा सके?

संपादित करें: मैं जो उम्मीद कर रहा हूं वह एक एपीआई कॉल है जो मेरे लिए ऐसा करता है।

मे यह कर सकती हु:

    String s = ... // comes from external source
    File currentFile = new File(System.getProperty("user.home"), URLEncoder.encode(s, "UTF-8"));
    PrintWriter currentWriter = new PrintWriter(currentFile);

लेकिन मुझे यकीन नहीं है कि क्या URLEncoder यह इस उद्देश्य के लिए विश्वसनीय है।


1
स्ट्रिंग एन्कोडिंग का उद्देश्य क्या है?
स्टीफन सी।

3
@ स्टेफन सी: स्ट्रिंग को एन्कोडिंग करने का उद्देश्य फ़ाइल नाम के रूप में उपयोग करने के लिए उपयुक्त है, जैसा कि java.net.URLEncoder URLs के लिए करता है।
स्टीव मैकलियोड

1
ओह मैं समझा। क्या एन्कोडिंग को प्रतिवर्ती होने की आवश्यकता है?
स्टीफन सी

@ स्टीफन सी: नहीं, इसे प्रतिवर्ती होने की आवश्यकता नहीं है, लेकिन मैं मूल स्ट्रिंग के जितना संभव हो उतना करीब जैसा परिणाम चाहूंगा।
स्टीव मैक्लियॉड

1
क्या एन्कोडिंग को मूल नाम को अस्पष्ट करने की आवश्यकता है? क्या इसे 1-टू -1 करने की आवश्यकता है; यानी टक्कर ठीक है?
स्टीफन सी

जवाबों:


17

यदि आप चाहते हैं कि परिणाम मूल फ़ाइल से मिलता-जुलता हो, तो SHA-1 या कोई अन्य हैशिंग योजना इसका उत्तर नहीं है। यदि टकराव से बचा जाना चाहिए, तो "खराब" पात्रों को सरल प्रतिस्थापन या हटाने का जवाब नहीं है।

इसके बजाय आप ऐसा कुछ चाहते हैं। (नोट: इसे एक उदाहरण के रूप में माना जाना चाहिए, कॉपी और पेस्ट करने के लिए कुछ नहीं। "

char fileSep = '/'; // ... or do this portably.
char escape = '%'; // ... or some other legal char.
String s = ...
int len = s.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
    char ch = s.charAt(i);
    if (ch < ' ' || ch >= 0x7F || ch == fileSep || ... // add other illegal chars
        || (ch == '.' && i == 0) // we don't want to collide with "." or ".."!
        || ch == escape) {
        sb.append(escape);
        if (ch < 0x10) {
            sb.append('0');
        }
        sb.append(Integer.toHexString(ch));
    } else {
        sb.append(ch);
    }
}
File currentFile = new File(System.getProperty("user.home"), sb.toString());
PrintWriter currentWriter = new PrintWriter(currentFile);

यह समाधान एक प्रतिवर्ती एन्कोडिंग (बिना टकराव के साथ) देता है जहां एन्कोडेड स्ट्रिंग्स अधिकांश मामलों में मूल स्ट्रिंग्स से मिलते जुलते हैं। मैं मान रहा हूँ कि आप 8-बिट अक्षरों का उपयोग कर रहे हैं।

URLEncoder काम करता है, लेकिन इसका नुकसान यह है कि यह पूरी तरह से कानूनी फ़ाइल नाम वर्णों को कूटबद्ध करता है।

यदि आप एक गैर-गारंटी-से-प्रतिवर्ती समाधान चाहते हैं, तो उन्हें भागने के दृश्यों के साथ बदलने के बजाय 'खराब' वर्णों को हटा दें।


उपरोक्त एन्कोडिंग के रिवर्स को लागू करने के लिए समान रूप से सीधा-आगे होना चाहिए।


105

मेरा सुझाव "श्वेत सूची" दृष्टिकोण रखना है, जिसका अर्थ है कि बुरे चरित्रों को आज़माना और छानना नहीं। इसके बजाय परिभाषित करें कि क्या ठीक है। आप या तो फ़ाइल नाम को अस्वीकार कर सकते हैं या फ़िल्टर कर सकते हैं। यदि आप इसे फ़िल्टर करना चाहते हैं:

String name = s.replaceAll("\\W+", "");

क्या करता है किसी भी चरित्र है कि की जगह नहीं है कुछ भी नहीं के साथ एक नंबर, पत्र या अंडरस्कोर। वैकल्पिक रूप से आप उन्हें दूसरे चरित्र (जैसे अंडरस्कोर) से बदल सकते हैं।

समस्या यह है कि अगर यह एक साझा निर्देशिका है तो आप फ़ाइल नाम की टक्कर नहीं चाहते हैं। यहां तक ​​कि अगर उपयोगकर्ता के भंडारण क्षेत्र उपयोगकर्ता द्वारा अलग किए जाते हैं, तो आप खराब पात्रों को छानकर एक टकराने वाले फ़ाइलनाम के साथ समाप्त हो सकते हैं। एक उपयोगकर्ता जो नाम रखता है वह अक्सर उपयोगी होता है यदि वे कभी भी इसे डाउनलोड करना चाहते हैं।

इस कारण से मैं उपयोगकर्ता को यह दर्ज करने की अनुमति देता हूं कि वे क्या चाहते हैं, मेरे स्वयं के चयन (जैसे userId_fileId) की एक योजना के आधार पर फ़ाइल नाम को संग्रहीत करें और फिर उपयोगकर्ता का फ़ाइल नाम डेटाबेस तालिका में संग्रहीत करें। इस तरह से आप इसे वापस उपयोगकर्ता के लिए प्रदर्शित कर सकते हैं, चीजों को स्टोर कर सकते हैं कि आप कैसे चाहते हैं और आप सुरक्षा से समझौता नहीं करते हैं या अन्य फ़ाइलों को मिटा देते हैं।

आप फ़ाइल को हैश भी कर सकते हैं (जैसे एमडी 5 हैश) लेकिन फिर आप उन फाइलों को सूचीबद्ध नहीं कर सकते जिन्हें उपयोगकर्ता डाल रहा है (वैसे भी सार्थक नाम के साथ नहीं)।

EDIT: जावा के लिए फिक्स्ड रेगेक्स


मुझे नहीं लगता कि पहले खराब समाधान प्रदान करना एक अच्छा विचार है। इसके अलावा, MD5 एक लगभग फटा हुआ हैश एल्गोरिथ्म है। मैं कम से कम SHA-1 या बेहतर सलाह देता हूं।
वोग

19
एक अद्वितीय फ़ाइल नाम बनाने के प्रयोजनों के लिए जो परवाह करता है कि क्या एल्गोरिथ्म "टूट गया" है?
cletus

3
@ क्लेटस: समस्या यह है कि विभिन्न तार एक ही फ़ाइल नाम के लिए मैप करेंगे; यानी टक्कर।
स्टीफन सी।

3
एक टकराव को जानबूझकर करना होगा, मूल प्रश्न हमलावर द्वारा चुने जा रहे इन तारों के बारे में बात नहीं करता है।
tialaramex

8
आपको "\\W+"जावा में regexp के लिए उपयोग करने की आवश्यकता है । बैकस्लैश पहले स्ट्रिंग पर ही लागू होता है, और \Wएक वैध भागने का क्रम नहीं है। मैंने उत्तर को संपादित करने की कोशिश की, लेकिन ऐसा लगता है कि किसी ने मेरे संपादन को अस्वीकार कर दिया :(
vadipp

35

यह इस पर निर्भर करता है कि एन्कोडिंग प्रतिवर्ती होना चाहिए या नहीं।

प्रतिवर्ती

java.net.URLEncoderविशेष वर्णों को बदलने के लिए URL एन्कोडिंग ( ) का उपयोग करें %xx। ध्यान दें कि आप उन विशेष मामलों का ध्यान रखते हैं जहां स्ट्रिंग बराबर ., बराबर ..या खाली है! This कई प्रोग्राम फ़ाइल नाम बनाने के लिए URL एन्कोडिंग का उपयोग करते हैं, इसलिए यह एक मानक तकनीक है जिसे हर कोई समझता है।

अचल

दिए गए स्ट्रिंग के हैश (जैसे SHA-1) का उपयोग करें। आधुनिक हैश एल्गोरिदम ( एमडी 5 नहीं ) को टक्कर-मुक्त माना जा सकता है। वास्तव में, यदि आप एक टक्कर पाते हैं, तो आपके पास क्रिप्टोग्राफी में एक ब्रेक-थ्रू होगा।


¹ आप उपसर्ग का उपयोग करके सभी 3 विशेष मामलों को सुरुचिपूर्ण ढंग से संभाल सकते हैं जैसे कि "myApp-"। यदि आप फ़ाइल को सीधे रखते हैं $HOME, तो आपको ऐसा करना होगा, वैसे भी मौजूदा फ़ाइलों जैसे ".bashrc" के साथ टकराव से बचने के लिए।
public static String encodeFilename(String s)
{
    try
    {
        return "myApp-" + java.net.URLEncoder.encode(s, "UTF-8");
    }
    catch (java.io.UnsupportedEncodingException e)
    {
        throw new RuntimeException("UTF-8 is an unknown encoding!?");
    }
}


2
URLEnoder का विचार जो एक विशेष वर्ण है, सही नहीं हो सकता है।
स्टीफन सी।

4
@vog: URLEncoder "" के लिए विफल रहता है। तथा ".."। इन्हें एन्कोड किया जाना चाहिए वरना आप $ Home
Stephen C

6
@vog: "*" की अनुमति केवल अधिकांश यूनिक्स-आधारित फाइल सिस्टम में है, NTFS और FAT32 इसका समर्थन नहीं करते हैं।
जोनाथन

1
"।" और ".." को डॉट्स से बचकर% 2E पर ले जाया जा सकता है जब स्ट्रिंग केवल डॉट्स है (यदि आप एस्केप सीक्वेंस को कम करना चाहते हैं)। "*" को "% 2A" से भी बदला जा सकता है।
विप्र

1
ध्यान दें कि कोई भी दृष्टिकोण जो फ़ाइल नाम को लंबा करता है (एकल वर्णों को% 20 या जो भी बदलकर) कुछ फ़ाइल नामों को अमान्य कर देगा जो लंबाई सीमा के करीब हैं (यूनिक्स प्रणालियों के लिए 255 वर्ण)
smcg

24

यहाँ मेरा उपयोग है:

public String sanitizeFilename(String inputName) {
    return inputName.replaceAll("[^a-zA-Z0-9-_\\.]", "_");
}

यह जो करता है वह प्रत्येक वर्ण को प्रतिस्थापित किया जाता है जो एक अक्षर के साथ अक्षर, संख्या, अंडरस्कोर या डॉट नहीं है, रेगेक्स का उपयोग करके।

इसका मतलब यह है कि "How to Convert £ to $" कुछ इस तरह बनेगा "How_to_convert___to__"। बेशक, यह परिणाम बहुत उपयोगकर्ता के अनुकूल नहीं है, लेकिन यह सुरक्षित है और परिणामी निर्देशिका / फ़ाइल नाम हर जगह काम करने की गारंटी है। मेरे मामले में, परिणाम उपयोगकर्ता को नहीं दिखाया गया है, और इस प्रकार यह कोई समस्या नहीं है, लेकिन आप regex को बदलने के लिए अधिक अनुमत हो सकते हैं।

एक और समस्या यह है कि मेरे सामने एक और समस्या यह थी कि मुझे कभी-कभी समान नाम मिलते हैं (क्योंकि यह उपयोगकर्ता इनपुट पर आधारित है), इसलिए आपको इसके बारे में पता होना चाहिए, क्योंकि आपके पास एक ही निर्देशिका में एक ही नाम के साथ कई निर्देशिकाएं / फाइलें नहीं हो सकती हैं। । मैंने अभी के समय और तारीख और इससे बचने के लिए एक छोटा यादृच्छिक स्ट्रिंग तैयार किया है। (वास्तविक यादृच्छिक स्ट्रिंग, फ़ाइल नाम का हैश नहीं है, क्योंकि समान फ़ाइल नाम के परिणामस्वरूप समान हैश होगा)

इसके अलावा, आपको परिणामी स्ट्रिंग को छोटा या अन्यथा करने की आवश्यकता हो सकती है, क्योंकि यह 255 वर्ण सीमा से अधिक हो सकती है जो कुछ प्रणालियों में है।


6
एक और समस्या यह है कि यह उन भाषाओं के लिए विशिष्ट है जो ASCII वर्णों का उपयोग करते हैं। अन्य भाषाओं के लिए, इसमें कुछ भी नहीं बल्कि अंडरस्कोर वाले फिल्नामें शामिल होंगे।
एंडी थॉमस

13

सामान्य समाधान की तलाश करने वालों के लिए, ये सामान्य संकट हो सकते हैं:

  • फ़ाइल नाम स्ट्रिंग से मिलता जुलता होना चाहिए।
  • एन्कोडिंग जहां संभव हो, प्रतिवर्ती होनी चाहिए।
  • टकराव की संभावना कम से कम होनी चाहिए।

इसे प्राप्त करने के लिए हम regex का उपयोग अवैध पात्रों से मेल खाने के लिए कर सकते हैं, उन्हें प्रतिशत-एन्कोड कर सकते हैं, फिर एन्कोडेड स्ट्रिंग की लंबाई में बाधा डाल सकते हैं।

private static final Pattern PATTERN = Pattern.compile("[^A-Za-z0-9_\\-]");

private static final int MAX_LENGTH = 127;

public static String escapeStringAsFilename(String in){

    StringBuffer sb = new StringBuffer();

    // Apply the regex.
    Matcher m = PATTERN.matcher(in);

    while (m.find()) {

        // Convert matched character to percent-encoded.
        String replacement = "%"+Integer.toHexString(m.group().charAt(0)).toUpperCase();

        m.appendReplacement(sb,replacement);
    }
    m.appendTail(sb);

    String encoded = sb.toString();

    // Truncate the string.
    int end = Math.min(encoded.length(),MAX_LENGTH);
    return encoded.substring(0,end);
}

पैटर्न्स

उपरोक्त पैटर्न POSIX कल्पना में अनुमत वर्णों के रूढ़िवादी सबसेट पर आधारित है ।

यदि आप डॉट वर्ण की अनुमति देना चाहते हैं, तो उपयोग करें:

private static final Pattern PATTERN = Pattern.compile("[^A-Za-z0-9_\\-\\.]");

बस तार से सावधान रहें "।" तथा ".."

यदि आप असंवेदनशील फाइल सिस्टम पर टकराव से बचना चाहते हैं, तो आपको राजधानियों से बचना होगा:

private static final Pattern PATTERN = Pattern.compile("[^a-z0-9_\\-]");

या निचले मामलों के पत्रों से बच जाएं:

private static final Pattern PATTERN = Pattern.compile("[^A-Z0-9_\\-]");

श्वेतसूची का उपयोग करने के बजाय, आप अपने विशिष्ट फाइल सिस्टम के लिए आरक्षित वर्णों को ब्लैकलिस्ट करने का विकल्प चुन सकते हैं। ईजी यह रेगेक्स FAT32 फाइल सिस्टम को सूट करता है:

private static final Pattern PATTERN = Pattern.compile("[%\\.\"\\*/:<>\\?\\\\\\|\\+,\\.;=\\[\\]]");

लंबाई

एंड्रॉइड पर, 127 वर्ण सुरक्षित सीमा है। कई फाइल सिस्टम 255 वर्णों की अनुमति देते हैं।

यदि आप अपने स्ट्रिंग के सिर के बजाय पूंछ को बनाए रखना पसंद करते हैं, तो उपयोग करें:

// Truncate the string.
int start = Math.max(0,encoded.length()-MAX_LENGTH);
return encoded.substring(start,encoded.length());

डिकोडिंग

फ़ाइल नाम को मूल स्ट्रिंग में बदलने के लिए, उपयोग करें:

URLDecoder.decode(filename, "UTF-8");

सीमाएं

क्योंकि लंबे समय तक तार काटे जाते हैं, एन्कोडिंग या डिकोडिंग के दौरान भ्रष्टाचार होने पर नाम टकराने की संभावना होती है।


1
पॉज़िक्स हाइफ़न की अनुमति देता है - आपको इसे पैटर्न में जोड़ना चाहिए -Pattern.compile("[^A-Za-z0-9_\\-]")
mkdev

हाइफ़न गयी। धन्यवाद :)
शार्कली

मुझे नहीं लगता कि प्रतिशत-एन्कोडिंग खिड़कियों पर
दयालुता से

1
गैर-अंग्रेजी भाषाओं पर विचार नहीं करता है।
नैट्स

5

निम्नलिखित regex का उपयोग करने का प्रयास करें जो हर अमान्य फ़ाइल नाम वर्ण को एक स्थान से बदल देता है:

public static String toValidFileName(String input)
{
    return input.replaceAll("[:\\\\/*\"?|<>']", " ");
}

सीएलआई के लिए रिक्त स्थान गंदा है; के साथ बदलने पर विचार करें _या -
sdgfsdh


2

यह शायद सबसे प्रभावी तरीका नहीं है, लेकिन यह दिखाता है कि जावा 8 पाइपलाइनों का उपयोग कैसे करना है:

private static String sanitizeFileName(String name) {
    return name
            .chars()
            .mapToObj(i -> (char) i)
            .map(c -> Character.isWhitespace(c) ? '_' : c)
            .filter(c -> Character.isLetterOrDigit(c) || c == '-' || c == '_')
            .map(String::valueOf)
            .collect(Collectors.joining());
}

कस्टम कलेक्टर जो StringBuilder का उपयोग करता है, का निर्माण करके समाधान में सुधार किया जा सकता है, इसलिए आपको प्रत्येक हल्के-वजन वाले चरित्र को भारी-वजन वाले स्ट्रिंग में डालने की आवश्यकता नहीं है।


-1

आप अमान्य वर्ण ('/', '\', '?', '*') को निकाल सकते हैं और फिर उसका उपयोग कर सकते हैं।


1
यह संघर्षों के नामकरण की संभावना को पेश करेगा। यानी, "tes? T", "tes * t" और "test" एक ही फ़ाइल "test" में जाएंगे।
वोग

सच। फिर उन्हें बदल दें। उदाहरण के लिए, '/' -> स्लैश, '*' -> सितारा ... या सुझाए गए के रूप में हैश का उपयोग करें।
बर्कहार्ड

4
आप नामकरण संघर्ष की संभावना के लिए हमेशा खुले हैं
ब्रायन एग्न्यू

2
"?" और "*" फ़ाइल नामों में वर्णों की अनुमति है। उन्हें केवल शेल कमांड में भाग जाने की आवश्यकता है, क्योंकि आमतौर पर ग्लोबिंग का उपयोग किया जाता है। फ़ाइल API स्तर पर, हालांकि, कोई समस्या नहीं है।
वोग

2
@ ब्रायन एग्न्यू: वास्तव में सच नहीं है। प्रतिवर्ती भागने की योजना का उपयोग करके अमान्य वर्णों को एन्कोड करने वाली योजनाएं टक्कर नहीं देंगी।
स्टीफन सी।
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.