Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiline value #28

Closed
david-arteaga opened this issue Apr 22, 2019 · 8 comments
Closed

Multiline value #28

david-arteaga opened this issue Apr 22, 2019 · 8 comments
Assignees

Comments

@david-arteaga
Copy link

Is there any way to use a multi-line value in the .env file?

I've tried using double quotes, single quotes, backticks plus the \n char but I get the literal \n characters in the string when evaluated in Java code.

@cdimascio
Copy link
Owner

@david-arteaga thanks for the ticket

I created a quick simple test to try and recreate your issue

Here is my .env file

MULTI_LINE=hello\nworld

Here is a quick Java test (included only the relevant bits)

   ...
    private val envVars = mapOf(
        "MULTI_LINE" to "hello\\nworld"
    )
   ...

    @test
    fun multiLine() {
        val dotenv = Dotenv.configure()
            .ignoreIfMalformed()
            .load()

        assertEquals(dotenv["MULTI_LINE"]!!, envVars["MULTI_LINE"]!!)
    }

The above test passes.

Can you provide the .env file you are using and what you expect to see in Java

Thanks

@cdimascio cdimascio self-assigned this Apr 30, 2019
@david-arteaga
Copy link
Author

Ok so I played around with some examples and I think I got to the issue.

This is my .env file:

MULTILINE=1\n2

This is the code I am running

public static void main(String[] args) {
  String multilineString = "1\n2";

  Dotenv d = Dotenv.load();
  String multilineFromDotEnv = d.get("MULTILINE");

  System.out.println("Characters from multiline");
  printChars(multilineString);
  System.out.println("\n\nCharacters from dotenv");
  printChars(multilineFromDotEnv);

  System.out.println("\n\nPrinted string with multiline");
  System.out.println(multilineString);
  System.out.println("Printed string from dotenv");
  System.out.println(multilineFromDotEnv);

  System.out.println("\n\nAre they equal? : " + multilineString.equals(multilineFromDotEnv));
}

private static void printChars(String all) {
  all.chars().forEach(System.out::println);
}

This is the output I get:

Characters from multiline
49
10
50


Characters from dotenv
49
92
110
50


Printed string with multiline
1
2
Printed string from dotenv
1\n2


Are they equal? : false

So as you can see in the character list from dotenv, the characters get interpreted literally, as a \+ n character.

I would expect for there to be a way to actually have a real multiline string from dotenv.

I ran into this problem trying to use a .pem certificate in the .env file, and the library I was using rightfully complained saying the certificate was incorrect.

I can imagine there are many other use cases for this, but this is the one I found.

I want the string that is loaded from dotenv to actually have the newline character in the string, not to have the \+ ncharacters.

Is there any way to do this with the library?
What I did in the end was just to put an arbitrary string where there should be newlines in the .env file and then replace them in java code.

@cdimascio
Copy link
Owner

Thanks for the details. This is helpful. Will have a look

@cdimascio
Copy link
Owner

@david-arteaga

I ran a few more tests. Ultimately, Java's System.getenv(...) and java-dotenv's dotenv.get(...) behave the same way for multi-line EVs. Given that this is the case, the current behavior should remain. I don't think dotenv should diverge from Java's behavior.

Here is the test. It's essentially an extension of yours

package tests;

import io.github.cdimascio.dotenv.Dotenv;

import java.util.ArrayList;
import java.util.List;

public class Test
{
    public static void main(String[] args) {
        String multilineString = "1\n2";

        Dotenv d = Dotenv.configure().ignoreIfMalformed().load();
        String multilineFromDotEnv = d.get("MULTILINE");
        String multilineFromSytemEnv = System.getenv("MULTILINE");

        System.out.println("Characters from");
        System.out.println(" - multiline: " + collectChars(multilineString));
        System.out.println(" - dotenv   : " + collectChars(multilineFromDotEnv));
        System.out.println(" - System   : " + collectChars(multilineFromSytemEnv));

        System.out.println();
        System.out.println("String from");
        System.out.println(" - multiline: " + multilineString);
        System.out.println(" - dotenv   : " + multilineFromDotEnv);
        System.out.println(" - System   : " + multilineFromSytemEnv);

        System.out.println();
        System.out.println("Does System.getenv(...) equal dotenv.get(...): " + multilineFromSytemEnv.equals(multilineFromDotEnv));

        System.out.println("Does System.getenv equal multiline String?   : " + multilineString.equals(multilineFromSytemEnv));
        System.out.println("Does dotenv.get equal multiline String?      : " + multilineString.equals(multilineFromSytemEnv));
    }

    private static List<Integer> collectChars(String all) {
        return all.chars().collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
    }
}

@cdimascio
Copy link
Owner

cdimascio commented May 5, 2019

All in all, this still begs the question, how do you solve your original issue.

Here's a suggestion. Base 64 encode the multi-line value in the EV, and decode it after its read in.
Here is an example, you'll notice that the bae64 encoding will preserve the newlines and both Java system and dotenv produce the same and desired result

# EVs 
MULTILINE=1\n2
# base 64 encoded version of string above
B64_MULTI_LINE=MQoy
package tests;

import io.github.cdimascio.dotenv.Dotenv;

import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

public class Test
{
    public static void main(String[] args) {
        String multilineString = "1\n2";
        runWith("MULTILINE", "1\n2");
        System.out.println("=================");

        String encodedString = Base64.getEncoder().encodeToString(multilineString.getBytes());

        runWith("B64_MULTI_LINE", encodedString);

        byte[] decodedBytes = Base64.getDecoder().decode(encodedString);
        System.out.println(new String(decodedBytes));
    }

    public static void runWith(String ev, String multilineString) {
        Dotenv d = Dotenv.configure().ignoreIfMalformed().load();
        String multilineFromDotEnv = d.get(ev);
        String multilineFromSytemEnv = System.getenv(ev);

        System.out.println("Characters from");
        System.out.println(" - multiline: " + collectChars(multilineString));
        System.out.println(" - dotenv   : " + collectChars(multilineFromDotEnv));
        System.out.println(" - System   : " + collectChars(multilineFromSytemEnv));

        System.out.println();
        System.out.println("String from");
        System.out.println(" - multiline: " + multilineString);
        System.out.println(" - dotenv   : " + multilineFromDotEnv);
        System.out.println(" - System   : " + multilineFromSytemEnv);

        System.out.println();
        System.out.println("Does System.getenv(...) equal dotenv.get(...): " + multilineFromSytemEnv.equals(multilineFromDotEnv));

        System.out.println("Does System.getenv equal multiline String?   : " + multilineString.equals(multilineFromSytemEnv));
        System.out.println("Does dotenv.get equal multiline String?      : " + multilineString.equals(multilineFromSytemEnv));

        String encodedString = Base64.getEncoder().encodeToString(multilineString.getBytes());
    }

    private static List<Integer> collectChars(String all) {
        return all.chars().collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
    }
}

Hope this helps

@david-arteaga
Copy link
Author

Yeah Base64 definitely gets the job done. Did not occur to me at the moment so ended up putting something like __NEWLINE__ in the .env file and then replacing it when reading the values in the Java code.
Thanks for taking the time to go over the Java behavior! I do agree with you that it should stay the same as what System.getenv does.
Maybe we could add something to the README there is a reference to the issue in the future. Coming in, I thought the newlines would just work and took me well over an hour to realize that was the issue I had, given the use case I had.

@cdimascio
Copy link
Owner

Done. I’ve added a note in the FAQ section.

@david-arteaga
Copy link
Author

Just for reference the FAQ section is here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants