• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jlaffaye / ftp / 8113579643

01 Mar 2024 03:23PM UTC coverage: 72.431%. Remained the same
8113579643

Pull #365

github

web-flow
Bump github.com/stretchr/testify from 1.8.4 to 1.9.0

Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.4 to 1.9.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.4...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #365: Bump github.com/stretchr/testify from 1.8.4 to 1.9.0

712 of 983 relevant lines covered (72.43%)

18.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.19
/parse.go
1
package ftp
2

3
import (
4
        "errors"
5
        "fmt"
6
        "strconv"
7
        "strings"
8
        "time"
9
)
10

11
var errUnsupportedListLine = errors.New("unsupported LIST line")
12
var errUnsupportedListDate = errors.New("unsupported LIST date")
13
var errUnknownListEntryType = errors.New("unknown entry type")
14

15
type parseFunc func(string, time.Time, *time.Location) (*Entry, error)
16

17
var listLineParsers = []parseFunc{
18
        parseRFC3659ListLine,
19
        parseLsListLine,
20
        parseDirListLine,
21
        parseHostedFTPLine,
22
}
23

24
var dirTimeFormats = []string{
25
        "01-02-06  03:04PM",
26
        "2006-01-02  15:04",
27
        "01-02-2006  03:04PM",
28
        "01-02-2006  15:04",
29
}
30

31
// parseRFC3659ListLine parses the style of directory line defined in RFC 3659.
32
func parseRFC3659ListLine(line string, _ time.Time, loc *time.Location) (*Entry, error) {
45✔
33
        return parseNextRFC3659ListLine(line, loc, &Entry{})
45✔
34
}
45✔
35

36
func parseNextRFC3659ListLine(line string, loc *time.Location, e *Entry) (*Entry, error) {
51✔
37
        iSemicolon := strings.Index(line, ";")
51✔
38
        iWhitespace := strings.Index(line, " ")
51✔
39

51✔
40
        if iSemicolon < 0 || iSemicolon > iWhitespace {
86✔
41
                return nil, errUnsupportedListLine
35✔
42
        }
35✔
43

44
        name := line[iWhitespace+1:]
16✔
45
        if e.Name == "" {
30✔
46
                e.Name = name
14✔
47
        } else if e.Name != name {
16✔
48
                // All lines must have the same name
×
49
                return nil, errUnsupportedListLine
×
50
        }
×
51

52
        for _, field := range strings.Split(line[:iWhitespace-1], ";") {
83✔
53
                i := strings.Index(field, "=")
67✔
54
                if i < 1 {
68✔
55
                        return nil, errUnsupportedListLine
1✔
56
                }
1✔
57

58
                key := strings.ToLower(field[:i])
66✔
59
                value := field[i+1:]
66✔
60

66✔
61
                switch key {
66✔
62
                case "modify":
14✔
63
                        var err error
14✔
64
                        e.Time, err = time.ParseInLocation("20060102150405", value, loc)
14✔
65
                        if err != nil {
14✔
66
                                return nil, err
×
67
                        }
×
68
                case "type":
13✔
69
                        switch value {
13✔
70
                        case "dir", "cdir", "pdir":
6✔
71
                                e.Type = EntryTypeFolder
6✔
72
                        case "file":
7✔
73
                                e.Type = EntryTypeFile
7✔
74
                        }
75
                case "size":
9✔
76
                        if err := e.setSize(value); err != nil {
9✔
77
                                return nil, err
×
78
                        }
×
79
                }
80
        }
81
        return e, nil
15✔
82
}
83

