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  }