File: ftp.go

package info (click to toggle)
golang-github-thlib-go-timezone-local 0.0~git20210907.ef149e4-5
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, sid, trixie
  • size: 228 kB
  • sloc: makefile: 4
file content (225 lines) | stat: -rw-r--r-- 5,319 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
package tzdata

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"net"
	"net/url"
	"strconv"
	"strings"
	"time"
)

// FTP status codes, defined in RFC 959
const (
	StatusInitiating    = 100
	StatusRestartMarker = 110
	StatusReadyMinute   = 120
	StatusAlreadyOpen   = 125
	StatusAboutToSend   = 150

	StatusCommandOK             = 200
	StatusCommandNotImplemented = 202
	StatusSystem                = 211
	StatusDirectory             = 212
	StatusFile                  = 213
	StatusHelp                  = 214
	StatusName                  = 215
	StatusReady                 = 220
	StatusClosing               = 221
	StatusDataConnectionOpen    = 225
	StatusClosingDataConnection = 226
	StatusPassiveMode           = 227
	StatusLongPassiveMode       = 228
	StatusExtendedPassiveMode   = 229
	StatusLoggedIn              = 230
	StatusLoggedOut             = 231
	StatusLogoutAck             = 232
	StatusAuthOK                = 234
	StatusRequestedFileActionOK = 250
	StatusPathCreated           = 257

	StatusUserOK             = 331
	StatusLoginNeedAccount   = 332
	StatusRequestFilePending = 350

	StatusNotAvailable             = 421
	StatusCanNotOpenDataConnection = 425
	StatusTransfertAborted         = 426
	StatusInvalidCredentials       = 430
	StatusHostUnavailable          = 434
	StatusFileActionIgnored        = 450
	StatusActionAborted            = 451
	Status452                      = 452

	StatusBadCommand              = 500
	StatusBadArguments            = 501
	StatusNotImplemented          = 502
	StatusBadSequence             = 503
	StatusNotImplementedParameter = 504
	StatusNotLoggedIn             = 530
	StatusStorNeedAccount         = 532
	StatusFileUnavailable         = 550
	StatusPageTypeUnknown         = 551
	StatusExceededStorage         = 552
	StatusBadFileName             = 553
)

// pasv will parse the PASV response into an address
func pasvToAddr(line string) (string, error) {
	// PASV response format : 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
	start := strings.Index(line, "(")
	end := strings.LastIndex(line, ")")
	if start == -1 || end == -1 {
		return "", errors.New("invalid PASV response format")
	}

	// We have to split the response string
	pasvData := strings.Split(line[start+1:end], ",")
	if len(pasvData) < 6 {
		return "", errors.New("invalid PASV response format")
	}

	// Let's compute the port number
	portPart1, err := strconv.Atoi(pasvData[4])
	if err != nil {
		return "", err
	}

	portPart2, err := strconv.Atoi(pasvData[5])
	if err != nil {
		return "", err
	}

	// Recompose port
	port := portPart1*256 + portPart2

	// Make the IP address to connect to
	host := strings.Join(pasvData[0:4], ".")
	return net.JoinHostPort(host, strconv.Itoa(port)), nil
}

// UpdateOldNames fetches the list of old tz names and returns a mapping
func FTPDownload(target string) (bytes.Buffer, error) {
	var buf bytes.Buffer
	var err error

	// Parse the url
	u, err := url.Parse(target)
	if err != nil {
		return buf, err
	}
	port := u.Port()
	if port == "" {
		port = "21"
	}
	origin := net.JoinHostPort(u.Host, port)

	// Connect to the command server
	conn, err := net.DialTimeout("tcp", origin, 30*time.Second)
	if err != nil {
		return buf, err
	}
	defer conn.Close()
	r := bufio.NewReader(conn)
	if err != nil {
		return buf, err
	}
	resp, err := r.ReadString('\n')
	if err != nil {
		return buf, err
	}
	if !strings.HasPrefix(resp, "220") {
		return buf, fmt.Errorf("failed to connect: %v", resp)
	}

	// Send username
	_, err = conn.Write([]byte("USER anonymous\n"))
	if err != nil {
		return buf, err
	}
	resp, err = r.ReadString('\n')
	if err != nil {
		return buf, err
	}
	if !strings.HasPrefix(resp, "331") {
		return buf, fmt.Errorf("failed to login: %v", resp)
	}

	// Send password
	_, err = conn.Write([]byte("PASS anonymous\n"))
	if err != nil {
		return buf, err
	}
	resp, err = r.ReadString('\n')
	if err != nil {
		return buf, err
	}
	if !strings.HasPrefix(resp, "230") {
		return buf, fmt.Errorf("failed to login: %v", resp)
	}

	// Binary mode
	_, err = conn.Write([]byte("TYPE I\n"))
	if err != nil {
		return buf, err
	}
	resp, err = r.ReadString('\n')
	if err != nil {
		return buf, err
	}
	if !strings.HasPrefix(resp, "200") {
		return buf, fmt.Errorf("failed to switch to binary mode: %v", resp)
	}

	// Get the file transfer address
	_, err = conn.Write([]byte("PASV\n"))
	if err != nil {
		return buf, err
	}
	resp, err = r.ReadString('\n')
	if err != nil {
		return buf, err
	}
	dataAddr, err := pasvToAddr(resp)
	if err != nil {
		return buf, err
	}

	// Connect to the data transfer address
	dataConn, err := net.DialTimeout("tcp", dataAddr, 30*time.Second)
	if err != nil {
		return buf, err
	}
	defer dataConn.Close()

	// Send the command to download the file through the data transfer connection
	_, err = conn.Write([]byte(fmt.Sprintf("RETR %v\n", u.Path)))
	if err != nil {
		return buf, err
	}
	resp, err = r.ReadString('\n')
	if err != nil {
		return buf, err
	}
	if !strings.HasPrefix(resp, "150") {
		return buf, fmt.Errorf("RETR failed: %v", resp)
	}

	// Get the file response
	io.Copy(&buf, dataConn)

	// Get the transfer complete response
	resp, err = r.ReadString('\n')
	if err != nil {
		return buf, err
	}
	if !strings.HasPrefix(resp, "226") {
		return buf, fmt.Errorf("transfer failed: %v", resp)
	}

	return buf, err
}