diff --git a/Gopkg.lock b/Gopkg.lock index 157d8f50..4795297b 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -7,7 +7,7 @@ name = "github.com/bitrise-io/bitrise" packages = ["models"] pruneopts = "UT" - revision = "f7458dd6a6c5612fd3e92ab021fb7f18a7d0abc4" + revision = "85fcf1f508cb6d92ddd2ddf788988fde6cf5178f" [[projects]] branch = "master" @@ -15,7 +15,7 @@ name = "github.com/bitrise-io/envman" packages = ["models"] pruneopts = "UT" - revision = "eb2e53257e527de59351cec1ecdffb50dadd5a07" + revision = "b62ffe18bb760bf14002dd1a80b059180ef910f1" [[projects]] branch = "master" @@ -38,7 +38,7 @@ [[projects]] branch = "master" - digest = "1:390a96a47623fbafd79a9bb015b58444134210db7859647235ad98b94c2f07af" + digest = "1:c752648f9ec1c0f43af891ef888a43831d99573e4cabec34b89a74f0e1497e9f" name = "github.com/bitrise-io/go-utils" packages = [ "colorstring", @@ -56,11 +56,11 @@ "ziputil", ] pruneopts = "UT" - revision = "7a4402b387ebcdf3e2a0aa3b18d4c8636768f9a0" + revision = "cb35044c1bab53fdbfa1e6ea3b56478c25275c9c" [[projects]] branch = "master" - digest = "1:50b4bd882c227361587748d6b941fd19d1fc2ba9147ee256b69f587a4b6b81d0" + digest = "1:b56f4c1e2f6834da9549215c3c56b0e590cf04e8a400928a2e079ac4c0322187" name = "github.com/bitrise-io/go-xcode" packages = [ "certificateutil", @@ -70,9 +70,10 @@ "plistutil", "profileutil", "utility", + "xcarchive", ] pruneopts = "UT" - revision = "c1544b4453f2869335f71fb53c0360bdb91d01b4" + revision = "07ccf582b6b1afa2c33e133447a3e22c811ffd90" [[projects]] branch = "master" @@ -80,7 +81,7 @@ name = "github.com/bitrise-io/stepman" packages = ["models"] pruneopts = "UT" - revision = "af0e468635253eaf3a66dca748af4a5966d161b1" + revision = "605a625dade4553d2e2c5dcc9e3459844cd2f5b4" [[projects]] branch = "master" @@ -95,8 +96,8 @@ name = "github.com/bitrise-steplib/steps-xcode-test" packages = ["pretty"] pruneopts = "UT" - revision = "4f55fd573bc53b2c355baaeabc116cd5bf294d5a" - version = "2.3.1" + revision = "c05bd3a50ee36040bd047cf4618615083a9d0b2c" + version = "2.3.2" [[projects]] branch = "master" @@ -107,12 +108,12 @@ revision = "d7302db945fa6ea264fb79d8e13e931ea514a602" [[projects]] - digest = "1:3af6be4fee7c08f81f13d36f04ffb63ad4b6b5aaba12cce96095c7c2863d4912" + digest = "1:cbec35fe4d5a4fba369a656a8cd65e244ea2c743007d8f6c1ccb132acf9d1296" name = "github.com/gorilla/mux" packages = ["."] pruneopts = "UT" - revision = "ed099d42384823742bba0bf9a72b53b55c9e2e38" - version = "v1.7.2" + revision = "00bdffe0f3c77e27d2cf6f5c70232a2d3e4d9c15" + version = "v1.7.3" [[projects]] digest = "1:88e0b0baeb9072f0a4afbcf12dda615fc8be001d1802357538591155998da21b" @@ -123,20 +124,20 @@ version = "v1.2.0" [[projects]] - digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b" + branch = "master" + digest = "1:deff41c4160cc5b487a1d1a69a58925f2d3ac11befa0692b50e76940594e20f5" name = "github.com/pkg/errors" packages = ["."] pruneopts = "UT" - revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" - version = "v0.8.1" + revision = "27936f6d90f9c8e1145f11ed52ffffbfdb9e0af7" [[projects]] - branch = "master" digest = "1:6baa565fe16f8657cf93469b2b8a6c61a277827734400d27e44d589547297279" name = "github.com/ryanuber/go-glob" packages = ["."] pruneopts = "UT" revision = "51a8f68e6c24dc43f1e371749c89a267de4ebc53" + version = "v1.0.0" [[projects]] branch = "master" @@ -168,6 +169,7 @@ "github.com/bitrise-io/go-xcode/plistutil", "github.com/bitrise-io/go-xcode/profileutil", "github.com/bitrise-io/go-xcode/utility", + "github.com/bitrise-io/go-xcode/xcarchive", "github.com/bitrise-io/xcode-project/serialized", "github.com/bitrise-steplib/steps-xcode-test/pretty", "github.com/gorilla/mux", diff --git a/Gopkg.toml b/Gopkg.toml index 66e71cb3..ceab61cc 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -24,8 +24,8 @@ version = "1.7.2" [[constraint]] + branch = "master" name = "github.com/pkg/errors" - version = "0.8.1" [[constraint]] branch = "master" diff --git a/bitrise.yml b/bitrise.yml index a2aadc3d..ac5905dc 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -51,6 +51,17 @@ workflows: - notify_user_groups: $NOTIFY_USER_GROUPS - notify_email_list: $NOTIFY_EMAIL_LIST - is_enable_public_page: "true" + - path::./: + title: IOS Test XcArchive + run_if: true + inputs: + - build_url: $BITRISE_BUILD_URL + - build_api_token: $BITRISE_BUILD_API_TOKEN + - is_compress: "false" + - deploy_path: ./archives + - notify_user_groups: $NOTIFY_USER_GROUPS + - notify_email_list: $NOTIFY_EMAIL_LIST + - is_enable_public_page: "true" - path::./: title: IOS Test compressed with default name run_if: true diff --git a/main.go b/main.go index a97479e1..3ed15e16 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "html/template" "os" "path/filepath" + "strings" "github.com/bitrise-steplib/steps-deploy-to-bitrise-io/test" @@ -44,6 +45,8 @@ type PublicInstallPage struct { URL string } +const zippedXcarchiveExt = ".xcarchive.zip" + func fail(format string, v ...interface{}) { log.Errorf(format, v...) os.Exit(1) @@ -54,14 +57,13 @@ func main() { if err := stepconf.Parse(&config); err != nil { fail("Issue with input: %s", err) } + if err := validateGoTemplate(config.PublicInstallPageMapFormat); err != nil { fail("PublicInstallPageMapFormat - %s", err) } stepconf.Print(config) - fmt.Println() - log.SetEnableDebugLog(config.DebugMode) absDeployPth, err := pathutil.AbsPath(config.DeployPath) @@ -69,17 +71,97 @@ func main() { fail("Failed to expand path: %s, error: %s", config.DeployPath, err) } - filesToDeploy := []string{} - tmpDir, err := pathutil.NormalizedOSTempDirPath("__deploy-to-bitrise-io__") if err != nil { fail("Failed to create tmp dir, error: %s", err) } - // Collect files to deploy + filesToDeploy, err := collectFilesToDeploy(absDeployPth, config, tmpDir) + if err != nil { + fail("%s", err) + } + clearedFilesToDeploy := clearDeployFiles(filesToDeploy) + fmt.Println() + log.Infof("List of files to deploy") + logDeployFiles(clearedFilesToDeploy) + + fmt.Println() + log.Infof("Deploying files") + + publicInstallPages, err := deploy(clearedFilesToDeploy, config) + if err != nil { + fail("%s", err) + } + fmt.Println() + log.Donef("Success") + log.Printf("You can find the Artifact on Bitrise, on the Build's page: %s", config.BuildURL) + + if err := exportInstallPages(publicInstallPages, config); err != nil { + fail("%s", err) + } + deployTestResults(config) +} + +func exportInstallPages(publicInstallPages map[string]string, config Config) error { + if len(publicInstallPages) > 0 { + temp := template.New("Public Install Page template") + var pages []PublicInstallPage + for file, url := range publicInstallPages { + pages = append(pages, PublicInstallPage{ + File: file, + URL: url, + }) + } + + if err := tools.ExportEnvironmentWithEnvman("BITRISE_PUBLIC_INSTALL_PAGE_URL", pages[0].URL); err != nil { + return fmt.Errorf("failed to export BITRISE_PUBLIC_INSTALL_PAGE_URL, error: %s", err) + } + log.Printf("The public install page url is now available in the Environment Variable: BITRISE_PUBLIC_INSTALL_PAGE_URL (value: %s)\n", pages[0].URL) + + temp, err := temp.Parse(config.PublicInstallPageMapFormat) + if err != nil { + return fmt.Errorf("error during parsing PublicInstallPageMap: %s", err) + } + + buf := new(bytes.Buffer) + if err := temp.Execute(buf, pages); err != nil { + return fmt.Errorf("execute: %s", err) + } + + if err := tools.ExportEnvironmentWithEnvman("BITRISE_PUBLIC_INSTALL_PAGE_URL_MAP", buf.String()); err != nil { + return fmt.Errorf("failed to export BITRISE_PUBLIC_INSTALL_PAGE_URL_MAP, error: %s", err) + } + log.Printf("A map of deployed files and their public install page urls is now available in the Environment Variable: BITRISE_PUBLIC_INSTALL_PAGE_URL_MAP (value: %s)", buf.String()) + log.Printf("") + } + return nil +} + +func logDeployFiles(clearedFilesToDeploy []string) { + for _, pth := range clearedFilesToDeploy { + log.Printf("- %s", pth) + } +} + +func clearDeployFiles(filesToDeploy []string) []string { + var clearedFilesToDeploy []string + for _, pth := range filesToDeploy { + for _, fileBaseNameToSkip := range fileBaseNamesToSkip { + if filepath.Base(pth) == fileBaseNameToSkip { + log.Warnf("skipping: %s", pth) + } else { + clearedFilesToDeploy = append(clearedFilesToDeploy, pth) + } + + } + } + return clearedFilesToDeploy +} + +func collectFilesToDeploy(absDeployPth string, config Config, tmpDir string) (filesToDeploy []string, err error) { isDeployPathDir, err := pathutil.IsDirExists(absDeployPth) if err != nil { - fail("Failed to check if DeployPath (%s) is a directory or a file, error: %s", absDeployPth, err) + return nil, fmt.Errorf("failed to check if DeployPath (%s) is a directory or a file, error: %s", absDeployPth, err) } if !isDeployPathDir { @@ -98,7 +180,7 @@ func main() { tmpZipPath := filepath.Join(tmpDir, zipName+".zip") if err := ziputil.ZipDir(absDeployPth, tmpZipPath, true); err != nil { - fail("Failed to zip output dir, error: %s", err) + return nil, fmt.Errorf("failed to zip output dir, error: %s", err) } filesToDeploy = []string{tmpZipPath} @@ -109,55 +191,55 @@ func main() { pattern := filepath.Join(absDeployPth, "*") pths, err := filepath.Glob(pattern) if err != nil { - fail("Failed to list files in DeployPath, error: %s", err) + return nil, fmt.Errorf("failed to list files in DeployPath, error: %s", err) } for _, pth := range pths { if isDir, err := pathutil.IsDirExists(pth); err != nil { - fail("Failed to check if path (%s) is a directory or a file, error: %s", pth, err) + return nil, fmt.Errorf("failed to check if path (%s) is a directory or a file, error: %s", pth, err) } else if !isDir { filesToDeploy = append(filesToDeploy, pth) } } } - clearedFilesToDeploy := []string{} - for _, pth := range filesToDeploy { - for _, fileBaseNameToSkip := range fileBaseNamesToSkip { - if filepath.Base(pth) == fileBaseNameToSkip { - log.Warnf("skipping: %s", pth) + return filesToDeploy, nil +} + +func deployTestResults(config Config) { + if config.AddonAPIToken != "" { + fmt.Println() + log.Infof("Upload test results") + + testResults, err := test.ParseTestResults(config.TestDeployDir) + if err != nil { + log.Warnf("error during parsing test results: ", err) + } else { + log.Printf("- uploading (%d) test results", len(testResults)) + + if err := testResults.Upload(config.AddonAPIToken, config.AddonAPIBaseURL, config.AppSlug, config.BuildSlug); err != nil { + log.Warnf("Failed to upload test results: ", err) } else { - clearedFilesToDeploy = append(clearedFilesToDeploy, pth) + log.Donef("Success") } - } } +} - fmt.Println() - log.Infof("List of files to deploy") - for _, pth := range clearedFilesToDeploy { - log.Printf("- %s", pth) - } - // --- - - // Deploy files - fmt.Println() - log.Infof("Deploying files") - +func deploy(clearedFilesToDeploy []string, config Config) (map[string]string, error) { publicInstallPages := make(map[string]string) - for _, pth := range clearedFilesToDeploy { - ext := filepath.Ext(pth) + fileType := getFileType(pth) fmt.Println() - switch ext { + switch fileType { case ".ipa": log.Donef("Uploading ipa file: %s", pth) installPage, err := uploaders.DeployIPA(pth, config.BuildURL, config.APIToken, config.NotifyUserGroups, config.NotifyEmailList, config.IsPublicPageEnabled) if err != nil { - fail("Deploy failed, error: %s", err) + return nil, fmt.Errorf("deploy failed, error: %s", err) } if installPage != "" { @@ -168,7 +250,7 @@ func main() { installPage, err := uploaders.DeployAPK(pth, config.BuildURL, config.APIToken, config.NotifyUserGroups, config.NotifyEmailList, config.IsPublicPageEnabled) if err != nil { - fail("Deploy failed, error: %s", err) + return nil, fmt.Errorf("deploy failed, error: %s", err) } if installPage != "" { @@ -179,18 +261,23 @@ func main() { installPage, err := uploaders.DeployAAB(pth, config.BuildURL, config.APIToken, config.NotifyUserGroups, config.NotifyEmailList, config.IsPublicPageEnabled) if err != nil { - fail("Deploy failed, error: %s", err) + return nil, fmt.Errorf("deploy failed, error: %s", err) } if installPage != "" { publicInstallPages[filepath.Base(pth)] = installPage } + case zippedXcarchiveExt: + log.Donef("Uploading xcarchive file: %s", pth) + if err := uploaders.DeployXcarchive(pth, config.BuildURL, config.APIToken); err != nil { + return nil, fmt.Errorf("deploy failed, error: %s", err) + } default: log.Donef("Uploading file: %s", pth) installPage, err := uploaders.DeployFile(pth, config.BuildURL, config.APIToken, config.NotifyUserGroups, config.NotifyEmailList, config.IsPublicPageEnabled) if err != nil { - fail("Deploy failed, error: %s", err) + return nil, fmt.Errorf("deploy failed, error: %s", err) } if installPage != "" { @@ -200,61 +287,14 @@ func main() { } } } + return publicInstallPages, nil +} - fmt.Println() - log.Donef("Success") - log.Printf("You can find the Artifact on Bitrise, on the Build's page: %s", config.BuildURL) - - if len(publicInstallPages) > 0 { - temp := template.New("Public Install Page template") - var pages []PublicInstallPage - for file, url := range publicInstallPages { - pages = append(pages, PublicInstallPage{ - File: file, - URL: url, - }) - } - - if err := tools.ExportEnvironmentWithEnvman("BITRISE_PUBLIC_INSTALL_PAGE_URL", pages[0].URL); err != nil { - fail("Failed to export BITRISE_PUBLIC_INSTALL_PAGE_URL, error: %s", err) - } - log.Printf("The public install page url is now available in the Environment Variable: BITRISE_PUBLIC_INSTALL_PAGE_URL (value: %s)\n", pages[0].URL) - - temp, err := temp.Parse(config.PublicInstallPageMapFormat) - if err != nil { - fail("Error during parsing PublicInstallPageMap: ", err) - } - - buf := new(bytes.Buffer) - if err := temp.Execute(buf, pages); err != nil { - fail("Execute: ", err) - } - - if err := tools.ExportEnvironmentWithEnvman("BITRISE_PUBLIC_INSTALL_PAGE_URL_MAP", buf.String()); err != nil { - fail("Failed to export BITRISE_PUBLIC_INSTALL_PAGE_URL_MAP, error: %s", err) - } - log.Printf("A map of deployed files and their public install page urls is now available in the Environment Variable: BITRISE_PUBLIC_INSTALL_PAGE_URL_MAP (value: %s)", buf.String()) - log.Printf("") - } - - // Deploy test files - if config.AddonAPIToken != "" { - fmt.Println() - log.Infof("Upload test results") - - testResults, err := test.ParseTestResults(config.TestDeployDir) - if err != nil { - log.Warnf("Error during parsing test results: ", err) - } else { - log.Printf("- uploading (%d) test results", len(testResults)) - - if err := testResults.Upload(config.AddonAPIToken, config.AddonAPIBaseURL, config.AppSlug, config.BuildSlug); err != nil { - log.Warnf("Failed to upload test results: ", err) - } else { - log.Donef("Success") - } - } +func getFileType(pth string) string { + if strings.HasSuffix(pth, zippedXcarchiveExt) { + return zippedXcarchiveExt } + return filepath.Ext(pth) } func validateGoTemplate(publicInstallPageMapFormat string) error { diff --git a/uploaders/xcarchiveuploader.go b/uploaders/xcarchiveuploader.go new file mode 100644 index 00000000..4adeb028 --- /dev/null +++ b/uploaders/xcarchiveuploader.go @@ -0,0 +1,91 @@ +package uploaders + +import ( + "encoding/json" + "fmt" + "path/filepath" + + "github.com/bitrise-io/go-utils/pathutil" + + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-xcode/plistutil" + "github.com/bitrise-io/go-xcode/xcarchive" +) + +// DeployXcarchive ... +func DeployXcarchive(pth, buildURL, token string) error { + log.Printf("analyzing xcarchive") + unzippedPth, err := xcarchive.UnzipXcarchive(pth) + if err != nil { + return err + } + + ismacos, err := xcarchive.IsMacOS(filepath.Join(unzippedPth, pathutil.GetFileName(pth))) + if err != nil { + return fmt.Errorf("could not check if given project is macOS or not, error: %s", err) + } + // MacOS project is not supported, so won't be deployed. + if ismacos { + return nil + } + + infoPlistPth, err := xcarchive.GetEmbeddedInfoPlistPath(filepath.Join(unzippedPth, pathutil.GetFileName(pth))) + if err != nil { + return fmt.Errorf("failed to unwrap Info.plist from xcarchive, error: %s", err) + } + + infoPlistData, err := plistutil.NewPlistDataFromFile(infoPlistPth) + if err != nil { + return fmt.Errorf("failed to parse Info.plist, error: %s", err) + } + + appTitle, _ := infoPlistData.GetString("CFBundleName") + bundleID, _ := infoPlistData.GetString("CFBundleIdentifier") + version, _ := infoPlistData.GetString("CFBundleShortVersionString") + buildNumber, _ := infoPlistData.GetString("CFBundleVersion") + minOSVersion, _ := infoPlistData.GetString("MinimumOSVersion") + deviceFamilyList, _ := infoPlistData.GetUInt64Array("UIDeviceFamily") + + appInfo := map[string]interface{}{ + "app_title": appTitle, + "bundle_id": bundleID, + "version": version, + "build_number": buildNumber, + "min_OS_version": minOSVersion, + "device_family_list": deviceFamilyList, + } + + // --- + + fileSize, err := fileSizeInBytes(pth) + if err != nil { + return fmt.Errorf("failed to get xcarchive size, error: %s", err) + } + + xcarchiveInfoMap := map[string]interface{}{ + "file_size_bytes": fmt.Sprintf("%f", fileSize), + "app_info": appInfo, + } + + artifactInfoBytes, err := json.Marshal(xcarchiveInfoMap) + if err != nil { + return fmt.Errorf("failed to marshal xcarchive infos, error: %s", err) + } + + log.Printf(" xcarchive infos: %v", appInfo) + + uploadURL, artifactID, err := createArtifact(buildURL, token, pth, "ios-xcarchive") + if err != nil { + return fmt.Errorf("failed to create xcarchive artifact, error: %s", err) + } + + if err := uploadArtifact(uploadURL, pth, ""); err != nil { + return fmt.Errorf("failed to upload xcarchive artifact, error: %s", err) + } + + _, err = finishArtifact(buildURL, token, artifactID, string(artifactInfoBytes), "", "", "false") + if err != nil { + return fmt.Errorf("failed to finish xcarchive artifact, error: %s", err) + } + return nil +} diff --git a/vendor/github.com/bitrise-io/go-utils/pathutil/pathutil.go b/vendor/github.com/bitrise-io/go-utils/pathutil/pathutil.go index 1ef74f93..a88ff5f0 100644 --- a/vendor/github.com/bitrise-io/go-utils/pathutil/pathutil.go +++ b/vendor/github.com/bitrise-io/go-utils/pathutil/pathutil.go @@ -179,3 +179,8 @@ func NormalizedOSTempDirPath(tmpDirNamePrefix string) (retPth string, err error) } return } + +// GetFileName returns the name of the file from a given path or the name of the directory if it is a directory +func GetFileName(path string) string { + return strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) +} diff --git a/vendor/github.com/bitrise-io/go-utils/pkcs12/LICENSE b/vendor/github.com/bitrise-io/go-utils/pkcs12/LICENSE new file mode 100644 index 00000000..bcecd3d9 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/pkcs12/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2015, 2018, 2019 Opsmate, Inc. All rights reserved. +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/bitrise-io/go-utils/pkcs12/README.md b/vendor/github.com/bitrise-io/go-utils/pkcs12/README.md new file mode 100644 index 00000000..f10f9f10 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/pkcs12/README.md @@ -0,0 +1,35 @@ +# package pkcs12 + +[![GoDoc](https://godoc.org/software.sslmate.com/src/go-pkcs12?status.svg)](https://godoc.org/software.sslmate.com/src/go-pkcs12) + + import "software.sslmate.com/src/go-pkcs12" + +Package pkcs12 implements some of PKCS#12 (also known as P12 or PFX). +It is intended for decoding P12/PFX files for use with the `crypto/tls` +package, and for encoding P12/PFX files for use by legacy applications which +do not support newer formats. Since PKCS#12 uses weak encryption +primitives, it SHOULD NOT be used for new applications. + +This package is forked from `golang.org/x/crypto/pkcs12`, which is frozen. +The implementation is distilled from https://tools.ietf.org/html/rfc7292 +and referenced documents. + +This repository holds supplementary Go cryptography libraries. + +## Import Path + +Note that although the source code and issue tracker for this package are hosted +on GitHub, the import path is: + + software.sslmate.com/src/go-pkcs12 + +Please be sure to use this path when you `go get` and `import` this package. + +## Download/Install + +The easiest way to install is to run `go get -u software.sslmate.com/src/go-pkcs12`. You +can also manually git clone the repository to `$GOPATH/src/software.sslmate.com/src/go-pkcs12`. + +## Report Issues / Send Patches + +Open an issue or PR at https://github.com/SSLMate/go-pkcs12 diff --git a/vendor/github.com/bitrise-io/go-utils/pkcs12/bmp-string.go b/vendor/github.com/bitrise-io/go-utils/pkcs12/bmp-string.go old mode 100755 new mode 100644 diff --git a/vendor/github.com/bitrise-io/go-utils/pkcs12/crypto.go b/vendor/github.com/bitrise-io/go-utils/pkcs12/crypto.go old mode 100755 new mode 100644 index 07423e35..30f93389 --- a/vendor/github.com/bitrise-io/go-utils/pkcs12/crypto.go +++ b/vendor/github.com/bitrise-io/go-utils/pkcs12/crypto.go @@ -1,3 +1,4 @@ +// Copyright 2015, 2018, 2019 Opsmate, Inc. All rights reserved. // Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -63,7 +64,7 @@ type pbeParams struct { Iterations int } -func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.BlockMode, int, error) { +func pbeCipherFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.Block, []byte, error) { var cipherType pbeCipher switch { @@ -72,18 +73,27 @@ func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher case algorithm.Algorithm.Equal(oidPBEWithSHAAnd40BitRC2CBC): cipherType = shaWith40BitRC2CBC{} default: - return nil, 0, NotImplementedError("algorithm " + algorithm.Algorithm.String() + " is not supported") + return nil, nil, NotImplementedError("algorithm " + algorithm.Algorithm.String() + " is not supported") } var params pbeParams if err := unmarshal(algorithm.Parameters.FullBytes, ¶ms); err != nil { - return nil, 0, err + return nil, nil, err } key := cipherType.deriveKey(params.Salt, password, params.Iterations) iv := cipherType.deriveIV(params.Salt, password, params.Iterations) block, err := cipherType.create(key) + if err != nil { + return nil, nil, err + } + + return block, iv, nil +} + +func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.BlockMode, int, error) { + block, iv, err := pbeCipherFor(algorithm, password) if err != nil { return nil, 0, err } @@ -129,3 +139,35 @@ type decryptable interface { Algorithm() pkix.AlgorithmIdentifier Data() []byte } + +func pbEncrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.BlockMode, int, error) { + block, iv, err := pbeCipherFor(algorithm, password) + if err != nil { + return nil, 0, err + } + + return cipher.NewCBCEncrypter(block, iv), block.BlockSize(), nil +} + +func pbEncrypt(info encryptable, decrypted []byte, password []byte) error { + cbc, blockSize, err := pbEncrypterFor(info.Algorithm(), password) + if err != nil { + return err + } + + psLen := blockSize - len(decrypted)%blockSize + encrypted := make([]byte, len(decrypted)+psLen) + copy(encrypted[:len(decrypted)], decrypted) + copy(encrypted[len(decrypted):], bytes.Repeat([]byte{byte(psLen)}, psLen)) + cbc.CryptBlocks(encrypted, encrypted) + + info.SetData(encrypted) + + return nil +} + +// encryptable abstracts a object that contains ciphertext. +type encryptable interface { + Algorithm() pkix.AlgorithmIdentifier + SetData([]byte) +} diff --git a/vendor/github.com/bitrise-io/go-utils/pkcs12/errors.go b/vendor/github.com/bitrise-io/go-utils/pkcs12/errors.go old mode 100755 new mode 100644 diff --git a/vendor/github.com/bitrise-io/go-utils/pkcs12/internal/rc2/rc2.go b/vendor/github.com/bitrise-io/go-utils/pkcs12/internal/rc2/rc2.go old mode 100755 new mode 100644 index 8c709025..7499e3fb --- a/vendor/github.com/bitrise-io/go-utils/pkcs12/internal/rc2/rc2.go +++ b/vendor/github.com/bitrise-io/go-utils/pkcs12/internal/rc2/rc2.go @@ -122,7 +122,6 @@ func (c *rc2Cipher) Encrypt(dst, src []byte) { r3 = r3 + c.k[r2&63] for j <= 40 { - // mix r0 r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) r0 = rotl16(r0, 1) @@ -151,7 +150,6 @@ func (c *rc2Cipher) Encrypt(dst, src []byte) { r3 = r3 + c.k[r2&63] for j <= 60 { - // mix r0 r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) r0 = rotl16(r0, 1) @@ -244,7 +242,6 @@ func (c *rc2Cipher) Decrypt(dst, src []byte) { r0 = r0 - c.k[r3&63] for j >= 0 { - // unmix r3 r3 = rotl16(r3, 16-5) r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) diff --git a/vendor/github.com/bitrise-io/go-utils/pkcs12/mac.go b/vendor/github.com/bitrise-io/go-utils/pkcs12/mac.go old mode 100755 new mode 100644 index 5f38aa7d..b7b05de1 --- a/vendor/github.com/bitrise-io/go-utils/pkcs12/mac.go +++ b/vendor/github.com/bitrise-io/go-utils/pkcs12/mac.go @@ -1,3 +1,4 @@ +// Copyright 2015, 2018, 2019 Opsmate, Inc. All rights reserved. // Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -43,3 +44,17 @@ func verifyMac(macData *macData, message, password []byte) error { } return nil } + +func computeMac(macData *macData, message, password []byte) error { + if !macData.Mac.Algorithm.Algorithm.Equal(oidSHA1) { + return NotImplementedError("unknown digest algorithm: " + macData.Mac.Algorithm.Algorithm.String()) + } + + key := pbkdf(sha1Sum, 20, 64, macData.MacSalt, password, macData.Iterations, 3, 20) + + mac := hmac.New(sha1.New, key) + mac.Write(message) + macData.Mac.Digest = mac.Sum(nil) + + return nil +} diff --git a/vendor/github.com/bitrise-io/go-utils/pkcs12/pbkdf.go b/vendor/github.com/bitrise-io/go-utils/pkcs12/pbkdf.go old mode 100755 new mode 100644 diff --git a/vendor/github.com/bitrise-io/go-utils/pkcs12/pkcs12.go b/vendor/github.com/bitrise-io/go-utils/pkcs12/pkcs12.go old mode 100755 new mode 100644 index 51820daa..c257a3eb --- a/vendor/github.com/bitrise-io/go-utils/pkcs12/pkcs12.go +++ b/vendor/github.com/bitrise-io/go-utils/pkcs12/pkcs12.go @@ -1,25 +1,38 @@ +// Copyright 2015, 2018, 2019 Opsmate, Inc. All rights reserved. // Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package pkcs12 implements some of PKCS#12. +// Package pkcs12 implements some of PKCS#12 (also known as P12 or PFX). +// It is intended for decoding P12/PFX files for use with the crypto/tls +// package, and for encoding P12/PFX files for use by legacy applications which +// do not support newer formats. Since PKCS#12 uses weak encryption +// primitives, it SHOULD NOT be used for new applications. // -// This implementation is distilled from https://tools.ietf.org/html/rfc7292 -// and referenced documents. It is intended for decoding P12/PFX-stored -// certificates and keys for use with the crypto/tls package. +// This package is forked from golang.org/x/crypto/pkcs12, which is frozen. +// The implementation is distilled from https://tools.ietf.org/html/rfc7292 +// and referenced documents. package pkcs12 import ( "crypto/ecdsa" "crypto/rsa" + "crypto/sha1" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/hex" "encoding/pem" "errors" + "io" ) +// DefaultPassword is the string "changeit", a commonly-used password for +// PKCS#12 files. Due to the weak encryption used by PKCS#12, it is +// RECOMMENDED that you use DefaultPassword when encoding PKCS#12 files, +// and protect the PKCS#12 files using other means. +const DefaultPassword = "changeit" + var ( oidDataContentType = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 7, 1}) oidEncryptedDataContentType = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 7, 6}) @@ -57,6 +70,8 @@ func (i encryptedContentInfo) Algorithm() pkix.AlgorithmIdentifier { func (i encryptedContentInfo) Data() []byte { return i.EncryptedContent } +func (i *encryptedContentInfo) SetData(data []byte) { i.EncryptedContent = data } + type safeBag struct { Id asn1.ObjectIdentifier Value asn1.RawValue `asn1:"tag:0,explicit"` @@ -81,6 +96,10 @@ func (i encryptedPrivateKeyInfo) Data() []byte { return i.EncryptedData } +func (i *encryptedPrivateKeyInfo) SetData(data []byte) { + i.EncryptedData = data +} + // PEM block types const ( certificateType = "CERTIFICATE" @@ -100,7 +119,11 @@ func unmarshal(in []byte, out interface{}) error { return nil } -// ConvertToPEM converts all "safe bags" contained in pfxData to PEM blocks. +// ToPEM converts all "safe bags" contained in pfxData to PEM blocks. +// DO NOT USE THIS FUNCTION. ToPEM creates invalid PEM blocks; private keys +// are encoded as raw RSA or EC private keys rather than PKCS#8 despite being +// labeled "PRIVATE KEY". To decode a PKCS#12 file, use DecodeChain instead, +// and use the encoding/pem package to convert to PEM if necessary. func ToPEM(pfxData []byte, password string) ([]*pem.Block, error) { encodedPassword, err := bmpString(password) if err != nil { @@ -208,102 +231,90 @@ func convertAttribute(attribute *pkcs12Attribute) (key, value string, err error) // Decode extracts a certificate and private key from pfxData. This function // assumes that there is only one certificate and only one private key in the -// pfxData. +// pfxData. Since PKCS#12 files often contain more than one certificate, you +// probably want to use DecodeChain instead. func Decode(pfxData []byte, password string) (privateKey interface{}, certificate *x509.Certificate, err error) { - encodedPassword, err := bmpString(password) - if err != nil { - return nil, nil, err + var caCerts []*x509.Certificate + privateKey, certificate, caCerts, err = DecodeChain(pfxData, password) + if len(caCerts) != 0 { + err = errors.New("pkcs12: expected exactly two safe bags in the PFX PDU") } + return +} - bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword) +// DecodeChain extracts a certificate, a CA certificate chain, and private key +// from pfxData. This function assumes that there is at least one certificate +// and only one private key in the pfxData. The first certificate is assumed to +// be the leaf certificate, and subsequent certificates, if any, are assumed to +// comprise the CA certificate chain. +func DecodeChain(pfxData []byte, password string) (privateKey interface{}, certificate *x509.Certificate, caCerts []*x509.Certificate, err error) { + certificates, privateKeys, err := DecodeAll(pfxData, password) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - if len(bags) != 2 { - err = errors.New("pkcs12: expected exactly two safe bags in the PFX PDU") - return + if len(certificates) == 0 { + return nil, nil, nil, errors.New("pkcs12: certificate missing") } - for _, bag := range bags { - switch { - case bag.Id.Equal(oidCertBag): - if certificate != nil { - err = errors.New("pkcs12: expected exactly one certificate bag") - } - - certsData, err := decodeCertBag(bag.Value.Bytes) - if err != nil { - return nil, nil, err - } - certs, err := x509.ParseCertificates(certsData) - if err != nil { - return nil, nil, err - } - if len(certs) != 1 { - err = errors.New("pkcs12: expected exactly one certificate in the certBag") - return nil, nil, err - } - certificate = certs[0] - - case bag.Id.Equal(oidPKCS8ShroundedKeyBag): - if privateKey != nil { - err = errors.New("pkcs12: expected exactly one key bag") - } - - if privateKey, err = decodePkcs8ShroudedKeyBag(bag.Value.Bytes, encodedPassword); err != nil { - return nil, nil, err - } - } + certificate = certificates[0] + if len(certificates) > 1 { + caCerts = certificates[1:] } - if certificate == nil { - return nil, nil, errors.New("pkcs12: certificate missing") + if len(privateKeys) == 0 { + return nil, nil, nil, errors.New("pkcs12: private key missing") } - if privateKey == nil { - return nil, nil, errors.New("pkcs12: private key missing") + if len(privateKeys) > 1 { + return nil, nil, nil, errors.New("pkcs12: expected exactly one key bag") } + privateKey = privateKeys[0] + return } -// DecodeAllCerts extracts a certificates from pfxData. -func DecodeAllCerts(pfxData []byte, password string) (certificates []*x509.Certificate, err error) { +// DecodeAll extracts all certificates and private keys from pfxData. +func DecodeAll(pfxData []byte, password string) ([]*x509.Certificate, []interface{}, error) { encodedPassword, err := bmpString(password) if err != nil { - return nil, err + return nil, nil, err } bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword) if err != nil { - return nil, err + return nil, nil, err } - certificates = []*x509.Certificate{} + var certificates []*x509.Certificate + var privateKeys []interface{} for _, bag := range bags { switch { case bag.Id.Equal(oidCertBag): certsData, err := decodeCertBag(bag.Value.Bytes) if err != nil { - return nil, err + return nil, nil, err } certs, err := x509.ParseCertificates(certsData) if err != nil { - return nil, err + return nil, nil, err } if len(certs) != 1 { err = errors.New("pkcs12: expected exactly one certificate in the certBag") - return nil, err + return nil, nil, err } certificates = append(certificates, certs[0]) - } - } - if certificates == nil || len(certificates) == 0 { - return nil, errors.New("pkcs12: certificate missing") + case bag.Id.Equal(oidPKCS8ShroundedKeyBag): + privateKey, err := decodePkcs8ShroudedKeyBag(bag.Value.Bytes, encodedPassword) + if err != nil { + return nil, nil, err + } + privateKeys = append(privateKeys, privateKey) + } } - return + return certificates, privateKeys, nil } func getSafeContents(p12Data, password []byte) (bags []safeBag, updatedPassword []byte, err error) { @@ -383,3 +394,163 @@ func getSafeContents(p12Data, password []byte) (bags []safeBag, updatedPassword return bags, password, nil } + +// Encode produces pfxData containing one private key (privateKey), an +// end-entity certificate (certificate), and any number of CA certificates +// (caCerts). +// +// The private key is encrypted with the provided password, but due to the +// weak encryption primitives used by PKCS#12, it is RECOMMENDED that you +// specify a hard-coded password (such as pkcs12.DefaultPassword) and protect +// the resulting pfxData using other means. +// +// The rand argument is used to provide entropy for the encryption, and +// can be set to rand.Reader from the crypto/rand package. +// +// Encode emulates the behavior of OpenSSL's PKCS12_create: it creates two +// SafeContents: one that's encrypted with RC2 and contains the certificates, +// and another that is unencrypted and contains the private key shrouded with +// 3DES The private key bag and the end-entity certificate bag have the +// LocalKeyId attribute set to the SHA-1 fingerprint of the end-entity +// certificate. +func Encode(rand io.Reader, privateKey interface{}, certificate *x509.Certificate, caCerts []*x509.Certificate, password string) (pfxData []byte, err error) { + encodedPassword, err := bmpString(password) + if err != nil { + return nil, err + } + + var pfx pfxPdu + pfx.Version = 3 + + var certFingerprint = sha1.Sum(certificate.Raw) + var localKeyIdAttr pkcs12Attribute + localKeyIdAttr.Id = oidLocalKeyID + localKeyIdAttr.Value.Class = 0 + localKeyIdAttr.Value.Tag = 17 + localKeyIdAttr.Value.IsCompound = true + if localKeyIdAttr.Value.Bytes, err = asn1.Marshal(certFingerprint[:]); err != nil { + return nil, err + } + + var certBags []safeBag + var certBag *safeBag + if certBag, err = makeCertBag(certificate.Raw, []pkcs12Attribute{localKeyIdAttr}); err != nil { + return nil, err + } + certBags = append(certBags, *certBag) + + for _, cert := range caCerts { + if certBag, err = makeCertBag(cert.Raw, []pkcs12Attribute{}); err != nil { + return nil, err + } + certBags = append(certBags, *certBag) + } + + var keyBag safeBag + keyBag.Id = oidPKCS8ShroundedKeyBag + keyBag.Value.Class = 2 + keyBag.Value.Tag = 0 + keyBag.Value.IsCompound = true + if keyBag.Value.Bytes, err = encodePkcs8ShroudedKeyBag(rand, privateKey, encodedPassword); err != nil { + return nil, err + } + keyBag.Attributes = append(keyBag.Attributes, localKeyIdAttr) + + // Construct an authenticated safe with two SafeContents. + // The first SafeContents is encrypted and contains the cert bags. + // The second SafeContents is unencrypted and contains the shrouded key bag. + var authenticatedSafe [2]contentInfo + if authenticatedSafe[0], err = makeSafeContents(rand, certBags, encodedPassword); err != nil { + return nil, err + } + if authenticatedSafe[1], err = makeSafeContents(rand, []safeBag{keyBag}, nil); err != nil { + return nil, err + } + + var authenticatedSafeBytes []byte + if authenticatedSafeBytes, err = asn1.Marshal(authenticatedSafe[:]); err != nil { + return nil, err + } + + // compute the MAC + pfx.MacData.Mac.Algorithm.Algorithm = oidSHA1 + pfx.MacData.MacSalt = make([]byte, 8) + if _, err = rand.Read(pfx.MacData.MacSalt); err != nil { + return nil, err + } + pfx.MacData.Iterations = 1 + if err = computeMac(&pfx.MacData, authenticatedSafeBytes, encodedPassword); err != nil { + return nil, err + } + + pfx.AuthSafe.ContentType = oidDataContentType + pfx.AuthSafe.Content.Class = 2 + pfx.AuthSafe.Content.Tag = 0 + pfx.AuthSafe.Content.IsCompound = true + if pfx.AuthSafe.Content.Bytes, err = asn1.Marshal(authenticatedSafeBytes); err != nil { + return nil, err + } + + if pfxData, err = asn1.Marshal(pfx); err != nil { + return nil, errors.New("pkcs12: error writing P12 data: " + err.Error()) + } + return +} + +func makeCertBag(certBytes []byte, attributes []pkcs12Attribute) (certBag *safeBag, err error) { + certBag = new(safeBag) + certBag.Id = oidCertBag + certBag.Value.Class = 2 + certBag.Value.Tag = 0 + certBag.Value.IsCompound = true + if certBag.Value.Bytes, err = encodeCertBag(certBytes); err != nil { + return nil, err + } + certBag.Attributes = attributes + return +} + +func makeSafeContents(rand io.Reader, bags []safeBag, password []byte) (ci contentInfo, err error) { + var data []byte + if data, err = asn1.Marshal(bags); err != nil { + return + } + + if password == nil { + ci.ContentType = oidDataContentType + ci.Content.Class = 2 + ci.Content.Tag = 0 + ci.Content.IsCompound = true + if ci.Content.Bytes, err = asn1.Marshal(data); err != nil { + return + } + } else { + randomSalt := make([]byte, 8) + if _, err = rand.Read(randomSalt); err != nil { + return + } + + var algo pkix.AlgorithmIdentifier + algo.Algorithm = oidPBEWithSHAAnd40BitRC2CBC + if algo.Parameters.FullBytes, err = asn1.Marshal(pbeParams{Salt: randomSalt, Iterations: 2048}); err != nil { + return + } + + var encryptedData encryptedData + encryptedData.Version = 0 + encryptedData.EncryptedContentInfo.ContentType = oidDataContentType + encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm = algo + if err = pbEncrypt(&encryptedData.EncryptedContentInfo, data, password); err != nil { + return + } + + ci.ContentType = oidEncryptedDataContentType + ci.Content.Class = 2 + ci.Content.Tag = 0 + ci.Content.IsCompound = true + if ci.Content.Bytes, err = asn1.Marshal(encryptedData); err != nil { + return + } + } + return +} diff --git a/vendor/github.com/bitrise-io/go-utils/pkcs12/safebags.go b/vendor/github.com/bitrise-io/go-utils/pkcs12/safebags.go old mode 100755 new mode 100644 index def1f7b9..be83a497 --- a/vendor/github.com/bitrise-io/go-utils/pkcs12/safebags.go +++ b/vendor/github.com/bitrise-io/go-utils/pkcs12/safebags.go @@ -1,3 +1,4 @@ +// Copyright 2015, 2018, 2019 Opsmate, Inc. All rights reserved. // Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -8,6 +9,7 @@ import ( "crypto/x509" "encoding/asn1" "errors" + "io" ) var ( @@ -45,6 +47,36 @@ func decodePkcs8ShroudedKeyBag(asn1Data, password []byte) (privateKey interface{ return privateKey, nil } +func encodePkcs8ShroudedKeyBag(rand io.Reader, privateKey interface{}, password []byte) (asn1Data []byte, err error) { + var pkData []byte + if pkData, err = x509.MarshalPKCS8PrivateKey(privateKey); err != nil { + return nil, errors.New("pkcs12: error encoding PKCS#8 private key: " + err.Error()) + } + + randomSalt := make([]byte, 8) + if _, err = rand.Read(randomSalt); err != nil { + return nil, errors.New("pkcs12: error reading random salt: " + err.Error()) + } + var paramBytes []byte + if paramBytes, err = asn1.Marshal(pbeParams{Salt: randomSalt, Iterations: 2048}); err != nil { + return nil, errors.New("pkcs12: error encoding params: " + err.Error()) + } + + var pkinfo encryptedPrivateKeyInfo + pkinfo.AlgorithmIdentifier.Algorithm = oidPBEWithSHAAnd3KeyTripleDESCBC + pkinfo.AlgorithmIdentifier.Parameters.FullBytes = paramBytes + + if err = pbEncrypt(&pkinfo, pkData, password); err != nil { + return nil, errors.New("pkcs12: error encrypting PKCS#8 shrouded key bag: " + err.Error()) + } + + if asn1Data, err = asn1.Marshal(pkinfo); err != nil { + return nil, errors.New("pkcs12: error encoding PKCS#8 shrouded key bag: " + err.Error()) + } + + return asn1Data, nil +} + func decodeCertBag(asn1Data []byte) (x509Certificates []byte, err error) { bag := new(certBag) if err := unmarshal(asn1Data, bag); err != nil { @@ -55,3 +87,13 @@ func decodeCertBag(asn1Data []byte) (x509Certificates []byte, err error) { } return bag.Data, nil } + +func encodeCertBag(x509Certificates []byte) (asn1Data []byte, err error) { + var bag certBag + bag.Id = oidCertTypeX509Certificate + bag.Data = x509Certificates + if asn1Data, err = asn1.Marshal(bag); err != nil { + return nil, errors.New("pkcs12: error encoding cert bag: " + err.Error()) + } + return asn1Data, nil +} diff --git a/vendor/github.com/bitrise-io/go-xcode/certificateutil/filter.go b/vendor/github.com/bitrise-io/go-xcode/certificateutil/filter.go index 0a55b41c..24fe025e 100644 --- a/vendor/github.com/bitrise-io/go-xcode/certificateutil/filter.go +++ b/vendor/github.com/bitrise-io/go-xcode/certificateutil/filter.go @@ -1,5 +1,7 @@ package certificateutil +import "sort" + // FilterCertificateInfoModelsByFilterFunc ... func FilterCertificateInfoModelsByFilterFunc(certificates []CertificateInfoModel, filterFunc func(certificate CertificateInfoModel) bool) []CertificateInfoModel { filteredCertificates := []CertificateInfoModel{} @@ -13,22 +15,44 @@ func FilterCertificateInfoModelsByFilterFunc(certificates []CertificateInfoModel return filteredCertificates } -// FilterValidCertificateInfos ... -func FilterValidCertificateInfos(certificateInfos []CertificateInfoModel) []CertificateInfoModel { - certificateInfosByName := map[string]CertificateInfoModel{} +// ValidCertificateInfo contains the certificate infos filtered as valid, invalid and duplicated common name certificates +type ValidCertificateInfo struct { + ValidCertificates, + InvalidCertificates, + DuplicatedCertificates []CertificateInfoModel +} +// FilterValidCertificateInfos filters out invalid and duplicated common name certificaates +func FilterValidCertificateInfos(certificateInfos []CertificateInfoModel) ValidCertificateInfo { + var invalidCertificates []CertificateInfoModel + nameToCerts := map[string][]CertificateInfoModel{} for _, certificateInfo := range certificateInfos { - if certificateInfo.CheckValidity() == nil { - activeCertificate, ok := certificateInfosByName[certificateInfo.CommonName] - if !ok || certificateInfo.EndDate.After(activeCertificate.EndDate) { - certificateInfosByName[certificateInfo.CommonName] = certificateInfo - } + if certificateInfo.CheckValidity() != nil { + invalidCertificates = append(invalidCertificates, certificateInfo) + continue + } + + nameToCerts[certificateInfo.CommonName] = append(nameToCerts[certificateInfo.CommonName], certificateInfo) + } + + var validCertificates, duplicatedCertificates []CertificateInfoModel + for _, certs := range nameToCerts { + if len(certs) == 0 { + continue + } + + sort.Slice(certs, func(i, j int) bool { + return certs[i].EndDate.Before(certs[j].EndDate) + }) + validCertificates = append(validCertificates, certs[0]) + if len(certs) > 1 { + duplicatedCertificates = append(duplicatedCertificates, certs[1:]...) } } - validCertificates := []CertificateInfoModel{} - for _, validCertificate := range certificateInfosByName { - validCertificates = append(validCertificates, validCertificate) + return ValidCertificateInfo{ + ValidCertificates: validCertificates, + InvalidCertificates: invalidCertificates, + DuplicatedCertificates: duplicatedCertificates, } - return validCertificates } diff --git a/vendor/github.com/bitrise-io/go-xcode/certificateutil/info_model.go b/vendor/github.com/bitrise-io/go-xcode/certificateutil/info_model.go index 941bb488..bf478967 100644 --- a/vendor/github.com/bitrise-io/go-xcode/certificateutil/info_model.go +++ b/vendor/github.com/bitrise-io/go-xcode/certificateutil/info_model.go @@ -1,14 +1,14 @@ package certificateutil import ( + "crypto/rand" "crypto/sha1" "crypto/x509" - "encoding/json" "fmt" "strings" "time" - "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pkcs12" ) // CertificateInfoModel ... @@ -22,32 +22,21 @@ type CertificateInfoModel struct { Serial string SHA1Fingerprint string - certificate x509.Certificate + Certificate x509.Certificate + PrivateKey interface{} } // String ... func (info CertificateInfoModel) String() string { - printable := map[string]interface{}{} - printable["name"] = info.CommonName - printable["serial"] = info.Serial - printable["team"] = fmt.Sprintf("%s (%s)", info.TeamName, info.TeamID) - printable["expire"] = info.EndDate.String() - - errors := []string{} - if err := info.CheckValidity(); err != nil { - errors = append(errors, err.Error()) - } - if len(errors) > 0 { - printable["errors"] = errors - } + team := fmt.Sprintf("%s (%s)", info.TeamName, info.TeamID) + certInfo := fmt.Sprintf("Serial: %s, Name: %s, Team: %s, Expiry: %s", info.Serial, info.CommonName, team, info.EndDate) - data, err := json.MarshalIndent(printable, "", "\t") + err := info.CheckValidity() if err != nil { - log.Errorf("Failed to marshal: %v, error: %s", printable, err) - return "" + certInfo = certInfo + fmt.Sprintf(", error: %s", err) } - return string(data) + return certInfo } // CheckValidity ... @@ -64,11 +53,16 @@ func CheckValidity(certificate x509.Certificate) error { // CheckValidity ... func (info CertificateInfoModel) CheckValidity() error { - return CheckValidity(info.certificate) + return CheckValidity(info.Certificate) +} + +// EncodeToP12 encodes a CertificateInfoModel in pkcs12 (.p12) format. +func (info CertificateInfoModel) EncodeToP12(passphrase string) ([]byte, error) { + return pkcs12.Encode(rand.Reader, info.PrivateKey, &info.Certificate, nil, passphrase) } // NewCertificateInfo ... -func NewCertificateInfo(certificate x509.Certificate) CertificateInfoModel { +func NewCertificateInfo(certificate x509.Certificate, privateKey interface{}) CertificateInfoModel { fingerprint := sha1.Sum(certificate.Raw) fingerprintStr := fmt.Sprintf("%x", fingerprint) @@ -80,30 +74,10 @@ func NewCertificateInfo(certificate x509.Certificate) CertificateInfoModel { StartDate: certificate.NotBefore, Serial: certificate.SerialNumber.String(), SHA1Fingerprint: fingerprintStr, - certificate: certificate, - } -} -// CertificateInfos ... -func CertificateInfos(certificates []*x509.Certificate) []CertificateInfoModel { - infos := []CertificateInfoModel{} - for _, certificate := range certificates { - if certificate != nil { - info := NewCertificateInfo(*certificate) - infos = append(infos, info) - } + Certificate: certificate, + PrivateKey: privateKey, } - - return infos -} - -// NewCertificateInfosFromPKCS12 ... -func NewCertificateInfosFromPKCS12(pkcs12Pth, password string) ([]CertificateInfoModel, error) { - certificates, err := CertificatesFromPKCS12File(pkcs12Pth, password) - if err != nil { - return nil, err - } - return CertificateInfos(certificates), nil } // InstalledCodesigningCertificateInfos ... @@ -112,7 +86,15 @@ func InstalledCodesigningCertificateInfos() ([]CertificateInfoModel, error) { if err != nil { return nil, err } - return CertificateInfos(certificates), nil + + infos := []CertificateInfoModel{} + for _, certificate := range certificates { + if certificate != nil { + infos = append(infos, NewCertificateInfo(*certificate, nil)) + } + } + + return infos, nil } // InstalledInstallerCertificateInfos ... @@ -122,7 +104,14 @@ func InstalledInstallerCertificateInfos() ([]CertificateInfoModel, error) { return nil, err } - installerCertificates := FilterCertificateInfoModelsByFilterFunc(CertificateInfos(certificates), func(cert CertificateInfoModel) bool { + infos := []CertificateInfoModel{} + for _, certificate := range certificates { + if certificate != nil { + infos = append(infos, NewCertificateInfo(*certificate, nil)) + } + } + + installerCertificates := FilterCertificateInfoModelsByFilterFunc(infos, func(cert CertificateInfoModel) bool { return strings.Contains(cert.CommonName, "Installer") }) diff --git a/vendor/github.com/bitrise-io/go-xcode/certificateutil/test_util.go b/vendor/github.com/bitrise-io/go-xcode/certificateutil/test_util.go new file mode 100644 index 00000000..e5e54d65 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-xcode/certificateutil/test_util.go @@ -0,0 +1,80 @@ +package certificateutil + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "math/big" + "time" +) + +// GenerateTestCertificate creates a certificate (signed by a self-signed CA cert) for test purposes +func GenerateTestCertificate(serial int64, teamID, teamName, commonName string, expiry time.Time) (*x509.Certificate, *rsa.PrivateKey, error) { + CAtemplate := &x509.Certificate{ + IsCA: true, + BasicConstraintsValid: true, + SubjectKeyId: []byte{1, 2, 3}, + SerialNumber: big.NewInt(1234), + Subject: pkix.Name{ + Country: []string{"US"}, + Organization: []string{"Pear Worldwide Developer Relations"}, + CommonName: "Pear Worldwide Developer Relations CA", + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(1, 0, 0), + // see http://golang.org/pkg/crypto/x509/#KeyUsage + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + } + + // generate private key + privatekey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + + // Self-signed certificate, parent is the template + CAcertData, err := x509.CreateCertificate(rand.Reader, CAtemplate, CAtemplate, &privatekey.PublicKey, privatekey) + if err != nil { + return nil, nil, err + } + CAcert, err := x509.ParseCertificate(CAcertData) + if err != nil { + return nil, nil, err + } + + template := &x509.Certificate{ + IsCA: true, + BasicConstraintsValid: true, + SerialNumber: big.NewInt(serial), + Subject: pkix.Name{ + Country: []string{"US"}, + Organization: []string{teamName}, + OrganizationalUnit: []string{teamID}, + CommonName: commonName, + }, + NotBefore: time.Now(), + NotAfter: expiry, + // see http://golang.org/pkg/crypto/x509/#KeyUsage + KeyUsage: x509.KeyUsageDigitalSignature, + } + + // generate private key + privatekey, err = rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + + certData, err := x509.CreateCertificate(rand.Reader, template, CAcert, &privatekey.PublicKey, privatekey) + if err != nil { + return nil, nil, err + } + + cert, err := x509.ParseCertificate(certData) + if err != nil { + return nil, nil, err + } + + return cert, privatekey, nil +} diff --git a/vendor/github.com/bitrise-io/go-xcode/certificateutil/util.go b/vendor/github.com/bitrise-io/go-xcode/certificateutil/util.go index 3e450849..4fc9691a 100644 --- a/vendor/github.com/bitrise-io/go-xcode/certificateutil/util.go +++ b/vendor/github.com/bitrise-io/go-xcode/certificateutil/util.go @@ -18,18 +18,34 @@ func commandError(printableCmd string, cmdOut string, cmdErr error) error { return errors.Wrapf(cmdErr, "%s failed, out: %s", printableCmd, cmdOut) } -// CertificatesFromPKCS12Content ... -func CertificatesFromPKCS12Content(content []byte, password string) ([]*x509.Certificate, error) { - certificates, err := pkcs12.DecodeAllCerts(content, password) +// CertificatesFromPKCS12Content returns an array of CertificateInfoModel +// Used to parse p12 file containing multiple codesign identities (exported from macOS Keychain) +func CertificatesFromPKCS12Content(content []byte, password string) ([]CertificateInfoModel, error) { + certificates, privateKeys, err := pkcs12.DecodeAll(content, password) if err != nil { return nil, err } - return certificates, nil + if len(certificates) != len(privateKeys) { + return nil, errors.New("pkcs12: different number of certificates and private keys found") + } + + if len(certificates) == 0 { + return nil, errors.New("pkcs12: no certificate and private key pair found") + } + + infos := []CertificateInfoModel{} + for i, certificate := range certificates { + if certificate != nil { + infos = append(infos, NewCertificateInfo(*certificate, privateKeys[i])) + } + } + + return infos, nil } // CertificatesFromPKCS12File ... -func CertificatesFromPKCS12File(pkcs12Pth, password string) ([]*x509.Certificate, error) { +func CertificatesFromPKCS12File(pkcs12Pth, password string) ([]CertificateInfoModel, error) { content, err := fileutil.ReadBytesFromFile(pkcs12Pth) if err != nil { return nil, err diff --git a/vendor/github.com/bitrise-io/go-xcode/ipa/ipa.go b/vendor/github.com/bitrise-io/go-xcode/ipa/ipa.go index 16f173ca..7ca859e2 100644 --- a/vendor/github.com/bitrise-io/go-xcode/ipa/ipa.go +++ b/vendor/github.com/bitrise-io/go-xcode/ipa/ipa.go @@ -1,47 +1,13 @@ package ipa import ( - "fmt" "path/filepath" - "strings" "github.com/bitrise-io/go-utils/pathutil" "github.com/bitrise-io/go-utils/ziputil" "github.com/bitrise-io/go-xcode/utility" ) -func findFileInPayloadAppDir(payloadPth, preferedAppName, fileName string) (string, error) { - appDir := filepath.Join(payloadPth, preferedAppName+".app") - - filePth := filepath.Join(appDir, fileName) - if exist, err := pathutil.IsPathExists(filePth); err != nil { - return "", err - } else if exist { - return filePth, nil - } - // --- - - // It's somewhere else - let's find it! - apps, err := utility.ListEntries(payloadPth, utility.ExtensionFilter(".app", true)) - if err != nil { - return "", err - } - - for _, app := range apps { - pths, err := utility.ListEntries(app, utility.BaseFilter(fileName, true)) - if err != nil { - return "", err - } - - if len(pths) > 0 { - return pths[0], nil - } - } - // --- - - return "", fmt.Errorf("failed to find %s", fileName) -} - func unwrapFileEmbeddedInPayloadAppDir(ipaPth, fileName string) (string, error) { tmpDir, err := pathutil.NormalizedOSTempDirPath("__ipa__") if err != nil { @@ -52,10 +18,9 @@ func unwrapFileEmbeddedInPayloadAppDir(ipaPth, fileName string) (string, error) return "", err } - payloadPth := filepath.Join(tmpDir, "Payload") - ipaName := strings.TrimSuffix(filepath.Base(ipaPth), filepath.Ext(ipaPth)) + appDir := filepath.Join(tmpDir, "Payload") - return findFileInPayloadAppDir(payloadPth, ipaName, fileName) + return utility.FindFileInAppDir(appDir, fileName) } // UnwrapEmbeddedMobileProvision ... diff --git a/vendor/github.com/bitrise-io/go-xcode/profileutil/info_model.go b/vendor/github.com/bitrise-io/go-xcode/profileutil/info_model.go index 907f5c2a..3df14609 100644 --- a/vendor/github.com/bitrise-io/go-xcode/profileutil/info_model.go +++ b/vendor/github.com/bitrise-io/go-xcode/profileutil/info_model.go @@ -164,7 +164,12 @@ func NewProvisioningProfileInfo(provisioningProfile pkcs7.PKCS7) (ProvisioningPr certificates = append(certificates, certificate) } } - info.DeveloperCertificates = certificateutil.CertificateInfos(certificates) + + for _, certificate := range certificates { + if certificate != nil { + info.DeveloperCertificates = append(info.DeveloperCertificates, certificateutil.NewCertificateInfo(*certificate, nil)) + } + } } info.Entitlements = profile.GetEntitlements() diff --git a/vendor/github.com/bitrise-io/go-xcode/utility/path.go b/vendor/github.com/bitrise-io/go-xcode/utility/path.go index 4642d3e5..45830d3f 100644 --- a/vendor/github.com/bitrise-io/go-xcode/utility/path.go +++ b/vendor/github.com/bitrise-io/go-xcode/utility/path.go @@ -1,9 +1,12 @@ package utility import ( + "fmt" "io/ioutil" "path/filepath" "strings" + + "github.com/bitrise-io/go-utils/pathutil" ) // FilterFunc ... @@ -67,3 +70,34 @@ func BaseFilter(base string, allowed bool) FilterFunc { return (allowed == strings.EqualFold(base, b)), nil } } + +// FindFileInAppDir ... +func FindFileInAppDir(appDir, fileName string) (string, error) { + filePth := filepath.Join(appDir, fileName) + if exist, err := pathutil.IsPathExists(filePth); err != nil { + return "", err + } else if exist { + return filePth, nil + } + // --- + + // It's somewhere else - let's find it! + apps, err := ListEntries(appDir, ExtensionFilter(".app", true)) + if err != nil { + return "", err + } + + for _, app := range apps { + pths, err := ListEntries(app, BaseFilter(fileName, true)) + if err != nil { + return "", err + } + + if len(pths) > 0 { + return pths[0], nil + } + } + // --- + + return "", fmt.Errorf("failed to find %s", fileName) +} diff --git a/vendor/github.com/bitrise-io/go-xcode/xcarchive/ios.go b/vendor/github.com/bitrise-io/go-xcode/xcarchive/ios.go new file mode 100644 index 00000000..056a8499 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-xcode/xcarchive/ios.go @@ -0,0 +1,351 @@ +package xcarchive + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/go-xcode/plistutil" + "github.com/bitrise-io/go-xcode/profileutil" + "github.com/bitrise-io/go-xcode/utility" +) + +type iosBaseApplication struct { + Path string + InfoPlist plistutil.PlistData + Entitlements plistutil.PlistData + ProvisioningProfile profileutil.ProvisioningProfileInfoModel +} + +// BundleIdentifier ... +func (app iosBaseApplication) BundleIdentifier() string { + bundleID, _ := app.InfoPlist.GetString("CFBundleIdentifier") + return bundleID +} + +func newIosBaseApplication(path string) (iosBaseApplication, error) { + infoPlist := plistutil.PlistData{} + { + infoPlistPath := filepath.Join(path, "Info.plist") + if exist, err := pathutil.IsPathExists(infoPlistPath); err != nil { + return iosBaseApplication{}, fmt.Errorf("failed to check if Info.plist exists at: %s, error: %s", infoPlistPath, err) + } else if !exist { + return iosBaseApplication{}, fmt.Errorf("Info.plist not exists at: %s", infoPlistPath) + } + plist, err := plistutil.NewPlistDataFromFile(infoPlistPath) + if err != nil { + return iosBaseApplication{}, err + } + infoPlist = plist + } + + provisioningProfile := profileutil.ProvisioningProfileInfoModel{} + { + provisioningProfilePath := filepath.Join(path, "embedded.mobileprovision") + if exist, err := pathutil.IsPathExists(provisioningProfilePath); err != nil { + return iosBaseApplication{}, fmt.Errorf("failed to check if profile exists at: %s, error: %s", provisioningProfilePath, err) + } else if !exist { + return iosBaseApplication{}, fmt.Errorf("profile not exists at: %s", provisioningProfilePath) + } + + profile, err := profileutil.NewProvisioningProfileInfoFromFile(provisioningProfilePath) + if err != nil { + return iosBaseApplication{}, err + } + provisioningProfile = profile + } + + entitlements := plistutil.PlistData{} + { + entitlementsPath := filepath.Join(path, "archived-expanded-entitlements.xcent") + if exist, err := pathutil.IsPathExists(entitlementsPath); err != nil { + return iosBaseApplication{}, fmt.Errorf("failed to check if entitlements exists at: %s, error: %s", entitlementsPath, err) + } else if exist { + plist, err := plistutil.NewPlistDataFromFile(entitlementsPath) + if err != nil { + return iosBaseApplication{}, err + } + entitlements = plist + } + } + + return iosBaseApplication{ + Path: path, + InfoPlist: infoPlist, + Entitlements: entitlements, + ProvisioningProfile: provisioningProfile, + }, nil +} + +// IosExtension ... +type IosExtension struct { + iosBaseApplication +} + +// NewIosExtension ... +func NewIosExtension(path string) (IosExtension, error) { + baseApp, err := newIosBaseApplication(path) + if err != nil { + return IosExtension{}, err + } + + return IosExtension{ + baseApp, + }, nil +} + +// IosWatchApplication ... +type IosWatchApplication struct { + iosBaseApplication + Extensions []IosExtension +} + +// NewIosWatchApplication ... +func NewIosWatchApplication(path string) (IosWatchApplication, error) { + baseApp, err := newIosBaseApplication(path) + if err != nil { + return IosWatchApplication{}, err + } + + extensions := []IosExtension{} + pattern := filepath.Join(utility.EscapeGlobPath(path), "PlugIns/*.appex") + pths, err := filepath.Glob(pattern) + if err != nil { + return IosWatchApplication{}, fmt.Errorf("failed to search for watch application's extensions using pattern: %s, error: %s", pattern, err) + } + for _, pth := range pths { + extension, err := NewIosExtension(pth) + if err != nil { + return IosWatchApplication{}, err + } + + extensions = append(extensions, extension) + } + + return IosWatchApplication{ + iosBaseApplication: baseApp, + Extensions: extensions, + }, nil +} + +// IosApplication ... +type IosApplication struct { + iosBaseApplication + WatchApplication *IosWatchApplication + Extensions []IosExtension +} + +// NewIosApplication ... +func NewIosApplication(path string) (IosApplication, error) { + baseApp, err := newIosBaseApplication(path) + if err != nil { + return IosApplication{}, err + } + + var watchApp *IosWatchApplication + { + pattern := filepath.Join(utility.EscapeGlobPath(path), "Watch/*.app") + pths, err := filepath.Glob(pattern) + if err != nil { + return IosApplication{}, err + } + if len(pths) > 0 { + watchPath := pths[0] + app, err := NewIosWatchApplication(watchPath) + if err != nil { + return IosApplication{}, err + } + watchApp = &app + } + } + + extensions := []IosExtension{} + { + pattern := filepath.Join(utility.EscapeGlobPath(path), "PlugIns/*.appex") + pths, err := filepath.Glob(pattern) + if err != nil { + return IosApplication{}, fmt.Errorf("failed to search for watch application's extensions using pattern: %s, error: %s", pattern, err) + } + for _, pth := range pths { + extension, err := NewIosExtension(pth) + if err != nil { + return IosApplication{}, err + } + + extensions = append(extensions, extension) + } + } + + return IosApplication{ + iosBaseApplication: baseApp, + WatchApplication: watchApp, + Extensions: extensions, + }, nil +} + +// IosArchive ... +type IosArchive struct { + Path string + InfoPlist plistutil.PlistData + Application IosApplication +} + +// NewIosArchive ... +func NewIosArchive(path string) (IosArchive, error) { + infoPlist := plistutil.PlistData{} + { + infoPlistPath := filepath.Join(path, "Info.plist") + if exist, err := pathutil.IsPathExists(infoPlistPath); err != nil { + return IosArchive{}, fmt.Errorf("failed to check if Info.plist exists at: %s, error: %s", infoPlistPath, err) + } else if !exist { + return IosArchive{}, fmt.Errorf("Info.plist not exists at: %s", infoPlistPath) + } + plist, err := plistutil.NewPlistDataFromFile(infoPlistPath) + if err != nil { + return IosArchive{}, err + } + infoPlist = plist + } + + application := IosApplication{} + { + appPath := "" + if appRelativePathToProducts, found := applicationFromPlist(infoPlist); found { + appPath = filepath.Join(path, "Products", appRelativePathToProducts) + } else { + var err error + if appPath, err = applicationFromArchive(path); err != nil { + return IosArchive{}, err + } + } + if exist, err := pathutil.IsPathExists(appPath); err != nil { + return IosArchive{}, fmt.Errorf("failed to check if app exists, path: %s, error: %s", appPath, err) + } else if !exist { + return IosArchive{}, fmt.Errorf("application not found on path: %s, error: %s", appPath, err) + } + + app, err := NewIosApplication(appPath) + if err != nil { + return IosArchive{}, err + } + application = app + } + + return IosArchive{ + Path: path, + InfoPlist: infoPlist, + Application: application, + }, nil +} + +func applicationFromPlist(InfoPlist plistutil.PlistData) (string, bool) { + if properties, found := InfoPlist.GetMapStringInterface("ApplicationProperties"); found { + return properties.GetString("ApplicationPath") + } + return "", false +} + +func applicationFromArchive(path string) (string, error) { + pattern := filepath.Join(utility.EscapeGlobPath(path), "Products/Applications/*.app") + pths, err := filepath.Glob(pattern) + if err != nil { + return "", err + } + if len(pths) == 0 { + return "", fmt.Errorf("failed to find main app, using pattern: %s", pattern) + } + return pths[0], nil +} + +// IsXcodeManaged ... +func (archive IosArchive) IsXcodeManaged() bool { + return archive.Application.ProvisioningProfile.IsXcodeManaged() +} + +// SigningIdentity ... +func (archive IosArchive) SigningIdentity() string { + if properties, found := archive.InfoPlist.GetMapStringInterface("ApplicationProperties"); found { + identity, _ := properties.GetString("SigningIdentity") + return identity + } + return "" +} + +// BundleIDEntitlementsMap ... +func (archive IosArchive) BundleIDEntitlementsMap() map[string]plistutil.PlistData { + bundleIDEntitlementsMap := map[string]plistutil.PlistData{} + + bundleID := archive.Application.BundleIdentifier() + bundleIDEntitlementsMap[bundleID] = archive.Application.Entitlements + + for _, plugin := range archive.Application.Extensions { + bundleID := plugin.BundleIdentifier() + bundleIDEntitlementsMap[bundleID] = plugin.Entitlements + } + + if archive.Application.WatchApplication != nil { + watchApplication := *archive.Application.WatchApplication + + bundleID := watchApplication.BundleIdentifier() + bundleIDEntitlementsMap[bundleID] = watchApplication.Entitlements + + for _, plugin := range watchApplication.Extensions { + bundleID := plugin.BundleIdentifier() + bundleIDEntitlementsMap[bundleID] = plugin.Entitlements + } + } + + return bundleIDEntitlementsMap +} + +// BundleIDProfileInfoMap ... +func (archive IosArchive) BundleIDProfileInfoMap() map[string]profileutil.ProvisioningProfileInfoModel { + bundleIDProfileMap := map[string]profileutil.ProvisioningProfileInfoModel{} + + bundleID := archive.Application.BundleIdentifier() + bundleIDProfileMap[bundleID] = archive.Application.ProvisioningProfile + + for _, plugin := range archive.Application.Extensions { + bundleID := plugin.BundleIdentifier() + bundleIDProfileMap[bundleID] = plugin.ProvisioningProfile + } + + if archive.Application.WatchApplication != nil { + watchApplication := *archive.Application.WatchApplication + + bundleID := watchApplication.BundleIdentifier() + bundleIDProfileMap[bundleID] = watchApplication.ProvisioningProfile + + for _, plugin := range watchApplication.Extensions { + bundleID := plugin.BundleIdentifier() + bundleIDProfileMap[bundleID] = plugin.ProvisioningProfile + } + } + + return bundleIDProfileMap +} + +// FindDSYMs ... +func (archive IosArchive) FindDSYMs() (string, []string, error) { + dsymsDirPth := filepath.Join(archive.Path, "dSYMs") + dsyms, err := utility.ListEntries(dsymsDirPth, utility.ExtensionFilter(".dsym", true)) + if err != nil { + return "", []string{}, err + } + + appDSYM := "" + frameworkDSYMs := []string{} + for _, dsym := range dsyms { + if strings.HasSuffix(dsym, ".app.dSYM") { + appDSYM = dsym + } else { + frameworkDSYMs = append(frameworkDSYMs, dsym) + } + } + if appDSYM == "" && len(frameworkDSYMs) == 0 { + return "", []string{}, fmt.Errorf("no dsym found") + } + + return appDSYM, frameworkDSYMs, nil +} diff --git a/vendor/github.com/bitrise-io/go-xcode/xcarchive/macos.go b/vendor/github.com/bitrise-io/go-xcode/xcarchive/macos.go new file mode 100644 index 00000000..736d86bf --- /dev/null +++ b/vendor/github.com/bitrise-io/go-xcode/xcarchive/macos.go @@ -0,0 +1,260 @@ +package xcarchive + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/bitrise-io/go-xcode/plistutil" + "github.com/bitrise-io/go-xcode/profileutil" + "github.com/bitrise-io/go-xcode/utility" + + "github.com/bitrise-io/go-utils/pathutil" +) + +type macosBaseApplication struct { + Path string + InfoPlist plistutil.PlistData + Entitlements plistutil.PlistData + ProvisioningProfile *profileutil.ProvisioningProfileInfoModel +} + +// BundleIdentifier ... +func (app macosBaseApplication) BundleIdentifier() string { + bundleID, _ := app.InfoPlist.GetString("CFBundleIdentifier") + return bundleID +} + +func newMacosBaseApplication(path string) (macosBaseApplication, error) { + infoPlist := plistutil.PlistData{} + { + infoPlistPath := filepath.Join(path, "Contents/Info.plist") + if exist, err := pathutil.IsPathExists(infoPlistPath); err != nil { + return macosBaseApplication{}, fmt.Errorf("failed to check if Info.plist exists at: %s, error: %s", infoPlistPath, err) + } else if !exist { + return macosBaseApplication{}, fmt.Errorf("Info.plist not exists at: %s", infoPlistPath) + } + plist, err := plistutil.NewPlistDataFromFile(infoPlistPath) + if err != nil { + return macosBaseApplication{}, err + } + infoPlist = plist + } + + var provisioningProfile *profileutil.ProvisioningProfileInfoModel + { + provisioningProfilePath := filepath.Join(path, "Contents/Resources/embedded.mobileprovision") + if exist, err := pathutil.IsPathExists(provisioningProfilePath); err != nil { + return macosBaseApplication{}, fmt.Errorf("failed to check if profile exists at: %s, error: %s", provisioningProfilePath, err) + } else if exist { + profile, err := profileutil.NewProvisioningProfileInfoFromFile(provisioningProfilePath) + if err != nil { + return macosBaseApplication{}, err + } + provisioningProfile = &profile + } + } + + entitlements := plistutil.PlistData{} + { + entitlementsPath := filepath.Join(path, "Contents/Resources/archived-expanded-entitlements.xcent") + if exist, err := pathutil.IsPathExists(entitlementsPath); err != nil { + return macosBaseApplication{}, fmt.Errorf("failed to check if entitlements exists at: %s, error: %s", entitlementsPath, err) + } else if exist { + plist, err := plistutil.NewPlistDataFromFile(entitlementsPath) + if err != nil { + return macosBaseApplication{}, err + } + entitlements = plist + } + } + + return macosBaseApplication{ + Path: path, + InfoPlist: infoPlist, + Entitlements: entitlements, + ProvisioningProfile: provisioningProfile, + }, nil +} + +// MacosExtension ... +type MacosExtension struct { + macosBaseApplication +} + +// NewMacosExtension ... +func NewMacosExtension(path string) (MacosExtension, error) { + baseApp, err := newMacosBaseApplication(path) + if err != nil { + return MacosExtension{}, err + } + + return MacosExtension{ + baseApp, + }, nil +} + +// MacosApplication ... +type MacosApplication struct { + macosBaseApplication + Extensions []MacosExtension +} + +// NewMacosApplication ... +func NewMacosApplication(path string) (MacosApplication, error) { + baseApp, err := newMacosBaseApplication(path) + if err != nil { + return MacosApplication{}, err + } + + extensions := []MacosExtension{} + { + pattern := filepath.Join(utility.EscapeGlobPath(path), "Contents/PlugIns/*.appex") + pths, err := filepath.Glob(pattern) + if err != nil { + return MacosApplication{}, fmt.Errorf("failed to search for watch application's extensions using pattern: %s, error: %s", pattern, err) + } + for _, pth := range pths { + extension, err := NewMacosExtension(pth) + if err != nil { + return MacosApplication{}, err + } + + extensions = append(extensions, extension) + } + } + + return MacosApplication{ + macosBaseApplication: baseApp, + Extensions: extensions, + }, nil +} + +// MacosArchive ... +type MacosArchive struct { + Path string + InfoPlist plistutil.PlistData + Application MacosApplication +} + +// NewMacosArchive ... +func NewMacosArchive(path string) (MacosArchive, error) { + infoPlist := plistutil.PlistData{} + { + infoPlistPath := filepath.Join(path, "Info.plist") + if exist, err := pathutil.IsPathExists(infoPlistPath); err != nil { + return MacosArchive{}, fmt.Errorf("failed to check if Info.plist exists at: %s, error: %s", infoPlistPath, err) + } else if !exist { + return MacosArchive{}, fmt.Errorf("Info.plist not exists at: %s", infoPlistPath) + } + plist, err := plistutil.NewPlistDataFromFile(infoPlistPath) + if err != nil { + return MacosArchive{}, err + } + infoPlist = plist + } + + application := MacosApplication{} + { + pattern := filepath.Join(utility.EscapeGlobPath(path), "Products/Applications/*.app") + pths, err := filepath.Glob(pattern) + if err != nil { + return MacosArchive{}, err + } + + appPath := "" + if len(pths) > 0 { + appPath = pths[0] + } else { + return MacosArchive{}, fmt.Errorf("failed to find main app, using pattern: %s", pattern) + } + + app, err := NewMacosApplication(appPath) + if err != nil { + return MacosArchive{}, err + } + application = app + } + + return MacosArchive{ + Path: path, + InfoPlist: infoPlist, + Application: application, + }, nil +} + +// IsXcodeManaged ... +func (archive MacosArchive) IsXcodeManaged() bool { + if archive.Application.ProvisioningProfile != nil { + return archive.Application.ProvisioningProfile.IsXcodeManaged() + } + return false +} + +// SigningIdentity ... +func (archive MacosArchive) SigningIdentity() string { + properties, found := archive.InfoPlist.GetMapStringInterface("ApplicationProperties") + if found { + identity, _ := properties.GetString("SigningIdentity") + return identity + } + return "" +} + +// BundleIDEntitlementsMap ... +func (archive MacosArchive) BundleIDEntitlementsMap() map[string]plistutil.PlistData { + bundleIDEntitlementsMap := map[string]plistutil.PlistData{} + + bundleID := archive.Application.BundleIdentifier() + bundleIDEntitlementsMap[bundleID] = archive.Application.Entitlements + + for _, plugin := range archive.Application.Extensions { + bundleID := plugin.BundleIdentifier() + bundleIDEntitlementsMap[bundleID] = plugin.Entitlements + } + + return bundleIDEntitlementsMap +} + +// BundleIDProfileInfoMap ... +func (archive MacosArchive) BundleIDProfileInfoMap() map[string]profileutil.ProvisioningProfileInfoModel { + bundleIDProfileMap := map[string]profileutil.ProvisioningProfileInfoModel{} + + if archive.Application.ProvisioningProfile != nil { + bundleID := archive.Application.BundleIdentifier() + bundleIDProfileMap[bundleID] = *archive.Application.ProvisioningProfile + } + + for _, plugin := range archive.Application.Extensions { + if plugin.ProvisioningProfile != nil { + bundleID := plugin.BundleIdentifier() + bundleIDProfileMap[bundleID] = *plugin.ProvisioningProfile + } + } + + return bundleIDProfileMap +} + +// FindDSYMs ... +func (archive MacosArchive) FindDSYMs() (string, []string, error) { + dsymsDirPth := filepath.Join(archive.Path, "dSYMs") + dsyms, err := utility.ListEntries(dsymsDirPth, utility.ExtensionFilter(".dsym", true)) + if err != nil { + return "", []string{}, err + } + + appDSYM := "" + frameworkDSYMs := []string{} + for _, dsym := range dsyms { + if strings.HasSuffix(dsym, ".app.dSYM") { + appDSYM = dsym + } else { + frameworkDSYMs = append(frameworkDSYMs, dsym) + } + } + if appDSYM == "" && len(frameworkDSYMs) == 0 { + return "", []string{}, fmt.Errorf("no dsym found") + } + + return appDSYM, frameworkDSYMs, nil +} diff --git a/vendor/github.com/bitrise-io/go-xcode/xcarchive/xcarchive.go b/vendor/github.com/bitrise-io/go-xcode/xcarchive/xcarchive.go new file mode 100644 index 00000000..a647cfc5 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-xcode/xcarchive/xcarchive.go @@ -0,0 +1,67 @@ +package xcarchive + +import ( + "path/filepath" + + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/go-utils/ziputil" + "github.com/bitrise-io/go-xcode/plistutil" + "github.com/bitrise-io/go-xcode/utility" +) + +// IsMacOS try to find the Contents dir under the .app/. +// If its finds it the archive is MacOs. If it does not the archive is iOS. +func IsMacOS(archPath string) (bool, error) { + log.Debugf("Checking archive is MacOS or iOS") + infoPlistPath := filepath.Join(archPath, "Info.plist") + + plist, err := plistutil.NewPlistDataFromFile(infoPlistPath) + if err != nil { + return false, err + } + + appProperties, found := plist.GetMapStringInterface("ApplicationProperties") + if !found { + return false, err + } + + applicationPath, found := appProperties.GetString("ApplicationPath") + if !found { + return false, err + } + + applicationPath = filepath.Join(archPath, "Products", applicationPath) + contentsPath := filepath.Join(applicationPath, "Contents") + + exist, err := pathutil.IsDirExists(contentsPath) + if err != nil { + return false, err + } + + return exist, nil +} + +// UnzipXcarchive ... +func UnzipXcarchive(xcarchivePth string) (string, error) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__xcarhive__") + if err != nil { + return "", err + } + + return tmpDir, ziputil.UnZip(xcarchivePth, tmpDir) +} + +// GetEmbeddedMobileProvisionPath ... +func GetEmbeddedMobileProvisionPath(xcarchivePth string) (string, error) { + return utility.FindFileInAppDir(getAppSubfolder(xcarchivePth), "embedded.mobileprovision") +} + +// GetEmbeddedInfoPlistPath ... +func GetEmbeddedInfoPlistPath(xcarchivePth string) (string, error) { + return utility.FindFileInAppDir(getAppSubfolder(xcarchivePth), "Info.plist") +} + +func getAppSubfolder(basepth string) string { + return filepath.Join(basepth, "Products", "Applications") +} diff --git a/vendor/github.com/gorilla/mux/.travis.yml b/vendor/github.com/gorilla/mux/.travis.yml deleted file mode 100644 index d003ad92..00000000 --- a/vendor/github.com/gorilla/mux/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -language: go - - -matrix: - include: - - go: 1.7.x - - go: 1.8.x - - go: 1.9.x - - go: 1.10.x - - go: 1.11.x - - go: 1.x - env: LATEST=true - - go: tip - allow_failures: - - go: tip - -install: - - # Skip - -script: - - go get -t -v ./... - - diff -u <(echo -n) <(gofmt -d .) - - if [[ "$LATEST" = true ]]; then go vet .; fi - - go test -v -race ./... diff --git a/vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md b/vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md deleted file mode 100644 index 232be82e..00000000 --- a/vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,11 +0,0 @@ -**What version of Go are you running?** (Paste the output of `go version`) - - -**What version of gorilla/mux are you at?** (Paste the output of `git rev-parse HEAD` inside `$GOPATH/src/github.com/gorilla/mux`) - - -**Describe your problem** (and what you have tried so far) - - -**Paste a minimal, runnable, reproduction of your issue below** (use backticks to format it) - diff --git a/vendor/github.com/gorilla/mux/README.md b/vendor/github.com/gorilla/mux/README.md index c661599a..92e422ee 100644 --- a/vendor/github.com/gorilla/mux/README.md +++ b/vendor/github.com/gorilla/mux/README.md @@ -2,6 +2,7 @@ [![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux) [![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux) +[![CircleCI](https://circleci.com/gh/gorilla/mux.svg?style=svg)](https://circleci.com/gh/gorilla/mux) [![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge) ![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png) @@ -29,6 +30,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv * [Walking Routes](#walking-routes) * [Graceful Shutdown](#graceful-shutdown) * [Middleware](#middleware) +* [Handling CORS Requests](#handling-cors-requests) * [Testing Handlers](#testing-handlers) * [Full Example](#full-example) @@ -491,6 +493,73 @@ r.Use(amw.Middleware) Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it. +### Handling CORS Requests + +[CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) intends to make it easier to strictly set the `Access-Control-Allow-Methods` response header. + +* You will still need to use your own CORS handler to set the other CORS headers such as `Access-Control-Allow-Origin` +* The middleware will set the `Access-Control-Allow-Methods` header to all the method matchers (e.g. `r.Methods(http.MethodGet, http.MethodPut, http.MethodOptions)` -> `Access-Control-Allow-Methods: GET,PUT,OPTIONS`) on a route +* If you do not specify any methods, then: +> _Important_: there must be an `OPTIONS` method matcher for the middleware to set the headers. + +Here is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers: + +```go +package main + +import ( + "net/http" + "github.com/gorilla/mux" +) + +func main() { + r := mux.NewRouter() + + // IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers + r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions) + r.Use(mux.CORSMethodMiddleware(r)) + + http.ListenAndServe(":8080", r) +} + +func fooHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + if r.Method == http.MethodOptions { + return + } + + w.Write([]byte("foo")) +} +``` + +And an request to `/foo` using something like: + +```bash +curl localhost:8080/foo -v +``` + +Would look like: + +```bash +* Trying ::1... +* TCP_NODELAY set +* Connected to localhost (::1) port 8080 (#0) +> GET /foo HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/7.59.0 +> Accept: */* +> +< HTTP/1.1 200 OK +< Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS +< Access-Control-Allow-Origin: * +< Date: Fri, 28 Jun 2019 20:13:30 GMT +< Content-Length: 3 +< Content-Type: text/plain; charset=utf-8 +< +* Connection #0 to host localhost left intact +foo +``` + ### Testing Handlers Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_. diff --git a/vendor/github.com/gorilla/mux/doc.go b/vendor/github.com/gorilla/mux/doc.go index 38957dee..bd5a38b5 100644 --- a/vendor/github.com/gorilla/mux/doc.go +++ b/vendor/github.com/gorilla/mux/doc.go @@ -295,7 +295,7 @@ A more complex authentication middleware, which maps session token to users, cou r := mux.NewRouter() r.HandleFunc("/", handler) - amw := authenticationMiddleware{} + amw := authenticationMiddleware{tokenUsers: make(map[string]string)} amw.Populate() r.Use(amw.Middleware) diff --git a/vendor/github.com/gorilla/mux/middleware.go b/vendor/github.com/gorilla/mux/middleware.go index ceb812ce..cf2b26dc 100644 --- a/vendor/github.com/gorilla/mux/middleware.go +++ b/vendor/github.com/gorilla/mux/middleware.go @@ -32,37 +32,19 @@ func (r *Router) useInterface(mw middleware) { r.middlewares = append(r.middlewares, mw) } -// CORSMethodMiddleware sets the Access-Control-Allow-Methods response header -// on a request, by matching routes based only on paths. It also handles -// OPTIONS requests, by settings Access-Control-Allow-Methods, and then -// returning without calling the next http handler. +// CORSMethodMiddleware automatically sets the Access-Control-Allow-Methods response header +// on requests for routes that have an OPTIONS method matcher to all the method matchers on +// the route. Routes that do not explicitly handle OPTIONS requests will not be processed +// by the middleware. See examples for usage. func CORSMethodMiddleware(r *Router) MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - var allMethods []string - - err := r.Walk(func(route *Route, _ *Router, _ []*Route) error { - for _, m := range route.matchers { - if _, ok := m.(*routeRegexp); ok { - if m.Match(req, &RouteMatch{}) { - methods, err := route.GetMethods() - if err != nil { - return err - } - - allMethods = append(allMethods, methods...) - } - break - } - } - return nil - }) - + allMethods, err := getAllMethodsForRoute(r, req) if err == nil { - w.Header().Set("Access-Control-Allow-Methods", strings.Join(append(allMethods, "OPTIONS"), ",")) - - if req.Method == "OPTIONS" { - return + for _, v := range allMethods { + if v == http.MethodOptions { + w.Header().Set("Access-Control-Allow-Methods", strings.Join(allMethods, ",")) + } } } @@ -70,3 +52,28 @@ func CORSMethodMiddleware(r *Router) MiddlewareFunc { }) } } + +// getAllMethodsForRoute returns all the methods from method matchers matching a given +// request. +func getAllMethodsForRoute(r *Router, req *http.Request) ([]string, error) { + var allMethods []string + + err := r.Walk(func(route *Route, _ *Router, _ []*Route) error { + for _, m := range route.matchers { + if _, ok := m.(*routeRegexp); ok { + if m.Match(req, &RouteMatch{}) { + methods, err := route.GetMethods() + if err != nil { + return err + } + + allMethods = append(allMethods, methods...) + } + break + } + } + return nil + }) + + return allMethods, err +} diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml index d4b92663..d2dfad40 100644 --- a/vendor/github.com/pkg/errors/.travis.yml +++ b/vendor/github.com/pkg/errors/.travis.yml @@ -1,15 +1,10 @@ language: go go_import_path: github.com/pkg/errors go: - - 1.4.x - - 1.5.x - - 1.6.x - - 1.7.x - - 1.8.x - - 1.9.x - 1.10.x - 1.11.x + - 1.12.x - tip script: - - go test -v ./... + - make check diff --git a/vendor/github.com/pkg/errors/Makefile b/vendor/github.com/pkg/errors/Makefile new file mode 100644 index 00000000..ce9d7cde --- /dev/null +++ b/vendor/github.com/pkg/errors/Makefile @@ -0,0 +1,44 @@ +PKGS := github.com/pkg/errors +SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS)) +GO := go + +check: test vet gofmt misspell unconvert staticcheck ineffassign unparam + +test: + $(GO) test $(PKGS) + +vet: | test + $(GO) vet $(PKGS) + +staticcheck: + $(GO) get honnef.co/go/tools/cmd/staticcheck + staticcheck -checks all $(PKGS) + +misspell: + $(GO) get github.com/client9/misspell/cmd/misspell + misspell \ + -locale GB \ + -error \ + *.md *.go + +unconvert: + $(GO) get github.com/mdempsky/unconvert + unconvert -v $(PKGS) + +ineffassign: + $(GO) get github.com/gordonklaus/ineffassign + find $(SRCDIRS) -name '*.go' | xargs ineffassign + +pedantic: check errcheck + +unparam: + $(GO) get mvdan.cc/unparam + unparam ./... + +errcheck: + $(GO) get github.com/kisielk/errcheck + errcheck $(PKGS) + +gofmt: + @echo Checking code is gofmted + @test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)" diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md index 6483ba2a..cf771e7d 100644 --- a/vendor/github.com/pkg/errors/README.md +++ b/vendor/github.com/pkg/errors/README.md @@ -41,11 +41,18 @@ default: [Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). +## Roadmap + +With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows: + +- 0.9. Remove pre Go 1.9 support, address outstanding pull requests (if possible) +- 1.0. Final release. + ## Contributing -We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. +Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports. -Before proposing a change, please discuss your change by raising an issue. +Before sending a PR, please discuss your change by raising an issue. ## License diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go index 7421f326..8617beef 100644 --- a/vendor/github.com/pkg/errors/errors.go +++ b/vendor/github.com/pkg/errors/errors.go @@ -82,7 +82,7 @@ // // if err, ok := err.(stackTracer); ok { // for _, f := range err.StackTrace() { -// fmt.Printf("%+s:%d", f) +// fmt.Printf("%+s:%d\n", f, f) // } // } // diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go index 2874a048..779a8348 100644 --- a/vendor/github.com/pkg/errors/stack.go +++ b/vendor/github.com/pkg/errors/stack.go @@ -5,10 +5,13 @@ import ( "io" "path" "runtime" + "strconv" "strings" ) // Frame represents a program counter inside a stack frame. +// For historical reasons if Frame is interpreted as a uintptr +// its value represents the program counter + 1. type Frame uintptr // pc returns the program counter for this frame; @@ -37,6 +40,15 @@ func (f Frame) line() int { return line } +// name returns the name of this function, if known. +func (f Frame) name() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + return fn.Name() +} + // Format formats the frame according to the fmt.Formatter interface. // // %s source file @@ -54,22 +66,16 @@ func (f Frame) Format(s fmt.State, verb rune) { case 's': switch { case s.Flag('+'): - pc := f.pc() - fn := runtime.FuncForPC(pc) - if fn == nil { - io.WriteString(s, "unknown") - } else { - file, _ := fn.FileLine(pc) - fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) - } + io.WriteString(s, f.name()) + io.WriteString(s, "\n\t") + io.WriteString(s, f.file()) default: io.WriteString(s, path.Base(f.file())) } case 'd': - fmt.Fprintf(s, "%d", f.line()) + io.WriteString(s, strconv.Itoa(f.line())) case 'n': - name := runtime.FuncForPC(f.pc()).Name() - io.WriteString(s, funcname(name)) + io.WriteString(s, funcname(f.name())) case 'v': f.Format(s, 's') io.WriteString(s, ":") @@ -77,6 +83,16 @@ func (f Frame) Format(s fmt.State, verb rune) { } } +// MarshalText formats a stacktrace Frame as a text string. The output is the +// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs. +func (f Frame) MarshalText() ([]byte, error) { + name := f.name() + if name == "unknown" { + return []byte(name), nil + } + return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil +} + // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). type StackTrace []Frame @@ -94,16 +110,30 @@ func (st StackTrace) Format(s fmt.State, verb rune) { switch { case s.Flag('+'): for _, f := range st { - fmt.Fprintf(s, "\n%+v", f) + io.WriteString(s, "\n") + f.Format(s, verb) } case s.Flag('#'): fmt.Fprintf(s, "%#v", []Frame(st)) default: - fmt.Fprintf(s, "%v", []Frame(st)) + st.formatSlice(s, verb) } case 's': - fmt.Fprintf(s, "%s", []Frame(st)) + st.formatSlice(s, verb) + } +} + +// formatSlice will format this StackTrace into the given buffer as a slice of +// Frame, only valid when called with '%s' or '%v'. +func (st StackTrace) formatSlice(s fmt.State, verb rune) { + io.WriteString(s, "[") + for i, f := range st { + if i > 0 { + io.WriteString(s, " ") + } + f.Format(s, verb) } + io.WriteString(s, "]") } // stack represents a stack of program counters.