jfrog / jenkins-artifactory-plugin

Jenkins artifactory plugin
http://jenkins-ci.org/
116 stars 190 forks source link

File 'artifact.properties' contains escaped characters #222

Closed jgsogo closed 4 years ago

jgsogo commented 4 years ago

Hi! There is a problem when using Conan with build Info (it may happen to other packages as well). In the Conan workflow some properties are stored in a artifacts.properties file and the Conan client send these properties associated with the uploaded artifacts.

The problem is that the plugin is storing the properties with escaped characters, like:

#Build properties
#Tue Dec 03 16:25:21 UTC 2019
artifact_property_build.name=demo-ci-conan \:\: libA \:\: test_with_build_infov1
artifact_property_build.timestamp=1575390321589
artifact_property_build.number=16
artifact_property_vcs.revision=b52875297df57c091e9f811ef3a2b54476cdd553
artifact_property_vcs.url=https\://github.com/conan-community/conan-zlib.git

I've drilled down into the code and I've found the following:

  1. Here we are saving this file https://github.com/jfrog/jenkins-artifactory-plugin/blob/879ba6cb7b5ba14c7ff3ffb4184e4fa249744eb5/src/main/java/org/jfrog/hudson/pipeline/scripted/steps/conan/RunCommandStep.java#L120

    fos = new FileOutputStream(conanProperties.getCanonicalFile());
    props.store(fos, "Build properties");   
  2. It gets into java/util/Properties.java

    public void store(OutputStream out, String comments)
        throws IOException
    {
        store0(new BufferedWriter(new OutputStreamWriter(out, "8859_1")),
               comments,
               true);
    }
  3. Which, in turn, converts the value of the properties:

    val = saveConvert(val, false, escUnicode);
  4. And yes, the implementation (still in java/util/Properties.java) escapes some characters

    /*
     * Converts unicodes to encoded \uxxxx and escapes
     * special characters with a preceding slash
     */
    private String saveConvert(String theString,
                               boolean escapeSpace,
                               boolean escapeUnicode) {
        int len = theString.length();
        int bufLen = len * 2;
        if (bufLen < 0) {
            bufLen = Integer.MAX_VALUE;
        }
        StringBuffer outBuffer = new StringBuffer(bufLen);
    
        for(int x=0; x<len; x++) {
            char aChar = theString.charAt(x);
            // Handle common case first, selecting largest block that
            // avoids the specials below
            if ((aChar > 61) && (aChar < 127)) {
                if (aChar == '\\') {
                    outBuffer.append('\\'); outBuffer.append('\\');
                    continue;
                }
                outBuffer.append(aChar);
                continue;
            }
            switch(aChar) {
                case ' ':
                    if (x == 0 || escapeSpace)
                        outBuffer.append('\\');
                    outBuffer.append(' ');
                    break;
                case '\t':outBuffer.append('\\'); outBuffer.append('t');
                          break;
                case '\n':outBuffer.append('\\'); outBuffer.append('n');
                          break;
                case '\r':outBuffer.append('\\'); outBuffer.append('r');
                          break;
                case '\f':outBuffer.append('\\'); outBuffer.append('f');
                          break;
                case '=': // Fall through
                case ':': // Fall through
                case '#': // Fall through
                case '!':
                    outBuffer.append('\\'); outBuffer.append(aChar);
                    break;
                default:
                    if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode ) {
                        outBuffer.append('\\');
                        outBuffer.append('u');
                        outBuffer.append(toHex((aChar >> 12) & 0xF));
                        outBuffer.append(toHex((aChar >>  8) & 0xF));
                        outBuffer.append(toHex((aChar >>  4) & 0xF));
                        outBuffer.append(toHex( aChar        & 0xF));
                    } else {
                        outBuffer.append(aChar);
                    }
            }
        }
        return outBuffer.toString();
    }

So, we end up with those characters escaped in the artifacts.properties file.

Conan reads this file, and send the properties with those unexpected \: characters.

czoido commented 4 years ago

It looks like the store method escapes #, !, =, and : characters to ensure that the Properties file is stored in a format suitable for using the load(Reader) method. So if you don't read this file using that Java method you are going to fail to interpret those escaped characters. https://docs.oracle.com/javase/7/docs/api/java/util/Properties.html#store(java.io.Writer,%20java.lang.String)

eyalbe4 commented 4 years ago

Thank you @jgsogo and @czoido for reporting and pinpointing the root cause of this issue! I created above PR. It is currently being reviewed.