84
// parseLsListLine parses a directory line in a format based on the output of
85
// the UNIX ls command.
86
func parseLsListLine(line string, now time.Time, loc *time.Location) (*Entry, error) {
37✔
87

37✔
88
        // Has the first field a length of exactly 10 bytes
37✔
89
        // - or 10 bytes with an additional '+' character for indicating ACLs?
37✔
90
        // If not, return.
37✔
91
        if i := strings.IndexByte(line, ' '); !(i == 10 || (i == 11 && line[10] == '+')) {
47✔
92
                return nil, errUnsupportedListLine
10✔
93
        }
10✔
94

95
        scanner := newScanner(line)
27✔
96
        fields := scanner.NextFields(6)
27✔
97

27✔
98
        if len(fields) < 6 {
30✔
99
                return nil, errUnsupportedListLine
3✔
100
        }
3✔
101

102
        if fields[1] == "folder" && fields[2] == "0" {
26✔
103
                e := &Entry{
2✔
104
                        Type: EntryTypeFolder,
2✔
105
                        Name: scanner.Remaining(),
2✔
106
                }
2✔
107
                if err := e.setTime(fields[3:6], now, loc); err != nil {
2✔
108
                        return nil, err
×
109
                }
×
110

111
                return e, nil
2✔
112
        }
113

114
        if fields[1] == "0" {
24✔
115
                fields = append(fields, scanner.Next())
2✔
116
                e := &Entry{
2✔
117
                        Type: EntryTypeFile,
2✔
118
                        Name: scanner.Remaining(),
2✔
119
                }
2✔
120

2✔
121
                if err := e.setSize(fields[2]); err != nil {
3✔
122
                        return nil, errUnsupportedListLine
1✔
123
                }
1✔
124
                if err := e.setTime(fields[4:7], now, loc); err != nil {
1✔
125
                        return nil, err
×
126
                }
×
127

128
                return e, nil
1✔
129
        }
130

131
        // Read two more fields
132
        fields = append(fields, scanner.NextFields(2)...)
20✔
133
        if len(fields) < 8 {
20✔
134
                return nil, errUnsupportedListLine
×
135
        }
×
136

137
        e := &Entry{
20✔
138
                Name: scanner.Remaining(),
20✔
139
        }
20✔
140
        switch fields[0][0] {
20✔
141
        case '-':
11✔
142
                e.Type = EntryTypeFile
11✔
143
                if err := e.setSize(fields[4]); err != nil {
11✔
144
                        return nil, err
×
145
                }
×
146
        case 'd':
5✔
147
                e.Type = EntryTypeFolder
5✔
148
        case 'l':
3✔
149
                e.Type = EntryTypeLink
3✔
150

3✔
151
                // Split link name and target
3✔
152
                if i := strings.Index(e.Name, " -> "); i > 0 {
6✔
153
                        e.Target = e.Name[i+4:]
3✔
154
                        e.Name = e.Name[:i]
3✔
155
                }
3✔
156
        default:
1✔
157
                return nil, errUnknownListEntryType
1✔
158
        }
159

160
        if err := e.setTime(fields[5:8], now, loc); err != nil {
20✔
161
                return nil, err
1✔
162
        }
1✔
163

164
        return e, nil
18✔
165
}
166

167
// parseDirListLine parses a directory line in a format based on the output of
168
// the MS-DOS DIR command.
169
func parseDirListLine(line string, now time.Time, loc *time.Location) (*Entry, error) {
14✔
170
        e := &Entry{}
14✔
171
        var err error
14✔
172

14✔
173
        // Try various time formats that DIR might use, and stop when one works.
14✔
174
        for _, format := range dirTimeFormats {
62✔
175
                if len(line) > len(format) {
72✔
176
                        e.Time, err = time.ParseInLocation(format, line[:len(format)], loc)
24✔
177
                        if err == nil {
28✔
178
                                line = line[len(format):]
4✔
179
                                break
4✔
180
                        }
181
                }
182
        }
183
        if err != nil {
18✔
184
                // None of the time formats worked.
4✔
185
                return nil, errUnsupportedListLine
4✔
186
        }
4✔
187

188
        line = strings.TrimLeft(line, " ")
10✔
189
        if strings.HasPrefix(line, "<DIR>") {
12✔
190
                e.Type = EntryTypeFolder
2✔
191
                line = strings.TrimPrefix(line, "<DIR>")
2✔
192
        } else {
10✔
193
                space := strings.Index(line, " ")
8✔
194
                if space == -1 {
9✔
195
                        return nil, errUnsupportedListLine
1✔
196
                }
1✔
197
                e.Size, err = strconv.ParseUint(line[:space], 10, 64)
7✔
198
                if err != nil {
12✔
199
                        return nil, errUnsupportedListLine
5✔
200
                }
5✔
201
                e.Type = EntryTypeFile
2✔
202
                line = line[space:]
2✔
203
        }
204

205
        e.Name = strings.TrimLeft(line, " ")
4✔
206
        return e, nil
4✔
207
}
208

209
// parseHostedFTPLine parses a directory line in the non-standard format used
210
// by hostedftp.com
211
// -r--------   0 user group     65222236 Feb 24 00:39 UABlacklistingWeek8.csv
212
// (The link count is inexplicably 0)
213
func parseHostedFTPLine(line string, now time.Time, loc *time.Location) (*Entry, error) {
10✔
214
        // Has the first field a length of 10 bytes?
10✔
215
        if strings.IndexByte(line, ' ') != 10 {
18✔
216
                return nil, errUnsupportedListLine
8✔
217
        }
8✔
218

219
        scanner := newScanner(line)
2✔
220
        fields := scanner.NextFields(2)
2✔
221

2✔
222
        if len(fields) < 2 || fields[1] != "0" {
3✔
223
                return nil, errUnsupportedListLine
1✔
224
        }
1✔
225

226
        // Set link count to 1 and attempt to parse as Unix.
227
        return parseLsListLine(fields[0]+" 1 "+scanner.Remaining(), now, loc)
1✔
228
}
229

230
// parseListLine parses the various non-standard format returned by the LIST
231
// FTP command.
232
func parseListLine(line string, now time.Time, loc *time.Location) (*Entry, error) {
42✔
233
        for _, f := range listLineParsers {
144✔
234
                e, err := f(line, now, loc)
102✔
235
                if err != errUnsupportedListLine {
135✔
236
                        return e, err
33✔
237
                }
33✔
238
        }
239
        return nil, errUnsupportedListLine
9✔
240
}
241

242
func (e *Entry) setSize(str string) (err error) {
22✔
243
        e.Size, err = strconv.ParseUint(str, 0, 64)
22✔
244
        return
22✔
245
}
22✔
246

247
func (e *Entry) setTime(fields []string, now time.Time, loc *time.Location) (err error) {
26✔
248
        if strings.Contains(fields[2], ":") { // contains time
40✔
249
                thisYear, _, _ := now.Date()
14✔
250
                timeStr := fmt.Sprintf("%s %s %d %s", fields[1], fields[0], thisYear, fields[2])
14✔
251
                e.Time, err = time.ParseInLocation("_2 Jan 2006 15:04", timeStr, loc)
14✔
252

14✔
253
                /*
14✔
254
                        On unix, `info ls` shows:
14✔
255

14✔
256
                        10.1.6 Formatting file timestamps
14✔
257
                        ---------------------------------
14✔
258

14✔
259
                        A timestamp is considered to be “recent” if it is less than six
14✔
260
                        months old, and is not dated in the future.  If a timestamp dated today
14✔
261
                        is not listed in recent form, the timestamp is in the future, which
14✔
262
                        means you probably have clock skew problems which may break programs
14✔
263
                        like ‘make’ that rely on file timestamps.
14✔
264
                */
14✔
265
                if !e.Time.Before(now.AddDate(0, 6, 0)) {
16✔
266
                        e.Time = e.Time.AddDate(-1, 0, 0)
2✔
267
                }
2✔
268

269
        } else { // only the date
12✔
270
                if len(fields[2]) != 4 {
13✔
271
                        return errUnsupportedListDate
1✔
272
                }
1✔
273
                timeStr := fmt.Sprintf("%s %s %s 00:00", fields[1], fields[0], fields[2])
11✔
274
                e.Time, err = time.ParseInLocation("_2 Jan 2006 15:04", timeStr, loc)
11✔
275
        }
276
        return
25✔
277
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc