Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/msfjarvis/gdrive into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
norima committed Oct 18, 2023
2 parents 1c70c54 + 41a96c5 commit 14058a0
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 582 deletions.
6 changes: 6 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"recommendations": [
"golang.go",
"jnoortheen.nix-ide"
]
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"nixEnvSelector.nixFile": "${workspaceRoot}/flake.nix"
}
16 changes: 2 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
gdrive
# gdrive [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/)
======


## Important
~~This tool is no longer maintained.~~ This fork intends to collate important fixes that are necessary to keep this thing working. This fork also does **not** include binaries, on purpose.

To build your own binaries, follow the steps [here](https://github.com/prasmussen/gdrive/issues/426) to get your own client ID and secret.

Then, supply the obtained Client ID and secret to `go build`:

```shell
go build -ldflags "-X main.ClientId=${CLIENT_ID} -X main.ClientSecret=${CLIENT_SECRET}"
```

## Overview
gdrive is a command line utility for interacting with Google Drive.

## Important
~~This tool is no longer maintained.~~ This fork intends to collate important fixes that are necessary to keep this thing working. This fork also does **not** include binaries, on purpose.
This fork intends to collate important fixes that are necessary to keep this thing working. This fork also does **not** include binaries, on purpose.
149 changes: 149 additions & 0 deletions auth/listener.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package auth

import (
"context"
"fmt"
"net"
"net/http"
"os"
"time"

"golang.org/x/oauth2"
)

type authorize struct{ authUrl string }
type callback struct {
done chan string
bad chan bool
state string
}

func (a authorize) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Header().Add("Location", a.authUrl)
w.WriteHeader(302)
fmt.Fprintln(w, "<html><head>")
fmt.Fprintln(w, "<title>Redirect to authentication server</title>")
fmt.Fprintln(w, "</head><body>")
fmt.Fprintf(w, "Click <a href=\"%s\">here</a> to authorize gdrive to use Google Drive\n",
a.authUrl)
fmt.Fprintln(w, "</body></html>")
}

func (c callback) ServeHTTP(w http.ResponseWriter, req *http.Request) {
err := req.ParseForm()
if err != nil {
fmt.Printf("Could not parse form on /callback: %s\n", err)
w.WriteHeader(400)
fmt.Fprintln(w, "<html><head>")
fmt.Fprintln(w, "<title>Bad request</title>")
fmt.Fprintln(w, "</head><body>")
fmt.Fprintln(w, "Bad request: Missing authentication response")
fmt.Fprintln(w, "</body></html>")
return
}
if req.Form.Has("error") {
fmt.Printf("authentication failed, server response is %s\n", req.Form.Get("error"))
c.bad <- true
fmt.Fprintln(w, "<html><head>")
fmt.Fprintln(w, "<title>Google Drive authentication failed</title>")
fmt.Fprintln(w, "</head><body>")
fmt.Fprintf(w, "Authentication failed or refused: %s\n", req.Form.Get("error"))
fmt.Fprintln(w, "</body></html>")
return
}

if !req.Form.Has("code") || !req.Form.Has("state") {
fmt.Println("callback request is missing parameters")
w.WriteHeader(400)
fmt.Fprintln(w, "<html><head>")
fmt.Fprintln(w, "<title>Bad request</title>")
fmt.Fprintln(w, "</head><body>")
fmt.Fprintln(w, "Bad request: response is missing the code or state parameters")
fmt.Fprintln(w, "</body></html>")
return
}

code := req.Form.Get("code")
state := req.Form.Get("state")
if state != c.state {
fmt.Printf("Callback state mismatch: %s vs %s", state, c.state)
w.WriteHeader(400)
fmt.Fprintln(w, "<html><head>")
fmt.Fprintln(w, "<title>Bad request</title>")
fmt.Fprintln(w, "</head><body>")
fmt.Fprintln(w, "Bad request: response state mismatch")
fmt.Fprintln(w, "</body></html>")
return
}
fmt.Fprintln(w, "<html><head>")
fmt.Fprintln(w, "<title>Authentication response received</title>")
fmt.Fprintln(w, "</head><body>")
fmt.Fprintln(w, "Authentication response has been received. Check the terminal where gdrive is running")
fmt.Fprintln(w, "</body></html>")

c.done <- code
}

