Skip to content

Commit 0172fa9

Browse files
committed
feat(archive): allow archive selected paths
Use param `name` to specify sub items to archive, e.g. "filename" or "directory-name". Could be a deeper path like "sub-dir/sub-sub-dir" or "sub-dir/file".
1 parent e775a4f commit 0172fa9

File tree

11 files changed

+234
-20
lines changed

11 files changed

+234
-20
lines changed

doc/en-US/api.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,34 @@ Only work when "archive" is enabled.
6767
GET <path>?tar
6868
GET <path>?tgz
6969
GET <path>?zip
70+
POST <path>?tar
71+
POST <path>?tgz
72+
POST <path>?zip
7073
```
7174

7275
Example:
7376
```sh
7477
curl http://localhost/tmp/?zip > tmp.zip
7578
```
7679

80+
To archive specific sub items under current directory, pass `name` params:
81+
```
82+
GET <path>?tar&name=<path1>&name=<path2>&...name=<pathN>
83+
GET <path>?tgz&name=<path1>&name=<path2>&...name=<pathN>
84+
GET <path>?zip&name=<path1>&name=<path2>&...name=<pathN>
85+
```
86+
87+
```
88+
POST <path>?tar
89+
90+
name=<path1>&name=<path2>&...name=<pathN>
91+
```
92+
93+
Example:
94+
```sh
95+
curl -X POST -d 'name=subdir1&name=subdir2/subdir21&name=file1&name=subdir3/file31' http://localhost/tmp/?zip > tmp.zip
96+
```
97+
7798
# Create directories in specific path
7899
Only work when "mkdir" is enabled.
79100
```

doc/zh-CN/api.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,34 @@ curl http://localhost/ghfs/file?download
6565
GET <path>?tar
6666
GET <path>?tgz
6767
GET <path>?zip
68+
POST <path>?tar
69+
POST <path>?tgz
70+
POST <path>?zip
6871
```
6972

7073
举例:
7174
```sh
7275
curl http://localhost/tmp/?zip > tmp.zip
7376
```
7477

78+
要打包当前目录下的指定子项,用`name`参数指定:
79+
```
80+
GET <path>?tar&name=<path1>&name=<path2>&...name=<pathN>
81+
GET <path>?tgz&name=<path1>&name=<path2>&...name=<pathN>
82+
GET <path>?zip&name=<path1>&name=<path2>&...name=<pathN>
83+
```
84+
85+
```
86+
POST <path>?tar
87+
88+
name=<path1>&name=<path2>&...name=<pathN>
89+
```
90+
91+
举例:
92+
```sh
93+
curl -X POST -d 'name=subdir1&name=subdir2/subdir21&name=file1&name=subdir3/file31' http://localhost/tmp/?zip > tmp.zip
94+
```
95+
7596
# 在指定路径下创建目录
7697
仅在“mkdir”选项启用时有效。
7798
```

src/serverHandler/archive.go

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,45 @@ import (
66
"net/url"
77
"os"
88
"path"
9+
"strings"
910
)
1011

1112
type archiveCallback func(f *os.File, fInfo os.FileInfo, relPath string) error
1213

13-
func (h *handler) visitFs(
14+
func matchSelection(name string, selections []string) (matchName, matchPrefix bool, childSelections []string) {
15+
if len(selections) == 0 {
16+
return true, false, nil
17+
}
18+
19+
for _, sel := range selections {
20+
if sel == name {
21+
matchName = true
22+
continue
23+
}
24+
25+
slashIndex := strings.IndexByte(sel, '/')
26+
if slashIndex <= 0 {
27+
continue
28+
}
29+
30+
prefix := sel[:slashIndex]
31+
if prefix == name {
32+
childSel := sel[slashIndex+1:]
33+
if len(childSel) > 0 {
34+
matchPrefix = true
35+
childSelections = append(childSelections, childSel)
36+
}
37+
continue
38+
}
39+
}
40+
41+
return
42+
}
43+
44+
func (h *handler) visitTreeNode(
1445
fsPath, rawReqPath, relPath string,
15-
statFs bool,
46+
statNode bool,
47+
childSelections []string,
1648
archiveCallback archiveCallback,
1749
) {
1850
var fInfo os.FileInfo
@@ -21,14 +53,13 @@ func (h *handler) visitFs(
2153
err := func() error {
2254
var f *os.File
2355
var err error
24-
if statFs {
56+
if statNode {
2557
f, err = os.Open(fsPath)
2658
if f != nil {
2759
defer f.Close()
2860
}
29-
h.errHandler.LogError(err)
3061

31-
if err != nil {
62+
if h.errHandler.LogError(err) {
3263
if os.IsExist(err) {
3364
return err
3465
}
@@ -68,15 +99,21 @@ func (h *handler) visitFs(
6899

69100
// childInfo can be regular dir/file, or aliased item that shadows regular dir/file
70101
for _, childInfo := range childInfos {
71-
childPath := "/" + childInfo.Name()
102+
childName := childInfo.Name()
103+
matchChildName, matchChildPrefix, childChildSelections := matchSelection(childName, childSelections)
104+
if !matchChildName && !matchChildPrefix {
105+
continue
106+
}
107+
108+
childPath := "/" + childName
72109
childFsPath := fsPath + childPath
73110
childRawReqPath := util.CleanUrlPath(rawReqPath + childPath)
74111
childRelPath := relPath + childPath
75112

76113
if childAlias, hasChildAlias := h.aliases.byUrlPath(childRawReqPath); hasChildAlias {
77-
h.visitFs(childAlias.fsPath, childRawReqPath, childRelPath, true, archiveCallback)
114+
h.visitTreeNode(childAlias.fsPath, childRawReqPath, childRelPath, true, childChildSelections, archiveCallback)
78115
} else {
79-
h.visitFs(childFsPath, childRawReqPath, childRelPath, statFs, archiveCallback)
116+
h.visitTreeNode(childFsPath, childRawReqPath, childRelPath, statNode, childChildSelections, archiveCallback)
80117
}
81118
}
82119
}
@@ -86,6 +123,7 @@ func (h *handler) archive(
86123
w http.ResponseWriter,
87124
r *http.Request,
88125
pageData *responseData,
126+
selections []string,
89127
fileSuffix string,
90128
contentType string,
91129
cbWriteFile archiveCallback,
@@ -106,11 +144,12 @@ func (h *handler) archive(
106144
return
107145
}
108146

109-
h.visitFs(
147+
h.visitTreeNode(
110148
path.Clean(h.root+pageData.handlerReqPath),
111149
pageData.rawReqPath,
112150
"",
113-
!h.emptyRoot,
151+
pageData.Item != nil, // not empty root
152+
selections,
114153
func(f *os.File, fInfo os.FileInfo, relPath string) error {
115154
go h.logArchive(targetFilename, relPath, r)
116155
err := cbWriteFile(f, fInfo, relPath)
@@ -129,3 +168,26 @@ func writeArchiveHeader(w http.ResponseWriter, contentType, filename string) {
129168
header.Set("Cache-Control", "public, max-age=0")
130169
w.WriteHeader(http.StatusOK)
131170
}
171+
172+
func (h *handler) getArchiveSelections(r *http.Request) ([]string, bool) {
173+
if h.errHandler.LogError(r.ParseForm()) {
174+
return nil, false
175+
}
176+
inputs := r.Form["name"]
177+
if len(inputs) == 0 {
178+
return nil, true
179+
}
180+
181+
count := len(inputs)
182+
selections := make([]string, count)
183+
for i := 0; i < count; i++ {
184+
var ok bool
185+
selections[i], ok = getCleanDirFilePath(inputs[i])
186+
if !ok {
187+
h.logger.LogErrorString("archive: illegal path " + inputs[i])
188+
return nil, false
189+
}
190+
}
191+
192+
return selections, true
193+
}

src/serverHandler/archiveTar.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ func writeTar(tw *tar.Writer, f *os.File, fInfo os.FileInfo, archivePath string)
4848
}
4949

5050
func (h *handler) tar(w http.ResponseWriter, r *http.Request, pageData *responseData) {
51+
selections, ok := h.getArchiveSelections(r)
52+
if !ok {
53+
return
54+
}
55+
5156
tw := tar.NewWriter(w)
5257
defer func() {
5358
err := tw.Close()
@@ -58,6 +63,7 @@ func (h *handler) tar(w http.ResponseWriter, r *http.Request, pageData *response
5863
w,
5964
r,
6065
pageData,
66+
selections,
6167
".tar",
6268
"application/octet-stream",
6369
func(f *os.File, fInfo os.FileInfo, relPath string) error {
@@ -67,6 +73,11 @@ func (h *handler) tar(w http.ResponseWriter, r *http.Request, pageData *response
6773
}
6874

6975
func (h *handler) tgz(w http.ResponseWriter, r *http.Request, pageData *responseData) {
76+
selections, ok := h.getArchiveSelections(r)
77+
if !ok {
78+
return
79+
}
80+
7081
gzw, err := gzip.NewWriterLevel(w, gzip.BestSpeed)
7182
if h.errHandler.LogError(err) {
7283
return
@@ -86,6 +97,7 @@ func (h *handler) tgz(w http.ResponseWriter, r *http.Request, pageData *response
8697
w,
8798
r,
8899
pageData,
100+
selections,
89101
".tar.gz",
90102
"application/octet-stream",
91103
func(f *os.File, fInfo os.FileInfo, relPath string) error {

src/serverHandler/archiveZip.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ func writeZip(zw *zip.Writer, f *os.File, fInfo os.FileInfo, archivePath string)
3838
}
3939

4040
func (h *handler) zip(w http.ResponseWriter, r *http.Request, pageData *responseData) {
41+
selections, ok := h.getArchiveSelections(r)
42+
if !ok {
43+
return
44+
}
45+
4146
zipWriter := zip.NewWriter(w)
4247
defer func() {
4348
err := zipWriter.Close()
@@ -48,6 +53,7 @@ func (h *handler) zip(w http.ResponseWriter, r *http.Request, pageData *response
4853
w,
4954
r,
5055
pageData,
56+
selections,
5157
".zip",
5258
"application/zip",
5359
func(f *os.File, fInfo os.FileInfo, relPath string) error {

src/serverHandler/archive_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package serverHandler
2+
3+
import "testing"
4+
5+
func TestGetMatchInfo(t *testing.T) {
6+
var matchName, matchPrefix bool
7+
var childList []string
8+
9+
var expect = func(isMatchName, isMatchPrefix bool, isChildList ...string) bool {
10+
if isMatchName != matchName {
11+
return false
12+
}
13+
if isMatchPrefix != matchPrefix {
14+
return false
15+
}
16+
17+
if len(isChildList) != len(childList) {
18+
return false
19+
}
20+
21+
if isChildList != nil && childList != nil {
22+
for i := 0; i < len(isChildList); i++ {
23+
if isChildList[i] != childList[i] {
24+
return false
25+
}
26+
}
27+
}
28+
29+
return true
30+
}
31+
32+
matchName, matchPrefix, childList = matchSelection("", nil)
33+
if !expect(true, false) {
34+
t.Error(matchName, matchPrefix, childList)
35+
}
36+
37+
matchName, matchPrefix, childList = matchSelection("", []string{})
38+
if !expect(true, false) {
39+
t.Error(matchName, matchPrefix, childList)
40+
}
41+
42+
matchName, matchPrefix, childList = matchSelection("", []string{"dir-x"})
43+
if !expect(false, false) {
44+
t.Error(matchName, matchPrefix, childList)
45+
}
46+
47+
matchName, matchPrefix, childList = matchSelection("dir-a", nil)
48+
if !expect(true, false) {
49+
t.Error(matchName, matchPrefix, childList)
50+
}
51+
52+
matchName, matchPrefix, childList = matchSelection("dir-a", []string{"dir-x"})
53+
if !expect(false, false) {
54+
t.Error(matchName, matchPrefix, childList)
55+
}
56+
57+
matchName, matchPrefix, childList = matchSelection("dir-a", []string{"dir-a"})
58+
if !expect(true, false) {
59+
t.Error(matchName, matchPrefix, childList)
60+
}
61+
62+
matchName, matchPrefix, childList = matchSelection("dir-a", []string{"dir-a/dir-a1"})
63+
if !expect(false, true, "dir-a1") {
64+
t.Error(matchName, matchPrefix, childList)
65+
}
66+
67+
matchName, matchPrefix, childList = matchSelection("dir-a", []string{"dir-a/dir-a1", "dir-a/dir-a2", "dir-a/dir-a1/dir-a11", "dir-b"})
68+
if !expect(false, true, "dir-a1", "dir-a2", "dir-a1/dir-a11") {
69+
t.Error(matchName, matchPrefix, childList)
70+
}
71+
72+
matchName, matchPrefix, childList = matchSelection("dir-a", []string{"dir-a", "dir-a/dir-a1"})
73+
if !expect(true, true, "dir-a1") {
74+
t.Error(matchName, matchPrefix, childList)
75+
}
76+
}

src/serverHandler/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
110110

111111
// regular flows
112112

113-
if len(r.URL.RawQuery) > 0 {
114-
switch r.URL.RawQuery {
113+
if len(r.URL.RawQuery) >= 3 {
114+
switch r.URL.RawQuery[:3] {
115115
case "tar":
116116
if data.CanArchive {
117117
h.tar(w, r, data)

src/serverLog/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"os"
55
)
66

7-
const CHAN_BUFFER = 7
7+
const CHAN_BUFFER = 15
88

99
type Logger struct {
1010
accLogMan *logMan

test/case/018.archive.bash

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,18 @@ cleanup
1212

1313
archive="$fs"/downloaded/a.tar.tmp
1414
curl_get_body 'http://127.0.0.1:3003/a?tar' > "$archive"
15-
(tar -tf "$archive" | grep -q a1.txt) || fail "a1.txt not in $(basename $archive)"
16-
(tar -tf "$archive" | grep -q a2.txt) || fail "a2.txt not in $(basename $archive)"
15+
(tar -tf "$archive" | grep -q '^a1.txt$') || fail "a1.txt should in $(basename $archive)"
16+
(tar -tf "$archive" | grep -q '^a2.txt$') || fail "a2.txt should in $(basename $archive)"
1717

1818
archive="$fs"/downloaded/b.tar.tmp
1919
curl_get_body 'http://127.0.0.1:3003/b/?tar' > "$archive"
20-
(tar -tf "$archive" | grep -q b1.txt) || fail "b1.txt not in $(basename $archive)"
21-
(tar -tf "$archive" | grep -q b2.txt) || fail "b2.txt not in $(basename $archive)"
20+
(tar -tf "$archive" | grep -q '^b1.txt$') || fail "b1.txt should in $(basename $archive)"
21+
(tar -tf "$archive" | grep -q '^b2.txt$') || fail "b2.txt should in $(basename $archive)"
22+
23+
archive="$fs"/downloaded/a-part.tar.tmp
24+
curl_get_body 'http://127.0.0.1:3003/a?tar&name=a1.txt' > "$archive"
25+
(tar -tf "$archive" | grep -q '^a1.txt$') || fail "a1.txt should in $(basename $archive)"
26+
(tar -tf "$archive" | grep -q 'a2.txt') && fail "a2.txt should not in $(basename $archive)"
2227

2328
cleanup
2429
jobs -p | xargs kill

0 commit comments

Comments
 (0)