diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..82db92a --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,25 @@ +name: Lint + +on: + pull_request: + branches: + - main + +permissions: + contents: read + pull-requests: read + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: '1.20' + cache: false + - name: Lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest + only-new-issues: true \ No newline at end of file diff --git a/cmd/find/find.go b/cmd/find/find.go new file mode 100644 index 0000000..0dc0afd --- /dev/null +++ b/cmd/find/find.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "log" + "os" + + "github.com/sansecio/gocommerce" +) + +func main() { + if len(os.Args) <= 1 { + log.Fatalln("No document root specified.") + } + + store := gocommerce.FindStoreAtRoot(os.Args[1]) + if store == nil { + log.Fatalf("Unable to find store at %s\n", os.Args[1]) + } + + ver, err := store.Platform.Version(os.Args[1]) + if err != nil { + ver = "unknown" + } + fmt.Printf("Found %s (ver: %s) at %s\n", store.Platform.Name(), ver, os.Args[1]) + fmt.Printf("DBC: %+v\n", store.Config.DB) +} diff --git a/fixture/opencart4/admin/index.php b/fixture/opencart4/admin/index.php new file mode 100644 index 0000000..f1e61e3 --- /dev/null +++ b/fixture/opencart4/admin/index.php @@ -0,0 +1,20 @@ +\\s'(\\d)',[^=]+=>\\s'(\\d)',[^=]+=>\\s'(\\d)',[^=]+=>\\s'(\\d)'," ) diff --git a/magento1_config_test.go b/magento1_config_test.go index 17e344d..8a38050 100644 --- a/magento1_config_test.go +++ b/magento1_config_test.go @@ -6,10 +6,9 @@ import ( "github.com/stretchr/testify/assert" ) -var magento1 = Magento1{} - func TestParseConfigSimpleMagento1DB(t *testing.T) { - dbc := dbConfigFromSource(t, fixtureBase+"/magento1/app/etc/local.xml", &magento1) + m1 := Magento1{} + dbc := dbConfigFromSource(t, fixtureBase+"/magento1/app/etc/local.xml", &m1) assert.Equal(t, "localhost", dbc.Host) assert.Equal(t, "mag1", dbc.Name) @@ -20,13 +19,15 @@ func TestParseConfigSimpleMagento1DB(t *testing.T) { } func TestParseConfigHostWithPort(t *testing.T) { - dbc := dbConfigFromSource(t, fixtureBase+"/magento1/configs/app/etc/local.xml.hostport", &magento1) + m1 := Magento1{} + dbc := dbConfigFromSource(t, fixtureBase+"/magento1/configs/app/etc/local.xml.hostport", &m1) assert.Equal(t, "localhost", dbc.Host) assert.Equal(t, 3307, dbc.Port) } func TestParseEmptyPassword(t *testing.T) { - dbc := dbConfigFromSource(t, fixtureBase+"/magento1/configs/app/etc/local.xml.nopass", &magento1) + m1 := Magento1{} + dbc := dbConfigFromSource(t, fixtureBase+"/magento1/configs/app/etc/local.xml.nopass", &m1) assert.Equal(t, "jeroen:@tcp(db:3306)/jeroen_schweigmann?allowOldPasswords=true", dbc.DSN()) } diff --git a/magento2.go b/magento2.go index 2c8bd16..6629c50 100644 --- a/magento2.go +++ b/magento2.go @@ -13,6 +13,11 @@ import ( ) type ( + Magento2 struct { + basePlatform + Magerun string + } + composerRoot struct { Name string `json:"name"` Version string `json:"version"` diff --git a/magento2_config_test.go b/magento2_config_test.go index 538253b..00818c1 100644 --- a/magento2_config_test.go +++ b/magento2_config_test.go @@ -6,10 +6,9 @@ import ( "github.com/stretchr/testify/assert" ) -var magento2 = Magento2{} - func TestParseConfigMultiDB(t *testing.T) { - dbc := dbConfigFromSource(t, fixtureBase+"/magento2/configs/multidb.php", &magento2) + m2 := Magento2{} + dbc := dbConfigFromSource(t, fixtureBase+"/magento2/configs/multidb.php", &m2) assert.Equal(t, "goodhost", dbc.Host) assert.Equal(t, "gooddb", dbc.Name) assert.Equal(t, "gooduser", dbc.User) @@ -19,7 +18,8 @@ func TestParseConfigMultiDB(t *testing.T) { } func TestParseConfigSimpleDB(t *testing.T) { - dbc := dbConfigFromSource(t, fixtureBase+"/magento2/configs/simple.php", &magento2) + m2 := Magento2{} + dbc := dbConfigFromSource(t, fixtureBase+"/magento2/configs/simple.php", &m2) assert.Equal(t, "goodhost", dbc.Host) assert.Equal(t, "gooddb", dbc.Name) assert.Equal(t, "gooduser", dbc.User) @@ -30,37 +30,44 @@ func TestParseConfigSimpleDB(t *testing.T) { } func TestParseConfigEmpty(t *testing.T) { - dbc := dbConfigFromSource(t, fixtureBase+"/magento2/configs/empty.php", &magento2) + m2 := Magento2{} + dbc := dbConfigFromSource(t, fixtureBase+"/magento2/configs/empty.php", &m2) assert.Nil(t, dbc) } func TestActualConfig(t *testing.T) { - dbc := dbConfigFromSource(t, fixtureBase+"/magento2/app/etc/env.php", &magento2) + m2 := Magento2{} + dbc := dbConfigFromSource(t, fixtureBase+"/magento2/app/etc/env.php", &m2) assert.Equal(t, "", dbc.Prefix) assert.Equal(t, "app:sldfjlskdfklds@tcp(localhost:3306)/magento2?allowOldPasswords=true", dbc.DSN()) } func TestCrash1(t *testing.T) { - dbc := dbConfigFromSource(t, fixtureBase+"/magento2/configs/crash1.php", &magento2) + m2 := Magento2{} + dbc := dbConfigFromSource(t, fixtureBase+"/magento2/configs/crash1.php", &m2) assert.Equal(t, "xx:xx@tcp(localhost:3306)/xx?allowOldPasswords=true", dbc.DSN()) } func TestCrash2(t *testing.T) { - dbc := dbConfigFromSource(t, fixtureBase+"/magento2/configs/crash2.php", &magento2) + m2 := Magento2{} + dbc := dbConfigFromSource(t, fixtureBase+"/magento2/configs/crash2.php", &m2) assert.Equal(t, "xx:xx@tcp(10.10.20.39:3306)/xx?allowOldPasswords=true", dbc.DSN()) } func TestPHPParserDoesNotChokeOnListItems(t *testing.T) { - dbc := dbConfigFromSource(t, fixtureBase+"/magento2/configs/crash3.php", &magento2) + m2 := Magento2{} + dbc := dbConfigFromSource(t, fixtureBase+"/magento2/configs/crash3.php", &m2) assert.Equal(t, "myuser:mypass@tcp(myhost:3306)/mydb?allowOldPasswords=true", dbc.DSN()) } func TestPortInHost(t *testing.T) { - dbc := dbConfigFromSource(t, fixtureBase+"/magento2/configs/hostport.php", &magento2) + m2 := Magento2{} + dbc := dbConfigFromSource(t, fixtureBase+"/magento2/configs/hostport.php", &m2) assert.Equal(t, "gooduser:verylongpassword@tcp(goodhost:3309)/gooddb?allowOldPasswords=true", dbc.DSN()) } func TestScanNonExistantFile(t *testing.T) { - dbc := dbConfigFromSource(t, "/do/not/exist", &magento2) + m2 := Magento2{} + dbc := dbConfigFromSource(t, "/do/not/exist", &m2) assert.Nil(t, dbc) } diff --git a/opencart4.go b/opencart4.go new file mode 100644 index 0000000..41c3d78 --- /dev/null +++ b/opencart4.go @@ -0,0 +1,88 @@ +package gocommerce + +import ( + "errors" + "os" + "path/filepath" + "regexp" + "strconv" +) + +type OpenCart4 struct { + basePlatform +} + +var ( + ocURLRgx = regexp.MustCompile(`define\('HTTP_SERVER',\s?'([^']+)'\);`) + ocVerRgx = regexp.MustCompile(`define\('VERSION',\s?'([^']+)'\);`) + ocLookupRgx = map[string]string{ + "host": `define\('DB_HOSTNAME\',\s?'([^']+)'\);`, + "user": `define\('DB_USERNAME\',\s?'([^']+)'\);`, + "pass": `define\('DB_PASSWORD\',\s?'([^']+)'\);`, + "db": `define\('DB_DATABASE\',\s?'([^']+)'\);`, + "port": `define\('DB_PORT\',\s?'([^']+)'\);`, + "prefix": `define\('DB_PREFIX\',\s?'([^']+)'\);`, + } +) + +func (oc4 *OpenCart4) ParseConfig(cfgPath string) (*StoreConfig, error) { + data, err := os.ReadFile(cfgPath) + if err != nil { + return nil, err + } + + matches := map[string]string{} + for k, v := range ocLookupRgx { + m := regexp.MustCompile(v).FindStringSubmatch(string(data)) + if len(m) != 2 { + continue + } + matches[k] = m[1] + } + + port, err := strconv.Atoi(matches["port"]) + if err != nil { + return nil, err + } + + return &StoreConfig{ + DB: &DBConfig{ + Host: matches["host"], + User: matches["user"], + Pass: matches["pass"], + Name: matches["db"], + Prefix: matches["prefix"], + Port: port, + }, + }, nil +} + +func (oc4 *OpenCart4) BaseURLs(docroot string) ([]string, error) { + cfgPath := filepath.Join(docroot, "config.php") + cfg, err := os.ReadFile(cfgPath) + if err != nil { + return nil, err + } + + match := ocURLRgx.FindSubmatch(cfg) + if len(match) < 2 { + return nil, errors.New("base url not found in config") + } + + return []string{string(match[1])}, nil +} + +func (oc4 *OpenCart4) Version(docroot string) (string, error) { + cfgPath := filepath.Join(docroot, "admin", "index.php") + cfg, err := os.ReadFile(cfgPath) + if err != nil { + return "", err + } + + match := ocVerRgx.FindSubmatch(cfg) + if len(match) < 2 { + return "", errors.New("version not found in config") + } + + return string(match[1]), nil +} diff --git a/opencart4_config_test.go b/opencart4_config_test.go new file mode 100644 index 0000000..0f7fb65 --- /dev/null +++ b/opencart4_config_test.go @@ -0,0 +1,28 @@ +package gocommerce + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestOpenCartConfig(t *testing.T) { + oc4 := OpenCart4{} + dbc := dbConfigFromSource(t, fixtureBase+"opencart4/config.php", &oc4) + assert.Equal(t, dbc.DSN(), "root:root@tcp(localhost:3306)/opencart?allowOldPasswords=true") +} + +func TestOpenCartURL(t *testing.T) { + oc4 := OpenCart4{} + urls, err := oc4.BaseURLs(fixtureBase + "opencart4") + assert.NoError(t, err) + assert.NotEmpty(t, urls) + assert.Equal(t, "http://sansec.io/", urls[0]) +} + +func TestOpenCartVersion(t *testing.T) { + oc4 := OpenCart4{} + ver, err := oc4.Version(fixtureBase + "opencart4") + assert.NoError(t, err) + assert.Equal(t, "4.0.2.3", ver) +} diff --git a/phpcfg/phpcfg_test.go b/phpcfg/phpcfg_test.go index 754cd7f..1351367 100644 --- a/phpcfg/phpcfg_test.go +++ b/phpcfg/phpcfg_test.go @@ -1,7 +1,9 @@ package phpcfg -import "testing" -import "github.com/google/go-cmp/cmp" +import ( + "testing" + "github.com/google/go-cmp/cmp" +) func TestParseShortArray(t *testing.T) { src := ` diff --git a/platform.go b/platform.go index b57a833..ff5c6f7 100644 --- a/platform.go +++ b/platform.go @@ -33,32 +33,6 @@ type ( uniquePath string // A relative path that is sufficiently unique to identify a particular platform } - Magento1 struct { - basePlatform - Magerun string - } - - Magento2 struct { - basePlatform - Magerun string - } - - Shopware5 struct { - basePlatform - } - - Shopware6 struct { - basePlatform - } - - Prestashop7 struct { - basePlatform - } - - WooCommerce struct { - basePlatform - } - PlatformInterface interface { Name() string ParseConfig(cfgPath string) (*StoreConfig, error) @@ -69,54 +43,59 @@ type ( } ) -var ( - AllPlatforms = []PlatformInterface{ - &Magento1{ - basePlatform{ - "Magento 1", - "app/etc/local.xml", - "app/etc/local.xml", - }, - "n98-magerun", +var AllPlatforms = []PlatformInterface{ + &Magento1{ + basePlatform{ + "Magento 1", + "app/etc/local.xml", + "app/etc/local.xml", }, - &Magento2{ - basePlatform{ - "Magento 2", - "app/etc/env.php", - "app/etc/env.php", - }, - "n98-magerun2", + "n98-magerun", + }, + &Magento2{ + basePlatform{ + "Magento 2", + "app/etc/env.php", + "app/etc/env.php", }, - &Shopware5{ - basePlatform{ - "Shopware 5", - "config.php", - "engine/Shopware/Application.php", - }, + "n98-magerun2", + }, + &Shopware5{ + basePlatform{ + "Shopware 5", + "config.php", + "engine/Shopware/Application.php", }, - &Shopware6{ - basePlatform{ - "Shopware 6", - ".env", - "vendor/shopware/core/Framework/ShopwareException.php", - }, + }, + &Shopware6{ + basePlatform{ + "Shopware 6", + ".env", + "vendor/shopware/core/Framework/ShopwareException.php", }, - &Prestashop7{ - basePlatform{ - "Prestashop 7 ", - "app/config/parameters.php", - "app/config/parameters.php", - }, + }, + &Prestashop7{ + basePlatform{ + "Prestashop 7", + "app/config/parameters.php", + "app/config/parameters.php", }, - &WooCommerce{ - basePlatform{ - "WooCommerce", - "wp-config.php", - "wp-config.php", - }, + }, + &WooCommerce{ + basePlatform{ + "WooCommerce", + "wp-config.php", + "wp-config.php", }, - } -) + }, + &OpenCart4{ + basePlatform{ + "Prestashop 4", + "config.php", + "system/engine/config.php", + }, + }, +} func (b *basePlatform) Name() string { return b.name diff --git a/prestashop7.go b/prestashop7.go index 037c145..f14fdb6 100644 --- a/prestashop7.go +++ b/prestashop7.go @@ -2,6 +2,10 @@ package gocommerce import "github.com/sansecio/gocommerce/phpcfg" +type Prestashop7 struct { + basePlatform +} + func (p *Prestashop7) ParseConfig(cfgPath string) (*StoreConfig, error) { cm, err := phpcfg.ParsePath(cfgPath) if err != nil { diff --git a/prestashop7_config_test.go b/prestashop7_config_test.go index 8b57654..006b2da 100644 --- a/prestashop7_config_test.go +++ b/prestashop7_config_test.go @@ -6,9 +6,8 @@ import ( "gotest.tools/assert" ) -var prestashop7 = Prestashop7{} - -func TestNormal(t *testing.T) { - dbc := dbConfigFromSource(t, fixtureBase+"/prestashop7/configs/normal.php", &prestashop7) +func TestPrestashopConfig(t *testing.T) { + p7 := Prestashop7{} + dbc := dbConfigFromSource(t, fixtureBase+"/prestashop7/configs/normal.php", &p7) assert.Equal(t, "presta_user:qwerty123@tcp(127.0.0.1:3306)/presta_db?allowOldPasswords=true", dbc.DSN()) } diff --git a/shopware5.go b/shopware5.go index b5cd0fe..d4ae7d9 100644 --- a/shopware5.go +++ b/shopware5.go @@ -1 +1,5 @@ package gocommerce + +type Shopware5 struct { + basePlatform +} diff --git a/shopware6.go b/shopware6.go index 27a4004..2b2d3e1 100644 --- a/shopware6.go +++ b/shopware6.go @@ -7,6 +7,10 @@ import ( "strconv" ) +type Shopware6 struct { + basePlatform +} + const ( // DATABASE_URL=mysql://db-user-1:rhPb5xC2242444mFZDB@localhost:3306/db-1 DBURL = `(?m)^\s*DATABASE_URL\s*="?\s*mysql://(.+?):(.+?)@(.+?):(\d+)/(.+?)"?$` diff --git a/shopware6_config_test.go b/shopware6_config_test.go index b0b4c7d..ebb546e 100644 --- a/shopware6_config_test.go +++ b/shopware6_config_test.go @@ -4,9 +4,8 @@ import ( "testing" ) -var shopware6 = Shopware6{} - func TestConfigToDSN(t *testing.T) { + sw6 := Shopware6{} want := DBConfig{ Host: "localhost", Port: 3306, @@ -15,12 +14,13 @@ func TestConfigToDSN(t *testing.T) { Pass: "rhPb5xC2242444mFZDB", } - if got := dbConfigFromSource(t, fixtureBase+"/shopware6/configs/.env", &shopware6); got.DSN() != want.DSN() { + if got := dbConfigFromSource(t, fixtureBase+"/shopware6/configs/.env", &sw6); got.DSN() != want.DSN() { t.Errorf("ConfigToDSN() = %v, want %v", got, want) } } func TestConfigWithQuotesToDSN(t *testing.T) { + sw6 := Shopware6{} want := DBConfig{ Host: "localhost", Port: 3306, @@ -29,7 +29,7 @@ func TestConfigWithQuotesToDSN(t *testing.T) { Pass: "PASS", } - if got := dbConfigFromSource(t, fixtureBase+"/shopware6/configs/.env.quotes", &shopware6); got.DSN() != want.DSN() { + if got := dbConfigFromSource(t, fixtureBase+"/shopware6/configs/.env.quotes", &sw6); got.DSN() != want.DSN() { t.Errorf("ConfigToDSN() = %v, want %v", got.DSN(), want.DSN()) } } diff --git a/woocommerce.go b/woocommerce.go index c58f356..dab77f1 100644 --- a/woocommerce.go +++ b/woocommerce.go @@ -5,6 +5,10 @@ import ( "regexp" ) +type WooCommerce struct { + basePlatform +} + var wpLookupRgx = map[string]string{ "user": `define\(\s*['"]DB_USER['"]\s*,\s*['"](\S+?)['"]\s*\);`, "pass": `define\(\s*['"]DB_PASSWORD['"]\s*,\s*['"]([^']{0,64})['"]\s*\);`, diff --git a/woocommerce_config_test.go b/woocommerce_config_test.go index 6f64f46..1c97270 100644 --- a/woocommerce_config_test.go +++ b/woocommerce_config_test.go @@ -4,8 +4,6 @@ import ( "testing" ) -var woocommerce = WooCommerce{} - func TestWooCommerceConfigToDSN(t *testing.T) { tests := map[string]DBConfig{ "wp-config.php": { @@ -58,9 +56,10 @@ func TestWooCommerceConfigToDSN(t *testing.T) { }, } + wc := WooCommerce{} for cnf, want := range tests { path := fixtureBase + "/wordpress/configs/" + cnf - got := dbConfigFromSource(t, path, &woocommerce) + got := dbConfigFromSource(t, path, &wc) if got.DSN() != want.DSN() { t.Errorf("%v: ConfigToDSN() = %v, want %v", cnf, got, want) @@ -71,5 +70,4 @@ func TestWooCommerceConfigToDSN(t *testing.T) { } } - }