Skip to content

Commit 913f256

Browse files
authored
Merge pull request src-d#178 from camathieu/1.2
1.2
2 parents 4acdf2a + bf26f4b commit 913f256

File tree

11 files changed

+423
-57
lines changed

11 files changed

+423
-57
lines changed

client/archive/zip/zip.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ type Backend struct {
4949
func NewZipBackend(config map[string]interface{}) (zb *Backend, err error) {
5050
zb = new(Backend)
5151
zb.Config = NewZipBackendConfig(config)
52-
if _, err := os.Stat(zb.Config.Zip); os.IsNotExist(err) || os.IsPermission(err) {
52+
if _, err = os.Stat(zb.Config.Zip); os.IsNotExist(err) || os.IsPermission(err) {
5353
if zb.Config.Zip, err = exec.LookPath("zip"); err != nil {
5454
err = errors.New("zip binary not found in $PATH, please install or edit ~/.plickrc")
5555
}

client/plik.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -224,15 +224,10 @@ Options:
224224
}
225225
}
226226

227-
// Comments
227+
// Display commands
228228
if !uploadInfo.Stream {
229-
var totalSize int64
230229
printf("\nCommands : \n")
231230
for _, file := range uploadInfo.Files {
232-
233-
// Increment size
234-
totalSize += file.CurrentSize
235-
236231
// Print file information (only url if quiet mode is enabled)
237232
if config.Config.Quiet {
238233
fmt.Println(getFileURL(uploadInfo, file))
@@ -657,9 +652,9 @@ func updateClient(updateFlag bool) (err error) {
657652
}
658653

659654
if version != "" {
660-
printf("Plik client sucessfully updated to %s\n", version)
655+
printf("Plik client successfully updated to %s\n", version)
661656
} else {
662-
printf("Plik client sucessfully updated\n")
657+
printf("Plik client successfully updated\n")
663658
}
664659

665660
return

documentation/api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ Get file :
5858
- **GET** /$mode/:uploadid:/:fileid:/:filename:/yubikey/:yubikeyOtp:
5959
- Same as previous call, except that you can specify a Yubikey OTP in the URL if the upload is Yubikey restricted.
6060

61+
- **GET** /archive/:uploadid:/:filename:
62+
- Download uploaded files in a zip archive. :filename: must end with .zip
63+
6164
Remove file :
6265

6366
- **DELETE** /$mode/:uploadid:/:fileid:/:filename:

server/handlers/getArchive.go

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/**
2+
3+
Plik upload server
4+
5+
The MIT License (MIT)
6+
7+
Copyright (c) <2015>
8+
- Mathieu Bodjikian <[email protected]>
9+
- Charles-Antoine Mathieu <[email protected]>
10+
11+
Permission is hereby granted, free of charge, to any person obtaining a copy
12+
of this software and associated documentation files (the "Software"), to deal
13+
in the Software without restriction, including without limitation the rights
14+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15+
copies of the Software, and to permit persons to whom the Software is
16+
furnished to do so, subject to the following conditions:
17+
18+
The above copyright notice and this permission notice shall be included in
19+
all copies or substantial portions of the Software.
20+
21+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27+
THE SOFTWARE.
28+
**/
29+
30+
package handlers
31+
32+
import (
33+
"archive/zip"
34+
"fmt"
35+
"io"
36+
"net/http"
37+
"strings"
38+
39+
"github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/gorilla/mux"
40+
"github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/root-gg/juliet"
41+
"github.com/root-gg/plik/server/common"
42+
"github.com/root-gg/plik/server/dataBackend"
43+
"github.com/root-gg/plik/server/metadataBackend"
44+
)
45+
46+
// GetArchive download all file of the upload in a zip archive
47+
func GetArchive(ctx *juliet.Context, resp http.ResponseWriter, req *http.Request) {
48+
log := common.GetLogger(ctx)
49+
50+
// If a download domain is specified verify that the request comes from this specific domain
51+
if common.Config.DownloadDomainURL != nil {
52+
if req.Host != common.Config.DownloadDomainURL.Host {
53+
downloadURL := fmt.Sprintf("%s://%s%s",
54+
common.Config.DownloadDomainURL.Scheme,
55+
common.Config.DownloadDomainURL.Host,
56+
req.RequestURI)
57+
log.Warningf("Invalid download domain %s, expected %s", req.Host, common.Config.DownloadDomainURL.Host)
58+
http.Redirect(resp, req, downloadURL, 301)
59+
return
60+
}
61+
}
62+
63+
// Get upload from context
64+
upload := common.GetUpload(ctx)
65+
if upload == nil {
66+
// This should never append
67+
log.Critical("Missing upload in getFileHandler")
68+
common.Fail(ctx, req, resp, "Internal error", 500)
69+
return
70+
}
71+
72+
// Get files to archive
73+
var files []*common.File
74+
for _, file := range upload.Files {
75+
// If upload has OneShot option, test if one of the files has not been already downloaded once
76+
if upload.OneShot && file.Status == "downloaded" {
77+
log.Warningf("File %s has already been downloaded", file.Name)
78+
common.Fail(ctx, req, resp, fmt.Sprintf("File %s has already been downloaded", file.Name), 404)
79+
return
80+
}
81+
82+
// If the file is marked as deleted by a previous call, we abort request
83+
if file.Status == "removed" {
84+
log.Warningf("File %s has been removed", file.Name)
85+
common.Fail(ctx, req, resp, "File %s has been removed", 404)
86+
return
87+
}
88+
89+
files = append(files, file)
90+
}
91+
92+
if len(files) == 0 {
93+
common.Fail(ctx, req, resp, "Nothing to archive", 404)
94+
return
95+
}
96+
97+
// Set content type
98+
resp.Header().Set("Content-Type", "application/zip")
99+
100+
/* Additional security headers for possibly unsafe content */
101+
resp.Header().Set("X-Content-Type-Options", "nosniff")
102+
resp.Header().Set("X-XSS-Protection", "1; mode=block")
103+
resp.Header().Set("X-Frame-Options", "DENY")
104+
resp.Header().Set("Content-Security-Policy", "default-src 'none'; script-src 'none'; style-src 'none'; img-src 'none'; connect-src 'none'; font-src 'none'; object-src 'none'; media-src 'none'; child-src 'none'; form-action 'none'; frame-ancestors 'none'; plugin-types ''; sandbox ''")
105+
106+
// Get the file name from the url params
107+
vars := mux.Vars(req)
108+
fileName := vars["filename"]
109+
if fileName == "" {
110+
log.Warning("Missing file name")
111+
common.Fail(ctx, req, resp, "Missing file name", 400)
112+
return
113+
}
114+
115+
if strings.HasSuffix(".zip", fileName) {
116+
log.Warningf("Invalid file name %s. Missing .zip extention", fileName)
117+
common.Fail(ctx, req, resp, fmt.Sprintf("Invalid file name %s. Missing .zip extention", fileName), 400)
118+
return
119+
}
120+
121+
// If "dl" GET params is set
122+
// -> Set Content-Disposition header
123+
// -> The client should download file instead of displaying it
124+
dl := req.URL.Query().Get("dl")
125+
if dl != "" {
126+
resp.Header().Set("Content-Disposition", fmt.Sprintf(`attachement; filename="%s"`, fileName))
127+
} else {
128+
resp.Header().Set("Content-Disposition", fmt.Sprintf(`filename="%s"`, fileName))
129+
}
130+
131+
// HEAD Request => Do not print file, user just wants http headers
132+
// GET Request => Print file content
133+
if req.Method == "GET" {
134+
// Get file in data backend
135+
136+
if upload.Stream {
137+
common.Fail(ctx, req, resp, "Archive feature is not available in stream mode", 404)
138+
return
139+
}
140+
141+
backend := dataBackend.GetDataBackend()
142+
143+
// The zip archive is piped directly to http response body without buffering
144+
archive := zip.NewWriter(resp)
145+
146+
for _, file := range files {
147+
fileReader, err := backend.GetFile(ctx, upload, file.ID)
148+
if err != nil {
149+
log.Warningf("Failed to get file %s in upload %s : %s", file.Name, upload.ID, err)
150+
common.Fail(ctx, req, resp, fmt.Sprintf("Failed to read file %s", file.Name), 404)
151+
return
152+
}
153+
154+
fileWriter, err := archive.Create(file.Name)
155+
if err != nil {
156+
log.Warningf("Failed to add file %s to the archive : %s", file.Name, err)
157+
common.Fail(ctx, req, resp, fmt.Sprintf("Failed to add file %s to the archive", file.Name), 500)
158+
return
159+
}
160+
161+
// Update metadata if oneShot option is set
162+
if upload.OneShot {
163+
file.Status = "downloaded"
164+
err = metadataBackend.GetMetaDataBackend().AddOrUpdateFile(ctx, upload, file)
165+
if err != nil {
166+
log.Warningf("Error while deleting file %s from upload %s metadata : %s", file.Name, upload.ID, err)
167+
}
168+
}
169+
170+
// File is piped directly to zip archive thus to the http response body without buffering
171+
_, err = io.Copy(fileWriter, fileReader)
172+
if err != nil {
173+
log.Warningf("Error while copying file to response : %s", err)
174+
}
175+
176+
err = fileReader.Close()
177+
if err != nil {
178+
log.Warningf("Error while closing file reader : %s", err)
179+
}
180+
181+
// Remove file from data backend if oneShot option is set
182+
if upload.OneShot {
183+
err = backend.RemoveFile(ctx, upload, file.ID)
184+
if err != nil {
185+
log.Warningf("Error while deleting file %s from upload %s : %s", file.Name, upload.ID, err)
186+
return
187+
}
188+
}
189+
}
190+
191+
err := archive.Close()
192+
if err != nil {
193+
log.Warningf("Failed to close zip archive : %s", err)
194+
return
195+
}
196+
197+
// Remove upload if no files anymore
198+
RemoveUploadIfNoFileAvailable(ctx, upload)
199+
}
200+
}

server/handlers/getFile.go

Lines changed: 2 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import (
3636
"strconv"
3737
"strings"
3838

39-
"github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/gorilla/mux"
4039
"github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/root-gg/juliet"
4140
"github.com/root-gg/plik/server/common"
4241
"github.com/root-gg/plik/server/dataBackend"
@@ -92,53 +91,12 @@ func GetFile(ctx *juliet.Context, resp http.ResponseWriter, req *http.Request) {
9291
return
9392
}
9493

95-
// If upload is yubikey protected, user must send an OTP when he wants to get a file.
96-
if upload.Yubikey != "" {
97-
98-
// Error if yubikey is disabled on server, and enabled on upload
99-
if !common.Config.YubikeyEnabled {
100-
log.Warningf("Got a Yubikey upload but Yubikey backend is disabled")
101-
common.Fail(ctx, req, resp, "Yubikey are disabled on this server", 403)
102-
return
103-
}
104-
105-
vars := mux.Vars(req)
106-
token := vars["yubikey"]
107-
if token == "" {
108-
log.Warningf("Missing yubikey token")
109-
common.Fail(ctx, req, resp, "Invalid yubikey token", 401)
110-
return
111-
}
112-
if len(token) != 44 {
113-
log.Warningf("Invalid yubikey token : %s", token)
114-
common.Fail(ctx, req, resp, "Invalid yubikey token", 401)
115-
return
116-
}
117-
if token[:12] != upload.Yubikey {
118-
log.Warningf("Invalid yubikey device : %s", token)
119-
common.Fail(ctx, req, resp, "Invalid yubikey token", 401)
120-
return
121-
}
122-
123-
_, isValid, err := common.Config.YubiAuth.Verify(token)
124-
if err != nil {
125-
log.Warningf("Failed to validate yubikey token : %s", err)
126-
common.Fail(ctx, req, resp, "Invalid yubikey token", 500)
127-
return
128-
}
129-
if !isValid {
130-
log.Warningf("Invalid yubikey token : %s", token)
131-
common.Fail(ctx, req, resp, "Invalid yubikey token", 401)
132-
return
133-
}
134-
}
135-
13694
// Avoid rendering HTML in browser
13795
if strings.Contains(file.Type, "html") {
13896
file.Type = "text/plain"
13997
}
14098

141-
if file.Type == "" || strings.Contains(file.Type, "flash") {
99+
if file.Type == "" || strings.Contains(file.Type, "flash") || strings.Contains(file.Type, "pdf") {
142100
file.Type = "application/octet-stream"
143101
}
144102

@@ -175,6 +133,7 @@ func GetFile(ctx *juliet.Context, resp http.ResponseWriter, req *http.Request) {
175133
} else {
176134
backend = dataBackend.GetDataBackend()
177135
}
136+
178137
fileReader, err := backend.GetFile(ctx, upload, file.ID)
179138
if err != nil {
180139
log.Warningf("Failed to get file %s in upload %s : %s", file.Name, upload.ID, err)

server/handlers/misc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func GetQrCode(ctx *juliet.Context, resp http.ResponseWriter, req *http.Request)
120120
}
121121

122122
// RemoveUploadIfNoFileAvailable iterates on upload files and remove upload files
123-
// and metadata if all the files have been downloaded (usefull for OneShot uploads)
123+
// and metadata if all the files have been downloaded (useful for OneShot uploads)
124124
func RemoveUploadIfNoFileAvailable(ctx *juliet.Context, upload *common.Upload) {
125125
log := common.GetLogger(ctx)
126126

0 commit comments

Comments
 (0)