func AuthCodeHTTP(conf *oauth2.Config, state, challenge string) (func() (string, error), error) {

authChallengeMeth := oauth2.SetAuthURLParam("code_challenge_method", "S256")
authChallengeVal := oauth2.SetAuthURLParam("code_challenge", challenge)

ln, err := net.Listen("tcp4", "127.0.0.1:0")
if err != nil {
return nil, err
}

hostPort := ln.Addr().String()
_, port, err := net.SplitHostPort(hostPort)
if err != nil {
return nil, err
}

mux := http.NewServeMux()
srv := &http.Server{Handler: mux}

go func() {
err := srv.Serve(ln)
if err != http.ErrServerClosed {
fmt.Printf("Cannot start http server: %s", err)
os.Exit(1)
}
}()
myconf := conf
myconf.RedirectURL = fmt.Sprintf("http://127.0.0.1:%s/callback", port)

authUrl := myconf.AuthCodeURL(state, oauth2.AccessTypeOffline, authChallengeMeth, authChallengeVal)
authorizer := authorize{authUrl: authUrl}
mux.Handle("/authorize", authorizer)
callback := callback{state: state,
done: make(chan string, 1),
bad: make(chan bool, 1),
}
mux.Handle("/callback", callback)

return func() (string, error) {
var code string
var err error
fmt.Println("Authentication needed")
fmt.Println("Go to the following url in your browser:")
fmt.Printf("http://127.0.0.1:%s/authorize\n\n", port)
fmt.Println("Waiting for authentication response")

select {
case <-callback.bad:
err = fmt.Errorf("authentication did not complete successfully")
code = ""
case code = <-callback.done:
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer func() {
cancel()
}()

if stoperr := srv.Shutdown(ctx); stoperr != nil {
fmt.Printf("Server Shutdown Failed:%+v\n", stoperr)
}
return code, err
}, nil
}
59 changes: 54 additions & 5 deletions auth/oauth.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package auth

import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"net/http"
"time"

"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)

type authCodeFn func(string) func() string
type authCodeFn func(*oauth2.Config, string, string) (func() (string, error), error)

func NewFileSourceClient(clientId, clientSecret, tokenFile string, authFn authCodeFn) (*http.Client, error) {
conf := getConfig(clientId, clientSecret)
Expand All @@ -23,11 +27,26 @@ func NewFileSourceClient(clientId, clientSecret, tokenFile string, authFn authCo
// Require auth code if token file does not exist
// or refresh token is missing
if !exists || token.RefreshToken == "" {
authUrl := conf.AuthCodeURL("state", oauth2.AccessTypeOffline)
authCode := authFn(authUrl)()
token, err = conf.Exchange(oauth2.NoContext, authCode)
state, err := makeState()
if err != nil {
return nil, fmt.Errorf("Failed to exchange auth code for token: %s", err)
return nil, fmt.Errorf("could not build state string: %s", err)
}
verifier, challenge, err := makeCodeChallenge()
if err != nil {
return nil, fmt.Errorf("could not set up PKCE challenge: %s", err)
}
authFnInt, err := authFn(conf, state, challenge)
if err != nil {
return nil, fmt.Errorf("could not receive auth code: %s", err)
}
authCode, err := authFnInt()
if err != nil {
return nil, fmt.Errorf("could not receive auth code: %s", err)
}
authVerifyVal := oauth2.SetAuthURLParam("code_verifier", verifier)
token, err = conf.Exchange(oauth2.NoContext, authCode, authVerifyVal)
if err != nil {
return nil, fmt.Errorf("failed to exchange auth code for token: %s", err)
}
}

Expand Down Expand Up @@ -95,3 +114,33 @@ func getConfig(clientId, clientSecret string) *oauth2.Config {
},
}
}

func makeState() (string, error) {
return makeString(12)
}

func makeCodeChallenge() (string, string, error) {
verifier, err := makeString(48)
if err != nil {
return "", "", err
}

hasher := sha256.New()
_, err = hasher.Write([]byte(verifier))
if err != nil {
return "", "", err
}

hash := hasher.Sum(nil)
challenge := base64.RawURLEncoding.EncodeToString(hash)

return verifier, challenge, nil
}

func makeString(n int) (string, error) {
data := make([]byte, n)
if _, err := io.ReadFull(rand.Reader, data); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(data), nil
}
25 changes: 25 additions & 0 deletions drive/delete.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package drive

import (
"context"
"fmt"
"io"

"google.golang.org/api/drive/v3"
"google.golang.org/api/googleapi"
)

type DeleteArgs struct {
Expand All @@ -21,6 +25,27 @@ func (self *Drive) Delete(args DeleteArgs) error {
return fmt.Errorf("'%s' is a directory, use the 'recursive' flag to delete directories", f.Name)
}

if false {
controlledStop := fmt.Errorf("Controlled stop")
var files []*drive.File
pageSize := 50
self.service.Files.List().SupportsTeamDrives(true).IncludeTeamDriveItems(true).Q(fmt.Sprintf("'%s' in parents", args.Id)).Fields([]googleapi.Field{"nextPageToken", "files(id,name)"}...).PageSize(int64(pageSize)).Pages(context.TODO(), func(fl *drive.FileList) error {
if len(fl.Files) != 0 {
files = append(files, fl.Files...)
} else {
return controlledStop
}
return nil
})
for _, f := range files {
fmt.Printf("Deleting file: %s\n", f.Name)
err = self.service.Files.Delete(f.Id).SupportsTeamDrives(true).Do()
if err != nil {
return fmt.Errorf("Failed to delete file: %s", err)
}
}
}

err = self.service.Files.Delete(args.Id).SupportsTeamDrives(true).Do()
if err != nil {
return fmt.Errorf("Failed to delete file: %s", err)
Expand Down
5 changes: 4 additions & 1 deletion drive/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type DownloadArgs struct {
Stdout bool
Timeout time.Duration
Try int
Quiet bool
}

func (self *Drive) Download(args DownloadArgs) error {
Expand Down Expand Up @@ -151,7 +152,9 @@ func (self *Drive) downloadBinary(f *drive.File, args DownloadArgs) (int64, int6

//Check if file exists to skip
if args.Skip && fileExists(fpath) {
fmt.Printf("File '%s' already exists, skipping\n", fpath)
if !args.Quiet {
fmt.Printf("File '%s' already exists, skipping\n", fpath)
}
return 0, 0, nil
}

Expand Down
43 changes: 43 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
};

outputs = { self, nixpkgs, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let pkgs = import nixpkgs { inherit system; };
in {
devShells.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [
git
go_1_19
gopls
];
};
});
}
Loading

0 comments on commit 14058a0

Please sign in to comment.