Skip to content

several fixes and features (rfc3659) #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

rvicentiu
Copy link

@rvicentiu rvicentiu commented Sep 13, 2016

  • UTF8 proper support activation
  • MLST facts configuration (don't count on server defaults)
  • LIST DOS/Windows "dir"-style response processing
  • CWD command support (required instead of LIST/MLSD path argument on some servers)
  • several fixes

changes tested on about 200 anonymous ftp servers

* some servers require it to enable features (such as UTF8 support)
* while some servers use UTF8 by default if supported, others require activation (and CLNT)
* without UTF8 enabled you cannot access files/paths with non-english names as the server will treat your input as ASCII
* request only information we can process in parseMLST
* support for filenames containing "; "
* fix for broken servers sending type "dir" with name "." or ".."
* some servers send a 426 when the data connection fails during transfer
* this response remains in the queue breaking all commands sent after it
* no point in spamming it before every LIST
* helps when accessing high latency servers
* some servers return "not found" with "MLST path" or "LIST path"
* running "CWD path" and "LIST" or "MLST" works instead
Copy link
Contributor

@muirmanders muirmanders left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, thanks for all the work! Seems like you have a lot more experience with FTP than me.

@@ -84,7 +84,7 @@ VpOorURz8ETlfAA=
-----END CERTIFICATE-----
CERT

curl -O http://download.pureftpd.org/pub/pure-ftpd/releases/obsolete/pure-ftpd-1.0.36.tar.gz
curl -k -O https://download.pureftpd.org/pub/pure-ftpd/releases/obsolete/pure-ftpd-1.0.36.tar.gz
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not sure why why need -k. What are you developing on that doesn't have a CA bundle?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do have the CA bundle... but from experience I've also ended up working with servers that do not have it or it is outdated (not my choice). If you think it is unsafe I can remove it as it is not really need, I just added it for compatibility.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should remove it.

}

func (pconn *persistentConn) setUnicode() error {
code, msg, err := pconn.sendCommand("OPTS UTF8 ON")
Copy link
Contributor

@muirmanders muirmanders Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifying UTF8 when available makes sense, but for my own edification, when is this required? Are there servers that won't send UTF-8 path names without this command?

Copy link
Author

@rvicentiu rvicentiu Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had an issue with several servers that treat UTF8 in client commands (like path in LIST / MLSD) as ASCII, thus replying with "not found", although they sent proper UTF-8 paths in replies

Again this is a feature not required in RFCs as UTF8 should be enabled by default if listed in FEAT but needed to work around broken servers (and there are quite a few of them).

@@ -112,6 +112,27 @@ func (c *Client) Getwd() (string, error) {
return dir, nil
}

// Setwd changes the current working directory.
func (c *Client) Setwd(path string) (error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't include this because it isn't compatible with the connection pool (would end up with connections in different directories). I considered making it a client-wide setting that gets applied whenever you get a connection (i.e. switches to the desired directory), but that could get complicated, too.

Copy link
Author

@rvicentiu rvicentiu Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know(and there is a comment on the commit with this :) ) but it seems it is used as a workaround by ftp clients when LIST or MLSD fails with path argument. I ran into the issue while testing and using CWD first before LIST (without path) worked.

I was considering the following:

  • renaming all functions that call getIdleConn() to *Ex() and replace them with a wrapper.
  • the Ex() versions would get the idx of the pconn as a param
  • the Ex() versions would call getIdleConn() only if idx was -1 otherwise use the idx pconn / fail.
  • SetWd() returns the pconn idx

some locking might be needed if the user starts several go routines using the same handle

as for me I really need this but I could move it to a separate branch if you do not

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if each connection remembers what dir it is in, and when a new connection is gotten it automatically does the cwd if it isn't in the correct dir (user's desired dir is stored on client object when they set via setwd())?

if err != nil {
if !commandNotSupporterdError(err) {
if pconn.mlsdNotSupported || err != nil {
if !pconn.mlsdNotSupported && !commandNotSupporterdError(err) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this commandNotSupported check be moved up directly after the dataStringList() call? It's a little weird having the nested checks if mlsdNotSupported.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right... missed that, I'll commit a fix

@@ -148,7 +180,9 @@ func (c *Client) ReadDir(path string) ([]os.FileInfo, error) {
info, err := parser(entry, true)
if err != nil {
c.debug("error in ReadDir: %s", err)
return nil, err
if !strings.Contains(err.Error(), "incomplete") {
Copy link
Contributor

@muirmanders muirmanders Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have examples of incomplete MLSD/LIST output? Is it safe to give the user back incomplete results (i.e. maybe we just failed parsing)?

Copy link
Author

@rvicentiu rvicentiu Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"............" is text utf8, this is a wireshark dump

220 Serv-U FTP Server v10.4 ready...
....
Type=dir;Modify=20160910211543.432;Perm=el; music
Type=dir;Modify=20160917224516.936;Perm=el; ............
....
Type=dir;Modify=20151128035821.241;Perm=el; video
Type=dir;Perm=el; ..........
Type=dir;Modify=20160911053801.535;Perm=el; www
...

"Type=dir;Perm=el; .........." is a directory that is no longer accesible and the server does not send any other information about it but we can still use the received list

}

func (pconn *persistentConn) setMLST() error {
code, msg, err := pconn.sendCommand("OPTS MLST type;size;modify;perm;unix.mode;")
Copy link
Contributor

@muirmanders muirmanders Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be UNIX.mode or does case not matter?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from RFC 3659:
"Fact names are case-insensitive. Size, size, SIZE, and SiZe are the same fact."

I've made some protocol dumps from a few ftp clients and they seem to use lower case so I kept it as such, though the RFC continues with:
"Further operating system specific keywords could be specified by
using the IANA operating system name as a prefix (examples only):

  OS/2.ea   -- OS/2 extended attributes
  MACOS.rf  -- MacIntosh resource forks
  UNIX.mode -- Unix file modes (permissions)"

if strings.Contains(matches[2], "DIR") {
mode |= os.ModeDir
} else {
size, err = strconv.ParseUint(matches[2], 10, 64)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is ParseUint going to work on sizes with a comma like in your example above?

Copy link
Author

@rvicentiu rvicentiu Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it will fail with err (size will be 0) but I have not found a single server that returns commas yet. Only running "dir" in local "command prompt" does that.

@@ -321,6 +348,66 @@ func (f *ftpFile) Sys() interface{} {
return f.raw
}

var dirRegex = regexp.MustCompile(`^\s*(\S+\s+\S+\s{0,1}\S{2})\s+(\S+)\s+(.*)`)
var dirTimeFormats = []string{
"01-02-06 03:04",
Copy link
Contributor

@muirmanders muirmanders Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be 15:04 for parsing of 24hr clock value without am/pm?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes...


var mtime time.Time
for _, format := range dirTimeFormats {
if len(entry) >= len(format) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this check needed? Are you certain time strings can never be shorter than the formats (eg. "2006-01-02 2:04" is shorter than "2006-01-02 15:04")?

Copy link
Author

@rvicentiu rvicentiu Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed a way to speed up processing large lists (thousands of files) and avoid false matches.
I have not yet found a server that doesn't pad with zero's but it might be possible.

// 08/07/2016 17:20 1,135 file-name
// or
// 02-10-16 12:10PM 1067784 file.exe
func parseDirLIST(entry string, loc *time.Location, skipSelfParent bool) (os.FileInfo, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What server returns this format?

Copy link
Author

@rvicentiu rvicentiu Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

220 Microsoft FTP Service
...
STAT
211-Microsoft FTP Service status:
Logged in user: anonymous
TYPE: ASCII; FORM: NONPRINT; STRUcture: FILE; transfer MODE: STREAM
Data connection: none
211 End of status.
SYST
215 Windows_NT`

LIST output looks like this:
02-10-16 12:10PM 8067784 OneDriveSetup.exe
03-03-16 05:52PM

OS
06-20-16 03:43PM 164559 print.pdf

the main issue is the date format so I tried to support the most common formats I have found

@muirmanders
Copy link
Contributor

muirmanders commented Sep 20, 2016

Sorry, read your checkin comments and updated a few of by PR comments.

@muirmanders
Copy link
Contributor

Hi - you have a bunch of good stuff in, but we still have some open discussions. If you don't have time to spend on this, do you want me to cherry pick the non-controversial parts and merge those?

@tesharp
Copy link

tesharp commented Aug 15, 2018

Any plan on merging this pull request? Would like to use this ftp client, but require support for Windows FTP Servers and this fixes those issues.

@muirmanders
Copy link
Contributor

@tesharp do you know which fixes are needed for your use case? This branch has a lot of changes and unresolved conversations so I can't merge it as is. The easiest thing is to pull out the minimal fixes to a separate branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants