Skip to content

Commit

Permalink
Test and fix pgpass parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
radeusgd committed Jul 15, 2022
1 parent 38d1c0a commit 9fd1abc
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ read : Text -> Integer -> Text -> [Pair Text Text]
read host port database =
pgpass_file = locate
if pgpass_file.is_nothing || (verify pgpass_file . not) then [] else
entries = parse pgpass_file
entries = parse_file pgpass_file
# TODO possibly? determine username from env
found = entries.find entry->
entry.matches host port database
case found.catch Nothing of
Nothing -> []
Just entry -> [Pair 'user' entry.username, Pair 'password' entry.password]
entry -> [Pair 'user' entry.username, Pair 'password' entry.password]

type Pgpass_Entry
## PRIVATE
Expand All @@ -45,22 +45,22 @@ type Pgpass_Entry
matches host port database username=Nothing =
wildcard='*'
host_match = self.host==wildcard || self.host==host
port_match = self.port==wildcard || self.port==port
port_match = self.port==wildcard || self.port==port.to_text
database_match = self.database==wildcard || self.database==database
username_match = username==Nothing || self.username==wildcard || self.username=username
host_match && port_match && database_match && username_match

## PRIVATE
Determines the location of the .pgpass file to use.
locate = case Environment.get "PGPASSFILE" of
path -> File.new path
Nothing -> case Platform.os of
Platform.Windows -> case Environment.get "APPDATA" of
Nothing -> Nothing
appdata -> File.new appdata / "postgresql" / "pgpass.conf"
_ -> case Environment.get "HOME" of
Nothing -> Nothing
home -> File.new home / ".pgpass"
path -> File.new path

## PRIVATE
Checks if the given .pgpass file can be used.
Expand All @@ -77,11 +77,11 @@ verify file = case Platform.os of
only_owner_can_access

## PRIVATE
parse file =
parse_file file =
parse line =
if line.starts_with "#" || line.is_empty then Nothing else
elements = parse_line line
if elements.length < 5 then Nothing else
if elements.length != 5 then Nothing else
Pgpass_Entry (elements.at 0) (elements.at 1) (elements.at 2) (elements.at 3) (elements.at 4)

File.read_text file . lines . map parse . filter (x -> x.is_nothing.not)
Expand All @@ -93,10 +93,15 @@ parse_line line =
next_entry =
existing_entries.append current_entry.toString
current_entry.setLength 0
characters = line.characters
go ix is_escape = case ix>=line.length of
True -> next_entry
True ->
if is_escape then
# Handle the trailing escape character.
current_entry.append '\\'
next_entry
False ->
c = line.charAt ix
c = characters.at ix
case c=='\\' of
True ->
if is_escape then
Expand All @@ -109,6 +114,9 @@ parse_line line =
False -> next_entry
go (ix+1) False
False ->
if is_escape then
# Handle escape character followed by other characters.
current_entry.append '\\'
# Any other character is just appended and escape is reset.
current_entry.append c
go (ix+1) False
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static <T> T with_environment_variable_override(
String oldValue = overrides.put(name, value);
boolean was_set = oldValue != null;
try {
// Giving 0 here as an argument, as using null would lead to incorrect behaviour.
// Giving 0 here as an argument, as using null would lead to incorrect behaviour, due to some weird Truffle peculiarity.
return action.apply(0);
} finally {
if (was_set) {
Expand Down
24 changes: 24 additions & 0 deletions test/Table_Tests/data/pgpass.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#hostname:port:database:username:password
localhost:5432:postgres:postgres:postgres
192.168.4.0:1234:foo:bar:baz
#some interesting comment

host with \: semicolons in it? what?:*:*:*:well yes, that is possible, the \:password\: can contain those as well

\::\::\::\::\:

::wrong amount of entries is skipped

:::::::::::

you can escape an escape too\: see \\\\:*:*:*:yes it is possible
other escapes like \n or \? :*:*:*:are just parsed as-is
a trailing escape character:*:*:*:is treated as a regular slash\
passwords should preserve leading space:*:*:*: pass
\\\::*:*:*:\\\:
#example.com:443:foo:bar:baz

\:\:1:*:database_name:user_that_has_no_password:

*:*:*:*:fallback_password
order_matters:1234:this:will_still_match_the_fallback_password:not_this_one
20 changes: 19 additions & 1 deletion test/Table_Tests/src/Database/Postgresql_Spec.enso
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from Standard.Base import all
import Standard.Base.System.Environment
import Standard.Base.Runtime.Ref
import Standard.Base.System

from Standard.Database import all
from Standard.Database.Connection.Connection import Sql_Error
Expand All @@ -12,6 +13,7 @@ import project.Common_Table_Spec
import project.Aggregate_Spec
from Standard.Table.Data.Aggregate_Column import all
from Standard.Database.Data.Sql import Sql_Type
from Standard.Database.Internal.Postgres.Pgpass import Pgpass_Entry

postgres_specific_spec connection pending =
Test.group "[PostgreSQL] Info" pending=pending <|
Expand Down Expand Up @@ -134,7 +136,23 @@ connection_setup_spec = Test.group "[PostgreSQL] Connection setup" <|

pgpass_spec = Test.group "[PostgreSQL] .pgpass" <|
Test.specify "should correctly parse the file, including escapes, blank lines and comments" <|
Nothing
pgpass_file = enso_project.data / "pgpass.conf"
result = Pgpass.parse_file pgpass_file
result.length . should_equal 12
e1 = Pgpass_Entry "localhost" "5432" "postgres" "postgres" "postgres"
e2 = Pgpass_Entry "192.168.4.0" "1234" "foo" "bar" "baz"
e3 = Pgpass_Entry "host with : semicolons in it? what?" "*" "*" "*" "well yes, that is possible, the :password: can contain those as well"
e4 = Pgpass_Entry ":" ":" ":" ":" ":"
e5 = Pgpass_Entry "you can escape an escape too: see \\" "*" "*" "*" "yes it is possible"
e6 = Pgpass_Entry "other escapes like \n or \? " "*" "*" "*" "are just parsed as-is"
e7 = Pgpass_Entry "a trailing escape character" "*" "*" "*" "is treated as a regular slash\"
e8 = Pgpass_Entry "passwords should preserve leading space" "*" "*" "*" " pass"
e9 = Pgpass_Entry "\:" "*" "*" "*" "\:"
e10 = Pgpass_Entry "::1" "*" "database_name" "user_that_has_no_password" ""
e11 = Pgpass_Entry "*" "*" "*" "*" "fallback_password"
e12 = Pgpass_Entry "order_matters" "1234" "this" "will_still_match_the_fallback_password" "not_this_one"
entries = [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12]
result.should_equal entries

Test.specify "should correctly match wildcards and use the first matching entry" <|
Nothing
Expand Down

0 comments on commit 9fd1abc

Please sign in to comment.