1
0
mirror of https://github.com/minio/mc.git synced 2025-11-12 01:02:26 +03:00
Files
mc/share-main.go
2015-09-12 21:51:29 -07:00

272 lines
7.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Minio Client (C) 2014, 2015 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"path/filepath"
"strings"
"time"
"github.com/fatih/color"
"github.com/minio/cli"
"github.com/minio/mc/pkg/client"
"github.com/minio/mc/pkg/console"
"github.com/minio/minio/pkg/probe"
)
// Share documents via URL.
var shareCmd = cli.Command{
Name: "share",
Usage: "Share documents via URL.",
Action: mainShare,
CustomHelpTemplate: `NAME:
mc {{.Name}} - {{.Usage}}
USAGE:
mc {{.Name}} TARGET [DURATION]
DURATION = NN[h|m|s] [DEFAULT=168h]
EXAMPLES:
1. Generate URL for sharing, with a default expiry of 7 days.
$ mc {{.Name}} https://s3.amazonaws.com/backup/2006-Mar-1/backup.tar.gz
2. Generate URL for sharing, with an expiry of 10 minutes.
$ mc {{.Name}} https://s3.amazonaws.com/backup/2006-Mar-1/backup.tar.gz 10m
3. Generate list of URLs for sharing a folder recursively, with expiration of 5 days each.
$ mc {{.Name}} https://s3.amazonaws.com/backup... 120h
4. Generate URL with space characters for sharing, with an expiry of 5 seconds.
$ mc {{.Name}} s3/miniocloud/nothing-like-anything 5s
5. List all the shared URLs which have not expired yet.
$ mc {{.Name}} list
`,
}
// ShareMessage is container for share command on success and failure messages
type ShareMessage struct {
Expiry time.Duration
URL string
}
func (s ShareMessage) String() string {
if !globalJSONFlag {
return console.Colorize("Share", fmt.Sprintf("Expiry: %s\n URL: %s", humanizeDuration(s.Expiry), s.URL))
}
shareMessageBytes, err := json.Marshal(struct {
Expiry time.Duration `json:"expireSeconds"`
SharedURL string `json:"sharedURL"`
}{
Expiry: time.Duration(s.Expiry.Seconds()),
SharedURL: s.URL,
})
fatalIf(probe.NewError(err), "Failed to marshal into JSON.")
// json encoding escapes ampersand into its unicode character which is not usable directly for share
// and fails with cloud storage. convert them back so that they are usable
shareMessageBytes = bytes.Replace(shareMessageBytes, []byte("\\u0026"), []byte("&"), -1)
return string(shareMessageBytes)
}
func checkShareSyntax(ctx *cli.Context) {
if !ctx.Args().Present() || ctx.Args().First() == "help" || len(ctx.Args()) > 2 {
cli.ShowCommandHelpAndExit(ctx, "share", 1) // last argument is exit code
}
if len(ctx.Args()) > 2 {
cli.ShowCommandHelpAndExit(ctx, "share", 1) // last argument is exit code
}
if ctx.Args().Get(0) == "list" {
if len(ctx.Args()) >= 2 {
cli.ShowCommandHelpAndExit(ctx, "share", 1) // last argument is exit code
}
}
if strings.TrimSpace(ctx.Args().Get(0)) == "" {
fatalIf(errInvalidArgument().Trace(), "Unable to validate empty argument.")
}
}
// mainShare - is a handler for mc share command
func mainShare(ctx *cli.Context) {
checkShareSyntax(ctx)
if !isSharedURLsDataDirExists() {
shareDir, err := getSharedURLsDataDir()
fatalIf(err.Trace(), "Unable to get shared URL data directory")
fatalIf(createSharedURLsDataDir().Trace(), "Unable to create shared URL data directory "+shareDir+".")
}
if !isSharedURLsDataFileExists() {
shareFile, err := getSharedURLsDataFile()
fatalIf(err.Trace(), "Unable to get shared URL data file")
fatalIf(createSharedURLsDataFile().Trace(), "Unable to create shared URL data file "+shareFile+".")
}
console.SetCustomTheme(map[string]*color.Color{
"Share": color.New(color.FgGreen, color.Bold),
"Expires": color.New(color.FgRed, color.Bold),
"URL": color.New(color.FgCyan, color.Bold),
})
args := ctx.Args()
config := mustGetMcConfig()
// if its only list, return quickly
if args.Get(0) == "list" {
err := doShareList()
fatalIf(err.Trace(), "Unable to list shared URLs.")
return
}
/// get first and last arguments
url := args.Get(0) // url to be shared
// default expiration is 7days
expires := time.Duration(604800) * time.Second
if len(args) == 2 {
var err error
expires, err = time.ParseDuration(args.Get(1))
fatalIf(probe.NewError(err), "Unable to parse time argument.")
}
targetURL := getAliasURL(url, config.Aliases)
// if recursive strip off the "..."
err := doShareURL(stripRecursiveURL(targetURL), isURLRecursive(targetURL), expires)
fatalIf(err.Trace(targetURL), "Unable to generate URL for sharing.")
}
// doShareList list shared url's
func doShareList() *probe.Error {
sURLs, err := loadSharedURLsV1()
if err != nil {
return err.Trace()
}
for url, data := range sURLs.URLs {
if time.Since(data.Date) > data.Message.Expiry {
delete(sURLs.URLs, url)
continue
}
expiry := data.Message.Expiry - time.Since(data.Date)
if !globalJSONFlag {
msg := console.Colorize("Share", "Name: ")
msg += console.Colorize("URL", url+"\n")
msg += console.Colorize("Share", "Expiry: ")
msg += console.Colorize("Expires", humanizeDuration(expiry))
msg += "\n"
console.Println(msg)
continue
}
shareListBytes, err := json.Marshal(struct {
Expiry time.Duration `json:"expiry"`
URL string `json:"url"`
}{
Expiry: time.Duration(expiry.Seconds()),
URL: url,
})
if err != nil {
return probe.NewError(err)
}
console.Println(string(shareListBytes))
}
if err := saveSharedURLsV1(sURLs); err != nil {
return err.Trace()
}
return nil
}
// doShareURL share files from target
func doShareURL(targetURL string, recursive bool, expires time.Duration) *probe.Error {
shareDate := time.Now().UTC()
sURLs, err := loadSharedURLsV1()
if err != nil {
return err.Trace()
}
var clnt client.Client
clnt, err = target2Client(targetURL)
if err != nil {
return err.Trace()
}
if expires.Seconds() < 1 {
return probe.NewError(errors.New("Too low expires, expiration cannot be less than 1 second."))
}
if expires.Seconds() > 604800 {
return probe.NewError(errors.New("Too high expires, expiration cannot be larger than 7 days."))
}
for contentCh := range clnt.List(recursive) {
if contentCh.Err != nil {
return contentCh.Err.Trace()
}
var newClnt client.Client
newClnt, err = url2Client(getNewTargetURL(clnt.URL(), contentCh.Content.Name))
if err != nil {
return err.Trace()
}
var sharedURL string
sharedURL, err = newClnt.Share(expires)
if err != nil {
return err.Trace()
}
shareMessage := ShareMessage{
Expiry: expires,
URL: sharedURL,
}
sURLs.URLs[newClnt.URL().String()] = struct {
Date time.Time
Message ShareMessage
}{
Date: shareDate,
Message: shareMessage,
}
console.Println(shareMessage)
}
saveSharedURLsV1(sURLs)
return nil
}
func getNewTargetURL(targetParser *client.URL, name string) string {
match, _ := filepath.Match("*.s3*.amazonaws.com", targetParser.Host)
if match {
targetParser.Path = string(targetParser.Separator) + name
} else {
targetParser.Path = string(targetParser.Separator) + path2Bucket(targetParser) + string(targetParser.Separator) + name
}
return targetParser.String()
}
// this code is necessary since, share only operates on cloud storage URLs not filesystem
func path2Bucket(u *client.URL) (bucketName string) {
pathSplits := strings.SplitN(u.Path, "?", 2)
splits := strings.SplitN(pathSplits[0], string(u.Separator), 3)
switch len(splits) {
case 0, 1:
bucketName = ""
case 2:
bucketName = splits[1]
case 3:
bucketName = splits[1]
}
return bucketName
}