-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Create ftpserver.lua #2345
Create ftpserver.lua #2345
Changes from all commits
8791710
edaf946
2a6f413
64fbf98
c011da5
212ee81
b707ab1
3281e28
12fbd5f
7628537
22b1736
3ef850d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# FTPServer Module | ||
|
||
This is a Lua module to access the [SPIFFS file system](../../docs/en/spiffs.md) via FTP protocol. | ||
FTP server uses only one specified user and password to authenticate clients. | ||
All clients need authentication - anonymous access is not supported yet. | ||
All files have RW access. | ||
Directory creation do not supported because SPIFFS do not have that. | ||
|
||
## Require | ||
```lua | ||
ftpserver = require("ftpserver") | ||
``` | ||
## Release | ||
```lua | ||
ftpserver:closeServer() | ||
ftpserver = nil | ||
package.loaded["ftpserver"]=nil | ||
``` | ||
|
||
# Methods | ||
|
||
## createServer() | ||
Starts listen on 20 and 21 ports for serve FTP clients. | ||
|
||
The module requires active network connection. | ||
|
||
#### Syntax | ||
`createServer(username, password)` | ||
|
||
#### Parameters | ||
- `username` string username for 'USER username' ftp command. | ||
- `password` string password. | ||
|
||
#### Returns | ||
nil | ||
|
||
#### Example | ||
```lua | ||
require("ftpserver").createServer("test","12345") | ||
``` | ||
|
||
## closeServer() | ||
Closes all server sockets. | ||
|
||
#### Syntax | ||
`closeServer()` | ||
|
||
#### Returns | ||
nil | ||
|
||
#### Example | ||
```lua | ||
ftpserver = require("ftpserver") | ||
ftpserver:createServer("test","12345") | ||
------------------------- | ||
-- Some program code | ||
------------------------- | ||
if needStopFtp = true then | ||
ftpserver:closeServer() | ||
ftpserver = nil | ||
package.loaded["ftpserver"] = nil | ||
end | ||
``` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
-- Start a simple ftp server | ||
-- createServer("user", "password") | ||
require("ftpserver").createServer("test","12345") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
-- a simple ftp server | ||
local file,net,pairs,print,string,table = file,net,pairs,print,string,table | ||
local ftp, ftp_data, ftp_srv | ||
do | ||
local createServer = function (user, pass) | ||
local data_fnc, data_sock = nil, nil | ||
ftp_data = net.createServer(net.TCP, 180) | ||
ftp_data:listen(1024, function (s) if data_fnc then data_fnc(s) else data_sock = s end end) | ||
ftp_srv = net.createServer(net.TCP, 180) | ||
ftp_srv:listen(21, function(socket) | ||
local s = 0 | ||
local cwd = "/" | ||
local buf = "" | ||
local t = 0 | ||
socket:on("receive", function(c, d) | ||
local a = {} | ||
for i in string.gmatch(d, "([^ \r\n]+)") do | ||
table.insert(a,i) | ||
end | ||
local a1,a2 = unpack(a) | ||
if a1 == nil or a1 == "" then return end | ||
if s == 0 and a1 == "USER" then | ||
if a2 ~= user then | ||
return c:send("530 user not found\r\n") | ||
end | ||
s = 1 | ||
return c:send("331 OK. Password required\r\n") | ||
end | ||
if s == 1 and a1 == "PASS" then | ||
if a2 ~= pass then | ||
return c:send("530 Try again\r\n") | ||
end | ||
s = 2 | ||
return c:send("230 OK.\r\n") | ||
end | ||
if s ~= 2 then | ||
return c:send("530 Not logged in, authorization required\r\n") | ||
end | ||
if a1 == "SYST" then | ||
return c:send("250 UNIX Type: L8\r\n") | ||
end | ||
if a1 == "CDUP" then | ||
return c:send("250 OK. Current directory is "..cwd.."\r\n") | ||
end | ||
if a1 == "CWD" then | ||
if a2 == "." then | ||
return c:send("257 \""..cwd.."\" is your current directory\r\n") | ||
end | ||
if a2 == "/" then | ||
cwd = a2 | ||
return c:send("250 OK. Current directory is "..cwd.."\r\n") | ||
end | ||
return c:send("550 Failed to change directory.\r\n") | ||
end | ||
if a1 == "PWD" then | ||
return c:send("257 \""..cwd.."\" is your current directory\r\n") | ||
end | ||
if a1 == "TYPE" then | ||
if a2 == "A" then | ||
t = 0 | ||
return c:send("200 TYPE is now ASII\r\n") | ||
end | ||
if a2 == "I" then | ||
t = 1 | ||
return c:send("200 TYPE is now 8-bit binary\r\n") | ||
end | ||
return c:send("504 Unknown TYPE") | ||
end | ||
if a1 == "MODE" then | ||
if a2 ~= "S" then | ||
return c:send("504 Only S(tream) is suported\r\n") | ||
end | ||
return c:send("200 S OK\r\n") | ||
end | ||
if a1 == "PASV" then | ||
local _,ip = c:getaddr() | ||
local _,_,i1,i2,i3,i4 = string.find(ip,"(%d+).(%d+).(%d+).(%d+)") | ||
return c:send("227 Entering Passive Mode ("..i1..","..i2..","..i3..","..i4..",4,0).\r\n") | ||
end | ||
if a1 == "LIST" or a1 == "NLST" then | ||
c:send("150 Accepted data connection\r\n") | ||
data_fnc = function(sd) | ||
local l = file.list(); | ||
for k,v in pairs(l) do | ||
sd:send( a1 == "NLST" and k.."\r\n" or "-rwxrwxrwx 1 esp esp "..v.." Jan 1 2018 "..k.."\r\n") | ||
end | ||
sd:close() | ||
data_fnc = nil | ||
c:send("226 Transfer complete.\r\n") | ||
end | ||
if data_sock then | ||
node.task.post(function() data_fnc(data_sock);data_sock=nil end) | ||
end | ||
return | ||
end | ||
if a1 == "RETR" then | ||
local f = file.open(a2:gsub("%/",""),"r") | ||
if f == nil then | ||
return c:send("550 File "..a2.." not found\r\n") | ||
end | ||
c:send("150 Accepted data connection\r\n") | ||
data_fnc = function(sd) | ||
sd:on("sent", function(cd) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if |
||
b=f:read(1024) | ||
if b then | ||
cd:send(b) | ||
b=nil | ||
else | ||
cd:close() | ||
f:close() | ||
data_fnc = nil | ||
c:send("226 Transfer complete.\r\n") | ||
end | ||
end) | ||
local b=f:read(1024) | ||
sd:send(b) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check for b not being nil, to prevent crashing reading an empty file. |
||
b=nil | ||
end | ||
if data_sock then | ||
node.task.post(function() data_fnc(data_sock);data_sock=nil end) | ||
end | ||
return | ||
end | ||
if a1 == "STOR" then | ||
local f = file.open(a2:gsub("%/",""),"w") | ||
if f == nil then | ||
return c:send("451 Can't open/create "..a2.."\r\n") | ||
end | ||
c:send("150 Accepted data connection\r\n") | ||
data_fnc = function(sd) | ||
sd:on("receive", function(cd, dd) | ||
f:write(dd) | ||
end) | ||
sd:on("disconnection", function(c) | ||
f:close() | ||
data_fnc = nil | ||
end) | ||
c:send("226 Transfer complete.\r\n") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The The
|
||
end | ||
if data_sock then | ||
node.task.post(function() data_fnc(data_sock);data_sock=nil end) | ||
end | ||
return | ||
end | ||
if a1 == "RNFR" then | ||
buf = a2 | ||
return c:send("350 RNFR accepted\r\n") | ||
end | ||
if a1 == "RNTO" and buf ~= "" then | ||
file.rename(buf, a2) | ||
buf = "" | ||
return c:send("250 File renamed\r\n") | ||
end | ||
if a1 == "DELE" then | ||
if a2 == nil or a2 == "" then | ||
return c:send("501 No file name\r\n") | ||
end | ||
file.remove(a2:gsub("%/","")) | ||
return c:send("250 Deleted "..a2.."\r\n") | ||
end | ||
if a1 == "SIZE" then | ||
local f = file.stat(a2) | ||
return c:send("213 "..(f and f.size or 1).."\r\n") | ||
end | ||
if a1 == "NOOP" then | ||
return c:send("200 OK\r\n") | ||
end | ||
if a1 == "QUIT" then | ||
return c:send("221 Goodbye\r\n", function (s) s:close() end) | ||
end | ||
c:send("500 Unknown error\r\n") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add support for SIZE command: this is generally used by web-browsers. if a1 == "SIZE" then
local st,size
st = file.stat(string.sub(a2, 2))
if st then
return c:send("213 "..st.size.."\r\n")
else
return c:send("550 Could not get file size.\r\n")
end
end |
||
end) | ||
socket:send("220--- Welcome to FTP for ESP8266/ESP32 ---\r\n220--- By NeiroN ---\r\n220 -- Version 1.8 --\r\n"); | ||
end) | ||
end | ||
local closeServer = function() | ||
ftp_data:close() | ||
ftp_srv:close() | ||
end | ||
ftp = { createServer = createServer, closeServer = closeServer } | ||
end | ||
return ftp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make the list commands work use this data_fnc. tested with 70+ files