github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/share.go (about) 1 // Copyright (c) 2015-2022 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "bytes" 22 "fmt" 23 "os" 24 "path/filepath" 25 "strings" 26 "time" 27 28 "github.com/fatih/color" 29 "github.com/minio/cli" 30 json "github.com/minio/colorjson" 31 "github.com/minio/mc/pkg/probe" 32 "github.com/minio/pkg/v2/console" 33 ) 34 35 const ( 36 // Default expiry is 7 days (168h). 37 shareDefaultExpiry = time.Duration(604800) * time.Second 38 ) 39 40 // Upload specific flags. 41 var ( 42 shareFlagContentType = cli.StringFlag{ 43 Name: "content-type, T", 44 Usage: "specify a content-type to allow", 45 } 46 shareFlagExpire = cli.StringFlag{ 47 Name: "expire, E", 48 Value: "168h", 49 Usage: "set expiry in NN[h|m|s]", 50 } 51 ) 52 53 // Structured share command message. 54 type shareMessage struct { 55 Status string `json:"status"` 56 ObjectURL string `json:"url"` 57 ShareURL string `json:"share"` 58 TimeLeft time.Duration `json:"timeLeft"` 59 ContentType string `json:"contentType,omitempty"` // Only used by upload cmd. 60 } 61 62 // String - Themefied string message for console printing. 63 func (s shareMessage) String() string { 64 msg := console.Colorize("URL", fmt.Sprintf("URL: %s\n", s.ObjectURL)) 65 msg += console.Colorize("Expire", fmt.Sprintf("Expire: %s\n", timeDurationToHumanizedDuration(s.TimeLeft))) 66 if s.ContentType != "" { 67 msg += console.Colorize("Content-type", fmt.Sprintf("Content-Type: %s\n", s.ContentType)) 68 } 69 70 // Highlight <FILE> specifically. "share upload" sub-commands use this identifier. 71 shareURL := strings.Replace(s.ShareURL, "<FILE>", console.Colorize("File", "<FILE>"), 1) 72 // Highlight <KEY> specifically for recursive operation. 73 shareURL = strings.Replace(shareURL, "<NAME>", console.Colorize("File", "<NAME>"), 1) 74 75 msg += console.Colorize("Share", fmt.Sprintf("Share: %s\n", shareURL)) 76 77 return msg 78 } 79 80 // JSON - JSONified message for scripting. 81 func (s shareMessage) JSON() string { 82 s.Status = "success" 83 shareMessageBytes, e := json.MarshalIndent(s, "", " ") 84 fatalIf(probe.NewError(e), "Unable to marshal into JSON.") 85 86 // JSON encoding escapes ampersand into its unicode character 87 // which is not usable directly for share and fails with cloud 88 // storage. convert them back so that they are usable. 89 shareMessageBytes = bytes.Replace(shareMessageBytes, []byte("\\u0026"), []byte("&"), -1) 90 shareMessageBytes = bytes.Replace(shareMessageBytes, []byte("\\u003c"), []byte("<"), -1) 91 shareMessageBytes = bytes.Replace(shareMessageBytes, []byte("\\u003e"), []byte(">"), -1) 92 93 return string(shareMessageBytes) 94 } 95 96 // shareSetColor sets colors share sub-commands. 97 func shareSetColor() { 98 // Additional command speific theme customization. 99 console.SetColor("URL", color.New(color.Bold)) 100 console.SetColor("Expire", color.New(color.FgCyan)) 101 console.SetColor("Content-type", color.New(color.FgBlue)) 102 console.SetColor("Share", color.New(color.FgGreen)) 103 console.SetColor("File", color.New(color.FgRed, color.Bold)) 104 } 105 106 // Get share dir name. 107 func getShareDir() (string, *probe.Error) { 108 configDir, err := getMcConfigDir() 109 if err != nil { 110 return "", err.Trace() 111 } 112 113 sharedURLsDataDir := filepath.Join(configDir, globalSharedURLsDataDir) 114 return sharedURLsDataDir, nil 115 } 116 117 // Get share dir name or die. (NOTE: This `Die` approach is only OK for mc like tools.). 118 func mustGetShareDir() string { 119 shareDir, err := getShareDir() 120 fatalIf(err.Trace(), "Unable to determine share folder.") 121 return shareDir 122 } 123 124 // Check if the share dir exists. 125 func isShareDirExists() bool { 126 if _, e := os.Stat(mustGetShareDir()); e != nil { 127 return false 128 } 129 return true 130 } 131 132 // Create config share dir. 133 func createShareDir() *probe.Error { 134 if e := os.MkdirAll(mustGetShareDir(), 0o700); e != nil { 135 return probe.NewError(e) 136 } 137 return nil 138 } 139 140 // Get share uploads file. 141 func getShareUploadsFile() string { 142 return filepath.Join(mustGetShareDir(), "uploads.json") 143 } 144 145 // Get share downloads file. 146 func getShareDownloadsFile() string { 147 return filepath.Join(mustGetShareDir(), "downloads.json") 148 } 149 150 // Check if share uploads file exists?. 151 func isShareUploadsExists() bool { 152 if _, e := os.Stat(getShareUploadsFile()); e != nil { 153 return false 154 } 155 return true 156 } 157 158 // Check if share downloads file exists?. 159 func isShareDownloadsExists() bool { 160 if _, e := os.Stat(getShareDownloadsFile()); e != nil { 161 return false 162 } 163 return true 164 } 165 166 // Initialize share uploads file. 167 func initShareUploadsFile() *probe.Error { 168 return newShareDBV1().Save(getShareUploadsFile()) 169 } 170 171 // Initialize share downloads file. 172 func initShareDownloadsFile() *probe.Error { 173 return newShareDBV1().Save(getShareDownloadsFile()) 174 } 175 176 // Initialize share directory, if not done already. 177 func initShareConfig() { 178 // Share directory. 179 if !isShareDirExists() { 180 fatalIf(createShareDir().Trace(mustGetShareDir()), 181 "Failed to create share `"+mustGetShareDir()+"` folder.") 182 if !globalQuiet && !globalJSON { 183 console.Infof("Successfully created `%s`.\n", mustGetShareDir()) 184 } 185 } 186 187 // Uploads share file. 188 if !isShareUploadsExists() { 189 fatalIf(initShareUploadsFile().Trace(getShareUploadsFile()), 190 "Failed to initialize share uploads `"+getShareUploadsFile()+"` file.") 191 if !globalQuiet && !globalJSON { 192 console.Infof("Initialized share uploads `%s` file.\n", getShareUploadsFile()) 193 } 194 } 195 196 // Downloads share file. 197 if !isShareDownloadsExists() { 198 fatalIf(initShareDownloadsFile().Trace(getShareDownloadsFile()), 199 "Failed to initialize share downloads `"+getShareDownloadsFile()+"` file.") 200 if !globalQuiet && !globalJSON { 201 console.Infof("Initialized share downloads `%s` file.\n", getShareDownloadsFile()) 202 } 203 } 204 }