diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..56ad87d
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,23 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+charset = utf-8
+
+[{*.lua,*.rockspec,.luacheckrc}]
+indent_style = space
+indent_size = 4
+
+[Makefile]
+indent_style = tab
+indent_size = 4
+
+[*.html]
+indent_style = space
+indent_size = 4
+
+[*.{c,h}]
+indent_style = space
+indent_size = 4
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..49c2c73
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,50 @@
+name: Build
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+
+jobs:
+ build:
+ name: Test ${{ matrix.luaVersion }} on ${{ matrix.platform }}
+ strategy:
+ fail-fast: false
+ matrix:
+ luaVersion: [ "5.4", "5.3", "5.2", "5.1", "luajit", "luajit-openresty" ]
+ platform: [ "ubuntu-22.04", "macos-11", "windows-2022" ]
+ runs-on: ${{ matrix.platform }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Setup ’msvc’
+ if: ${{ startsWith(matrix.platform, 'windows') && !startsWith(matrix.luaVersion, 'luajit') }}
+ uses: ilammy/msvc-dev-cmd@v1
+ - name: Setup ‘lua’
+ uses: leso-kn/gh-actions-lua@v11-staging
+ with:
+ luaVersion: ${{ matrix.luaVersion }}
+ - name: Setup ‘luarocks’
+ uses: hishamhm/gh-actions-luarocks@master
+ - name: Make and install
+ run: |
+ luarocks make -- luasocket-scm-3.rockspec
+ env:
+ DEBUG: DEBUG
+ - name: Run regression tests
+ shell: bash
+ run: |
+ cd test
+ lua hello.lua
+ lua testsrvr.lua > /dev/null &
+ lua testclnt.lua
+ lua stufftest.lua
+ lua excepttest.lua
+ lua test_bind.lua
+ lua test_getaddrinfo.lua
+ lua ltn12test.lua
+ lua mimetest.lua
+ lua urltest.lua
+ lua test_socket_error.lua
+ kill %1
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..acd66ea
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,34 @@
+name: Deploy
+
+on: [ push, workflow_dispatch ]
+
+jobs:
+
+ affected:
+ uses: lunarmodules/.github/.github/workflows/list_affected_rockspecs.yml@main
+
+ build:
+ needs: affected
+ if: ${{ needs.affected.outputs.rockspecs }}
+ uses: lunarmodules/.github/.github/workflows/test_build_rock.yml@main
+ with:
+ rockspecs: ${{ needs.affected.outputs.rockspecs }}
+
+ upload:
+ needs: [ affected, build ]
+ # Only run upload if:
+ # 1. We are on the canonical repository (no uploads from forks)
+ # 2. The current commit is either tagged or on the default branch (the workflow will upload dev/scm rockspecs any
+ # time they are touched, tagged ones whenever the edited rockspec and tag match)
+ # 3. Some rockspecs were changed — this implies the commit changing the rockspec is the same one that gets tagged
+ if: >-
+ ${{
+ github.repository == 'lunarmodules/luasocket' &&
+ ( github.ref_name == 'master' || startsWith(github.ref, 'refs/tags/') ) &&
+ needs.affected.outputs.rockspecs
+ }}
+ uses: lunarmodules/.github/.github/workflows/upload_to_luarocks.yml@main
+ with:
+ rockspecs: ${{ needs.affected.outputs.rockspecs }}
+ secrets:
+ apikey: ${{ secrets.LUAROCKS_APIKEY }}
diff --git a/.github/workflows/luacheck.yml b/.github/workflows/luacheck.yml
new file mode 100644
index 0000000..9cb784c
--- /dev/null
+++ b/.github/workflows/luacheck.yml
@@ -0,0 +1,13 @@
+name: Luacheck
+
+on: [push, pull_request]
+
+jobs:
+
+ luacheck:
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Luacheck
+ uses: lunarmodules/luacheck@v1
diff --git a/.gitignore b/.gitignore
index 8307483..9ed661c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,6 @@
*.dll*
*.user
*.sdf
-Lua.props
Debug
Release
*.manifest
diff --git a/.luacheckrc b/.luacheckrc
new file mode 100644
index 0000000..a3b4f63
--- /dev/null
+++ b/.luacheckrc
@@ -0,0 +1,29 @@
+unused_args = false
+redefined = false
+max_line_length = false
+
+not_globals = {
+ "string.len",
+ "table.getn",
+}
+
+include_files = {
+ "**/*.lua",
+ "**/*.rockspec",
+ ".busted",
+ ".luacheckrc",
+}
+
+exclude_files = {
+ "test/*.lua",
+ "test/**/*.lua",
+ "samples/*.lua",
+ "samples/**/*.lua",
+ "gem/*.lua",
+ "gem/**/*.lua",
+ -- GH Actions Lua Environment
+ ".lua",
+ ".luarocks",
+ ".install",
+}
+
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index fce8a96..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-language: erlang
-
-env:
- global:
- - LUAROCKS_BASE=luarocks-2.0.13
- matrix:
- - LUA=lua5.1 LUA_DEV=liblua5.1-dev LUA_VER=5.1 LUA_SFX=5.1 LUA_INCDIR=/usr/include/lua5.1
- - LUA=lua5.2 LUA_DEV=liblua5.2-dev LUA_VER=5.2 LUA_SFX=5.2 LUA_INCDIR=/usr/include/lua5.2
- - LUA=luajit LUA_DEV=libluajit-5.1-dev LUA_VER=5.1 LUA_SFX=jit LUA_INCDIR=/usr/include/luajit-2.0
-
-branches:
- only:
- - master
-
-before_install:
- - if [ $LUA = "luajit" ]; then
- sudo add-apt-repository ppa:mwild1/ppa -y && sudo apt-get update -y;
- fi
- - sudo apt-get install $LUA
- - sudo apt-get install $LUA_DEV
- - lua$LUA_SFX -v
- # Install a recent luarocks release
- - wget http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz
- - tar zxvpf $LUAROCKS_BASE.tar.gz
- - cd $LUAROCKS_BASE
- - ./configure
- --lua-version=$LUA_VER --lua-suffix=$LUA_SFX --with-lua-include="$LUA_INCDIR"
- - sudo make
- - sudo make install
- - cd $TRAVIS_BUILD_DIR
-
-
-install:
- - export DEBUG=DEBUG
- - sudo -E luarocks make luasocket-scm-0.rockspec
-
-script:
- - cd test
- - lua$LUA_SFX hello.lua
- - lua$LUA_SFX testsrvr.lua > /dev/null &
- - lua$LUA_SFX testclnt.lua
- - lua$LUA_SFX stufftest.lua
- - lua$LUA_SFX excepttest.lua
- - lua$LUA_SFX test_bind.lua
- - lua$LUA_SFX test_getaddrinfo.lua
- - lua$LUA_SFX ltn12test.lua
- - lua$LUA_SFX mimetest.lua
- - lua$LUA_SFX urltest.lua
- - lua$LUA_SFX test_socket_error.lua
-
-notifications:
- email:
- on_success: change
- on_failure: always
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..3a25186
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,65 @@
+# Changelog
+
+## [v3.1.0](https://github.com/lunarmodules/luasocket/releases/v3.1.0) — 2022-07-27
+
+* Add support for TCP Defer Accept – @Zash
+* Add support for TCP Fast Open – @Zash
+* Fix Windows (mingw32) builds – @goldenstein64
+* Avoid build warnings on 64-bit Windows – @rpatters1
+
+## [v3.0.0](https://github.com/lunarmodules/luasocket/releases/v3.0.0) — 2022-03-25
+
+The last time LuaSocket had a stable release tag was 14 years ago when 2.0.2 was tagged.
+A v3 release candidate was tagged 9 years ago.
+Since then it has been downloaded over 3 million times.
+Additionally the Git repository regularly gets several hundred clones a day.
+But 9 years is a long time and even the release candidate has grown a bit long in the tooth.
+Many Linux distros have packaged the current Git HEAD or some specific tested point as dated or otherwise labeled releases.
+256 commits later and having been migrated to the @lunarmodules org namespace on GitHub, please welcome v3.
+
+This release is a "safe-harbor" tag that represents a minimal amount of changes to get a release tagged.
+Beyond some CI tooling, very little code has changed since migration to @lunarmodules ([5b18e47..e47d98f](https://github.com/lunarmodules/luasocket/compare/5b18e47..e47d98f?w=1)):
+
+* Lua 5.4.3+ support – @pkulchenko, @Zash
+* Cleanup minor issues to get a code linter to pass – @Tieske, @jyoui, @alerque
+* Update Visual Studio build rules for Lua 5.1 – @ewestbrook
+* Set http transfer-encoding even without content-length – @tokenrove
+
+Prior to migration to @lunarmodules ([v3.0-rc1..5b18e47](https://github.com/lunarmodules/luasocket/compare/v3.0-rc1..5b18e47?w=1)) many things happened of which the author of this changelog is not fully apprised.
+Your best bet if it affects your project somehow is to read the commit log & diffs yourself.
+
+## [v3.0-rc1](https://github.com/lunarmodules/luasocket/releases/v3.0-rc1) — 2013-06-14
+
+Main changes for LuaSocket 3.0-rc1 are IPv6 support and Lua 5.2 compatibility.
+
+* Added: Compatible with Lua 5.2
+ - Note that unless you define LUA_COMPAT_MODULE, package tables will not be exported as globals!
+* Added: IPv6 support;
+ - Socket.connect and socket.bind support IPv6 addresses;
+ - Getpeername and getsockname support IPv6 addresses, and return the socket family as a third value;
+ - URL module updated to support IPv6 host names;
+ - New socket.tcp6 and socket.udp6 functions;
+ - New socket.dns.getaddrinfo and socket.dns.getnameinfo functions;
+* Added: getoption method;
+* Fixed: url.unescape was returning additional values;
+* Fixed: mime.qp, mime.unqp, mime.b64, and mime.unb64 could mistaking their own stack slots for functions arguments;
+* Fixed: Receiving zero-length datagram is now possible;
+* Improved: Hidden all internal library symbols;
+* Improved: Better error messages;
+* Improved: Better documentation of socket options.
+* Fixed: manual sample of HTTP authentication now uses correct "authorization" header (Alexandre Ittner);
+* Fixed: failure on bind() was destroying the socket (Sam Roberts);
+* Fixed: receive() returns immediatelly if prefix can satisfy bytes requested (M Joonas Pihlaja);
+* Fixed: multicast didn't work on Windows, or anywhere else for that matter (Herbert Leuwer, Adrian Sietsma);
+* Fixed: select() now reports an error when called with more sockets than FD_SETSIZE (Lorenzo Leonini);
+* Fixed: manual links to home.html changed to index.html (Robert Hahn);
+* Fixed: mime.unb64() would return an empty string on results that started with a null character (Robert Raschke);
+* Fixed: HTTP now automatically redirects on 303 and 307 (Jonathan Gray);
+* Fixed: calling sleep() with negative numbers could block forever, wasting CPU. Now it returns immediately (MPB);
+* Improved: FTP commands are now sent in upper case to help buggy servers (Anders Eurenius);
+* Improved: known headers now sent in canonic capitalization to help buggy servers (Joseph Stewart);
+* Improved: Clarified tcp:receive() in the manual (MPB);
+* Improved: Decent makefiles (LHF).
+* Fixed: RFC links in documentation now point to IETF (Cosmin Apreutesei).
+
+## [v2.0.2](https://github.com/lunarmodules/luasocket/releases/v2.0.2) — 2007-09-11
diff --git a/FIX b/FIX
deleted file mode 100644
index 40f30a1..0000000
--- a/FIX
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-http was preserving old host header during redirects
-fix smtp.send hang on source error
-add create field to FTP and SMTP and fix HTTP ugliness
-clean timeout argument to open functions in SMTP, HTTP and FTP
-eliminate globals from namespaces created by module().
-url.absolute was not working when base_url was already parsed
-http.request was redirecting even when the location header was empty
-tcp{client}:shutdown() was checking for group instead of class.
-tcp{client}:send() now returns i+sent-1...
-get rid of a = socket.try() in the manual, except for protected cases. replace it with assert.
-get rid of "base." kludge in package.loaded
-check all "require("http")" etc in the manual.
-make sure sock_gethostname.* only return success if the hp is not null!
-change 'l' prefix in C libraries to 'c' to avoid clash with LHF libraries
- don't forget the declarations in luasocket.h and mime.h!!!
-setpeername was using udp{unconnected}
-fixed a bug in http.lua that caused some requests to fail (Florian Berger)
-fixed a bug in select.c that prevented sockets with descriptor 0 from working (Renato Maia)
-fixed a "bug" that caused dns.toip to crash under uLinux
-fixed a "bug" that caused a crash in gethostbyname under VMS
-DEBUG and VERSION became _DEBUG and _VERSION
-send returns the right value if input is "". Alexander Marinov
diff --git a/LICENSE b/LICENSE
index b635451..a8ed03e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,5 +1,4 @@
-LuaSocket 3.0 license
-Copyright © 2004-2013 Diego Nehab
+Copyright (C) 2004-2022 Diego Nehab
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
diff --git a/Lua.props b/Lua.props
new file mode 100755
index 0000000..d748448
--- /dev/null
+++ b/Lua.props
@@ -0,0 +1,49 @@
+
+
- - |
Network support for the Lua language - |
-home · -download · -installation · -introduction · -reference -
--LuaSocket is a Lua extension library -that is composed by two parts: a C core that provides support for the TCP -and UDP transport layers, and a set of Lua modules that add support for -functionality commonly needed by applications that deal with the Internet. -
- --The core support has been implemented so that it is both efficient and -simple to use. It is available to any Lua application once it has been -properly initialized by the interpreter in use. The code has been tested -and runs well on several Windows and UNIX platforms.
- --Among the support modules, the most commonly used implement the -SMTP -(sending e-mails), -HTTP -(WWW access) and -FTP -(uploading and downloading files) client -protocols. These provide a very natural and generic interface to the -functionality defined by each protocol. -In addition, you will find that the -MIME (common encodings), -URL -(anything you could possible want to do with one) and -LTN12 -(filters, sinks, sources and pumps) modules can be very handy. -
- --The library is available under the same - -terms and conditions as the Lua language, the MIT license. The idea is -that if you can use Lua in a project, you should also be able to use -LuaSocket. -
- -
-Copyright © 1999-2013 Diego Nehab. All rights reserved.
-Author: Diego Nehab
-
-LuaSocket version 3.0-rc1 is now available for download! -It is compatible with Lua 5.1 and 5.2, and has -been tested on Windows XP, Linux, and Mac OS X. Chances -are it works well on most UNIX distributions and Windows flavors. -
- --The current version of the library can be found at -the LuaSocket -project page on GitHub. Besides the full C and Lua source code -for the library, the distribution contains several examples, -this user's manual and basic test procedures. -
- -Take a look at the installation section of the -manual to find out how to properly install the library. -
- - - --This marks the first release of LuaSocket that -wholeheartedly embraces the open-source development -philosophy. After a long hiatus, Matthew Wild finally -convinced me it was time for a release including IPv6 and -Lua 5.2 support. It was more work than we anticipated. -Special thanks to Sam Roberts, Florian Zeitz, and Paul -Aurich, Liam Devine, Alexey Melnichuk, and everybody else -that has helped bring this library back to life. -
- - - --Main changes for LuaSocket 3.0-rc1 are IPv6 support -and Lua 5.2 compatibility. -
- --All previous versions of the LuaSocket library can be downloaded -here. Although these versions are no longer supported, they are -still available for those that have compatibility issues. -
- - - --download · -installation · -introduction · -reference -
-
-
-Last modified by Diego Nehab on
-Tue Jun 11 18:50:23 HKT 2013
-
-
-
+
+
| Network support for the Lua language
+ | Network support for the Lua language
| |
+
home · download · installation · introduction · -reference +reference
-IPv4 name resolution functions -dns.toip +IPv4 name resolution functions +dns.toip and -dns.tohostname -return all information obtained from +dns.tohostname +return all information obtained from the resolver in a table of the form:
@@ -60,10 +60,10 @@ Note that the alias list can be empty.-The more general name resolution function -dns.getaddrinfo, which +The more general name resolution function +dns.getaddrinfo, which supports both IPv6 and IPv4, -returns all information obtained from +returns all information obtained from the resolver in a table of the form:
@@ -88,82 +88,82 @@ addresses, and "inet6" for IPv6 addresses. -+
socket.dns.getaddrinfo(address)
--Converts from host name to address. +
+Converts from host name to address.
--Address can be an IPv4 or IPv6 address or host name. +
+Address can be an IPv4 or IPv6 address or host name.
-+
The function returns a table with all information returned by the resolver. In case of error, the function returns nil -followed by an error message. +followed by an error message.
-+
socket.dns.gethostname()
--Returns the standard host name for the machine as a string. +
+Returns the standard host name for the machine as a string.
-+
socket.dns.tohostname(address)
-+
Converts from IPv4 address to host name.
--Address can be an IP address or host name. +
+Address can be an IP address or host name.
-+
The function returns a string with the canonic host name of the given address, followed by a table with all information returned by the resolver. In case of error, the function returns nil -followed by an error message. +followed by an error message.
-+
socket.dns.toip(address)
-+
Converts from host name to IPv4 address.
--Address can be an IP address or host name. +
+Address can be an IP address or host name.
-+
Returns a string with the first IP address found for address, followed by a table with all information returned by the resolver. In case of error, the function returns nil followed by an error -message. +message.
-+
home · download · installation · diff --git a/doc/ftp.html b/docs/ftp.html similarity index 78% rename from doc/ftp.html rename to docs/ftp.html index 3f23a4a..7f7da2e 100644 --- a/doc/ftp.html +++ b/docs/ftp.html @@ -1,5 +1,5 @@ - +
@@ -13,22 +13,22 @@ -
-
+
+
| Network support for the Lua language
+ | Network support for the Lua language
| |
+
home · download · installation · introduction · -reference +reference
FTP (File Transfer Protocol) is a protocol used to transfer files @@ -50,28 +50,28 @@ High level functions are provided supporting the most common operations. These high level functions are implemented on top of a lower level interface. Using the low-level interface, users can easily create their own functions to access any operation supported by the FTP -protocol. For that, check the implementation. +protocol. For that, check the implementation.
-To really benefit from this module, a good understanding of +To really benefit from this module, a good understanding of -LTN012, Filters sources and sinks is necessary. +LTN012, Filters sources and sinks is necessary.
-+
To obtain the ftp namespace, run:
-+-- loads the FTP module and any libraries it requires local ftp = require("socket.ftp")URLs MUST conform to -RFC 1738, -that is, an URL is a string in the form: +RFC 1738, +that is, an URL is a string in the form:
@@ -81,20 +81,19 @@ that is, an URL is a string in the form:The following constants in the namespace can be set to control the default behavior of -the FTP module: +the FTP module:
-
-- PASSWORD: default anonymous password. -
- PORT: default port used for the control connection; -
- TIMEOUT: sets the timeout for all I/O operations; -
- USER: default anonymous user; +
- PASSWORD: default anonymous password.
+- TIMEOUT: sets the timeout for all I/O operations;
+- USER: default anonymous user;
+
ftp.get(url)
-
ftp.get{
host = string,
@@ -110,19 +109,19 @@ ftp.get{
}+
The get function has two forms. The simple form has fixed functionality: it downloads the contents of a URL and returns it as a string. The generic form allows a lot more control, as explained below.
-+
If the argument of the get function is a table, the function -expects at least the fields host, sink, and one of +expects at least the fields host, sink, and one of argument or path (argument takes precedence). Host is the server to connect to. Sink is -the simple +the simple LTN12 sink that will receive the downloaded data. Argument or path give the target path to the resource in the server. The @@ -130,28 +129,28 @@ optional arguments are the following:
-
- user, password: User name and password used for -authentication. Defaults to "ftp:anonymous@anonymous.org"; +authentication. Defaults to "ftp:anonymous@anonymous.org";
- command: The FTP command used to obtain data. Defaults to -"retr", but see example below; -
- port: The port to used for the control connection. Defaults to 21; +"retr", but see example below;
+- port: The port to used for the control connection. Defaults to 21;
- type: The transfer mode. Can take values "i" or -"a". Defaults to whatever is the server default; -
- step: +"a". Defaults to whatever is the server default;
+- step: LTN12 pump step function used to pass data from the -server to the sink. Defaults to the LTN12 pump.step function; +server to the sink. Defaults to the LTN12 pump.step function;
- create: An optional function to be used instead of -socket.tcp when the communications socket is created. +socket.tcp when the communications socket is created.
+
If successful, the simple version returns the URL contents as a string, and the generic function returns 1. In case of error, both functions return nil and an error message describing the -error. +error.
-+-- load the ftp support local ftp = require("socket.ftp") @@ -160,7 +159,7 @@ local ftp = require("socket.ftp") f, e = ftp.get("ftp://ftp.tecgraf.puc-rio.br/pub/lua/lua.tar.gz;type=i")-+-- load needed modules local ftp = require("socket.ftp") local ltn12 = require("ltn12") @@ -179,7 +178,7 @@ end -+
ftp.put(url, content)
-
ftp.put{
host = string,
@@ -195,57 +194,57 @@ ftp.put{
}+
The put function has two forms. The simple form has fixed functionality: it uploads a string of content into a URL. The generic form -allows a lot more control, as explained below. +allows a lot more control, as explained below.
-+
If the argument of the put function is a table, the function -expects at least the fields host, source, and one of +expects at least the fields host, source, and one of argument or path (argument takes precedence). Host is the server to connect to. Source is -the simple -LTN12 -source that will provide the contents to be uploaded. +the simple +LTN12 +source that will provide the contents to be uploaded. Argument or path give the target path to the resource in the server. The optional arguments are the following:
-
- user, password: User name and password used for -authentication. Defaults to "ftp:anonymous@anonymous.org"; +authentication. Defaults to "ftp:anonymous@anonymous.org";
- command: The FTP command used to send data. Defaults to -"stor", but see example below; -
- port: The port to used for the control connection. Defaults to 21; +"stor", but see example below;
+- port: The port to used for the control connection. Defaults to 21;
- type: The transfer mode. Can take values "i" or -"a". Defaults to whatever is the server default; -
- step: -LTN12 +"a". Defaults to whatever is the server default;
+- step: +LTN12 pump step function used to pass data from the -server to the sink. Defaults to the LTN12 pump.step function; +server to the sink. Defaults to the LTN12 pump.step function;
- create: An optional function to be used instead of -socket.tcp when the communications socket is created. +socket.tcp when the communications socket is created.
+
Both functions return 1 if successful, or nil and an error message describing the reason for failure.
-+-- load the ftp support local ftp = require("socket.ftp") -- Log as user "fulano" on server "ftp.example.com", --- using password "silva", and store a file "README" with contents +-- using password "silva", and store a file "README" with contents -- "wrong password, of course" -f, e = ftp.put("ftp://fulano:silva@ftp.example.com/README", +f, e = ftp.put("ftp://fulano:silva@ftp.example.com/README", "wrong password, of course")-+-- load the ftp support local ftp = require("socket.ftp") local ltn12 = require("ltn12") @@ -254,7 +253,7 @@ local ltn12 = require("ltn12") -- using password "silva", and append to the remote file "LOG", sending the -- contents of the local file "LOCAL-LOG" f, e = ftp.put{ - host = "ftp.example.com", + host = "ftp.example.com", user = "fulano", password = "silva", command = "appe", @@ -266,15 +265,15 @@ f, e = ftp.put{ -+
- +
home · download · installation · introduction · -reference +reference
diff --git a/doc/http.html b/docs/http.html similarity index 76% rename from doc/http.html rename to docs/http.html index cd41c0d..52b8a30 100644 --- a/doc/http.html +++ b/docs/http.html @@ -1,10 +1,10 @@ - - +
LuaSocket: HTTP support @@ -13,22 +13,22 @@ -+
-
-- + - + Network support for the Lua language + Network support for the Lua language +
home · download · introduction · introduction · -reference +reference
@@ -36,12 +36,12 @@ -HTTP
+HTTP
HTTP (Hyper Text Transfer Protocol) is the protocol used to exchange information between web-browsers and servers. The http -namespace offers full support for the client side of the HTTP +namespace offers full support for the client side of the HTTP protocol (i.e., the facilities that would be used by a web-browser implementation). The implementation conforms to the HTTP/1.1 standard, @@ -50,16 +50,16 @@ implementation conforms to the HTTP/1.1 standard,
The module exports functions that provide HTTP functionality in different -levels of abstraction. From the simple +levels of abstraction. From the simple string oriented requests, through generic LTN12 based, down to even lower-level if you bother to look through the source code.
-+
To obtain the http namespace, run:
-+-- loads the HTTP module and any libraries it requires local http = require("socket.http")@@ -67,12 +67,12 @@ local http = require("socket.http")URLs must conform to RFC 1738, -that is, an URL is a string in the form: +that is, an URL is a string in the form:
@@ -97,31 +97,34 @@ headers = {-[http://][<user>[:<password>]@]<host>[:<port>][/<path>] +[http://][<user>[:<password>]@]<host>[:<port>][/<path>]
Field names are case insensitive (as specified by the standard) and all functions work with lowercase field names (but see -socket.headers.canonic). +socket.headers.canonic). Field values are left unmodified.
-+
Note: MIME headers are independent of order. Therefore, there is no problem -in representing them in a Lua table. +in representing them in a Lua table.
The following constants can be set to control the default behavior of -the HTTP module: +the HTTP module:
-
+- PORT: default port used for connections; -
- PROXY: default proxy used for connections; -
- TIMEOUT: sets the timeout for all I/O operations; -
- USERAGENT: default user agent reported to server. +
- PROXY: default proxy used for connections;
+- TIMEOUT: sets the timeout for all I/O operations;
+- USERAGENT: default user agent reported to server.
+Note: These constants are global. Changing them will also +change the behavior other code that might be using LuaSocket. +
-+
http.request(url [, body])
-
http.request{
url = string,
@@ -132,30 +135,31 @@ http.request{
[step = LTN12 pump step,]
[proxy = string,]
[redirect = boolean,]
- [create = function]
+ [create = function,]
+ [maxredirects = number]
}-The request function has two forms. The simple form downloads -a URL using the GET or POST method and is based -on strings. The generic form performs any HTTP method and is -LTN12 based. +
+The request function has two forms. The simple form downloads +a URL using the GET or POST method and is based +on strings. The generic form performs any HTTP method and is +LTN12 based.
-+
If the first argument of the request function is a string, it should be an url. In that case, if a body is provided as a string, the function will perform a POST method in the url. Otherwise, it performs a GET in the -url +url
--If the first argument is instead a table, the most important fields are +
+If the first argument is instead a table, the most important fields are the url and the simple -LTN12 -sink that will receive the downloaded content. +LTN12 +sink that will receive the downloaded content. Any part of the url can be overridden by including the appropriate field in the request table. If authentication information is provided, the function @@ -165,48 +169,51 @@ function discards the downloaded data. The optional parameters are the following:
-
-- method: The HTTP request method. Defaults to "GET"; -
- headers: Any additional HTTP headers to send with the request; -
- source: simple -LTN12 +
- method: The HTTP request method. Defaults to "GET";
+- headers: Any additional HTTP headers to send with the request;
+- source: simple +LTN12 source to provide the request body. If there is a body, you need to provide an appropriate "content-length" request header field, or the function will attempt to send the body as -"chunked" (something few servers support). Defaults to the empty source; -
- step: -LTN12 -pump step function used to move data. -Defaults to the LTN12 pump.step function. -
- proxy: The URL of a proxy server to use. Defaults to no proxy; -
- redirect: Set to false to prevent the -function from automatically following 301 or 302 server redirect messages; +"chunked" (something few servers support). Defaults to the empty source;
+- step: +LTN12 +pump step function used to move data. +Defaults to the LTN12 pump.step function.
+- proxy: The URL of a proxy server to use. Defaults to no proxy;
+- redirect: Set to false to prevent the +function from automatically following 301 or 302 server redirect messages;
- create: An optional function to be used instead of -socket.tcp when the communications socket is created. +socket.tcp when the communications socket is created.
+- maxredirects: An optional number specifying the maximum number of + redirects to follow. Defaults to 5 if not specified. A boolean + false value means no maximum (unlimited).
+
In case of failure, the function returns nil followed by an -error message. If successful, the simple form returns the response +error message. If successful, the simple form returns the response body as a string, followed by the response status code, the response headers and the response status line. The generic function returns the same information, except the first return value is just the number 1 (the body goes to the sink).
--Even when the server fails to provide the contents of the requested URL (URL not found, for example), +
+Even when the server fails to provide the contents of the requested URL (URL not found, for example), it usually returns a message body (a web page informing the URL was not found or some other useless page). To make sure the operation was successful, check the returned status code. For a list of the possible values and their meanings, refer to RFC 2616. +href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616.
-+
Here are a few examples with the simple interface:
-+-- load the http module local io = require("io") local http = require("socket.http") @@ -214,15 +221,15 @@ local ltn12 = require("ltn12") -- connect to server "www.cs.princeton.edu" and retrieves this manual -- file from "~diego/professional/luasocket/http.html" and print it to stdout -http.request{ - url = "http://www.cs.princeton.edu/~diego/professional/luasocket/http.html", +http.request{ + url = "http://www.cs.princeton.edu/~diego/professional/luasocket/http.html", sink = ltn12.sink.file(io.stdout) } -- connect to server "www.example.com" and tries to retrieve -- "/private/index.html". Fails because authentication is needed. b, c, h = http.request("http://www.example.com/private/index.html") --- b returns some useless page telling about the denied access, +-- b returns some useless page telling about the denied access, -- h returns authentication information -- and c returns with value 401 (Authentication Required) @@ -232,11 +239,11 @@ r, e = http.request("http://wrong.host/") -- r is nil, and e returns with value "host not found"-+
And here is an example using the generic interface:
-+-- load the http module http = require("socket.http") @@ -258,7 +265,7 @@ r, c, h = http.request { -- }-+
Note: When sending a POST request, simple interface adds a "Content-type: application/x-www-form-urlencoded" header to the request. This is the type used by @@ -266,21 +273,21 @@ HTML forms. If you need another type, use the generic interface.
-+
Note: Some URLs are protected by their servers from anonymous download. For those URLs, the server must receive some sort of authentication along with the request or it will deny -download and return status "401 Authentication Required". +download and return status "401 Authentication Required".
-+
The HTTP/1.1 standard defines two authentication methods: the Basic Authentication Scheme and the Digest Authentication Scheme, both explained in detail in RFC 2068.
-The Basic Authentication Scheme sends +
The Basic Authentication Scheme sends <user> and <password> unencrypted to the server and is therefore considered unsafe. Unfortunately, by the time of this implementation, @@ -289,7 +296,7 @@ Therefore, this is the method used by the toolkit whenever authentication is required.
-+-- load required modules http = require("socket.http") mime = require("mime") @@ -309,20 +316,20 @@ r, c = http.request { -+
- diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..ad92687 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,138 @@ + + + + + + ++
home · download · installation · introduction · -reference +reference
-Last modified by Diego Nehab on
-Thu Apr 20 00:25:26 EDT 2006 +Last modified by Eric Westbrook on
+Sat Feb 23 19:09:42 UTC 2019LuaSocket: Network support for the Lua language + + + + + + + +++ + + +
++ ++
++ + + + Network support for the Lua language + +home · +download · +installation · +introduction · +reference +
+
+What is LuaSocket?
+ ++LuaSocket is a Lua extension library +that is composed by two parts: a C core that provides support for the TCP +and UDP transport layers, and a set of Lua modules that add support for +functionality commonly needed by applications that deal with the Internet. +
+ ++The core support has been implemented so that it is both efficient and +simple to use. It is available to any Lua application once it has been +properly initialized by the interpreter in use. The code has been tested +and runs well on several Windows and UNIX platforms.
+ ++Among the support modules, the most commonly used implement the +SMTP +(sending e-mails), +HTTP +(WWW access) and +FTP +(uploading and downloading files) client +protocols. These provide a very natural and generic interface to the +functionality defined by each protocol. +In addition, you will find that the +MIME (common encodings), +URL +(anything you could possible want to do with one) and +LTN12 +(filters, sinks, sources and pumps) modules can be very handy. +
+ ++The library is available under the same + +terms and conditions as the Lua language, the MIT license. The idea is +that if you can use Lua in a project, you should also be able to use +LuaSocket. +
+ ++Copyright © 1999-2013 Diego Nehab. All rights reserved.
+ + + +
+Author: Diego Nehab +Download
+ ++LuaSocket version 3.1.0 is now available for download! +It is compatible with Lua 5.1 through 5.4. +Chances are it works well on most UNIX distributions and Windows flavors. +
+ ++The current version of the library can be found at +the LuaSocket +project page on GitHub. Besides the full C and Lua source code +for the library, the distribution contains several examples, +this user's manual and basic test procedures. +
+ +Take a look at the installation section of the +manual to find out how to properly install the library. +
+ + + +Special thanks
+ ++This marks the first release of LuaSocket that +wholeheartedly embraces the open-source development +philosophy. After a long hiatus, Matthew Wild finally +convinced me it was time for a release including IPv6 and +Lua 5.2 support. It was more work than we anticipated. +Special thanks to Sam Roberts, Florian Zeitz, and Paul +Aurich, Liam Devine, Alexey Melnichuk, and everybody else +that has helped bring this library back to life. +
+ + + +++ + + diff --git a/doc/installation.html b/docs/installation.html similarity index 99% rename from doc/installation.html rename to docs/installation.html index 28a9fbb..dcf9d36 100644 --- a/doc/installation.html +++ b/docs/installation.html @@ -89,7 +89,7 @@ it should be easy to use LuaSocket. Just fire the interpreter and use the Lua 5.2.2 Copyright (C) 1994-2013 Lua.org, PUC-Rio > socket = require("socket") > print(socket._VERSION) ---> LuaSocket 3.0-rc1 +--> LuaSocket 3.0.0
++ ++download · +installation · +introduction · +reference +
+Each module loads their dependencies automatically, so you only need to diff --git a/doc/introduction.html b/docs/introduction.html similarity index 100% rename from doc/introduction.html rename to docs/introduction.html diff --git a/logo.ps b/docs/logo.ps similarity index 100% rename from logo.ps rename to docs/logo.ps diff --git a/doc/ltn12.html b/docs/ltn12.html similarity index 72% rename from doc/ltn12.html rename to docs/ltn12.html index 54e66fb..fe3e3a0 100644 --- a/doc/ltn12.html +++ b/docs/ltn12.html @@ -1,4 +1,4 @@ - @@ -14,22 +14,22 @@ Pump, Support, Library"> -
+
-
-- + - + Network support for the Lua language + Network support for the Lua language +
home · download · installation · introduction · -reference +reference
@@ -37,7 +37,7 @@ Pump, Support, Library"> -LTN12
+LTN12
The ltn12 namespace implements the ideas described in @@ -46,11 +46,11 @@ functions. Please refer to the LTN for a deeper explanation of the functionality provided by this module.
-+
To obtain the ltn12 namespace, run:
-+-- loads the LTN21 module local ltn12 = require("ltn12")@@ -61,32 +61,32 @@ local ltn12 = require("ltn12") --ltn12.filter.chain(filter1, filter2 +
+ltn12.filter.chain(filter1, filter2 [, ... filterN])
-+
Returns a filter that passes all data it receives through each of a -series of given filters. +series of given filters.
-+
Filter1 to filterN are simple -filters. +filters.
-+
The function returns the chained filter.
-+
The nesting of filters can be arbitrary. For instance, the useless filter below doesn't do anything but return the data that was passed to it, unaltered.
-+-- load required modules local ltn12 = require("ltn12") local mime = require("mime") @@ -102,26 +102,26 @@ id = ltn12.filter.chain( -+
ltn12.filter.cycle(low [, ctx, extra])
-+
Returns a high-level filter that cycles though a low-level filter by -passing it each chunk and updating a context between calls. +passing it each chunk and updating a context between calls.
--Low is the low-level filter to be cycled, +
+Low is the low-level filter to be cycled, ctx is the initial context and extra is any extra argument the low-level filter might take.
--The function returns the high-level filter. +
+The function returns the high-level filter.
-+-- load the ltn12 module local ltn12 = require("ltn12") @@ -137,15 +137,15 @@ end -+
ltn12.pump.all(source, sink)
--Pumps all data from a source to a sink. +
+Pumps all data from a source to a sink.
-+
If successful, the function returns a value that evaluates to true. In case of error, the function returns a false value, followed by an error message. @@ -153,15 +153,15 @@ of error, the function returns a false value, followed by an err -
+
ltn12.pump.step(source, sink)
--Pumps one chunk of data from a source to a sink. +
+Pumps one chunk of data from a source to a sink.
-+
If successful, the function returns a value that evaluates to true. In case of error, the function returns a false value, followed by an error message. @@ -173,52 +173,52 @@ of error, the function returns a false value, followed by an err -
+
ltn12.sink.chain(filter, sink)
--Creates and returns a new sink that passes data through a filter before sending it to a given sink. +
+Creates and returns a new sink that passes data through a filter before sending it to a given sink.
-+
ltn12.sink.error(message)
-+
Creates and returns a sink that aborts transmission with the error message.
-+
ltn12.sink.file(handle, message)
--Creates a sink that sends data to a file. +
+Creates a sink that sends data to a file.
--Handle is a file handle. If handle is nil, -message should give the reason for failure. +
+Handle is a file handle. If handle is nil, +message should give the reason for failure.
-+
The function returns a sink that sends all data to the given handle and closes the file when done, or a sink that aborts the transmission with the error message
--In the following example, notice how the prototype is designed to +
+In the following example, notice how the prototype is designed to fit nicely with the io.open function.
-+-- load the ltn12 module local ltn12 = require("ltn12") @@ -231,45 +231,45 @@ ltn12.pump.all( -+
ltn12.sink.null()
--Returns a sink that ignores all data it receives. +
+Returns a sink that ignores all data it receives.
-+
ltn12.sink.simplify(sink)
--Creates and returns a simple sink given a fancy sink. +
+Creates and returns a simple sink given a fancy sink.
-+
ltn12.sink.table([table])
-+
Creates a sink that stores all chunks in a table. The chunks can later be efficiently concatenated into a single string.
-+
Table is used to hold the chunks. If -nil, the function creates its own table. +nil, the function creates its own table.
--The function returns the sink and the table used to store the chunks. +
+The function returns the sink and the table used to store the chunks.
-+-- load needed modules local http = require("socket.http") local ltn12 = require("ltn12") @@ -291,89 +291,89 @@ end -+
ltn12.source.cat(source1 [, source2, ..., sourceN])
-+
Creates a new source that produces the concatenation of the data produced -by a number of sources. +by a number of sources.
-+
Source1 to sourceN are the original -sources. +sources.
-+
The function returns the new source.
-+
ltn12.source.chain(source, filter)
--Creates a new source that passes data through a filter -before returning it. +
+Creates a new source that passes data through a filter +before returning it.
-+
The function returns the new source.
-+
ltn12.source.empty()
--Creates and returns an empty source. +
+Creates and returns an empty source.
-+
ltn12.source.error(message)
-+
Creates and returns a source that aborts transmission with the error message.
-+
ltn12.source.file(handle, message)
--Creates a source that produces the contents of a file. +
+Creates a source that produces the contents of a file.
--Handle is a file handle. If handle is nil, -message should give the reason for failure. +
+Handle is a file handle. If handle is nil, +message should give the reason for failure.
--The function returns a source that reads chunks of data from +
+The function returns a source that reads chunks of data from given handle and returns it to the user, closing the file when done, or a source that aborts the transmission with the error message
--In the following example, notice how the prototype is designed to +
+In the following example, notice how the prototype is designed to fit nicely with the io.open function.
-+-- load the ltn12 module local ltn12 = require("ltn12") @@ -386,31 +386,41 @@ ltn12.pump.all( -+
ltn12.source.simplify(source)
--Creates and returns a simple source given a fancy source. +
+Creates and returns a simple source given a fancy source.
-+
ltn12.source.string(string)
-+
Creates and returns a source that produces the contents of a -string, chunk by chunk. +string, chunk by chunk. +
+ + + ++ltn12.source.table(table) +
+ ++Creates and returns a source that produces the numerically-indexed values of a table successively beginning at 1. The source returns nil (end-of-stream) whenever a nil value is produced by the current index, which proceeds forward regardless.
-+
- +
home · download · installation · diff --git a/doc/luasocket.png b/docs/luasocket.png similarity index 100% rename from doc/luasocket.png rename to docs/luasocket.png diff --git a/doc/mime.html b/docs/mime.html similarity index 65% rename from doc/mime.html rename to docs/mime.html index ae136fd..ff4d8e8 100644 --- a/doc/mime.html +++ b/docs/mime.html @@ -1,10 +1,10 @@ -
- +LuaSocket: MIME module @@ -13,22 +13,22 @@ -+
-
-- + - + Network support for the Lua language + Network support for the Lua language +
home · download · installation · introduction · -reference +reference
@@ -36,14 +36,14 @@ -MIME
+MIME
The mime namespace offers filters that apply and remove common content transfer encodings, such as Base64 and Quoted-Printable. It also provides functions to break text into lines and change the end-of-line convention. -MIME is described mainly in +MIME is described mainly in RFC 2045, 2046, 2047, @@ -52,17 +52,17 @@ MIME is described mainly in
-All functionality provided by the MIME module -follows the ideas presented in +All functionality provided by the MIME module +follows the ideas presented in -LTN012, Filters sources and sinks. +LTN012, Filters sources and sinks.
-+
To obtain the mime namespace, run:
-+-- loads the MIME module and everything it requires local mime = require("mime")@@ -70,88 +70,60 @@ local mime = require("mime") -High-level filters
+High-level filters
- - --mime.normalize([marker]) -
- --Converts most common end-of-line markers to a specific given marker. -
- --Marker is the new marker. It defaults to CRLF, the canonic -end-of-line marker defined by the MIME standard. -
- --The function returns a filter that performs the conversion. -
- --Note: There is no perfect solution to this problem. Different end-of-line -markers are an evil that will probably plague developers forever. -This function, however, will work perfectly for text created with any of -the most common end-of-line markers, i.e. the Mac OS (CR), the Unix (LF), -or the DOS (CRLF) conventions. Even if the data has mixed end-of-line -markers, the function will still work well, although it doesn't -guarantee that the number of empty lines will be correct. -
-+
mime.decode("base64")
-
mime.decode("quoted-printable")+
Returns a filter that decodes data from a given transfer content encoding.
-+
mime.encode("base64")
-
mime.encode("quoted-printable" [, mode])+
Returns a filter that encodes data according to a given transfer content encoding.
-+
In the Quoted-Printable case, the user can specify whether the data is textual or binary, by passing the mode strings "text" or "binary". Mode defaults to "text".
-+
Although both transfer content encodings specify a limit for the line length, the encoding filters do not break text into lines (for -added flexibility). +added flexibility). Below is a filter that converts binary data to the Base64 transfer content encoding and breaks it into lines of the correct size.
-+base64 = ltn12.filter.chain( mime.encode("base64"), mime.wrap("base64") )-+
Note: Text data has to be converted to canonic form before being encoded.
-+base64 = ltn12.filter.chain( mime.normalize(), mime.encode("base64"), @@ -159,50 +131,79 @@ base64 = ltn12.filter.chain( )+ + ++mime.normalize([marker]) +
+ ++Converts most common end-of-line markers to a specific given marker. +
+ ++Marker is the new marker. It defaults to CRLF, the canonic +end-of-line marker defined by the MIME standard. +
+ ++The function returns a filter that performs the conversion. +
+ ++Note: There is no perfect solution to this problem. Different end-of-line +markers are an evil that will probably plague developers forever. +This function, however, will work perfectly for text created with any of +the most common end-of-line markers, i.e. the Mac OS (CR), the Unix (LF), +or the DOS (CRLF) conventions. Even if the data has mixed end-of-line +markers, the function will still work well, although it doesn't +guarantee that the number of empty lines will be correct. +
+ -+
mime.stuff()
-
+
Creates and returns a filter that performs stuffing of SMTP messages.
--Note: The smtp.send function +
+Note: The smtp.send function uses this filter automatically. You don't need to chain it with your -source, or apply it to your message body. +source, or apply it to your message body.
-+
mime.wrap("text" [, length])
-
mime.wrap("base64")
mime.wrap("quoted-printable")-Returns a filter that breaks data into lines. +
+Returns a filter that breaks data into lines.
--The "text" line-wrap filter simply breaks text into lines by -inserting CRLF end-of-line markers at appropriate positions. -Length defaults 76. +
+The "text" line-wrap filter simply breaks text into lines by +inserting CRLF end-of-line markers at appropriate positions. +Length defaults 76. The "base64" line-wrap filter works just like the default -"text" line-wrap filter with default length. +"text" line-wrap filter with default length. The function can also wrap "quoted-printable" lines, taking care not to break lines in the middle of an escaped character. In that case, the line length is fixed at 76.
-+
For example, to create an encoding filter for the Quoted-Printable transfer content encoding of text data, do the following:
-+qp = ltn12.filter.chain( mime.normalize(), mime.encode("quoted-printable"), @@ -210,155 +211,155 @@ qp = ltn12.filter.chain( )-+
Note: To break into lines with a different end-of-line convention, apply -a normalization filter after the line break filter. +a normalization filter after the line break filter.
-Low-level filters
+Low-level filters
-+
A, B = mime.b64(C [, D])
--Low-level filter to perform Base64 encoding. +
+Low-level filter to perform Base64 encoding.
--A is the encoded version of the largest prefix of -C..D -that can be encoded unambiguously. B has the remaining bytes of -C..D, before encoding. -If D is nil, A is padded with -the encoding of the remaining bytes of C. +
+A is the encoded version of the largest prefix of +C..D +that can be encoded unambiguously. B has the remaining bytes of +C..D, before encoding. +If D is nil, A is padded with +the encoding of the remaining bytes of C.
-+
Note: The simplest use of this function is to encode a string into it's Base64 transfer content encoding. Notice the extra parenthesis around the call to mime.b64, to discard the second return value.
-+print((mime.b64("diego:password"))) --> ZGllZ286cGFzc3dvcmQ=-+
A, n = mime.dot(m [, B])
-+
Low-level filter to perform SMTP stuffing and enable transmission of -messages containing the sequence "CRLF.CRLF". +messages containing the sequence "CRLF.CRLF".
-+
A is the stuffed version of B. 'n' gives the number of characters from the sequence CRLF seen in the end of B. 'm' should tell the same, but for the previous chunk.
-Note: The message body is defined to begin with +
Note: The message body is defined to begin with an implicit CRLF. Therefore, to stuff a message correctly, the -first m should have the value 2. +first m should have the value 2.
-+print((string.gsub(mime.dot(2, ".\r\nStuffing the message.\r\n.\r\n."), "\r\n", "\\n"))) --> ..\nStuffing the message.\n..\n..--Note: The smtp.send function -uses this filter automatically. You don't need to -apply it again. +
+Note: The smtp.send function +uses this filter automatically. You don't need to +apply it again.
-+
A, B = mime.eol(C [, D, marker])
--Low-level filter to perform end-of-line marker translation. +
+Low-level filter to perform end-of-line marker translation. For each chunk, the function needs to know if the last character of the previous chunk could be part of an end-of-line marker or not. This is the context the function receives besides the chunk. An updated version of -the context is returned after each new chunk. +the context is returned after each new chunk.
-+
A is the translated version of D. C is the ASCII value of the last character of the previous chunk, if it was a -candidate for line break, or 0 otherwise. +candidate for line break, or 0 otherwise. B is the same as C, but for the current chunk. Marker gives the new end-of-line marker and defaults to CRLF.
-+-- translates the end-of-line marker to UNIX -unix = mime.eol(0, dos, "\n") +unix = mime.eol(0, dos, "\n")-+
A, B = mime.qp(C [, D, marker])
--Low-level filter to perform Quoted-Printable encoding. +
+Low-level filter to perform Quoted-Printable encoding.
--A is the encoded version of the largest prefix of -C..D -that can be encoded unambiguously. B has the remaining bytes of -C..D, before encoding. -If D is nil, A is padded with -the encoding of the remaining bytes of C. -Throughout encoding, occurrences of CRLF are replaced by the +
+A is the encoded version of the largest prefix of +C..D +that can be encoded unambiguously. B has the remaining bytes of +C..D, before encoding. +If D is nil, A is padded with +the encoding of the remaining bytes of C. +Throughout encoding, occurrences of CRLF are replaced by the marker, which itself defaults to CRLF.
-+
Note: The simplest use of this function is to encode a string into it's -Quoted-Printable transfer content encoding. +Quoted-Printable transfer content encoding. Notice the extra parenthesis around the call to mime.qp, to discard the second return value.
--print((mime.qp("maçã"))) ++print((mime.qp("ma��"))) --> ma=E7=E3=-+
A, m = mime.qpwrp(n [, B, length])
--Low-level filter to break Quoted-Printable text into lines. +
+Low-level filter to break Quoted-Printable text into lines.
--A is a copy of B, broken into lines of at most -length bytes (defaults to 76). -'n' should tell how many bytes are left for the first -line of B and 'm' returns the number of bytes -left in the last line of A. +
+A is a copy of B, broken into lines of at most +length bytes (defaults to 76). +'n' should tell how many bytes are left for the first +line of B and 'm' returns the number of bytes +left in the last line of A.
-+
Note: Besides breaking text into lines, this function makes sure the line breaks don't fall in the middle of an escaped character combination. Also, this function only breaks lines that are bigger than length bytes. @@ -366,86 +367,86 @@ this function only breaks lines that are bigger than length bytes. -
+
A, B = mime.unb64(C [, D])
--Low-level filter to perform Base64 decoding. +
+Low-level filter to perform Base64 decoding.
--A is the decoded version of the largest prefix of -C..D -that can be decoded unambiguously. B has the remaining bytes of -C..D, before decoding. +
+A is the decoded version of the largest prefix of +C..D +that can be decoded unambiguously. B has the remaining bytes of +C..D, before decoding. If D is nil, A is the empty string -and B returns whatever couldn't be decoded. +and B returns whatever couldn't be decoded.
-+
Note: The simplest use of this function is to decode a string from it's -Base64 transfer content encoding. +Base64 transfer content encoding. Notice the extra parenthesis around the call to mime.unqp, to discard the second return value.
-+print((mime.unb64("ZGllZ286cGFzc3dvcmQ="))) --> diego:password-+
A, B = mime.unqp(C [, D])
-+
Low-level filter to remove the Quoted-Printable transfer content encoding -from data. +from data.
--A is the decoded version of the largest prefix of -C..D -that can be decoded unambiguously. B has the remaining bytes of -C..D, before decoding. -If D is nil, A is augmented with -the encoding of the remaining bytes of C. +
+A is the decoded version of the largest prefix of +C..D +that can be decoded unambiguously. B has the remaining bytes of +C..D, before decoding. +If D is nil, A is augmented with +the encoding of the remaining bytes of C.
-+
Note: The simplest use of this function is to decode a string from it's -Quoted-Printable transfer content encoding. +Quoted-Printable transfer content encoding. Notice the extra parenthesis around the call to mime.unqp, to discard the second return value.
-+print((mime.qp("ma=E7=E3="))) ---> maçã +--> ma��-+
A, m = mime.wrp(n [, B, length])
--Low-level filter to break text into lines with CRLF marker. -Text is assumed to be in the normalize form. +
+Low-level filter to break text into lines with CRLF marker. +Text is assumed to be in the normalize form.
--A is a copy of B, broken into lines of at most -length bytes (defaults to 76). -'n' should tell how many bytes are left for the first -line of B and 'm' returns the number of bytes -left in the last line of A. +
+A is a copy of B, broken into lines of at most +length bytes (defaults to 76). +'n' should tell how many bytes are left for the first +line of B and 'm' returns the number of bytes +left in the last line of A.
--Note: This function only breaks lines that are bigger than +
+Note: This function only breaks lines that are bigger than length bytes. The resulting line length does not include the CRLF marker.
@@ -453,10 +454,10 @@ marker. -+
- diff --git a/doc/reference.css b/docs/reference.css similarity index 100% rename from doc/reference.css rename to docs/reference.css diff --git a/doc/reference.html b/docs/reference.html similarity index 90% rename from doc/reference.html rename to docs/reference.html index 6067ba6..2bc5f78 100644 --- a/doc/reference.html +++ b/docs/reference.html @@ -1,11 +1,11 @@ - +Support, Manual">+
home · download · installation · @@ -466,7 +467,7 @@ marker.
Last modified by Diego Nehab on
-Thu Apr 20 00:25:44 EDT 2006 +Fri Mar 4 15:19:17 BRT 2016LuaSocket: Index to reference manual @@ -14,22 +14,22 @@ Support, Manual"> -+
-
-- + - + Network support for the Lua language + Network support for the Lua language +
home · download · installation · introduction · -reference +reference
@@ -92,14 +92,15 @@ Support, Manual"> table.-source: +source: cat, chain, empty, error, file, simplify, -string. +string, +table.@@ -147,6 +148,7 @@ Support, Manual"> connect, connect4, connect6, +_DATAGRAMSIZE, _DEBUG, dns, gettime, @@ -158,6 +160,7 @@ Support, Manual"> skip, sleep, _SETSIZE, +_SOCKETINVALID, source, tcp, tcp4, @@ -185,6 +188,7 @@ Support, Manual"> getpeername, getsockname, getstats, +gettimeout, listen, receive, send, @@ -205,6 +209,7 @@ Support, Manual"> getoption, getpeername, getsockname, +gettimeout, receive, receivefrom, send, @@ -233,10 +238,10 @@ Support, Manual"> -+
- +
home · download · installation · diff --git a/doc/smtp.html b/docs/smtp.html similarity index 78% rename from doc/smtp.html rename to docs/smtp.html index bbbff80..787d0b1 100644 --- a/doc/smtp.html +++ b/docs/smtp.html @@ -1,11 +1,11 @@ -
+Library, Support">LuaSocket: SMTP support @@ -14,22 +14,22 @@ Library, Support"> -+
-
-- + - + Network support for the Lua language + Network support for the Lua language +
home · download · installation · introduction · -reference +reference
@@ -37,14 +37,14 @@ Library, Support"> -SMTP
+SMTP
The smtp namespace provides functionality to send e-mail -messages. The high-level API consists of two functions: one to +messages. The high-level API consists of two functions: one to define an e-mail message, and another to actually send the message. Although almost all users will find that these functions provide more than enough functionality, the underlying implementation allows for even more -control (if you bother to read the code). +control (if you bother to read the code).
The implementation conforms to the Simple Mail Transfer Protocol, @@ -54,19 +54,19 @@ href="http://www.ietf.org/rfc/rfc2822.txt">RFC 2822, which governs the Internet Message Format. Multipart messages (those that contain attachments) are part of the MIME standard, but described mainly -in RFC 2046 +in RFC 2046.
In the description below, good understanding of LTN012, Filters -sources and sinks and the MIME module is -assumed. In fact, the SMTP module was the main reason for their +sources and sinks and the MIME module is +assumed. In fact, the SMTP module was the main reason for their creation.
-+
To obtain the smtp namespace, run:
-+-- loads the SMTP module and everything it requires local smtp = require("socket.smtp")@@ -92,201 +92,40 @@ headers = {
Field names are case insensitive (as specified by the standard) and all functions work with lowercase field names (but see -socket.headers.canonic). +socket.headers.canonic). Field values are left unmodified.
-+
Note: MIME headers are independent of order. Therefore, there is no problem -in representing them in a Lua table. +in representing them in a Lua table.
The following constants can be set to control the default behavior of -the SMTP module: +the SMTP module:
-
- - -- DOMAIN: domain used to greet the server; -
- PORT: default port used for the connection; -
- SERVER: default server used for the connection; -
- TIMEOUT: default timeout for all I/O operations; -
- ZONE: default time zone. +
- DOMAIN: domain used to greet the server;
+- PORT: default port used for the connection;
+- SERVER: default server used for the connection;
+- TIMEOUT: default timeout for all I/O operations;
+- ZONE: default time zone.
-smtp.send{
- -
- from = string,
- rcpt = string or string-table,
- source = LTN12 source,
- [user = string,]
- [password = string,]
- [server = string,]
- [port = number,]
- [domain = string,]
- [step = LTN12 pump step,]
- [create = function]
-} --Sends a message to a recipient list. Since sending messages is not as -simple as downloading an URL from a FTP or HTTP server, this function -doesn't have a simple interface. However, see the -message source factory for -a very powerful way to define the message contents. -
- - --The sender is given by the e-mail address in the from field. -Rcpt is a Lua table with one entry for each recipient e-mail -address, or a string -in case there is just one recipient. -The contents of the message are given by a simple -LTN12 -source. Several arguments are optional: -
--
- -- user, password: User and password for -authentication. The function will attempt LOGIN and PLAIN authentication -methods if supported by the server (both are unsafe); -
- server: Server to connect to. Defaults to "localhost"; -
- port: Port to connect to. Defaults to 25; -
- domain: Domain name used to greet the server; Defaults to the -local machine host name; -
- step: -LTN12 -pump step function used to pass data from the -source to the server. Defaults to the LTN12 pump.step function; -
- create: An optional function to be used instead of -socket.tcp when the communications socket is created. -
-If successful, the function returns 1. Otherwise, the function returns -nil followed by an error message. -
- --Note: SMTP servers can be very picky with the format of e-mail -addresses. To be safe, use only addresses of the form -"<fulano@example.com>" in the from and -rcpt arguments to the send function. In headers, e-mail -addresses can take whatever form you like.
- --Big note: There is a good deal of misconception with the use of the -destination address field headers, i.e., the 'To', 'Cc', -and, more importantly, the 'Bcc' headers. Do not add a -'Bcc' header to your messages because it will probably do the -exact opposite of what you expect. -
- --Only recipients specified in the rcpt list will receive a copy of the -message. Each recipient of an SMTP mail message receives a copy of the -message body along with the headers, and nothing more. The headers -are part of the message and should be produced by the -LTN12 -source function. The rcpt list is not -part of the message and will not be sent to anyone. -
- --RFC 2822 -has two important and short sections, "3.6.3. Destination address -fields" and "5. Security considerations", explaining the proper -use of these headers. Here is a summary of what it says: -
- --
- -- To: contains the address(es) of the primary recipient(s) -of the message; -
- Cc: (where the "Cc" means "Carbon Copy" in the sense of -making a copy on a typewriter using carbon paper) contains the -addresses of others who are to receive the message, though the -content of the message may not be directed at them; -
- Bcc: (where the "Bcc" means "Blind Carbon -Copy") contains addresses of recipients of the message whose addresses are not to be revealed to other recipients of the message. -
-The LuaSocket send function does not care or interpret the -headers you send, but it gives you full control over what is sent and -to whom it is sent: -
--
- -- If someone is to receive the message, the e-mail address has -to be in the recipient list. This is the only parameter that controls who -gets a copy of the message; -
- If there are multiple recipients, none of them will automatically -know that someone else got that message. That is, the default behavior is -similar to the Bcc field of popular e-mail clients; -
- It is up to you to add the To header with the list of primary -recipients so that other recipients can see it; -
- It is also up to you to add the Cc header with the -list of additional recipients so that everyone else sees it; -
- Adding a header Bcc is nonsense, unless it is -empty. Otherwise, everyone receiving the message will see it and that is -exactly what you don't want to happen! -
-I hope this clarifies the issue. Otherwise, please refer to -RFC 2821 -and -RFC 2822. -
- ---- load the smtp support -local smtp = require("socket.smtp") - --- Connects to server "localhost" and sends a message to users --- "fulano@example.com", "beltrano@example.com", --- and "sicrano@example.com". --- Note that "fulano" is the primary recipient, "beltrano" receives a --- carbon copy and neither of them knows that "sicrano" received a blind --- carbon copy of the message. -from = "<luasocket@example.com>" - -rcpt = { - "<fulano@example.com>", - "<beltrano@example.com>", - "<sicrano@example.com>" -} - -mesgt = { - headers = { - to = "Fulano da Silva <fulano@example.com>", - cc = '"Beltrano F. Nunes" <beltrano@example.com>', - subject = "My first message" - }, - body = "I hope this works. If it does, I can send you another 1000 copies." -} - -r, e = smtp.send{ - from = from, - rcpt = rcpt, - source = smtp.message(mesgt) -} -- -+
smtp.message(mesgt)
-+
Returns a simple -LTN12 source that sends an SMTP message body, possibly multipart (arbitrarily deep). +LTN12 source that sends an SMTP message body, possibly multipart (arbitrarily deep).
-+
The only parameter of the function is a table describing the message. Mesgt has the following form (notice the recursive structure):
@@ -296,7 +135,7 @@ The only parameter of the function is a table describing the message.mesgt = {
headers = header-table,
- body = LTN12 source or string or + body = LTN12 source or string or multipart-mesgt
}
@@ -312,36 +151,36 @@ multipart-mesgt = {
-+
For a simple message, all that is needed is a set of headers and the body. The message body can be given as a string -or as a simple -LTN12 +or as a simple +LTN12 source. For multipart messages, the body is a table that recursively defines each part as an independent message, plus an optional preamble and epilogue.
--The function returns a simple -LTN12 +
+The function returns a simple +LTN12 source that produces the -message contents as defined by mesgt, chunk by chunk. +message contents as defined by mesgt, chunk by chunk. Hopefully, the following example will make things clear. When in doubt, refer to the appropriate RFC as listed in the introduction.
-+-- load the smtp support and its friends local smtp = require("socket.smtp") local mime = require("mime") local ltn12 = require("ltn12") --- creates a source to send a message with two parts. The first part is +-- creates a source to send a message with two parts. The first part is -- plain text, the second part is a PNG image, encoded as base64. source = smtp.message{ headers = { - -- Remember that headers are *ignored* by smtp.send. + -- Remember that headers are *ignored* by smtp.send. from = "Sicrano de Oliveira <sicrano@example.com>", to = "Fulano da Silva <fulano@example.com>", subject = "Here is a message with attachments" @@ -352,20 +191,20 @@ source = smtp.message{ "Preamble will probably appear even in a MIME enabled client.", -- first part: no headers means plain text, us-ascii. -- The mime.eol low-level filter normalizes end-of-line markers. - [1] = { + [1] = { body = mime.eol(0, [[ - Lines in a message body should always end with CRLF. - The smtp module will *NOT* perform translation. However, the + Lines in a message body should always end with CRLF. + The smtp module will *NOT* perform translation. However, the send function *DOES* perform SMTP stuffing, whereas the message function does *NOT*. ]]) }, - -- second part: headers describe content to be a png image, + -- second part: headers describe content to be a png image, -- sent under the base64 transfer content encoding. - -- notice that nothing happens until the message is actually sent. - -- small chunks are loaded into memory right before transmission and + -- notice that nothing happens until the message is actually sent. + -- small chunks are loaded into memory right before transmission and -- translation happens on the fly. - [2] = { + [2] = { headers = { ["content-type"] = 'image/png; name="image.png"', ["content-disposition"] = 'attachment; filename="image.png"', @@ -392,12 +231,175 @@ r, e = smtp.send{ }+ + + ++smtp.send{
+ +
+ from = string,
+ rcpt = string or string-table,
+ source = LTN12 source,
+ [user = string,]
+ [password = string,]
+ [server = string,]
+ [port = number,]
+ [domain = string,]
+ [step = LTN12 pump step,]
+ [create = function]
+} ++Sends a message to a recipient list. Since sending messages is not as +simple as downloading an URL from a FTP or HTTP server, this function +doesn't have a simple interface. However, see the +message source factory for +a very powerful way to define the message contents. +
+ + ++The sender is given by the e-mail address in the from field. +Rcpt is a Lua table with one entry for each recipient e-mail +address, or a string +in case there is just one recipient. +The contents of the message are given by a simple +LTN12 +source. Several arguments are optional: +
++
+ +- user, password: User and password for +authentication. The function will attempt LOGIN and PLAIN authentication +methods if supported by the server (both are unsafe);
+- server: Server to connect to. Defaults to "localhost";
+- port: Port to connect to. Defaults to 25;
+- domain: Domain name used to greet the server; Defaults to the +local machine host name;
+- step: +LTN12 +pump step function used to pass data from the +source to the server. Defaults to the LTN12 pump.step function;
+- create: An optional function to be used instead of +socket.tcp when the communications socket is created.
++If successful, the function returns 1. Otherwise, the function returns +nil followed by an error message. +
+ ++Note: SMTP servers can be very picky with the format of e-mail +addresses. To be safe, use only addresses of the form +"<fulano@example.com>" in the from and +rcpt arguments to the send function. In headers, e-mail +addresses can take whatever form you like.
+ ++Big note: There is a good deal of misconception with the use of the +destination address field headers, i.e., the 'To', 'Cc', +and, more importantly, the 'Bcc' headers. Do not add a +'Bcc' header to your messages because it will probably do the +exact opposite of what you expect. +
+ ++Only recipients specified in the rcpt list will receive a copy of the +message. Each recipient of an SMTP mail message receives a copy of the +message body along with the headers, and nothing more. The headers +are part of the message and should be produced by the +LTN12 +source function. The rcpt list is not +part of the message and will not be sent to anyone. +
+ ++RFC 2822 +has two important and short sections, "3.6.3. Destination address +fields" and "5. Security considerations", explaining the proper +use of these headers. Here is a summary of what it says: +
+ ++
+ +- To: contains the address(es) of the primary recipient(s) +of the message;
+- Cc: (where the "Cc" means "Carbon Copy" in the sense of +making a copy on a typewriter using carbon paper) contains the +addresses of others who are to receive the message, though the +content of the message may not be directed at them;
+- Bcc: (where the "Bcc" means "Blind Carbon +Copy") contains addresses of recipients of the message whose addresses are not +to be revealed to other recipients of the message.
++The LuaSocket send function does not care or interpret the +headers you send, but it gives you full control over what is sent and +to whom it is sent: +
++
+ +- If someone is to receive the message, the e-mail address has +to be in the recipient list. This is the only parameter that controls who +gets a copy of the message;
+- If there are multiple recipients, none of them will automatically +know that someone else got that message. That is, the default behavior is +similar to the Bcc field of popular e-mail clients;
+- It is up to you to add the To header with the list of primary +recipients so that other recipients can see it;
+- It is also up to you to add the Cc header with the +list of additional recipients so that everyone else sees it;
+- Adding a header Bcc is nonsense, unless it is +empty. Otherwise, everyone receiving the message will see it and that is +exactly what you don't want to happen!
++I hope this clarifies the issue. Otherwise, please refer to +RFC 2821 +and +RFC 2822. +
+ ++-- load the smtp support +local smtp = require("socket.smtp") + +-- Connects to server "localhost" and sends a message to users +-- "fulano@example.com", "beltrano@example.com", +-- and "sicrano@example.com". +-- Note that "fulano" is the primary recipient, "beltrano" receives a +-- carbon copy and neither of them knows that "sicrano" received a blind +-- carbon copy of the message. +from = "<luasocket@example.com>" + +rcpt = { + "<fulano@example.com>", + "<beltrano@example.com>", + "<sicrano@example.com>" +} + +mesgt = { + headers = { + to = "Fulano da Silva <fulano@example.com>", + cc = '"Beltrano F. Nunes" <beltrano@example.com>', + subject = "My first message" + }, + body = "I hope this works. If it does, I can send you another 1000 copies." +} + +r, e = smtp.send{ + from = from, + rcpt = rcpt, + source = smtp.message(mesgt) +} ++ -+
- +
home · download · installation · diff --git a/doc/socket.html b/docs/socket.html similarity index 61% rename from doc/socket.html rename to docs/socket.html index e6a9bf8..c148114 100644 --- a/doc/socket.html +++ b/docs/socket.html @@ -1,10 +1,10 @@ -
- +LuaSocket: The socket namespace @@ -13,22 +13,22 @@ -+
-
-- + - + Network support for the Lua language + Network support for the Lua language +
home · download · installation · introduction · -reference +reference
@@ -36,47 +36,71 @@ -The socket namespace
+The socket namespace
-The socket namespace contains the core functionality of LuaSocket. +The socket namespace contains the core functionality of LuaSocket.
-+
To obtain the socket namespace, run:
---- loads the socket module ++-- loads the socket module local socket = require("socket")+ + ++socket.headers.canonic
+ +The socket.headers.canonic table +is used by the HTTP and SMTP modules to translate from +lowercase field names back into their canonic +capitalization. When a lowercase field name exists as a key +in this table, the associated value is substituted in +whenever the field name is sent out. +
+ ++You can obtain the headers namespace if case run-time +modifications are required by running: +
+ ++-- loads the headers module +local headers = require("headers") ++ + -+
socket.bind(address, port [, backlog])
-+
This function is a shortcut that creates and returns a TCP server object -bound to a local address and port, ready to +bound to a local address and port, ready to accept client connections. Optionally, -user can also specify the backlog argument to the -listen method (defaults to 32). +user can also specify the backlog argument to the +listen method (defaults to 32).
--Note: The server object returned will have the option "reuseaddr" +
+Note: The server object returned will have the option "reuseaddr" set to true.
-+
socket.connect[46](address, port [, locaddr] [, locport] [, family])
-+
This function is a shortcut that creates and returns a TCP client object connected to a remote address at a given port. Optionally, the user can also specify the local address and port to bind @@ -90,90 +114,79 @@ of connect are defined as simple helper functions that restrict the -
+
socket._DEBUG
-+
This constant is set to true if the library was compiled with debug support.
+ + ++socket._DATAGRAMSIZE +
+ ++Default datagram size used by calls to +receive and +receivefrom. +(Unless changed in compile time, the value is 8192.) +
+ -+
socket.gettime()
-+
Returns the UNIX time in seconds. You should subtract the values returned by this function -to get meaningful values. +to get meaningful values.
-+t = socket.gettime() -- do stuff print(socket.gettime() - t .. " seconds elapsed")- - --socket.headers.canonic
- -The socket.headers.canonic table -is used by the HTTP and SMTP modules to translate from -lowercase field names back into their canonic -capitalization. When a lowercase field name exists as a key -in this table, the associated value is substituted in -whenever the field name is sent out. -
- --You can obtain the headers namespace if case run-time -modifications are required by running: -
- ---- loads the headers module -local headers = require("headers") -- -+
socket.newtry(finalizer)
--Creates and returns a clean +
+Creates and returns a clean try -function that allows for cleanup before the exception -is raised. +function that allows for cleanup before the exception +is raised.
-+
Finalizer is a function that will be called before -try throws the exception. It will be called -in protected mode. +try throws the exception.
--The function returns your customized try function. +
+The function returns your customized try function.
--Note: This idea saved a lot of work with the -implementation of protocols in LuaSocket: +
+Note: This idea saved a lot of work with the +implementation of protocols in LuaSocket:
-+foo = socket.protect(function() -- connect somewhere local c = socket.try(socket.connect("somewhere", 42)) -- create a try function that closes 'c' on error local try = socket.newtry(function() c:close() end) - -- do everything reassured c will be closed + -- do everything reassured c will be closed try(c:send("hello there?\r\n")) local answer = try(c:receive()) ... @@ -185,46 +198,40 @@ end) -+
socket.protect(func)
-+
Converts a function that throws exceptions into a safe function. This -function only catches exceptions thrown by the try -and newtry functions. It does not catch normal +function only catches exceptions thrown by the try +and newtry functions. It does not catch normal Lua errors.
--Func is a function that calls -try (or assert, or error) -to throw exceptions. +
+Func is a function that calls +try (or assert, or error) +to throw exceptions.
--Returns an equivalent function that instead of throwing exceptions, -returns nil followed by an error message. -
- --Note: Beware that if your function performs some illegal operation that -raises an error, the protected function will catch the error and return it -as a string. This is because the try function -uses errors as the mechanism to throw exceptions. +
+Returns an equivalent function that instead of throwing exceptions in case of +a failed try call, returns nil +followed by an error message.
-+
socket.select(recvt, sendt [, timeout])
--Waits for a number of sockets to change status. +
+Waits for a number of sockets to change status.
-+
Recvt is an array with the sockets to test for characters available for reading. Sockets in the sendt array are watched to see if it is OK to immediately write on them. Timeout is the @@ -235,208 +242,226 @@ be empty tables or nil. Non-socket values (or values with non-numeric indices) in the arrays will be silently ignored.
-The function returns a list with the sockets ready for +
The function returns a list with the sockets ready for reading, a list with the sockets ready for writing and an error message. -The error message is "timeout" if a timeout condition was met and +The error message is "timeout" if a timeout +condition was met, "select failed" if the call +to select failed, and nil otherwise. The returned tables are doubly keyed both by integers and also by the sockets themselves, to simplify the test if a specific socket has -changed status. +changed status.
--Note: : select can monitor a limited number -of sockets, as defined by the constant socket._SETSIZE. This +
+Note: select can monitor a limited number +of sockets, as defined by the constant +socket._SETSIZE. This number may be as high as 1024 or as low as 64 by default, depending on the system. It is usually possible to change this at compile time. Invoking select with a larger number of sockets will raise an error.
--Important note: a known bug in WinSock causes select to fail +
+Important note: a known bug in WinSock causes select to fail on non-blocking TCP sockets. The function may return a socket as writable even though the socket is not ready for sending.
-+
Another important note: calling select with a server socket in the receive parameter before a call to accept does not guarantee -accept will return immediately. -Use the settimeout -method or accept might block forever. +accept will return immediately. +Use the settimeout +method or accept might block forever.
-+
Yet another note: If you close a socket and pass it to select, it will be ignored.
-+
Using select with non-socket objects: Any object that implements getfd and dirty can be used with select, allowing objects from other libraries to be used within a socket.select driven loop.
+ + ++socket._SETSIZE +
+ ++The maximum number of sockets that the select function can handle. +
+ + -+
socket.sink(mode, socket)
--Creates an +
+Creates an LTN12 -sink from a stream socket object. +sink from a stream socket object.
-+
Mode defines the behavior of the sink. The following options are available:
- "http-chunked": sends data through socket after applying the -chunked transfer coding, closing the socket when done; +chunked transfer coding, closing the socket when done;
- "close-when-done": sends all received data through the -socket, closing the socket when done; +socket, closing the socket when done;
- "keep-open": sends all received data through the -socket, leaving it open when done. +socket, leaving it open when done.
-Socket is the stream socket object used to send the data. +Socket is the stream socket object used to send the data.
--The function returns a sink with the appropriate behavior. +
+The function returns a sink with the appropriate behavior.
-+
socket.skip(d [, ret1, ret2 ... retN])
-+
Drops a number of arguments and returns the remaining.
-+
D is the number of arguments to drop. Ret1 to retN are the arguments.
-+
The function returns retd+1 to retN.
-+
Note: This function is useful to avoid creation of dummy variables:
---- get the status code and separator from SMTP server reply ++-- get the status code and separator from SMTP server reply local code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)"))-+
socket.sleep(time)
-+
Freezes the program execution during a given amount of time.
-+
Time is the number of seconds to sleep for. If time is negative, the function returns immediately.
-+
socket.source(mode, socket [, length])
--Creates an +
+Creates an LTN12 -source from a stream socket object. +source from a stream socket object.
-+
Mode defines the behavior of the source. The following options are available:
- "http-chunked": receives data from socket and removes the -chunked transfer coding before returning the data; +chunked transfer coding before returning the data;
- "by-length": receives a fixed number of bytes from the -socket. This mode requires the extra argument length; +socket. This mode requires the extra argument length;
- "until-closed": receives data from a socket until the other -side closes the connection. +side closes the connection.
-Socket is the stream socket object used to receive the data. +Socket is the stream socket object used to receive the data.
--The function returns a source with the appropriate behavior. +
+The function returns a source with the appropriate behavior.
- + --socket._SETSIZE +
+socket._SOCKETINVALID
--The maximum number of sockets that the select function can handle. +
+The OS value for an invalid socket. This can be used with +tcp:getfd and tcp:setfd methods.
-+
socket.try(ret1 [, ret2 ... retN])
--Throws an exception in case of error. The exception can only be caught -by the protect function. It does not explode -into an error message. +
+Throws an exception in case ret1 is falsy, using +ret2 as the error message. The exception is supposed to be caught +by a protected function only.
-+
Ret1 to retN can be arbitrary -arguments, but are usually the return values of a function call -nested with try. +arguments, but are usually the return values of a function call +nested with try.
-+
The function returns ret1 to retN if -ret1 is not nil. Otherwise, it calls error passing ret2. +ret1 is not nil or false. +Otherwise, it calls error passing ret2 wrapped +in a table with metatable used by protect to +distinguish exceptions from runtime errors.
-+-- connects or throws an exception with the appropriate error message c = socket.try(socket.connect("localhost", 80))-+
socket._VERSION
--This constant has a string describing the current LuaSocket version. +
+This constant has a string describing the current LuaSocket version.
-+
- +
home · download · installation · diff --git a/doc/tcp.html b/docs/tcp.html similarity index 71% rename from doc/tcp.html rename to docs/tcp.html index fb627a1..a26228d 100644 --- a/doc/tcp.html +++ b/docs/tcp.html @@ -13,17 +13,17 @@ -
+
-
-- + - + Network support for the Lua language + Network support for the Lua language +
home · download · installation · @@ -38,122 +38,45 @@
TCP
- - --socket.tcp() -
- --Creates and returns an TCP master object. A master object can -be transformed into a server object with the method -listen (after a call to bind) or into a client object with -the method connect. The only other -method supported by a master object is the -close method.
- --In case of success, a new master object is returned. In case of error, -nil is returned, followed by an error message. -
- --Note: The choice between IPv4 and IPv6 happens during a call to -bind or connect, depending on the address -family obtained from the resolver. -
- --Note: Before the choice between IPv4 and IPv6 happens, -the internal socket object is invalid and therefore setoption will fail. -
- - - --socket.tcp4() -
- --Creates and returns an IPv4 TCP master object. A master object can -be transformed into a server object with the method -listen (after a call to bind) or into a client object with -the method connect. The only other -method supported by a master object is the -close method.
- --In case of success, a new master object is returned. In case of error, -nil is returned, followed by an error message. -
- - - --socket.tcp6() -
- --Creates and returns an IPv6 TCP master object. A master object can -be transformed into a server object with the method -listen (after a call to bind) or into a client object with -the method connect. The only other -method supported by a master object is the -close method.
- --In case of success, a new master object is returned. In case of error, -nil is returned, followed by an error message. -
- --Note: The TCP object returned will have the option -"ipv6-v6only" set to true. -
- -+
server:accept()
-+
Waits for a remote connection on the server object and returns a client object representing that connection.
-+
If a connection is successfully initiated, a client object is returned. If a timeout condition is met, the method returns nil followed by the error string 'timeout'. Other errors are reported by nil followed by a message describing the error.
--Note: calling socket.select +
+Note: calling socket.select with a server object in the recvt parameter before a call to accept does not guarantee accept will return immediately. Use the settimeout method or accept +href="#settimeout">settimeout method or accept might block until another client shows up.
-+
master:bind(address, port)
-+
Binds a master object to address and port on the local host. +
-+
Address can be an IP address or a host name. Port must be an integer number in the range [0..64K). If address @@ -164,25 +87,25 @@ If port is 0, the system automatically chooses an ephemeral port.
-+
In case of success, the method returns 1. In case of error, the method returns nil followed by an error message.
--Note: The function socket.bind +
+Note: The function socket.bind is available and is a shortcut for the creation of server sockets.
-+
master:close()
-
client:close()
server:close()+
Closes a TCP object. The internal socket used by the object is closed and the local address to which the object was bound is made available to other applications. No further operations @@ -190,7 +113,7 @@ bound is made available to other applications. No further operations a closed socket.
-+
Note: It is important to close all used sockets once they are not needed, since, in many systems, each socket uses a file descriptor, which are limited system resources. Garbage-collected objects are @@ -199,90 +122,166 @@ automatically closed before destruction, though. -
+
master:connect(address, port)
-+
Attempts to connect a master object to a remote host, transforming it into a client object. Client objects support methods -send, -receive, -getsockname, -getpeername, -settimeout, -and close. +send, +receive, +getsockname, +getpeername, +settimeout, +and close.
-+
Address can be an IP address or a host name. Port must be an integer number in the range [1..64K).
-+
In case of error, the method returns nil followed by a string describing the error. In case of success, the method returns 1.
--Note: The function socket.connect +
+Note: The function socket.connect is available and is a shortcut for the creation of client sockets.
-+
Note: Starting with LuaSocket 2.0, -the settimeout +the settimeout method affects the behavior of connect, causing it to return with an error in case of a timeout. If that happens, you can still call socket.select with the socket in the +href="socket.html#select">socket.select with the socket in the sendt table. The socket will be writable when the connection is established.
-+
Note: Starting with LuaSocket 3.0, the host name resolution -depends on whether the socket was created by socket.tcp or socket.tcp6. Addresses from -the appropriate family are tried in succession until the -first success or until the last failure. +depends on whether the socket was created by +socket.tcp, +socket.tcp4 or +socket.tcp6. Addresses from +the appropriate family (or both) are tried in the order +returned by the resolver until the +first success or until the last failure. If the timeout was +set to zero, only the first address is tried.
+ + ++master:dirty()
+ +
+client:dirty()
+server:dirty() ++Check the read buffer status. +
+ ++Returns true if there is any data in the read buffer, false otherwise. +
+ ++Note: This is an internal method, use at your own risk. +
+ + + + ++master:getfd()
+ +
+client:getfd()
+server:getfd() ++Returns the underling socket descriptor or handle associated to the object. +
+ ++The descriptor or handle. In case the object has been closed, the return value +will be -1. For an invalid socket it will be +_SOCKETINVALID. +
+ ++Note: This is an internal method. Unlikely to be +portable. Use at your own risk. +
+ + + + ++client:getoption(option)
+ +
+server:getoption(option) ++Gets options for the TCP object. +See setoption for description of the +option names and values. +
+ ++Option is a string with the option name.
++
+ +- 'keepalive'
+- 'linger'
+- 'reuseaddr'
+- 'tcp-nodelay'
++The method returns the option value in case of success, or +nil followed by an error message otherwise. +
+ + -+
client:getpeername()
-+
Returns information about the remote side of a connected client object.
-+
Returns a string with the IP address of the peer, the port number that peer is using for the connection, and a string with the family ("inet" or "inet6"). In case of error, the method returns nil.
-+
Note: It makes no sense to call this method on server objects.
-+
master:getsockname()
-
client:getsockname()
server:getsockname()+
Returns the local address information associated to the object.
-+
The method returns a string with local IP address, a number with the local port, and a string with the family ("inet" or "inet6"). @@ -291,83 +290,97 @@ In case of error, the method returns nil. -
+
master:getstats()
-
client:getstats()
server:getstats()
+
Returns accounting information on the socket, useful for throttling of bandwidth.
-+
The method returns the number of bytes received, the number of bytes sent, and the age of the socket object in seconds.
+ + ++master:gettimeout()
+ +
+client:gettimeout()
+server:gettimeout() ++Returns the current block timeout followed by the curent +total timeout. +
+ + -+
master:listen(backlog)
-+
Specifies the socket is willing to receive connections, transforming the object into a server object. Server objects support the -accept, -getsockname, -setoption, -settimeout, -and close methods. +accept, +getsockname, +setoption, +settimeout, +and close methods.
-+
The parameter backlog specifies the number of client connections that can be queued waiting for service. If the queue is full and another client attempts connection, the connection is refused.
-+
In case of success, the method returns 1. In case of error, the method returns nil followed by an error message.
-+
client:receive([pattern [, prefix]])
-+
Reads data from a client object, according to the specified read pattern. Patterns follow the Lua file I/O format, and the difference in performance between all patterns is negligible.
-+
Pattern can be any of the following:
-
- '*a': reads from the socket until the connection is -closed. No end-of-line translation is performed; +closed. No end-of-line translation is performed;
- '*l': reads a line of text from the socket. The line is terminated by a LF character (ASCII 10), optionally preceded by a CR character (ASCII 13). The CR and LF characters are not included in the returned line. In fact, all CR characters are -ignored by the pattern. This is the default pattern; +ignored by the pattern. This is the default pattern;
- number: causes the method to read a specified number -of bytes from the socket. +of bytes from the socket.
+
Prefix is an optional string to be concatenated to the beginning of any received data before return.
-+
If successful, the method returns the received pattern. In case of error, the method returns nil followed by an error message, followed by a (possibly empty) string containing @@ -377,7 +390,7 @@ closed before the transmission was completed or the string 'timeout' in case there was a timeout during the operation.
-+
Important note: This function was changed severely. It used to support multiple patterns (but I have never seen this feature used) and now it doesn't anymore. Partial results used to be returned in the same @@ -388,22 +401,22 @@ too. -
+
client:send(data [, i [, j]])
-+
Sends data through client object.
-+
Data is the string to be sent. The optional arguments i and j work exactly like the standard string.sub Lua function to allow the selection of a substring to be sent.
-+
If successful, the method returns the index of the last byte within [i, j] that has been sent. Notice that, if i is 1 or absent, this is effectively the total @@ -417,7 +430,7 @@ was completed or the string 'timeout' in case there was a timeout during the operation.
-+
Note: Output is not buffered. For small strings, it is always better to concatenate them in Lua (with the '..' operator) and send the result in one call @@ -426,27 +439,27 @@ instead of calling the method several times. -
+
client:setoption(option [, value])
-
server:setoption(option [, value])+
Sets options for the TCP object. Options are only needed by low-level or time-critical applications. You should only modify an option if you are sure you need it.
-+
Option is a string with the option name, and value -depends on the option being set: +depends on the option being set:
-
- 'keepalive': Setting this option to true enables the periodic transmission of messages on a connected socket. Should the connected party fail to respond to these messages, the connection is -considered broken and processes using the socket are notified; +considered broken and processes using the socket are notified;
- 'linger': Controls the action taken when unsent data are queued on a socket and a close is performed. The value is a table with a @@ -457,101 +470,85 @@ it is able to transmit the data or until 'timeout' has passed. If 'on' is false and a close is issued, the system will process the close in a manner that allows the process to continue as quickly as possible. I do not advise you to set this to anything other than -zero; +zero;
- 'reuseaddr': Setting this option indicates that the rules used in validating addresses supplied in a call to -bind should allow reuse of local addresses; +bind should allow reuse of local addresses;
- 'tcp-nodelay': Setting this option to true -disables the Nagle's algorithm for the connection; +disables the Nagle's algorithm for the connection;
+ +- 'tcp-keepidle': value in seconds for TCP_KEEPIDLE Linux only!!
+ +- 'tcp-keepcnt': value for TCP_KEEPCNT Linux only!!
+ +- 'tcp-keepintvl': value for TCP_KEEPINTVL Linux only!!
+ +- 'tcp-defer-accept': value for TCP_DEFER_ACCEPT Linux only!!
+ +- 'tcp-fastopen': value for TCP_FASTOPEN Linux only!!
+ +- 'tcp-fastopen-connect': value for TCP_FASTOPEN_CONNECT Linux only!!
- 'ipv6-v6only': Setting this option to true restricts an inet6 socket to -sending and receiving only IPv6 packets. +sending and receiving only IPv6 packets.
+
The method returns 1 in case of success, or nil followed by an error message otherwise.
-+
Note: The descriptions above come from the man pages.
- - --client:getoption(option)
- -
-server:getoption(option) --Gets options for the TCP object. -See setoption for description of the -option names and values. -
- --Option is a string with the option name. -
- -
- -- 'keepalive' -
- 'linger' -
- 'reuseaddr' -
- 'tcp-nodelay' -
-The method returns the option value in case of success, or -nil followed by an error message otherwise. -
- -+
master:setstats(received, sent, age)
-
client:setstats(received, sent, age)
server:setstats(received, sent, age)
+
Resets accounting information on the socket, useful for throttling of bandwidth.
-+
Received is a number with the new number of bytes received. Sent is a number with the new number of bytes sent. Age is the new age in seconds.
-+
The method returns 1 in case of success and nil otherwise.
-+
master:settimeout(value [, mode])
-
client:settimeout(value [, mode])
server:settimeout(value [, mode])+
Changes the timeout values for the object. By default, all I/O operations are blocking. That is, any call to the methods -send, -receive, and -accept +send, +receive, and +accept will block indefinitely, until the operation completes. The settimeout method defines a limit on the amount of time the I/O methods can block. When a timeout is set and the specified amount of time has elapsed, the affected methods give up and fail with an error code.
-+
The amount of time to wait is specified as the value parameter, in seconds. There are two timeout modes and both can be used together for fine tuning: @@ -568,12 +565,12 @@ the amount of time LuaSocket can block a Lua script before returning from a call. -
+
The nil timeout value allows operations to block indefinitely. Negative timeout values have the same effect.
-+
Note: although timeout values have millisecond precision in LuaSocket, large blocks can cause I/O functions not to respect timeout values due to the time the library takes to transfer blocks to and from the OS @@ -582,7 +579,7 @@ and perform automatic name resolution might be blocked by the resolver for longer than the specified timeout value.
-+
Note: The old timeout method is deprecated. The name has been changed for sake of uniformity, since all other method names already contained verbs making their imperative nature obvious. @@ -590,94 +587,138 @@ contained verbs making their imperative nature obvious. -
+
client:shutdown(mode)
-
+
Shuts down part of a full-duplex connection.
-+
Mode tells which way of the connection should be shut down and can take the value:
+ -
- "both": disallow further sends and receives on the object. -This is the default mode; -
- "send": disallow further sends on the object; -
- "receive": disallow further receives on the object. +This is the default mode;
+- "send": disallow further sends on the object;
+- "receive": disallow further receives on the object.
+
This function returns 1.
- - --master:dirty()
- -
-client:dirty()
-server:dirty() --Check the read buffer status. -
- --Returns true if there is any data in the read buffer, false otherwise. -
- --Note: This is an internal method, any use is unlikely to be portable. -
- - - --master:getfd()
- -
-client:getfd()
-server:getfd() --Returns the underling socket descriptor or handle associated to the object. -
- --The descriptor or handle. In case the object has been closed, the return will be -1. -
- --Note: This is an internal method, any use is unlikely to be portable. -
- -+
master:setfd(fd)
-
client:setfd(fd)
server:setfd(fd)-Sets the underling socket descriptor or handle associated to the object. The current one is simply replaced, not closed, and no other change to the object state is made. +
+Sets the underling socket descriptor or handle associated to the object. The current one +is simply replaced, not closed, and no other change to the object state is made. +To set it as invalid use _SOCKETINVALID.
-+
No return value.
--Note: This is an internal method, any use is unlikely to be portable. +
+Note: This is an internal method. Unlikely to be +portable. Use at your own risk.
+ + ++socket.tcp() +
+ ++Creates and returns an TCP master object. A master object can +be transformed into a server object with the method +listen (after a call to bind) or into a client object with +the method connect. The only other +method supported by a master object is the +close method.
+ ++In case of success, a new master object is returned. In case of error, +nil is returned, followed by an error message. +
+ ++Note: The choice between IPv4 and IPv6 happens during a call to +bind or connect, depending on the address +family obtained from the resolver. +
+ ++Note: Before the choice between IPv4 and IPv6 happens, +the internal socket object is invalid and therefore setoption will fail. +
+ + + ++socket.tcp4() +
+ ++Creates and returns an IPv4 TCP master object. A master object can +be transformed into a server object with the method +listen (after a call to bind) or into a client object with +the method connect. The only other +method supported by a master object is the +close method.
+ ++In case of success, a new master object is returned. In case of error, +nil is returned, followed by an error message. +
+ + + ++socket.tcp6() +
+ ++Creates and returns an IPv6 TCP master object. A master object can +be transformed into a server object with the method +listen (after a call to bind) or into a client object with +the method connect. The only other +method supported by a master object is the +close method.
+ ++In case of success, a new master object is returned. In case of error, +nil is returned, followed by an error message. +
+ ++Note: The TCP object returned will have the option +"ipv6-v6only" set to true. +
+ + + -+
- +
home · download · installation · diff --git a/doc/udp.html b/docs/udp.html similarity index 89% rename from doc/udp.html rename to docs/udp.html index a300f2f..db711cb 100644 --- a/doc/udp.html +++ b/docs/udp.html @@ -13,17 +13,17 @@ -
+
-
-- + - + Network support for the Lua language + Network support for the Lua language +
home · download · installation · @@ -39,112 +39,6 @@
UDP
- - --socket.udp() -
- --Creates and returns an unconnected UDP object. -Unconnected objects support the -sendto, -receive, -receivefrom, -getoption, -getsockname, -setoption, -settimeout, -setpeername, -setsockname, and -close. -The setpeername -is used to connect the object. -
- --In case of success, a new unconnected UDP object -returned. In case of error, nil is returned, followed by -an error message. -
- --Note: The choice between IPv4 and IPv6 happens during a call to -sendto, setpeername, or sockname, depending on the address -family obtained from the resolver. -
- --Note: Before the choice between IPv4 and IPv6 happens, -the internal socket object is invalid and therefore setoption will fail. -
- - - --socket.udp4() -
- --Creates and returns an unconnected IPv4 UDP object. -Unconnected objects support the -sendto, -receive, -receivefrom, -getoption, -getsockname, -setoption, -settimeout, -setpeername, -setsockname, and -close. -The setpeername -is used to connect the object. -
- --In case of success, a new unconnected UDP object -returned. In case of error, nil is returned, followed by -an error message. -
- - - --socket.udp6() -
- --Creates and returns an unconnected IPv6 UDP object. -Unconnected objects support the -sendto, -receive, -receivefrom, -getoption, -getsockname, -setoption, -settimeout, -setpeername, -setsockname, and -close. -The setpeername -is used to connect the object. -
- --In case of success, a new unconnected UDP object -returned. In case of error, nil is returned, followed by -an error message. -
- --Note: The TCP object returned will have the option -"ipv6-v6only" set to true. -
-@@ -168,6 +62,40 @@ Garbage-collected objects are automatically closed before destruction, though.
+ + ++connected:getoption()
+ +
+unconnected:getoption() ++Gets an option value from the UDP object. +See setoption for +description of the option names and values. +
+ +Option is a string with the option name. +
+
+ + +- 'dontroute'
+- 'broadcast'
+- 'reuseaddr'
+- 'reuseport'
+- 'ip-multicast-loop'
+- 'ipv6-v6only'
+- 'ip-multicast-if'
+- 'ip-multicast-ttl'
+- 'ip-add-membership'
+- 'ip-drop-membership'
++The method returns the option value in case of +success, or +nil followed by an error message otherwise. +
+@@ -180,7 +108,7 @@ associated with a connected UDP object.
-+
Returns a string with the IP address of the peer, the port number that peer is using for the connection, and a string with the family ("inet" or "inet6"). @@ -203,7 +131,7 @@ Returns the local address information associated to the object.
-+
The method returns a string with local IP address, a number with the local port, and a string with the family ("inet" or "inet6"). @@ -218,6 +146,18 @@ first time (in which case it is bound to an ephemeral port and the wild-card address).
+ + ++connected:settimeout(value)
+ +
+unconnected:settimeout(value) ++Returns the current timeout value. +
+ +@@ -238,9 +178,12 @@ specifies the maximum size of the datagram to be retrieved. If there are more than size bytes available in the datagram, the excess bytes are discarded. If there are less then size bytes available in the current datagram, the -available bytes are returned. If size is omitted, the -maximum datagram size is used (which is currently limited by the -implementation to 8192 bytes). +available bytes are returned. +If size is omitted, the +compile-time constant +socket._DATAGRAMSIZE is used +(it defaults to 8192 bytes). Larger sizes will cause a +temporary buffer to be allocated for the operation.
@@ -262,40 +205,6 @@ address and port as extra return values (and is therefore slightly less efficient).
- - --connected:getoption()
- -
-unconnected:getoption() --Gets an option value from the UDP object. -See setoption for -description of the option names and values. -
- -Option is a string with the option name. -
-
- - -- 'dontroute' -
- 'broadcast' -
- 'reuseaddr' -
- 'reuseport' -
- 'ip-multicast-loop' -
- 'ipv6-v6only' -
- 'ip-multicast-if' -
- 'ip-multicast-ttl' -
- 'ip-add-membership' -
- 'ip-drop-membership' -
-The method returns the option value in case of -success, or -nil followed by an error message otherwise. -
-@@ -359,6 +268,75 @@ refuses to send a message to the specified address (i.e. no interface accepts the address).
+ + ++connected:setoption(option [, value])
+ +
+unconnected:setoption(option [, value]) ++Sets options for the UDP object. Options are +only needed by low-level or time-critical applications. You should +only modify an option if you are sure you need it.
+Option is a string with the option +name, and value depends on the option being set: +
+ ++
+ +- 'dontroute': Indicates that outgoing +messages should bypass the standard routing facilities. +Receives a boolean value;
+- 'broadcast': Requests permission to send +broadcast datagrams on the socket. +Receives a boolean value;
+- 'reuseaddr': Indicates that the rules used in +validating addresses supplied in a bind() call +should allow reuse of local addresses. +Receives a boolean value;
+- 'reuseport': Allows completely duplicate +bindings by multiple processes if they all set +'reuseport' before binding the port. +Receives a boolean value;
+- 'ip-multicast-loop': +Specifies whether or not a copy of an outgoing multicast +datagram is delivered to the sending host as long as it is a +member of the multicast group. +Receives a boolean value;
+- 'ipv6-v6only': +Specifies whether to restrict inet6 sockets to +sending and receiving only IPv6 packets. +Receive a boolean value;
+- 'ip-multicast-if': +Sets the interface over which outgoing multicast datagrams +are sent. +Receives an IP address;
+- 'ip-multicast-ttl': +Sets the Time To Live in the IP header for outgoing +multicast datagrams. +Receives a number;
+- 'ip-add-membership': +Joins the multicast group specified. +Receives a table with fields +multiaddr and interface, each containing an +IP address;
+- 'ip-drop-membership': Leaves the multicast +group specified. +Receives a table with fields +multiaddr and interface, each containing an +IP address.
++The method returns 1 in case of success, or +nil followed by an error message otherwise. +
+ ++Note: The descriptions above come from the man pages. +
+ +@@ -403,11 +381,11 @@ is recommended when the same peer is used for several transmissions and can result in up to 30% performance gains.
-+
Note: Starting with LuaSocket 3.0, the host name resolution depends on whether the socket was created by socket.udp or socket.udp6. Addresses from +href="#socket.udp">socket.udp or socket.udp6. Addresses from the appropriate family are tried in succession until the first success or until the last failure.
@@ -445,74 +423,6 @@ system or explicitly by setsockname, it cannot be changed. - - --connected:setoption(option [, value])
- -
-unconnected:setoption(option [, value]) --Sets options for the UDP object. Options are -only needed by low-level or time-critical applications. You should -only modify an option if you are sure you need it.
-Option is a string with the option -name, and value depends on the option being set: -
- --
- -- 'dontroute': Indicates that outgoing -messages should bypass the standard routing facilities. -Receives a boolean value; -
- 'broadcast': Requests permission to send -broadcast datagrams on the socket. -Receives a boolean value; -
- 'reuseaddr': Indicates that the rules used in -validating addresses supplied in a bind() call -should allow reuse of local addresses. -Receives a boolean value; -
- 'reuseport': Allows completely duplicate -bindings by multiple processes if they all set -'reuseport' before binding the port. -Receives a boolean value; -
- 'ip-multicast-loop': -Specifies whether or not a copy of an outgoing multicast -datagram is delivered to the sending host as long as it is a -member of the multicast group. -Receives a boolean value; -
- 'ipv6-v6only': -Specifies whether to restrict inet6 sockets to -sending and receiving only IPv6 packets. -Receive a boolean value; -
- 'ip-multicast-if': -Sets the interface over which outgoing multicast datagrams -are sent. -Receives an IP address; -
- 'ip-multicast-ttl': -Sets the Time To Live in the IP header for outgoing -multicast datagrams. -Receives a number; -
- 'ip-add-membership': -Joins the multicast group specified. -Receives a table with fields -multiaddr and interface, each containing an -IP address; -
- 'ip-drop-membership': Leaves the multicast -group specified. -Receives a table with fields -multiaddr and interface, each containing an -IP address. -
-The method returns 1 in case of success, or -nil followed by an error message otherwise. -
- --Note: The descriptions above come from the man pages. -
-@@ -553,12 +463,120 @@ all other method names already contained verbs making their imperative nature obvious.
+ + ++socket.udp() +
+ ++Creates and returns an unconnected UDP object. +Unconnected objects support the +sendto, +receive, +receivefrom, +getoption, +getsockname, +setoption, +settimeout, +setpeername, +setsockname, and +close. +The setpeername +is used to connect the object. +
+ ++In case of success, a new unconnected UDP object +returned. In case of error, nil is returned, followed by +an error message. +
+ ++Note: The choice between IPv4 and IPv6 happens during a call to +sendto, setpeername, or sockname, depending on the address +family obtained from the resolver. +
+ ++Note: Before the choice between IPv4 and IPv6 happens, +the internal socket object is invalid and therefore setoption will fail. +
+ + + ++socket.udp4() +
+ ++Creates and returns an unconnected IPv4 UDP object. +Unconnected objects support the +sendto, +receive, +receivefrom, +getoption, +getsockname, +setoption, +settimeout, +setpeername, +setsockname, and +close. +The setpeername +is used to connect the object. +
+ ++In case of success, a new unconnected UDP object +returned. In case of error, nil is returned, followed by +an error message. +
+ + + ++socket.udp6() +
+ ++Creates and returns an unconnected IPv6 UDP object. +Unconnected objects support the +sendto, +receive, +receivefrom, +getoption, +getsockname, +setoption, +settimeout, +setpeername, +setsockname, and +close. +The setpeername +is used to connect the object. +
+ ++In case of success, a new unconnected UDP object +returned. In case of error, nil is returned, followed by +an error message. +
+ ++Note: The TCP object returned will have the option +"ipv6-v6only" set to true. +
+ + + -+
- +
home · download · installation · diff --git a/doc/url.html b/docs/url.html similarity index 100% rename from doc/url.html rename to docs/url.html diff --git a/etc/README b/etc/README deleted file mode 100644 index cfd3e37..0000000 --- a/etc/README +++ /dev/null @@ -1,89 +0,0 @@ -This directory contains code that is more useful than the -samples. This code *is* supported. - - tftp.lua -- Trivial FTP client - -This module implements file retrieval by the TFTP protocol. -Its main use was to test the UDP code, but since someone -found it usefull, I turned it into a module that is almost -official (no uploads, yet). - - dict.lua -- Dict client - -The dict.lua module started with a cool simple client -for the DICT protocol, written by Luiz Henrique Figueiredo. -This new version has been converted into a library, similar -to the HTTP and FTP libraries, that can be used from within -any luasocket application. Take a look on the source code -and you will be able to figure out how to use it. - - lp.lua -- LPD client library - -The lp.lua module implements the client part of the Line -Printer Daemon protocol, used to print files on Unix -machines. It is courtesy of David Burgess! See the source -code and the lpr.lua in the examples directory. - - b64.lua - qp.lua - eol.lua - -These are tiny programs that perform Base64, -Quoted-Printable and end-of-line marker conversions. - - get.lua -- file retriever - -This little program is a client that uses the FTP and -HTTP code to implement a command line file graber. Just -run - - lua get.lua
[ ] - -to download a remote file (either ftp:// or http://) to -the specified local file. The program also prints the -download throughput, elapsed time, bytes already downloaded -etc during download. - - check-memory.lua -- checks memory consumption - -This is just to see how much memory each module uses. - - dispatch.lua -- coroutine based dispatcher - -This is a first try at a coroutine based non-blocking -dispatcher for LuaSocket. Take a look at 'check-links.lua' -and at 'forward.lua' to see how to use it. - - check-links.lua -- HTML link checker program - -This little program scans a HTML file and checks for broken -links. It is similar to check-links.pl by Jamie Zawinski, -but uses all facilities of the LuaSocket library and the Lua -language. It has not been thoroughly tested, but it should -work. Just run - - lua check-links.lua [-n] { } > output - -and open the result to see a list of broken links. Make sure -you check the '-n' switch. It runs in non-blocking mode, -using coroutines, and is MUCH faster! - - forward.lua -- coroutine based forward server - -This is a forward server that can accept several connections -and transfers simultaneously using non-blocking I/O and the -coroutine-based dispatcher. You can run, for example - - lua forward.lua 8080:proxy.com:3128 - -to redirect all local conections to port 8080 to the host -'proxy.com' at port 3128. - - unix.c and unix.h - -This is an implementation of Unix local domain sockets and -demonstrates how to extend LuaSocket with a new type of -transport. It has been tested on Linux and on Mac OS X. - -Good luck, -Diego. diff --git a/gem/ex1.lua b/gem/ex1.lua deleted file mode 100644 index 327a542..0000000 --- a/gem/ex1.lua +++ /dev/null @@ -1,4 +0,0 @@ -local CRLF = "\013\010" -local input = source.chain(source.file(io.stdin), normalize(CRLF)) -local output = sink.file(io.stdout) -pump.all(input, output) diff --git a/gem/ex10.lua b/gem/ex10.lua deleted file mode 100644 index 2b1b98f..0000000 --- a/gem/ex10.lua +++ /dev/null @@ -1,17 +0,0 @@ -function pump.step(src, snk) - local chunk, src_err = src() - local ret, snk_err = snk(chunk, src_err) - if chunk and ret then return 1 - else return nil, src_err or snk_err end -end - -function pump.all(src, snk, step) - step = step or pump.step - while true do - local ret, err = step(src, snk) - if not ret then - if err then return nil, err - else return 1 end - end - end -end diff --git a/gem/ex11.lua b/gem/ex11.lua deleted file mode 100644 index 1cbf01f..0000000 --- a/gem/ex11.lua +++ /dev/null @@ -1,7 +0,0 @@ -local input = source.chain( - source.file(io.open("input.bin", "rb")), - encode("base64")) -local output = sink.chain( - wrap(76), - sink.file(io.open("output.b64", "w"))) -pump.all(input, output) diff --git a/gem/ex12.lua b/gem/ex12.lua deleted file mode 100644 index de17d76..0000000 --- a/gem/ex12.lua +++ /dev/null @@ -1,34 +0,0 @@ -local smtp = require"socket.smtp" -local mime = require"mime" -local ltn12 = require"ltn12" - -CRLF = "\013\010" - -local message = smtp.message{ - headers = { - from = "Sicrano ", - to = "Fulano ", - subject = "A message with an attachment"}, - body = { - preamble = "Hope you can see the attachment" .. CRLF, - [1] = { - body = "Here is our logo" .. CRLF}, - [2] = { - headers = { - ["content-type"] = 'image/png; name="luasocket.png"', - ["content-disposition"] = - 'attachment; filename="luasocket.png"', - ["content-description"] = 'LuaSocket logo', - ["content-transfer-encoding"] = "BASE64"}, - body = ltn12.source.chain( - ltn12.source.file(io.open("luasocket.png", "rb")), - ltn12.filter.chain( - mime.encode("base64"), - mime.wrap()))}}} - -assert(smtp.send{ - rcpt = " ", - from = " ", - server = "localhost", - port = 2525, - source = message}) diff --git a/gem/ex2.lua b/gem/ex2.lua deleted file mode 100644 index 94bde66..0000000 --- a/gem/ex2.lua +++ /dev/null @@ -1,11 +0,0 @@ -function filter.cycle(lowlevel, context, extra) - return function(chunk) - local ret - ret, context = lowlevel(context, chunk, extra) - return ret - end -end - -function normalize(marker) - return filter.cycle(eol, 0, marker) -end diff --git a/gem/ex3.lua b/gem/ex3.lua deleted file mode 100644 index a43fefa..0000000 --- a/gem/ex3.lua +++ /dev/null @@ -1,15 +0,0 @@ -local function chainpair(f1, f2) - return function(chunk) - local ret = f2(f1(chunk)) - if chunk then return ret - else return (ret or "") .. (f2() or "") end - end -end - -function filter.chain(...) - local f = select(1, ...) - for i = 2, select('#', ...) do - f = chainpair(f, select(i, ...)) - end - return f -end diff --git a/gem/ex4.lua b/gem/ex4.lua deleted file mode 100644 index c670e0e..0000000 --- a/gem/ex4.lua +++ /dev/null @@ -1,5 +0,0 @@ -local qp = filter.chain(normalize(CRLF), encode("quoted-printable"), - wrap("quoted-printable")) -local input = source.chain(source.file(io.stdin), qp) -local output = sink.file(io.stdout) -pump.all(input, output) diff --git a/gem/ex5.lua b/gem/ex5.lua deleted file mode 100644 index 196b30a..0000000 --- a/gem/ex5.lua +++ /dev/null @@ -1,15 +0,0 @@ -function source.empty(err) - return function() - return nil, err - end -end - -function source.file(handle, io_err) - if handle then - return function() - local chunk = handle:read(20) - if not chunk then handle:close() end - return chunk - end - else return source.empty(io_err or "unable to open file") end -end diff --git a/gem/ex6.lua b/gem/ex6.lua deleted file mode 100644 index a3fdca0..0000000 --- a/gem/ex6.lua +++ /dev/null @@ -1,14 +0,0 @@ -function source.chain(src, f) - return function() - if not src then - return nil - end - local chunk, err = src() - if not chunk then - src = nil - return f(nil) - else - return f(chunk) - end - end -end diff --git a/gem/ex7.lua b/gem/ex7.lua deleted file mode 100644 index c766988..0000000 --- a/gem/ex7.lua +++ /dev/null @@ -1,16 +0,0 @@ -function sink.table(t) - t = t or {} - local f = function(chunk, err) - if chunk then table.insert(t, chunk) end - return 1 - end - return f, t -end - -local function null() - return 1 -end - -function sink.null() - return null -end diff --git a/gem/ex8.lua b/gem/ex8.lua deleted file mode 100644 index 81e288c..0000000 --- a/gem/ex8.lua +++ /dev/null @@ -1,5 +0,0 @@ -local input = source.file(io.stdin) -local output, t = sink.table() -output = sink.chain(normalize(CRLF), output) -pump.all(input, output) -io.write(table.concat(t)) diff --git a/gem/ex9.lua b/gem/ex9.lua deleted file mode 100644 index b857698..0000000 --- a/gem/ex9.lua +++ /dev/null @@ -1,3 +0,0 @@ -for chunk in source.file(io.stdin) do - io.write(chunk) -end diff --git a/gem/gem.c b/gem/gem.c deleted file mode 100644 index 976f74d..0000000 --- a/gem/gem.c +++ /dev/null @@ -1,54 +0,0 @@ -#include "lua.h" -#include "lauxlib.h" - -#define CR '\xD' -#define LF '\xA' -#define CRLF "\xD\xA" - -#define candidate(c) (c == CR || c == LF) -static int pushchar(int c, int last, const char *marker, - luaL_Buffer *buffer) { - if (candidate(c)) { - if (candidate(last)) { - if (c == last) - luaL_addstring(buffer, marker); - return 0; - } else { - luaL_addstring(buffer, marker); - return c; - } - } else { - luaL_putchar(buffer, c); - return 0; - } -} - -static int eol(lua_State *L) { - int context = luaL_checkint(L, 1); - size_t isize = 0; - const char *input = luaL_optlstring(L, 2, NULL, &isize); - const char *last = input + isize; - const char *marker = luaL_optstring(L, 3, CRLF); - luaL_Buffer buffer; - luaL_buffinit(L, &buffer); - if (!input) { - lua_pushnil(L); - lua_pushnumber(L, 0); - return 2; - } - while (input < last) - context = pushchar(*input++, context, marker, &buffer); - luaL_pushresult(&buffer); - lua_pushnumber(L, context); - return 2; -} - -static luaL_reg func[] = { - { "eol", eol }, - { NULL, NULL } -}; - -int luaopen_gem(lua_State *L) { - luaL_openlib(L, "gem", func, 0); - return 0; -} diff --git a/gem/gt.b64 b/gem/gt.b64 deleted file mode 100644 index a74c0b3..0000000 --- a/gem/gt.b64 +++ /dev/null @@ -1,206 +0,0 @@ -iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAtU0lEQVR42u19eXRURdb4rarXa5LO -RshKEshC2MLOBIjsCoMLGJhRPnUEcUGZEX7j4Iw6zqd+zjkzzowL6gzKMOoBRHAAPyQKUZQlxLAk -EIEkQkhCyEoISegs3f1eVf3+qPTj0Z3udEJImN/Pe/rkdF6/V6/q3qp7b92tEOccfoT+A9zfHfj/ -HX4kQD/DjwToZ/iRAP0MPxKgn+FHAvQz/EiAfgapvzvQQ3DfviCE+rtTPYH/AAKouEYIcc4ForUX -tXeKexhj6k8IIe2DvdUl0SYAcN7RGYQ63oAQ4hx8fBu6BXfC6vBcsHyDeNRi7cYboZQjBIRgl/lB -KQcAQnyl+q1IAC9YU7/s2bOnsrKSUupwOHQ63cMPP2wymRhjGOOrV6/m5ORYLJbg4OABAwZYLBaD -waBtQUsD34mqRT0hHc/abEpNjbWlxYEQCgw0RET463QEABjjjHFfyND/LEg737XsQpblhoaGioqK -CxcunD9/fv78+ampqepgZFk2mUwBAQEYY6PRSAhRG7Tb7cXFxXa73W63W63Wn/zkJ4sXL1YfVHGB -EFI5VZc0EDcwxjnnkoRbWhw7dxZt316Yn19TW9siyxQADAZddHRAWlrMffeNnDcvUa8nlDKEAGNv -7ffbClCnoYoFFRFiIufn53/88cfBwcERERERERHjxo2LjIz0ZbaqFLXb7ZcuXZIkKSoqShAYY7xn -z576+vpJkybFxcUZjUZfOJKKfQBACP75z/yXXtpfXX0JAAFIAAQAAXAADsAAZAA0dGjMa6/Nueee -FEoZQsgLDfqTAFqWIstyRUVFXFycJEniJ6vV2tTUFBUVRQhxkb0q2TTS7xr9tNxG/bdjtAjl5eXl -5ubW1dUhhJKTkzMyMkwmk0p4AMAYq91Tv1DKCMENDW0PPLBj797vEdJjrAfgjF2HP+d8B8YcAMry -5VP//vf5Oh3h3OM66P8V0NTU9N133+Xl5SmKsnr16qCgIBc8MsbE5HXXgjqdU9oRie8YY5c2W1tb -CwsLS0tLFy5cqEoILWnFI84rHGNUXW29/fYPCwsvSpI/pQLxntYNxxhjDIpinTNn1K5d/2Uy6Zwd -cNWO+o4A7mjFGOfk5OzcuTMsLGzixInjxo2zWCwqIlSpAL2k47tMc+18FN8vXLgAAHFxce4Cqa1N -njlzw9GjZZLkryiK6KP3twEgnY7I8tWf/WzCtm33McZVJVV7H3nppZf6BvXaL+rAFEVJSEhYvHjx -4MGDDQaDykxAw1h6S38XLxUcRnRGnXyiM4cOHdqyZUtDQ0N0dLSfn5/4SUz/Z57Zs3PnCZ0uQFEU -ANQV9jvIwxiTJOPp0xdCQgLS0gZRyjF2Hc5NXwEu866lpUWv1+v1enVBqFsnwWS0dLrZ4K7dlpSU -ZGZmVlVVpaen33PPPYL1HzlSOXnyewCk+6gSo2OhocaCgl9GR1vEOtCO7qbbglQsY4yPHj366quv -nj59GjScWtBGq0f2mVHBZbVxzhMSElatWvXzn//cORUAANau/Y5zB8YYoLsUQJxzQqSGhqb1648D -gFClXO+4eSNUZ9alS5e2b99eXl4+d+7cqVOnCrl361hvOt2LCNWlttY6bNjbTU22Hk9WhBDnjhEj -IgoKVoqdc1+vAFmW//WvfymK8uyzz86aNUvlP72HPrjBWaR2RkgIoXeJ2ZqbW9nUdBVj0uPGOecA -ujNn6s+cuQRui6CXd8JaJUedSsJUEBoaqtfrtdd9p4HQ3rTGL9UE1ik2BZ/trmnMRePinAFAQUEt -AMMYuXMP34EQRKnjzJlLqakRLr3uTQJoJarLzigyMpIxJiStVr/0pTXOQdgAMEaEYACOEPb+tKCU -UOEVhYq9qKCKTwYyzW0XL169cUaNEAJglZVXwc2Q3msE0GKfEFJYWGg2m+Pj41UtyMeJr8W7olCB -dFVS2mxKZeXVqqqrFRXN9fVtDQ1tbW2yw0EBQK8nJpNuwABTWJjfoEGB0dEBMTEWk0mHEBYPU8oY -Y04S+roEbTalt1Bkt1P3i728AjjnhJCjR49u3rw5IyNDEACcvBW8ajgqRhSFCUsvQhghVF/fmptb -efjwxWPHqs6da6iutlLqAFA86yQIQCJEHxkZkJQUMnFi9JQpg9LSYsLD/THusCtw3mHR7JIMfn66 -3sKP2dxJU70sAzDGBw4c2Llz5/333z958mRVqfD+lBb1GCNhxa2oaP788x8++6z4yJFKq9UKQAGI -+CCkw1jvqVkhPylllZVXKivrv/22EID4+wdMmhS9YEHKggVD4+KCxAqjlHkig9DfASA+PkismO7r -oNeAMQ6A4+ODwG0K9o4aqtoajx07tnnz5mXLlo0ePVplO12iXhjZMUYYI1mme/aUrF+f/9VXJTZb -CwAG0GFMhDHLxfjlHQTTF/KTMQogAzCDwW/27ITHHhs/f36SXk+8GO4VhUkSzsoqmTv3XxgbbkQI -A3BJQmfO/DI5eYAQhL1JAK0l68qVK1euXElMTOyS6av6EqViI4bb2+WNGwveeCO3uLgSAAAMhBCA -Dh/TjQMhCABRSgHsAJCUFL16ddrDD4/289OrfQDNahBGiKYm2/Dha2tqrAj1YCcMAIAxYsw+aVLs -kSMr3G2IN7QPcOqFXJ3IISEhCQkJvmBfaIeKIqQifPDBiREj3n3iiW3FxTUYmwgxCWT1FvYBgFJO -KQVAhJgwNp07V7ty5afDh7+7fn0e50AIVhTGmNZiCIrCgoKMixYNB7D3aCcMTvalPPjgGNEHl597 -vgI8Gd8FL/JkLnaf+IcPV6xatScv7zxCEsYGdQd0k6HDvs2Yg3PH6NFD3npr3vTp8Wqv1D0Hxqik -5MrYse+0tFCn48X3LSTHGDMmJySEnDjxy4AAfa+tAK1yWVpampubqxJDMLhOub9W2BKC29uVX/7y -i/T09/LyygjxQ0hPKe0T7AMAYoxTShGSCPEvKKiYMWP9E0/sbm11iKXgHAIoCktMDHnxxVkAbTpd -t9DFnahW/vSneQEBHYzOBS09IYA62THGra2tmzZtOnfunO9PCeF25Ejl+PHr3n13PyE6jI1O1Pex -dQgxBpRSjA2E6N9//+DYseuysyskCVPKBTsiBDHGn302ffHiCbJs1ekkJ3K7GC5CSKfDlFrXrJm1 -ePFwShnGnYyuJwTQ+vk2bdrk5+e3ZMkS9Scv2GeMU8p1OvLOO0enTn3v7Nk6QvwpFQbRfjTMIcYY -pZwQ/9LS+mnT3n/99e8kCQtmKNYB53zTpkV33jlGlpslSWzIPZFBhKUQjLksW596auZrr92hYt8d -Pz1cAQKhmZmZpaWlS5culSRJsKNOJYrWqY0xeuKJz3/1q38DYIz1lIrNYT9gHyFXAxGlFGM9xtIz -z+xctuwzYUESXnXOQacj//u/S3796zsUxU6pDSGQJEKIsHB0fAhBkkQQ4pS2Ygyvv77o3XfvFNjv -zagIVZLs27cvMDBwwoQJqpHHE98Xno3WVvlnP9v65ZcFkhSgKKybAu0GgQMgse2iVIQviIFjjDHG -YnvccZskYUWxzp49cseO+y0Wg+i82DFIEj58uOL55/cdPFgKYHfuDcUoGAAFYISY77572B//OGv4 -8DBFYd6jg3pIAE8hCF6w39xsu+uuTdnZZyXJv2+x34F6xhjndgAOoPfzM5nNEqXcarXLsg1AAdBh -rIcOB5GgQcukSQlffPGL0FCTGIJgSmI65+VV79xZnJNzsby8UQ3MSkgImT49PiNjWHJyqBrC5d3u -1A0CuHstvOv7KufBGFmtjnnzPsrJKZEkP0WhfTnxnV1t0+mMs2YlLVyYMnFiVHS0xWzWUcqammzn -zl359tuyf/+7sKSkGiEJIT1jFAAkiShK68SJg7OylgYFGcVAAECrqiKEZJm2tysIgdmsc14EWRY2 -FY/q+A0RQG3Re2yIerMsszvv3Pj114WS5N/n2McACufKz38+/uWXZ6SkDHDvs4rH7duLXnjh69LS -GkLMlHIALmgwbVry3r0PGwwd4T3gNDcJkqiUUC8SgjEWPoyuba6+CmFtAMH+/ftra2s7COjVuim0 -iEcf/axfsI8x5twRGGjYufPhrVsXJyeHUsrEdlf7oZTLMiUE33//yFOnVj7yyBRK2wgBAKQoVJL8 -Dh78YenSHerqV13cOl2HhUr1DmGMdDpSX3/p22/3C1+3FnU3RAC1obNnz+7atau9vd1L007WzwnB -r756YOPGI/0y9xmTo6IsBw8+vnBhiixT4dIRWNN+CEE6HRF7LoOBbNiw4JVX5lNqwxg5aeC/deux -F1/cRwimVJV/AM79ppAK6opvb2/ftWtXSUlJl9iHbsUFiXds2rQpOTl52rRpnoydzoAfJkk4M/Ps -Y4/twNjotIH0ndQFYP7+ur17l40ZEyHLVJKwpy26+q/Q7hWFzZw5uKVFyck5R4gwjQDGhgMHzqam -Ro8YMVBs472YuYKDg69cuVJQUJCWlubi5nQHn1aAuu5OnDhRU1MzZ84c7/cLda2mpuWJJz4DQJx3 -14Ryo4AxAnC8+ead48dHORxUhIx7R4Rzb48IwYyx116bm56eRGm7sMFxDgDSU0/9b0VFsyRhL/YS -8Yrbb7+9trY2Ly9Pxd4NEUCFc+fOTZgwYeDAgWL6u9+g2kcB4Omnd1dVNRCi57wvN7rC/mWbNWvo -8uXjKWU6He5SErrQQAjb116bCyAJAwnnjBBdXV3jr36122WY7sAYCwsLGz9+vOBCXbzURy3Iydap -oijafIfr7+kw4UoS3rLl1H/912ZCTJT2tZkBIcS5PTNz6fz5yaIzvicMqWillEsSzsjYsnNnASEm -oRQRgilt+/DD+x9+eKyzZe6GhA7M2O12Qoga7O3pdb6yIPEXY+w1qodzziUJNzXZXnghC0ByKgJ9 -BxgD546UlIjbb08AAEKuCUwfQTu0hx4aDYDUKcoYB9D9/vdfX77c5oURiZWk1+tFYD14FcVdEECr -fbq8wH36g9Ph8Ne/ZpeV1fU581HRp8ycOVinI6pVuQftCH1/6tTYoCALY1SIUs45IfrKyvo///mQ -kx6uyHVHTqc49JUA2na1Ar2zUXHOQZJweXnTO+/kAhj7nvmoMG5c9I08rlpABw70T0oKBVCc4xV+ -JNM//nHk3LkGwdw6fVz7txc2YoyxrVu3lpaWImecs4fbOACsXftdc7OVEOlGwgh6DJwDAImNDdTi -omcghhMTYwFg2glNCGltbX3jjRzoLNhWizSEUHl5+datW51G307AGwFU/amqqur48eOSJHm9EyQJ -V1Vd/fDDEwCG/jLxc84BkNEoAXRD8HpoCgDAZNJdP5PEIjBs2lRQXt4kFoEXFi9J0vHjxysrK8GD -PurTCvj+++9jYmJiY2O9CHQxFz766ERjYxMh0s1OO/AEIoDH4VBUDN4g2GyK20zihEhW69UPPsgD -z4tACIOYmJiYmBgRkd8pdEEAsXssKioaOnQoeBAj4pokYYeDbtpUAKDrD+eiOmwAoCIKE3ywBHgd -OwKAqqqrAC68XvBh/ebN37e3y5KEPWOGA0BycnJRURFowgOve0uX/bBarYqiCAJ4gI44hm++KS0q -qkVI31/TX2AHAPLza26kCTU5oKGhraTkCgBxGRHngLHu/PlLWVkl0FmwiRaGDx8uy3JTU1Onv3at -hgYEBKxevTo2NhY8y3TRvU8/PQ1ARZbnTcaytw4DSPv3lzHGvMxN39qB3NyLDQ3NGEvubYjYrU8/ -PeOpBRVXMTExq1evDgwM7PQ2bwRQce2Siu4OkoStVntW1vn+5T8AwBhHSHfqVPWBAxfAq5biCdSg -MQDYvPl7pwrE3V8EoP/669LGxnZP+qgAQojJZPLkG/BIAHXiMK/bWTWO6tixqsrKKwjp+rv2hBgk -FWqi6Ex3nU6UMknCBQW1//73GQADpZ1MKc4BY6murik3txKgI4PBS8ue3ANdywDkBPDo/AIA2Lev -FEDpNPSlbwExxhEyff756W3bTksSVhSP4RpuA7mWmgAAzz2XJcs2LxGJgtL79p33gjoXBLpDFwRo -bGwsLi7W1gXopAmMACAn56K7sOonEGUbpJUrPz93rkGnI7JMVX+Wx2ec2JdlJkn4j3888OWXZwgx -ednQcM4ByHffXVSR4OEeYIz98MMPjY2N3SCAQHphYeG2bdu8+h0BY9TY2H7mzCUA7+o/BwBJwuKD -8Q1F3HsFYVWWLl+23nXXxoqKZkED1UnrptJ0/KsojFKu15O///3Y73+/F2NTp8zn+gelwsLLly61 -CiO2xw4htHXr1sLCQnBj6dhz0wAADQ0N4eHhXpawuF5aeqW+vsVrKnOHl0pRWsSHMYcz1vWm0IAx -hrHh7NlLU6a8n51dIXwyAsXOND+uutFlmQonEsbouee+XrlyB8Z6sey9vINzQAg3NbWWlDQAeHMP -IIQiIyMvXboE18cVgpcMGTHrm5qagoKCwHMqj2iqqOgygEyI5FkjRgA0JMT/oYemMMbNZik7u+Lw -4dKbKbQ7aFBV1Txjxvqnnpry/PO3RUT4u3gyEOpYxAihb74pW7MmKz+/lBATpeCLFw9jRKlcVHR5 -ypRY7wMJCQnpdCvQBQFqampGjRrllQAcAM6fvwLAvOTxYIwYYxER/m++OU+WqU5H/vzn7MOHfyDE -IIzGN48GCOk452+/vf/DD/MXLhy+cGHK2LER4eH+BgNhjLe0OMrKmg4evLBly+mjR0sBgBA/Sn2N -GxNDPn/+CnheAeK62WwWDjIXNHZBgGnTpkVFRUFX4ebl5U2+ONc45yIwRKcjvZh54R1FnDPOESF+ -Vqt948bcjRuP6HTmsDA/k0lijDc12RsbW0SQIcZGABBJHD5uZYTtr7y8CTy4SVS8DR8+XPASn1iQ -2sqUKVPUnAsPdwIA1Na2+DhfCMGS1FHWrk8IAKJjlFIATIiZc5BlWl3d6JzjCIBIkr8QBt0NHhDR -QLW1LeDZ9C2iZuPi4uLj413Q65EAmjypTqrruOAUABobbW4Wq1sN1KhCBIAQujZwkSmlva27LTc2 -2gDAwxS9LoPapRwXdOkPgK58GkL/bWlx9GuAfzeQ5RyaWu/gWnC5Om7fmxMsqLXVIaLYfbv/OvDG -grR830vrjHFZ7gPvu8hX6ZhBIkyhM6q73MY830Mo5ZxTkQ/sXBmYENJVRTJXbMkyY4x7spZ5R6a3 -fUBLS8uWLVvq6+vBqzlFNQfdzG2wCM6hYg9BaZsT+7yz2xTnbe2aeobqDYKjUkVp4dxuNOojI4Ni -YkIiIgJNJj3nsqK0cE67lRPp3RAkfrpy5cqWLVuam5tdEOUtU16W5ZMnT6alpYWFhXnxhWGMhOHX -R5NLDwAhxLmSmDhw6dIxisIaG9vffvuou5EAIcS5nJoac999IxWFVVdffe+945p7OIDI226LjBzw -4INjfvrTxKSk0MBAA8ZI5AqUlFzZu/f8Bx/k1dZewdjkm2OVq3GPngiAEGptbT1x4oQIKtQi0xsB -JEkym83ecSra0uvJTfUBYIwoVZKSQl54YRoAlJc3/f3vx9yttOK21NTw55+/DQAKCmrfe++YBoMI -IWDM9sQT6X/961x/f9cAJ4vFEBUVMH16/G9/O3X58s+2by/A2OidBsJwrdcTX5Q6s9ks/Oq+pqmK -ux0Oh1cCdHS9D5wwKsZFioTnLl2z7WgvY4w4t/2f/zNt3bq7jUZJWEnb2uTy8qZz5xpqaqxCkDoc -1GIxbNt23223JTDmS342t1gMahKcJ7DZbACg07nW6/C2AvR6vUhE7Wq0KDTUBNC9ALQegLrKnUmK -ncO11S1h7UXG5Li4ga+8MotzTgi6etX+4ovf7thRePlyG6XcYCCDBwc//fRPHntsvMNB9Xry7LO3 -HTpUKp72/C4AYCEhRuiq8Ep7eztCSK/Xd4MAGOPHHntM1PL0nH8KABAdbfEgFW8VEEabO+5I9Pc3 -tLXJZrPu/vs/3bPnBMZ+jImodKWwsOrxxz9ubZVXr04DgPHjowIC/K1WG0Letzg8OtqiosIdBOqS -kpIef/xx99CeLvwB4eHhQgx42oWJ9e6s6dLfaO4KxoyJBACzWXfgQNmePWckKciZ44gAMCEGAOMn -n5wUN1ssBn9/PYA3didsQaIOjXcsmUymiIgI9xsk762L8nVqRpj78+JKSkooAOmrKgM9AcY6nPWt -rQ4AyM4uBxD7gA59X5hFAXBbm+K7QUIUAkpJGQDXMwltipxKg04R6G0jxjVlNzyB2AkPHTqAEEM/ -BoN2CZxzAN2nn5749NPjAICQjhADAEeoo2QQ54xzBaAlPn6okyRdj4UxBmBwEuAa6kGjC6hGuk43 -Yt6iDcUKsFqtfn5+nuISRVNxcUFxcUGlpZcRkm5VixAC4BgbCUGEYIdDobTdyV4wgC4gwBgVFTB9 -+k9efHG6ry0i4JzGxAQPHhwMzrmoTSRV+YdLQrX2YhcEqK+vX7du3YoVK8LDwz3xOEqZwSCNHRtR -WlqLsa6v7Mw9Ac5BURRZtpnNAWPHJo0eHT506IDBg4NiYizh4f4DBpj1euKJV7iD2HaMGRMhSj6p -GawIIVGhua2tbefOnQ888IBIquCaepLqsujCHxAYGMg5r62tDQ8PBw9iQEz5GTPit28/0d8Y9oZ8 -hDDnsr+/Yc2a2cuXj42OtrjkPAuk1NW1DhhgliRfeCkC4NOnx6tI4M6ikQcPHszOzo6MjLRarcXF -xXFxcRaLRSS3MsbKysqioqLE8RHehDDn3Gg0hoWFlZeXjx492jOlOABMnz4Yof7MCegCVQhxLkdF -WfbsWTpq1EBhvUEItbfLVVXWysqrZWWNZ8827N9fTik7cuQx8MG0RSkD0M+cORg6WLHgchgApkyZ -Eh8fn5WVxRj7/PPPbTabxWKJiopKTEwMDAz8+OOPn3zySXEgiDcCCGIOGjSouLgYPAgl9YyUUaPC -x42LyMu7eMP17W4UtPsvFUSm0IYN944aNdBmU4xG6fDhin/841hOzsXKyquybAdQMAbG6MiR8T7y -H8Yco0ZFjh0bKf510gA45xaLJSgoqLq6OiIiYuTIkefPn7948eKFCxf279/f0NCQkpISGRkJLn6J -zpArVMyU9vZ2tR5Kp3dSyiUJ3XNPSl5eGUJGgJu7DrwkmwLwyEg/l6uEIErtkycPmTcvyeGgRqP0 -t79995vf7EKIca5T62ASgh0Ouyj02hWIIgjyXXcNxRiJkihOSndwaUrpnDlzBMYSEhKGDBkixHJj -Y6PZbAY199UL9gVPTEpKSk5O9u6cEZczMob/z/8cuHkZ8S6ntbj/DsABsJiSLmMBoGlpMQCg15Pq -auvLL2cBSJKkUxQm3DLCNwDABUftCkSahnHx4hHunXGWLcCHDh3Ky8tDCA0aNGjq1KkiwCc0NFSV -85zzLjxiWsekp4Q/5KzNOXJk+OzZgwEcvgQoIoQAsBqn5eXj3CJdA6NRMplc3B8dWbQDBwbOnDmk -09GEh/uLb+XlV6xWGWNJRGupN0gSAXAMGxaqGbtHCzyAfcaM+HHjotQCNi5427VrV2ZmZnJycmJi -Yk5OjsPhOHnypOpcUbUgn6xa2mM/PBn9Bd9/9NEJaje8E4BzGaBFUVrVUC1PH84V56JmAKAoLDzc -f9y4CACbXt9R+EGSCCEYoPU3v7ltwACzqCbtAlZrh1k3IiJAr8ecc0lSH0eSRByOlvDw0Fdeud05 -duHkwm7hNuI7f/TR8eAWgC12r3V1dceOHVuxYsX8+fMTEhLi4uIGDRqUm5u7bds2uD5+ouvSxej6 -2kyeQDDBBQuGjRoVfepUDcZ6T6JYrI/x4wc98sjtAQEGr1l/YDJJu3efLS6uA5AqKpplmYrH//Sn -eXPm1FitzSK0i3PKOaxcOXvNmnS1sI8WKQD4++9rAcDhoEOGhDz2WNq77+5jTM8YEtoj5zBpUuLG -jfeKoiqEYEIwxgqAnXOjtmAlQpgxx9ChkYsWjQC38A6BpbKystDQUBEGcezYsYSEBAC4995733nn -ncrKypiYGLXUQBcEUGNSDh482NzcfPfdd3dapAA5yyHqdHjVqsmPProNIYO7KBaF6MUsnjVryJw5 -CV62PMLxK0m4vr61uPiiJPn98EPd4cMVM2YMttuVSZOi8/OfWrs2Ny+vRlFYQkLwL34xZt68RADY -uLHgrruSQ0PN6pZQxPLv23e+pKQhMTFUlunatT/9yU+it207U1fXoteThISQBQtS7rwzyWCQGhvb -jUbJaEQGg/SrX6W/8UZua6ujudnmHAvHGFOqPP30ZOFUEDWxtKgAAD8/v6tXrzocDs55ZWXlrFmz -AMBisRiNRhf/iq95wnq9/rvvvrNarWpghadF8NBDY1JTB1HaiStD6KyEYEKQpyqCngBjBMDWrNnb -0uIwGCRZpoMHB61dOz87+5EjRx7bvHnRHXckAMBf/5rzyiv7goONoIlY5hwwJm1tbatXfymyORnj -Dz00eteuJUeOPJ6dvfzDDxcuXJhiMEj5+TXp6RvKyhoRQna7smpVWlXVMw8/PAbARggSyg+l9pSU -qGXLxrlMf62eMmzYMKPRuHXr1ry8vIEDB0ZHRwPA6dOnKaXiu08uSe1948aNy8rKOnny5G233ebJ -LCoWgV5P/vCHmYsXb3KZzgCorU0+dOiC78YixlhgoLG2tgUAKwrD2HD8+IVZsz745z8XpqaGq3HO -oj/Nze0vv3zgzTe/iY+PPHSowmzWnTp1SdsUxsbMzNNz5360bt09Q4YEO+cQF1HTly+3vv320ddf -P9Ta2rxhw4m//W2uWoxAOFydwQ3AOX3xxZkmk+v0V3l1W1ub2Wx+5JFHNm7cKPhPTk5OTU1Nbm5u -RkaGwWDQchGf4gkFF9q9e/fJkyefe+457dmCbljukEJ33bUxM/MMIWZnpJ/qrunBeQgEAKsBDYzZ -JUmaPj1xxoy4uLggnY5cvtyan1+7e/cP9fUNGJsZY863IAA1XxyphVSMRuOcOUnp6bExMRaEUG2t -9ejR6qysksbGKxibADBjjgULRt5zT4rJJFVVWf/1r/yiolqEJIQQY+1z5qR89dVS7cFsKkIF9r/4 -4osFCxbodDpZlk+dOnXq1Kn6+nqz2Zyeni7OI9VObp8IIO6ur6/funXrkiVLhCbrKVZXBBsXFdVP -nPiP1lbFibsOGvTAaaNWkxT/OQ9BsQOoQZxC2OjV8Gz1LW7hPeJxUT6ROTmw+rhOhOUihDi3qSH1 -AHonq+BGI8rNXTF6dIRaDVQb+EYIaWxsfOutt1asWBEREUEpdT8IE67Hgk8pSuJLaGjok08+6QX7 -HS1ipChs2LCwV16ZA2BzMQyIXU+3Pi7dYYxxDoQYJcmfEDMhJvEFIaI66zXPos4eR86nTNc/TtXH -CTGpjSMkidgTgPY//GHW6NERatF3AfX19QL7lNLg4OCwsDCRGAwaxb2trU1dKNpJ373kKRfC+MaI -ThPi52RE/6HACSGUtt1+e0pW1jXmI5Bgs9lef/31gICABQsWDBo0CCH0zTffHDlyJCUlpbGx0Waz -ORyOpqamMWPGLFy40L3OW/fKVoLGeOuJBiLaUj2BdPLkdRUVTRjr+7tAdM+xL0rQR0YG5OauiI0N -FEPT8pPa2tq9e/eeOXNm6NChCxcuBIC33norPDw8MDDQZDL5+fkZDIbU1NROmUf3YtmcWZy0tbU1 -ICDAMw2u1e07ePDCnDkbZFn1Cv1n0aDj9BiEWFbWstmzh7gXylLnYmVl5e7du0tLS8ePH19RUbFg -wYLk5GRtbdtO0dW9mnGilYKCgrffflsEunRKvw5nm4QVhU2bFrdhwyIAu6hZeMvGrXQ6XBHKyLn9 -/ffvnT17iKi+6C5UBURHR69YsWLFihX19fXV1dUHDhxoaWkRKoOQLp1O1m4fZ4sQCgkJOXz4cFNT -0/Dhw9UW3TNDOOeEYEWhY8dG+vub9+49TYj+epXmVgYOgCQJUdr6xz/euWpVmkjs6TQHpr29/bPP -PtuxY0dRUdGkSZOmTZsWExNTVFSUlZVlt9tjY2NFPFanWUbdI4DQeXU6XVhY2K5duxISEgRf8xCa -isQ5RpTy9PRYQvTffHNGkv4jaNCBfUVpfeGFef/93zO0ey4XwwNj7P33329sbExPT9fr9YmJiQI/ -aWlpFoslPz9/xIgRJpMJPOjg3ZYB4NRwPv7448rKymeffRa8pvAh5ylVkoT/9Kfs5577nBAjY7jv -y8n5PkqEMMac0vaXX57/hz9M91SCXjipjh07lpWVtWbNGrWcoSzLe/bsSU9PDw4OppS6HMbuAt07 -yE3b0J133nnlirfsQO39hICi0N/9Lj0kxLRixQ7OMSG6W1I35RgTzmVK6TvvLF65cqIn7KuGkKqq -qoiICL1eL8syxlhUNTlx4oSiKPfee2+X7+v5ESYWi2Xw4MEuEqlTd42TBliW6eOPj//yy0eCg42U -tkuScKrcImJZJPITxtoDAgyff7505cqJskxdsK8OkznPlIuKiqqoqGhtbRWRz4qi6HS66dOni6TU -Ls9w7DYBtL1Rjy1xiezw9IgkYVmmc+cmHD/+1MSJgxWlhRDo6flcvYx9jDEhoCgtY8bEHj/+5F13 -JQudx9MACSFiso8dO9ZsNn/44YeiUqu48/Lly2qCu/cXd1sLguvLMoovly5dUhTFZDJ5OstE02+s -KCwkxLRs2Vi7nWRnn+dcIUTv9Oj2PUfqyBdjzME5Xb165iefLB440F/oPNrxav2INpvt8OHDR48e -tVqt0dHRI0eOzM7OPnjwoF6vlyQpNzf38OHD9913X1BQkJcM347GbySpSDWUbtiwwWq1Pv300ypt -vItlcWCLOI9lxYrdp0+XI2TEWHKu674hA3dGSimc21JSYtetu+v6s9w6hgiaEAWEUGNj4/r16yml -AwcOLCsrE5bnkJCQL7/8sqCgQJZlPz+/u+++e8SIEVor6U0hgIrQq1evvvHGG3FxcUuXLgXPSpH2 -EVU1stuVd9459uqr+5uaGvuKDNeh3mIJfP756atWpQkPl/ASg5PBqtNfDeh8//33CSHLly8HgLa2 -to8//ri0tHTVqlXh4eF2u729vT0gIEA1gnYZ5dgTFnQdARFijBmNxmHDhu3Zs+fixYujR4/2/mIt -OxJG3alTY5ctGwugP3WqzmazAiCMJe8FYHqGdwDkFKoK5+1+fuaVK9O3bFk8b16SKJWrMn2xshlj -Fy9erK2t9fPz0+v1CKGmpqY9e/YsWrQoKCiIUmowGMaNG1dcXHzmzJlJkyYRQoxGI3Kecuc9lkfA -jZ4nrHY0PDx8+fLl3377rcPhMBgM4HUdqNNKnISgKCwszO8vf7n9179Oe++9vPXr86qr6wEAQC8E -XbdOse3sdcI9KU4HdQBARMSARx8dt2LFhOhoC2PcRdcUgyopKdm5c6fVahWCbfHixampqeJXNW1L -WPx/+tOfrl+/vq6uLjw8XCj+XmoL9DIBtNSOj49ftmyZOgzBSbyXOVBrjgosRET4v/TSjDVrpmRm -nvvoo5P795e1tVkBAEAHIKk4UvPcPaFbcA6V0XGuUKoAcJMpYNq05IcfHn333UNFlqTgOcLCIxoU -6M7Pz//kk09mzJiRnp5OCMnMzBTFZgIDA+Pi4r766qvhw4cTQhRFAYCgoCBCiN1uB429wUffU68d -6KyuXK28cr/i4XEQfFk9XlkMoLraundvyZ49JTk5FysrmwDEKWDCQyk+1zXpNHIw50ds9PRRUUFT -pgyaNy9x7tzEmJiOoGj1CGn3GOnGxsa//OUv99xzT1pamjYmU8yn+vr6N998MyEh4cEHH9TpdAih -L7/88uTJk7/97W99n/i9TACVDNfaRSgvLy8iIiI6OrrL7bg7ISnlCF07q6u9Xf7hh4a8vOrvv68r -LKyvrLx66VKr1eqQZVlzJh4CwDqd5O+vHzjQLybGMmxYWGpq+PjxUcOGDTCZdFoFzNP5aoKlZGdn -Hzhw4He/+506lxFCLS0ttbW1JpMpOjq6srLygw8+UBRlxIgRjY2NFy9efOSRR4YMGeLLIeIu0Jtn -yrsYab///vtt27YtW7YsJSVFXQq+tAAA6lmaooSM0SiNGRMxenQ4dIh93txsa262NzfbbDZFVKrQ -6bDRKAUGGi0WQ1CQ0WVqi7P7xKmFWut8px0wGAytra1NTU2hoaGKopSXlx85cqS4uNhms1FKp0yZ -snjx4meeeSY3N/f8+fMhISH33nvvwIEDuQ8ZXZ0MuRdXgArq8L744ouvvvrqjjvumD17ttejNzy1 -I8JAROHBDtYv+IYXh6jTRX7tLFRN8lAXJdWdC679jTfeYIwlJiaWl5c3NDRERUVNmDBhyJAhZWVl -27dv/8UvfjF27NgunS39QwAt98cYnzlzZvPmzUuWLBk1apSWn/asu2pvPVVkVaN3tP92t32EUHV1 -dWZmZnNzc0JCwsSJE0U0lfhp3bp1gYGBS5YsURRF3eX2gPvfLAK406ClpcVgMOh0Og361KolXWvK -fQlaa4/LF+HVkiTp7bffjo6OzsjIELLtBvvfwyPNvYM6u4Uyqk2yFIYUdffgyX7Xl6BqONq9K3cm -1MmyzJ1nF0qSdOjQocrKysmTJ4NTON/g7OlNIawFtVtaHU5c+eijjzDGGRkZAwYM8FE43yTQmnVB -M+XVBVpXV/fBBx/Mnj07NTX16tWr+/bty8vLe+CBByIjIz2dpNZtRPXZ7FOXc2lp6RdffFFRUTF2 -7NhZs2aJBNjr+tQj8dDdzqjTXFWRtdtGZ2CHsmvXrtzcXJPJpChKWFhYRkZGbGyslwOsuwt9vfxV -Mpw9e3bHjh1JSUmLFi1y2eyoJtxep4SLyFH/LS8vz8zMHD16dHp6urtuc+nSpbq6uuDg4KioKME5 -u9xa3ooEUMejVmJUFEVRFJEuK8Zjs9lUY1ZH/9yQ1bP3goa0Ku7sdntOTk5+fn59fX1CQsIdd9wR -FxenfbX7svDdyuYj3CwZ4A7qNk0MQARTqmfNAYDNZlu7dq3FYpkwYUJSUpI4ckKrh2hnnIvBw9O7 -tPeD2ykuIm8rMTHxoYceEjsp7SMuEkIVxb27KPtHA3HX9gTDPXv27MmTJ8+fP2+1WtPS0jIyMnqw -uXdRIgU0NzdXVlYWFhaOHz8+ISFBZXoqu+uyQupNgr5bAVpwd2oCgCRJw4YNGz58uKIo586dcxED -R44cqampGTRoUGBgoMViCQ4OFhsLLaIZY4qiUEpFjSN1J7hjxw5ZlgkhgYGBqampLj1RVaA+EP6d -oKJ/dXABWg4LTkah5d0iSe3YsWMOh8Nms8myvHLlyujoaDGR29vb169f39LSIqwI4eHhK1euBKdh -ubq6uqioaMiQIZGRkULegJvZqh93grcEAQRop7N2q6xlVoyx1tZWq9U6YMAAbSDU8ePHEULiANOg -oKDY2FithHCRFv0y0z3BLUQAT6C6d7TaIfiAR5c9bZcBA/0C/wEEEKDtZ6duHy1a3Wtk37LwH0OA -/1fhphjjfgTf4f8C4VLHz/5KLxoAAAA8dEVYdGNvbW1lbnQAIEltYWdlIGdlbmVyYXRlZCBieSBH -TlUgR2hvc3RzY3JpcHQgKGRldmljZT1wbm1yYXcpCvqLFvMAAAAASUVORK5CYII= diff --git a/gem/input.bin b/gem/input.bin deleted file mode 100644 index d24a954..0000000 Binary files a/gem/input.bin and /dev/null differ diff --git a/gem/ltn012.tex b/gem/ltn012.tex deleted file mode 100644 index 8027ecc..0000000 --- a/gem/ltn012.tex +++ /dev/null @@ -1,695 +0,0 @@ -\documentclass[10pt]{article} -\usepackage{fancyvrb} -\usepackage{url} -\DefineVerbatimEnvironment{lua}{Verbatim}{fontsize=\small,commandchars=\@\#\%} -\DefineVerbatimEnvironment{C}{Verbatim}{fontsize=\small,commandchars=\@\#\%} -\DefineVerbatimEnvironment{mime}{Verbatim}{fontsize=\small,commandchars=\$\#\%} -\newcommand{\stick}[1]{\vbox{\setlength{\parskip}{0pt}#1}} -\newcommand{\bl}{\ensuremath{\mathtt{\backslash}}} -\newcommand{\CR}{\texttt{CR}} -\newcommand{\LF}{\texttt{LF}} -\newcommand{\CRLF}{\texttt{CR~LF}} -\newcommand{\nil}{\texttt{nil}} - -\title{Filters, sources, sinks, and pumps\\ - {\large or Functional programming for the rest of us}} -\author{Diego Nehab} - -\begin{document} - -\maketitle - -\begin{abstract} -Certain data processing operations can be implemented in the -form of filters. A filter is a function that can process -data received in consecutive invocations, returning partial -results each time it is called. Examples of operations that -can be implemented as filters include the end-of-line -normalization for text, Base64 and Quoted-Printable transfer -content encodings, the breaking of text into lines, SMTP -dot-stuffing, and there are many others. Filters become -even more powerful when we allow them to be chained together -to create composite filters. In this context, filters can be -seen as the internal links in a chain of data transformations. -Sources and sinks are the corresponding end points in these -chains. A source is a function that produces data, chunk by -chunk, and a sink is a function that takes data, chunk by -chunk. Finally, pumps are procedures that actively drive -data from a source to a sink, and indirectly through all -intervening filters. In this article, we describe the design of an -elegant interface for filters, sources, sinks, chains, and -pumps, and we illustrate each step with concrete examples. -\end{abstract} - -\section{Introduction} - -Within the realm of networking applications, we are often -required to apply transformations to streams of data. Examples -include the end-of-line normalization for text, Base64 and -Quoted-Printable transfer content encodings, breaking text -into lines with a maximum number of columns, SMTP -dot-stuffing, \texttt{gzip} compression, HTTP chunked -transfer coding, and the list goes on. - -Many complex tasks require a combination of two or more such -transformations, and therefore a general mechanism for -promoting reuse is desirable. In the process of designing -\texttt{LuaSocket~2.0}, we repeatedly faced this problem. -The solution we reached proved to be very general and -convenient. It is based on the concepts of filters, sources, -sinks, and pumps, which we introduce below. - -\emph{Filters} are functions that can be repeatedly invoked -with chunks of input, successively returning processed -chunks of output. Naturally, the result of -concatenating all the output chunks must be the same as the -result of applying the filter to the concatenation of all -input chunks. In fancier language, filters \emph{commute} -with the concatenation operator. More importantly, filters -must handle input data correctly no matter how the stream -has been split into chunks. - -A \emph{chain} is a function that transparently combines the -effect of one or more filters. The interface of a chain is -indistinguishable from the interface of its component -filters. This allows a chained filter to be used wherever -an atomic filter is accepted. In particular, chains can be -themselves chained to create arbitrarily complex operations. - -Filters can be seen as internal nodes in a network through -which data will flow, potentially being transformed many -times along the way. Chains connect these nodes together. -The initial and final nodes of the network are -\emph{sources} and \emph{sinks}, respectively. Less -abstractly, a source is a function that produces new chunks -of data every time it is invoked. Conversely, sinks are -functions that give a final destination to the chunks of -data they receive in sucessive calls. Naturally, sources -and sinks can also be chained with filters to produce -filtered sources and sinks. - -Finally, filters, chains, sources, and sinks are all passive -entities: they must be repeatedly invoked in order for -anything to happen. \emph{Pumps} provide the driving force -that pushes data through the network, from a source to a -sink, and indirectly through all intervening filters. - -In the following sections, we start with a simplified -interface, which we later refine. The evolution we present -is not contrived: it recreates the steps we ourselves -followed as we consolidated our understanding of these -concepts within our application domain. - -\subsection{A simple example} - -The end-of-line normalization of text is a good -example to motivate our initial filter interface. -Assume we are given text in an unknown end-of-line -convention (including possibly mixed conventions) out of the -commonly found Unix (\LF), Mac OS (\CR), and -DOS (\CRLF) conventions. We would like to be able to -use the folowing code to normalize the end-of-line markers: -\begin{quote} -\begin{lua} -@stick# -local CRLF = "\013\010" -local input = source.chain(source.file(io.stdin), normalize(CRLF)) -local output = sink.file(io.stdout) -pump.all(input, output) -% -\end{lua} -\end{quote} - -This program should read data from the standard input stream -and normalize the end-of-line markers to the canonic -\CRLF\ marker, as defined by the MIME standard. -Finally, the normalized text should be sent to the standard output -stream. We use a \emph{file source} that produces data from -standard input, and chain it with a filter that normalizes -the data. The pump then repeatedly obtains data from the -source, and passes it to the \emph{file sink}, which sends -it to the standard output. - -In the code above, the \texttt{normalize} \emph{factory} is a -function that creates our normalization filter, which -replaces any end-of-line marker with the canonic marker. -The initial filter interface is -trivial: a filter function receives a chunk of input data, -and returns a chunk of processed data. When there are no -more input data left, the caller notifies the filter by invoking -it with a \nil\ chunk. The filter responds by returning -the final chunk of processed data (which could of course be -the empty string). - -Although the interface is extremely simple, the -implementation is not so obvious. A normalization filter -respecting this interface needs to keep some kind of context -between calls. This is because a chunk boundary may lie between -the \CR\ and \LF\ characters marking the end of a single line. This -need for contextual storage motivates the use of -factories: each time the factory is invoked, it returns a -filter with its own context so that we can have several -independent filters being used at the same time. For -efficiency reasons, we must avoid the obvious solution of -concatenating all the input into the context before -producing any output chunks. - -To that end, we break the implementation into two parts: -a low-level filter, and a factory of high-level filters. The -low-level filter is implemented in C and does not maintain -any context between function calls. The high-level filter -factory, implemented in Lua, creates and returns a -high-level filter that maintains whatever context the low-level -filter needs, but isolates the user from its internal -details. That way, we take advantage of C's efficiency to -perform the hard work, and take advantage of Lua's -simplicity for the bookkeeping. - -\subsection{The Lua part of the filter} - -Below is the complete implementation of the factory of high-level -end-of-line normalization filters: -\begin{quote} -\begin{lua} -@stick# -function filter.cycle(lowlevel, context, extra) - return function(chunk) - local ret - ret, context = lowlevel(context, chunk, extra) - return ret - end -end -% - -@stick# -function normalize(marker) - return filter.cycle(eol, 0, marker) -end -% -\end{lua} -\end{quote} - -The \texttt{normalize} factory simply calls a more generic -factory, the \texttt{cycle}~factory, passing the low-level -filter~\texttt{eol}. The \texttt{cycle}~factory receives a -low-level filter, an initial context, and an extra -parameter, and returns a new high-level filter. Each time -the high-level filer is passed a new chunk, it invokes the -low-level filter with the previous context, the new chunk, -and the extra argument. It is the low-level filter that -does all the work, producing the chunk of processed data and -a new context. The high-level filter then replaces its -internal context, and returns the processed chunk of data to -the user. Notice that we take advantage of Lua's lexical -scoping to store the context in a closure between function -calls. - -\subsection{The C part of the filter} - -As for the low-level filter, we must first accept -that there is no perfect solution to the end-of-line marker -normalization problem. The difficulty comes from an -inherent ambiguity in the definition of empty lines within -mixed input. However, the following solution works well for -any consistent input, as well as for non-empty lines in -mixed input. It also does a reasonable job with empty lines -and serves as a good example of how to implement a low-level -filter. - -The idea is to consider both \CR\ and~\LF\ as end-of-line -\emph{candidates}. We issue a single break if any candidate -is seen alone, or if it is followed by a different -candidate. In other words, \CR~\CR~and \LF~\LF\ each issue -two end-of-line markers, whereas \CR~\LF~and \LF~\CR\ issue -only one marker each. It is easy to see that this method -correctly handles the most common end-of-line conventions. - -With this in mind, we divide the low-level filter into two -simple functions. The inner function~\texttt{pushchar} performs the -normalization itself. It takes each input character in turn, -deciding what to output and how to modify the context. The -context tells if the last processed character was an -end-of-line candidate, and if so, which candidate it was. -For efficiency, we use Lua's auxiliary library's buffer -interface: -\begin{quote} -\begin{C} -@stick# -@#define candidate(c) (c == CR || c == LF) -static int pushchar(int c, int last, const char *marker, - luaL_Buffer *buffer) { - if (candidate(c)) { - if (candidate(last)) { - if (c == last) - luaL_addstring(buffer, marker); - return 0; - } else { - luaL_addstring(buffer, marker); - return c; - } - } else { - luaL_pushchar(buffer, c); - return 0; - } -} -% -\end{C} -\end{quote} - -The outer function~\texttt{eol} simply interfaces with Lua. -It receives the context and input chunk (as well as an -optional custom end-of-line marker), and returns the -transformed output chunk and the new context. -Notice that if the input chunk is \nil, the operation -is considered to be finished. In that case, the loop will -not execute a single time and the context is reset to the -initial state. This allows the filter to be reused many -times: -\begin{quote} -\begin{C} -@stick# -static int eol(lua_State *L) { - int context = luaL_checkint(L, 1); - size_t isize = 0; - const char *input = luaL_optlstring(L, 2, NULL, &isize); - const char *last = input + isize; - const char *marker = luaL_optstring(L, 3, CRLF); - luaL_Buffer buffer; - luaL_buffinit(L, &buffer); - if (!input) { - lua_pushnil(L); - lua_pushnumber(L, 0); - return 2; - } - while (input < last) - context = pushchar(*input++, context, marker, &buffer); - luaL_pushresult(&buffer); - lua_pushnumber(L, context); - return 2; -} -% -\end{C} -\end{quote} - -When designing filters, the challenging part is usually -deciding what to store in the context. For line breaking, for -instance, it could be the number of bytes that still fit in the -current line. For Base64 encoding, it could be a string -with the bytes that remain after the division of the input -into 3-byte atoms. The MIME module in the \texttt{LuaSocket} -distribution has many other examples. - -\section{Filter chains} - -Chains greatly increase the power of filters. For example, -according to the standard for Quoted-Printable encoding, -text should be normalized to a canonic end-of-line marker -prior to encoding. After encoding, the resulting text must -be broken into lines of no more than 76 characters, with the -use of soft line breaks (a line terminated by the \texttt{=} -sign). To help specifying complex transformations like -this, we define a chain factory that creates a composite -filter from one or more filters. A chained filter passes -data through all its components, and can be used wherever a -primitive filter is accepted. - -The chaining factory is very simple. The auxiliary -function~\texttt{chainpair} chains two filters together, -taking special care if the chunk is the last. This is -because the final \nil\ chunk notification has to be -pushed through both filters in turn: -\begin{quote} -\begin{lua} -@stick# -local function chainpair(f1, f2) - return function(chunk) - local ret = f2(f1(chunk)) - if chunk then return ret - else return ret .. f2() end - end -end -% - -@stick# -function filter.chain(...) - local f = select(1, ...) - for i = 2, select('@#', ...) do - f = chainpair(f, select(i, ...)) - end - return f -end -% -\end{lua} -\end{quote} - -Thanks to the chain factory, we can -define the Quoted-Printable conversion as such: -\begin{quote} -\begin{lua} -@stick# -local qp = filter.chain(normalize(CRLF), encode("quoted-printable"), - wrap("quoted-printable")) -local input = source.chain(source.file(io.stdin), qp) -local output = sink.file(io.stdout) -pump.all(input, output) -% -\end{lua} -\end{quote} - -\section{Sources, sinks, and pumps} - -The filters we introduced so far act as the internal nodes -in a network of transformations. Information flows from node -to node (or rather from one filter to the next) and is -transformed along the way. Chaining filters together is our -way to connect nodes in this network. As the starting point -for the network, we need a source node that produces the -data. In the end of the network, we need a sink node that -gives a final destination to the data. - -\subsection{Sources} - -A source returns the next chunk of data each time it is -invoked. When there is no more data, it simply returns~\nil. -In the event of an error, the source can inform the -caller by returning \nil\ followed by the error message. - -Below are two simple source factories. The \texttt{empty} source -returns no data, possibly returning an associated error -message. The \texttt{file} source yields the contents of a file -in a chunk by chunk fashion: -\begin{quote} -\begin{lua} -@stick# -function source.empty(err) - return function() - return nil, err - end -end -% - -@stick# -function source.file(handle, io_err) - if handle then - return function() - local chunk = handle:read(2048) - if not chunk then handle:close() end - return chunk - end - else return source.empty(io_err or "unable to open file") end -end -% -\end{lua} -\end{quote} - -\subsection{Filtered sources} - -A filtered source passes its data through the -associated filter before returning it to the caller. -Filtered sources are useful when working with -functions that get their input data from a source (such as -the pumps in our examples). By chaining a source with one or -more filters, such functions can be transparently provided -with filtered data, with no need to change their interfaces. -Here is a factory that does the job: -\begin{quote} -\begin{lua} -@stick# -function source.chain(src, f) - return function() - if not src then - return nil - end - local chunk, err = src() - if not chunk then - src = nil - return f(nil) - else - return f(chunk) - end - end -end -% -\end{lua} -\end{quote} - -\subsection{Sinks} - -Just as we defined an interface for a source of data, we can -also define an interface for a data destination. We call -any function respecting this interface a sink. In our first -example, we used a file sink connected to the standard -output. - -Sinks receive consecutive chunks of data, until the end of -data is signaled by a \nil\ input chunk. A sink can be -notified of an error with an optional extra argument that -contains the error message, following a \nil\ chunk. -If a sink detects an error itself, and -wishes not to be called again, it can return \nil, -followed by an error message. A return value that -is not \nil\ means the sink will accept more data. - -Below are two useful sink factories. -The table factory creates a sink that stores -individual chunks into an array. The data can later be -efficiently concatenated into a single string with Lua's -\texttt{table.concat} library function. The \texttt{null} sink -simply discards the chunks it receives: -\begin{quote} -\begin{lua} -@stick# -function sink.table(t) - t = t or {} - local f = function(chunk, err) - if chunk then table.insert(t, chunk) end - return 1 - end - return f, t -end -% - -@stick# -local function null() - return 1 -end - -function sink.null() - return null -end -% -\end{lua} -\end{quote} - -Naturally, filtered sinks are just as useful as filtered -sources. A filtered sink passes each chunk it receives -through the associated filter before handing it down to the -original sink. In the following example, we use a source -that reads from the standard input. The input chunks are -sent to a table sink, which has been coupled with a -normalization filter. The filtered chunks are then -concatenated from the output array, and finally sent to -standard out: -\begin{quote} -\begin{lua} -@stick# -local input = source.file(io.stdin) -local output, t = sink.table() -output = sink.chain(normalize(CRLF), output) -pump.all(input, output) -io.write(table.concat(t)) -% -\end{lua} -\end{quote} - -\subsection{Pumps} - -Although not on purpose, our interface for sources is -compatible with Lua iterators. That is, a source can be -neatly used in conjunction with \texttt{for} loops. Using -our file source as an iterator, we can write the following -code: -\begin{quote} -\begin{lua} -@stick# -for chunk in source.file(io.stdin) do - io.write(chunk) -end -% -\end{lua} -\end{quote} - -Loops like this will always be present because everything -we designed so far is passive. Sources, sinks, filters: none -of them can do anything on their own. The operation of -pumping all data a source can provide into a sink is so -common that it deserves its own function: -\begin{quote} -\begin{lua} -@stick# -function pump.step(src, snk) - local chunk, src_err = src() - local ret, snk_err = snk(chunk, src_err) - if chunk and ret then return 1 - else return nil, src_err or snk_err end -end -% - -@stick# -function pump.all(src, snk, step) - step = step or pump.step - while true do - local ret, err = step(src, snk) - if not ret then - if err then return nil, err - else return 1 end - end - end -end -% -\end{lua} -\end{quote} - -The \texttt{pump.step} function moves one chunk of data from -the source to the sink. The \texttt{pump.all} function takes -an optional \texttt{step} function and uses it to pump all the -data from the source to the sink. -Here is an example that uses the Base64 and the -line wrapping filters from the \texttt{LuaSocket} -distribution. The program reads a binary file from -disk and stores it in another file, after encoding it to the -Base64 transfer content encoding: -\begin{quote} -\begin{lua} -@stick# -local input = source.chain( - source.file(io.open("input.bin", "rb")), - encode("base64")) -local output = sink.chain( - wrap(76), - sink.file(io.open("output.b64", "w"))) -pump.all(input, output) -% -\end{lua} -\end{quote} - -The way we split the filters here is not intuitive, on -purpose. Alternatively, we could have chained the Base64 -encode filter and the line-wrap filter together, and then -chain the resulting filter with either the file source or -the file sink. It doesn't really matter. - -\section{Exploding filters} - -Our current filter interface has one serious shortcoming. -Consider for example a \texttt{gzip} decompression filter. -During decompression, a small input chunk can be exploded -into a huge amount of data. To address this problem, we -decided to change the filter interface and allow exploding -filters to return large quantities of output data in a chunk -by chunk manner. - -More specifically, after passing each chunk of input to -a filter, and collecting the first chunk of output, the -user must now loop to receive other chunks from the filter until no -filtered data is left. Within these secondary calls, the -caller passes an empty string to the filter. The filter -responds with an empty string when it is ready for the next -input chunk. In the end, after the user passes a -\nil\ chunk notifying the filter that there is no -more input data, the filter might still have to produce too -much output data to return in a single chunk. The user has -to loop again, now passing \nil\ to the filter each time, -until the filter itself returns \nil\ to notify the -user it is finally done. - -Fortunately, it is very easy to modify a filter to respect -the new interface. In fact, the end-of-line translation -filter we presented earlier already conforms to it. The -complexity is encapsulated within the chaining functions, -which must now include a loop. Since these functions only -have to be written once, the user is rarely affected. -Interestingly, the modifications do not have a measurable -negative impact in the performance of filters that do -not need the added flexibility. On the other hand, for a -small price in complexity, the changes make exploding -filters practical. - -\section{A complex example} - -The LTN12 module in the \texttt{LuaSocket} distribution -implements all the ideas we have described. The MIME -and SMTP modules are tightly integrated with LTN12, -and can be used to showcase the expressive power of filters, -sources, sinks, and pumps. Below is an example -of how a user would proceed to define and send a -multipart message, with attachments, using \texttt{LuaSocket}: -\begin{quote} -\begin{mime} -local smtp = require"socket.smtp" -local mime = require"mime" -local ltn12 = require"ltn12" - -local message = smtp.message{ - headers = { - from = "Sicrano ", - to = "Fulano ", - subject = "A message with an attachment"}, - body = { - preamble = "Hope you can see the attachment" .. CRLF, - [1] = { - body = "Here is our logo" .. CRLF}, - [2] = { - headers = { - ["content-type"] = 'image/png; name="luasocket.png"', - ["content-disposition"] = - 'attachment; filename="luasocket.png"', - ["content-description"] = 'LuaSocket logo', - ["content-transfer-encoding"] = "BASE64"}, - body = ltn12.source.chain( - ltn12.source.file(io.open("luasocket.png", "rb")), - ltn12.filter.chain( - mime.encode("base64"), - mime.wrap()))}}} - -assert(smtp.send{ - rcpt = " ", - from = " ", - source = message}) -\end{mime} -\end{quote} - -The \texttt{smtp.message} function receives a table -describing the message, and returns a source. The -\texttt{smtp.send} function takes this source, chains it with the -SMTP dot-stuffing filter, connects a socket sink -with the server, and simply pumps the data. The message is never -assembled in memory. Everything is produced on demand, -transformed in small pieces, and sent to the server in chunks, -including the file attachment which is loaded from disk and -encoded on the fly. It just works. - -\section{Conclusions} - -In this article, we introduced the concepts of filters, -sources, sinks, and pumps to the Lua language. These are -useful tools for stream processing in general. Sources provide -a simple abstraction for data acquisition. Sinks provide an -abstraction for final data destinations. Filters define an -interface for data transformations. The chaining of -filters, sources and sinks provides an elegant way to create -arbitrarily complex data transformations from simpler -components. Pumps simply push the data through. - -\section{Acknowledgements} - -The concepts described in this text are the result of long -discussions with David Burgess. A version of this text has -been released on-line as the Lua Technical Note 012, hence -the name of the corresponding LuaSocket module, LTN12. Wim -Couwenberg contributed to the implementation of the module, -and Adrian Sietsma was the first to notice the -correspondence between sources and Lua iterators. - - -\end{document} diff --git a/gem/luasocket.png b/gem/luasocket.png deleted file mode 100644 index d24a954..0000000 Binary files a/gem/luasocket.png and /dev/null differ diff --git a/gem/makefile b/gem/makefile deleted file mode 100644 index a4287c2..0000000 --- a/gem/makefile +++ /dev/null @@ -1,14 +0,0 @@ -ltn012.pdf: ltn012.ps - ./myps2pdf ltn012.ps - -ltn012.ps: ltn012.dvi - dvips -G0 -t letter -o ltn012.ps ltn012.dvi - -ltn012.dvi: ltn012.tex - latex ltn012 - -clean: - rm -f *~ *.log *.aux *.bbl *.blg ltn012.pdf ltn012.ps ltn012.dvi ltn012.lof ltn012.toc ltn012.lot - -pdf: ltn012.pdf - open ltn012.pdf diff --git a/gem/myps2pdf b/gem/myps2pdf deleted file mode 100755 index 78c23e5..0000000 --- a/gem/myps2pdf +++ /dev/null @@ -1,113 +0,0 @@ -#!/bin/sh - -do_opt=1 -best=0 -rot=0 -a4=0 -eps=0 -usage="Usage: $0 [-no_opt] [-best] [-rot] [-a4] [-eps] in.ps [out.pdf]" - -case "x$1" in -"x-no_opt") do_opt=0 ; shift ;; -esac - -case "x$1" in -"x-best") best=1 ; shift ;; -esac - -case "x$1" in -"x-rot") rot=1 ; shift ;; -esac - -case "x$1" in -"x-a4") a4=1 ; shift ;; -esac - -case "x$1" in -"x-eps") eps=1 ; shift ;; -esac - -case $# in -2) ifilename=$1 ; ofilename=$2 ;; -1) ifilename=$1 - if `echo $1 | grep -i '\.e*ps$' > /dev/null` - then - ofilename=`echo $1 | sed 's/\..*$/.pdf/'` - else - echo "$usage" 1>&2 - exit 1 - fi ;; -*) echo "$usage" 1>&2 ; exit 1 ;; -esac - -if [ $best == 1 ] -then - options="-dPDFSETTINGS=/prepress \ - -r1200 \ - -dMonoImageResolution=1200 \ - -dGrayImageResolution=1200 \ - -dColorImageResolution=1200 \ - -dDownsampleMonoImages=false \ - -dDownsampleGrayImages=false \ - -dDownsampleColorImages=false \ - -dAutoFilterMonoImages=false \ - -dAutoFilterGrayImages=false \ - -dAutoFilterColorImages=false \ - -dMonoImageFilter=/FlateEncode \ - -dGrayImageFilter=/FlateEncode \ - -dColorImageFilter=/FlateEncode" -else - options="-dPDFSETTINGS=/prepress \ - -r600 \ - -dDownsampleMonoImages=true \ - -dDownsampleGrayImages=true \ - -dDownsampleColorImages=true \ - -dMonoImageDownsampleThreshold=2.0 \ - -dGrayImageDownsampleThreshold=1.5 \ - -dColorImageDownsampleThreshold=1.5 \ - -dMonoImageResolution=600 \ - -dGrayImageResolution=600 \ - -dColorImageResolution=600 \ - -dAutoFilterMonoImages=false \ - -dMonoImageFilter=/FlateEncode \ - -dAutoFilterGrayImages=true \ - -dAutoFilterColorImages=true" -fi - -if [ $rot == 1 ] -then - options="$options -dAutoRotatePages=/PageByPage" -fi - -if [ $eps == 1 ] -then - options="$options -dEPSCrop" -fi - -set -x - -if [ $a4 == 1 ] -then - # Resize from A4 to letter size - psresize -Pa4 -pletter "$ifilename" myps2pdf.temp.ps - ifilename=myps2pdf.temp.ps -fi - -gs -q -dSAFER -dNOPAUSE -dBATCH \ - -sDEVICE=pdfwrite -sPAPERSIZE=letter -sOutputFile=myps2pdf.temp.pdf \ - -dCompatibilityLevel=1.3 \ - $options \ - -dMaxSubsetPct=100 \ - -dSubsetFonts=true \ - -dEmbedAllFonts=true \ - -dColorConversionStrategy=/LeaveColorUnchanged \ - -dDoThumbnails=true \ - -dPreserveEPSInfo=true \ - -c .setpdfwrite -f "$ifilename" - -if [ $do_opt == 1 ] -then - pdfopt myps2pdf.temp.pdf $ofilename -else - mv myps2pdf.temp.pdf $ofilename -fi -rm -f myps2pdf.temp.pdf myps2pdf.temp.ps diff --git a/gem/t1.lua b/gem/t1.lua deleted file mode 100644 index 0c054c9..0000000 --- a/gem/t1.lua +++ /dev/null @@ -1,25 +0,0 @@ -source = {} -sink = {} -pump = {} -filter = {} - --- source.chain -dofile("ex6.lua") - --- source.file -dofile("ex5.lua") - --- normalize -require"gem" -eol = gem.eol -dofile("ex2.lua") - --- sink.file -require"ltn12" -sink.file = ltn12.sink.file - --- pump.all -dofile("ex10.lua") - --- run test -dofile("ex1.lua") diff --git a/gem/t1lf.txt b/gem/t1lf.txt deleted file mode 100644 index 8cddd1b..0000000 --- a/gem/t1lf.txt +++ /dev/null @@ -1,5 +0,0 @@ -this is a test file -it should have been saved as lf eol -but t1.lua will convert it to crlf eol -otherwise it is broken! - diff --git a/gem/t2.lua b/gem/t2.lua deleted file mode 100644 index a81ed73..0000000 --- a/gem/t2.lua +++ /dev/null @@ -1,36 +0,0 @@ -source = {} -sink = {} -pump = {} -filter = {} - --- filter.chain -dofile("ex3.lua") - --- normalize -require"gem" -eol = gem.eol -dofile("ex2.lua") - --- encode -require"mime" -encode = mime.encode - --- wrap -wrap = mime.wrap - --- source.chain -dofile("ex6.lua") - --- source.file -dofile("ex5.lua") - --- sink.file -require"ltn12" -sink.file = ltn12.sink.file - --- pump.all -dofile("ex10.lua") - --- run test -CRLF = "\013\010" -dofile("ex4.lua") diff --git a/gem/t2.txt b/gem/t2.txt deleted file mode 100644 index f484fe8..0000000 --- a/gem/t2.txt +++ /dev/null @@ -1,4 +0,0 @@ -esse é um texto com acentos -quoted-printable tem que quebrar linhas longas, com mais que 76 linhas de texto -fora que as quebras de linhas têm que ser normalizadas -vamos ver o que dá isso aqui diff --git a/gem/t2gt.qp b/gem/t2gt.qp deleted file mode 100644 index 355a845..0000000 --- a/gem/t2gt.qp +++ /dev/null @@ -1,5 +0,0 @@ -esse =E9 um texto com acentos -quoted-printable tem que quebrar linhas longas, com mais que 76 linhas de t= -exto -fora que as quebras de linhas t=EAm que ser normalizadas -vamos ver o que d=E1 isso aqui diff --git a/gem/t3.lua b/gem/t3.lua deleted file mode 100644 index 4bb98ba..0000000 --- a/gem/t3.lua +++ /dev/null @@ -1,25 +0,0 @@ -source = {} -sink = {} -pump = {} -filter = {} - --- source.file -dofile("ex5.lua") - --- sink.table -dofile("ex7.lua") - --- sink.chain -require"ltn12" -sink.chain = ltn12.sink.chain - --- normalize -require"gem" -eol = gem.eol -dofile("ex2.lua") - --- pump.all -dofile("ex10.lua") - --- run test -dofile("ex8.lua") diff --git a/gem/t4.lua b/gem/t4.lua deleted file mode 100644 index 8b8071c..0000000 --- a/gem/t4.lua +++ /dev/null @@ -1,10 +0,0 @@ -source = {} -sink = {} -pump = {} -filter = {} - --- source.file -dofile("ex5.lua") - --- run test -dofile("ex9.lua") diff --git a/gem/t5.lua b/gem/t5.lua deleted file mode 100644 index 7c569ea..0000000 --- a/gem/t5.lua +++ /dev/null @@ -1,30 +0,0 @@ -source = {} -sink = {} -pump = {} -filter = {} - --- source.chain -dofile("ex6.lua") - --- source.file -dofile("ex5.lua") - --- encode -require"mime" -encode = mime.encode - --- sink.chain -require"ltn12" -sink.chain = ltn12.sink.chain - --- wrap -wrap = mime.wrap - --- sink.file -sink.file = ltn12.sink.file - --- pump.all -dofile("ex10.lua") - --- run test -dofile("ex11.lua") diff --git a/gem/test.lua b/gem/test.lua deleted file mode 100644 index a937b9a..0000000 --- a/gem/test.lua +++ /dev/null @@ -1,46 +0,0 @@ -function readfile(n) - local f = io.open(n, "rb") - local s = f:read("*a") - f:close() - return s -end - -lf = readfile("t1lf.txt") -os.remove("t1crlf.txt") -os.execute("lua t1.lua < t1lf.txt > t1crlf.txt") -crlf = readfile("t1crlf.txt") -assert(crlf == string.gsub(lf, "\010", "\013\010"), "broken") - -gt = readfile("t2gt.qp") -os.remove("t2.qp") -os.execute("lua t2.lua < t2.txt > t2.qp") -t2 = readfile("t2.qp") -assert(gt == t2, "broken") - -os.remove("t1crlf.txt") -os.execute("lua t3.lua < t1lf.txt > t1crlf.txt") -crlf = readfile("t1crlf.txt") -assert(crlf == string.gsub(lf, "\010", "\013\010"), "broken") - -t = readfile("test.lua") -os.execute("lua t4.lua < test.lua > t") -t2 = readfile("t") -assert(t == t2, "broken") - -os.remove("output.b64") -gt = readfile("gt.b64") -os.execute("lua t5.lua") -t5 = readfile("output.b64") -assert(gt == t5, "failed") - -print("1 2 5 6 10 passed") -print("2 3 4 5 6 10 passed") -print("2 5 6 7 8 10 passed") -print("5 9 passed") -print("5 6 10 11 passed") - -os.remove("t") -os.remove("t2.qp") -os.remove("t1crlf.txt") -os.remove("t11.b64") -os.remove("output.b64") diff --git a/linux.cmd b/linux.cmd index bd59adc..6c6636b 100644 --- a/linux.cmd +++ b/linux.cmd @@ -1 +1 @@ -make PLAT=linux DEBUG=DEBUG LUAINC_linux_base=/home/diego/build/linux/include LUAPREFIX_linux=/home/diego/build/linux +make PLAT=linux DEBUG=DEBUG LUAINC_linux_base=/home/diego/build/ubuntu/include LUAPREFIX_linux=/home/diego/build/ubuntu diff --git a/ltn012.md b/ltn012.md new file mode 100644 index 0000000..fa26b4a --- /dev/null +++ b/ltn012.md @@ -0,0 +1,390 @@ +# Filters, sources and sinks: design, motivation and examples +### or Functional programming for the rest of us +by DiegoNehab + +## Abstract + +Certain operations can be implemented in the form of filters. A filter is a function that processes data received in consecutive function calls, returning partial results chunk by chunk. Examples of operations that can be implemented as filters include the end-of-line normalization for text, Base64 and Quoted-Printable transfer content encodings, the breaking of text into lines, SMTP byte stuffing, and there are many others. Filters become even more powerful when we allow them to be chained together to create composite filters. Filters can be seen as middle nodes in a chain of data transformations. Sources an sinks are the corresponding end points of these chains. A source is a function that produces data, chunk by chunk, and a sink is a function that takes data, chunk by chunk. In this technical note, we define an elegant interface for filters, sources, sinks and chaining. We evolve our interface progressively, until we reach a high degree of generality. We discuss difficulties that arise during the implementation of this interface and we provide solutions and examples. + +## Introduction + +Applications sometimes have too much information to process to fit in memory and are thus forced to process data in smaller parts. Even when there is enough memory, processing all the data atomically may take long enough to frustrate a user that wants to interact with the application. Furthermore, complex transformations can often be defined as series of simpler operations. Several different complex transformations might share the same simpler operations, so that an uniform interface to combine them is desirable. The following concepts constitute our solution to these problems. + +"Filters" are functions that accept successive chunks of input, and produce successive chunks of output. Furthermore, the result of concatenating all the output data is the same as the result of applying the filter over the concatenation of the input data. As a consequence, boundaries are irrelevant: filters have to handle input data split arbitrarily by the user. + +A "chain" is a function that combines the effect of two (or more) other functions, but whose interface is indistinguishable from the interface of one of its components. Thus, a chained filter can be used wherever an atomic filter can be used. However, its effect on data is the combined effect of its component filters. Note that, as a consequence, chains can be chained themselves to create arbitrarily complex operations that can be used just like atomic operations. + +Filters can be seen as internal nodes in a network through which data flows, potentially being transformed along its way. Chains connect these nodes together. To complete the picture, we need "sources" and "sinks" as initial and final nodes of the network, respectively. Less abstractly, a source is a function that produces new data every time it is called. On the other hand, sinks are functions that give a final destination to the data they receive. Naturally, sources and sinks can be chained with filters. + +Finally, filters, chains, sources, and sinks are all passive entities: they need to be repeatedly called in order for something to happen. "Pumps" provide the driving force that pushes data through the network, from a source to a sink. + + Hopefully, these concepts will become clear with examples. In the following sections, we start with simplified interfaces, which we improve several times until we can find no obvious shortcomings. The evolution we present is not contrived: it follows the steps we followed ourselves as we consolidated our understanding of these concepts. + +### A concrete example + +Some data transformations are easier to implement as filters than others. Examples of operations that can be implemented as filters include the end-of-line normalization for text, the Base64 and Quoted-Printable transfer content encodings, the breaking of text into lines, SMTP byte stuffing, and many others. Let's use the end-of-line normalization as an example to define our initial filter interface. We later discuss why the implementation might not be trivial. + +Assume we are given text in an unknown end-of-line convention (including possibly mixed conventions) out of the commonly found Unix (LF), Mac OS (CR), and DOS (CRLF) conventions. We would like to be able to write code like the following: +```lua +input = source.chain(source.file(io.stdin), normalize("\r\n")) +output = sink.file(io.stdout) +pump(input, output) +``` + +This program should read data from the standard input stream and normalize the end-of-line markers to the canonic CRLF marker defined by the MIME standard, finally sending the results to the standard output stream. For that, we use a "file source" to produce data from standard input, and chain it with a filter that normalizes the data. The pump then repeatedly gets data from the source, and moves it to the "file sink" that sends it to standard output. + +To make the discussion even more concrete, we start by discussing the implementation of the normalization filter. The `normalize` "factory" is a function that creates such a filter. Our initial filter interface is as follows: the filter receives a chunk of input data, and returns a chunk of processed data. When there is no more input data, the user notifies the filter by invoking it with a `nil` chunk. The filter then returns the final chunk of processed data. + +Although the interface is extremely simple, the implementation doesn't seem so obvious. Any filter respecting this interface needs to keep some kind of context between calls. This is because chunks can be broken between the CR and LF characters marking the end of a line. This need for context storage is what motivates the use of factories: each time the factory is called, it returns a filter with its own context so that we can have several independent filters being used at the same time. For the normalization filter, we know that the obvious solution (i.e. concatenating all the input into the context before producing any output) is not good enough, so we will have to find another way. + +We will break the implementation in two parts: a low-level filter, and a factory of high-level filters. The low-level filter will be implemented in C and will not carry any context between function calls. The high-level filter factory, implemented in Lua, will create and return a high-level filter that keeps whatever context the low-level filter needs, but isolates the user from its internal details. That way, we take advantage of C's efficiency to perform the dirty work, and take advantage of Lua's simplicity for the bookkeeping. + +### The Lua part of the implementation + +Below is the implementation of the factory of high-level end-of-line normalization filters: +```lua +function filter.cycle(low, ctx, extra) + return function(chunk) + local ret + ret, ctx = low(ctx, chunk, extra) + return ret + end +end + +function normalize(marker) + return cycle(eol, 0, marker) +end +``` + +The `normalize` factory simply calls a more generic factory, the `cycle` factory. This factory receives a low-level filter, an initial context and some extra value and returns the corresponding high-level filter. Each time the high level filer is called with a new chunk, it calls the low-level filter passing the previous context, the new chunk and the extra argument. The low-level filter produces the chunk of processed data and a new context. Finally, the high-level filter updates its internal context and returns the processed chunk of data to the user. It is the low-level filter that does all the work. Notice that this implementation takes advantage of the Lua 5.0 lexical scoping rules to store the context locally, between function calls. + +Moving to the low-level filter, we notice there is no perfect solution to the end-of-line marker normalization problem itself. The difficulty comes from an inherent ambiguity on the definition of empty lines within mixed input. However, the following solution works well for any consistent input, as well as for non-empty lines in mixed input. It also does a reasonable job with empty lines and serves as a good example of how to implement a low-level filter. + +Here is what we do: CR and LF are considered candidates for line break. We issue "one" end-of-line line marker if one of the candidates is seen alone, or followed by a "different" candidate. That is, CR CR and LF LF issue two end of line markers each, but CR LF and LF CR issue only one marker. This idea takes care of Mac OS, Mac OS X, VMS and Unix, DOS and MIME, as well as probably other more obscure conventions. + +### The C part of the implementation + +The low-level filter is divided into two simple functions. The inner function actually does the conversion. It takes each input character in turn, deciding what to output and how to modify the context. The context tells if the last character seen was a candidate and, if so, which candidate it was. +```c +#define candidate(c) (c == CR || c == LF) +static int process(int c, int last, const char *marker, luaL_Buffer *buffer) { + if (candidate(c)) { + if (candidate(last)) { + if (c == last) luaL_addstring(buffer, marker); + return 0; + } else { + luaL_addstring(buffer, marker); + return c; + } + } else { + luaL_putchar(buffer, c); + return 0; + } +} +``` + +The inner function makes use of Lua's auxiliary library's buffer interface for its efficiency and ease of use. The outer function simply interfaces with Lua. It receives the context and the input chunk (as well as an optional end-of-line marker), and returns the transformed output and the new context. +```c +static int eol(lua_State *L) { + int ctx = luaL_checkint(L, 1); + size_t isize = 0; + const char *input = luaL_optlstring(L, 2, NULL, &isize); + const char *last = input + isize; + const char *marker = luaL_optstring(L, 3, CRLF); + luaL_Buffer buffer; + luaL_buffinit(L, &buffer); + if (!input) { + lua_pushnil(L); + lua_pushnumber(L, 0); + return 2; + } + while (input < last) + ctx = process(*input++, ctx, marker, &buffer); + luaL_pushresult(&buffer); + lua_pushnumber(L, ctx); + return 2; +} +``` + +Notice that if the input chunk is `nil`, the operation is considered to be finished. In that case, the loop will not execute a single time and the context is reset to the initial state. This allows the filter to be reused indefinitely. It is a good idea to write filters like this, when possible. + +Besides the end-of-line normalization filter shown above, many other filters can be implemented with the same ideas. Examples include Base64 and Quoted-Printable transfer content encodings, the breaking of text into lines, SMTP byte stuffing etc. The challenging part is to decide what will be the context. For line breaking, for instance, it could be the number of bytes left in the current line. For Base64 encoding, it could be the bytes that remain in the division of the input into 3-byte atoms. + +## Chaining + +Filters become more powerful when the concept of chaining is introduced. Suppose you have a filter for Quoted-Printable encoding and you want to encode some text. According to the standard, the text has to be normalized into its canonic form prior to encoding. A nice interface that simplifies this task is a factory that creates a composite filter that passes data through multiple filters, but that can be used wherever a primitive filter is used. +```lua +local function chain2(f1, f2) + return function(chunk) + local ret = f2(f1(chunk)) + if chunk then return ret + else return ret .. f2() end + end +end + +function filter.chain(...) + local arg = {...} + local f = arg[1] + for i = 2, #arg do + f = chain2(f, arg[i]) + end + return f +end + +local chain = filter.chain(normalize("\r\n"), encode("quoted-printable")) +while 1 do + local chunk = io.read(2048) + io.write(chain(chunk)) + if not chunk then break end +end +``` + +The chaining factory is very simple. All it does is return a function that passes data through all filters and returns the result to the user. It uses the simpler auxiliary function that knows how to chain two filters together. In the auxiliary function, special care must be taken if the chunk is final. This is because the final chunk notification has to be pushed through both filters in turn. Thanks to the chain factory, it is easy to perform the Quoted-Printable conversion, as the above example shows. + +## Sources, sinks, and pumps + +As we noted in the introduction, the filters we introduced so far act as the internal nodes in a network of transformations. Information flows from node to node (or rather from one filter to the next) and is transformed on its way out. Chaining filters together is the way we found to connect nodes in the network. But what about the end nodes? In the beginning of the network, we need a node that provides the data, a source. In the end of the network, we need a node that takes in the data, a sink. + +### Sources + +We start with two simple sources. The first is the `empty` source: It simply returns no data, possibly returning an error message. The second is the `file` source, which produces the contents of a file in a chunk by chunk fashion, closing the file handle when done. +```lua +function source.empty(err) + return function() + return nil, err + end +end + +function source.file(handle, io_err) + if handle then + return function() + local chunk = handle:read(2048) + if not chunk then handle:close() end + return chunk + end + else return source.empty(io_err or "unable to open file") end +end +``` + +A source returns the next chunk of data each time it is called. When there is no more data, it just returns `nil`. If there is an error, the source can inform the caller by returning `nil` followed by an error message. Adrian Sietsma noticed that, although not on purpose, the interface for sources is compatible with the idea of iterators in Lua 5.0. That is, a data source can be nicely used in conjunction with `for` loops. Using our file source as an iterator, we can rewrite our first example: +```lua +local process = normalize("\r\n") +for chunk in source.file(io.stdin) do + io.write(process(chunk)) +end +io.write(process(nil)) +``` + +Notice that the last call to the filter obtains the last chunk of processed data. The loop terminates when the source returns `nil` and therefore we need that final call outside of the loop. + +### Maintaining state between calls + +It is often the case that a source needs to change its behavior after some event. One simple example would be a file source that wants to make sure it returns `nil` regardless of how many times it is called after the end of file, avoiding attempts to read past the end of the file. Another example would be a source that returns the contents of several files, as if they were concatenated, moving from one file to the next until the end of the last file is reached. + +One way to implement this kind of source is to have the factory declare extra state variables that the source can use via lexical scoping. Our file source could set the file handle itself to `nil` when it detects the end-of-file. Then, every time the source is called, it could check if the handle is still valid and act accordingly: +```lua +function source.file(handle, io_err) + if handle then + return function() + if not handle then return nil end + local chunk = handle:read(2048) + if not chunk then + handle:close() + handle = nil + end + return chunk + end + else return source.empty(io_err or "unable to open file") end +end +``` + +Another way to implement this behavior involves a change in the source interface to makes it more flexible. Let's allow a source to return a second value, besides the next chunk of data. If the returned chunk is `nil`, the extra return value tells us what happened. A second `nil` means that there is just no more data and the source is empty. Any other value is considered to be an error message. On the other hand, if the chunk was "not" `nil`, the second return value tells us whether the source wants to be replaced. If it is `nil`, we should proceed using the same source. Otherwise it has to be another source, which we have to use from then on, to get the remaining data. + +This extra freedom is good for someone writing a source function, but it is a pain for those that have to use it. Fortunately, given one of these "fancy" sources, we can transform it into a simple source that never needs to be replaced, using the following factory. +```lua +function source.simplify(src) + return function() + local chunk, err_or_new = src() + src = err_or_new or src + if not chunk then return nil, err_or_new + else return chunk end + end +end +``` + +The simplification factory allows us to write fancy sources and use them as if they were simple. Therefore, our next functions will only produce simple sources, and functions that take sources will assume they are simple. + +Going back to our file source, the extended interface allows for a more elegant implementation. The new source just asks to be replaced by an empty source as soon as there is no more data. There is no repeated checking of the handle. To make things simpler to the user, the factory itself simplifies the the fancy file source before returning it to the user: +```lua +function source.file(handle, io_err) + if handle then + return source.simplify(function() + local chunk = handle:read(2048) + if not chunk then + handle:close() + return "", source.empty() + end + return chunk + end) + else return source.empty(io_err or "unable to open file") end +end +``` + +We can make these ideas even more powerful if we use a new feature of Lua 5.0: coroutines. Coroutines suffer from a great lack of advertisement, and I am going to play my part here. Just like lexical scoping, coroutines taste odd at first, but once you get used with the concept, it can save your day. I have to admit that using coroutines to implement our file source would be overkill, so let's implement a concatenated source factory instead. +```lua +function source.cat(...) + local arg = {...} + local co = coroutine.create(function() + local i = 1 + while i <= #arg do + local chunk, err = arg[i]() + if chunk then coroutine.yield(chunk) + elseif err then return nil, err + else i = i + 1 end + end + end) + return function() + return shift(coroutine.resume(co)) + end +end +``` + +The factory creates two functions. The first is an auxiliary that does all the work, in the form of a coroutine. It reads a chunk from one of the sources. If the chunk is `nil`, it moves to the next source, otherwise it just yields returning the chunk. When it is resumed, it continues from where it stopped and tries to read the next chunk. The second function is the source itself, and just resumes the execution of the auxiliary coroutine, returning to the user whatever chunks it returns (skipping the first result that tells us if the coroutine terminated). Imagine writing the same function without coroutines and you will notice the simplicity of this implementation. We will use coroutines again when we make the filter interface more powerful. + +### Chaining Sources + +What does it mean to chain a source with a filter? The most useful interpretation is that the combined source-filter is a new source that produces data and passes it through the filter before returning it. Here is a factory that does it: +```lua +function source.chain(src, f) + return source.simplify(function() + local chunk, err = src() + if not chunk then return f(nil), source.empty(err) + else return f(chunk) end + end) +end +``` + +Our motivating example in the introduction chains a source with a filter. The idea of chaining a source with a filter is useful when one thinks about functions that might get their input data from a source. By chaining a simple source with one or more filters, the same function can be provided with filtered data even though it is unaware of the filtering that is happening behind its back. + +### Sinks + +Just as we defined an interface for an initial source of data, we can also define an interface for a final destination of data. We call any function respecting that interface a "sink". Below are two simple factories that return sinks. The table factory creates a sink that stores all obtained data into a table. The data can later be efficiently concatenated into a single string with the `table.concat` library function. As another example, we introduce the `null` sink: A sink that simply discards the data it receives. +```lua +function sink.table(t) + t = t or {} + local f = function(chunk, err) + if chunk then table.insert(t, chunk) end + return 1 + end + return f, t +end + +local function null() + return 1 +end + +function sink.null() + return null +end +``` + +Sinks receive consecutive chunks of data, until the end of data is notified with a `nil` chunk. An error is notified by an extra argument giving an error message after the `nil` chunk. If a sink detects an error itself and wishes not to be called again, it should return `nil`, optionally followed by an error message. A return value that is not `nil` means the source will accept more data. Finally, just as sources can choose to be replaced, so can sinks, following the same interface. Once again, it is easy to implement a `sink.simplify` factory that transforms a fancy sink into a simple sink. + +As an example, let's create a source that reads from the standard input, then chain it with a filter that normalizes the end-of-line convention and let's use a sink to place all data into a table, printing the result in the end. +```lua +local load = source.chain(source.file(io.stdin), normalize("\r\n")) +local store, t = sink.table() +while 1 do + local chunk = load() + store(chunk) + if not chunk then break end +end +print(table.concat(t)) +``` + +Again, just as we created a factory that produces a chained source-filter from a source and a filter, it is easy to create a factory that produces a new sink given a sink and a filter. The new sink passes all data it receives through the filter before handing it in to the original sink. Here is the implementation: +```lua +function sink.chain(f, snk) + return function(chunk, err) + local r, e = snk(f(chunk)) + if not r then return nil, e end + if not chunk then return snk(nil, err) end + return 1 + end +end +``` + +### Pumps + +There is a while loop that has been around for too long in our examples. It's always there because everything that we designed so far is passive. Sources, sinks, filters: None of them will do anything on their own. The operation of pumping all data a source can provide into a sink is so common that we will provide a couple helper functions to do that for us. +```lua +function pump.step(src, snk) + local chunk, src_err = src() + local ret, snk_err = snk(chunk, src_err) + return chunk and ret and not src_err and not snk_err, src_err or snk_err +end + +function pump.all(src, snk, step) + step = step or pump.step + while true do + local ret, err = step(src, snk) + if not ret then return not err, err end + end +end +``` + +The `pump.step` function moves one chunk of data from the source to the sink. The `pump.all` function takes an optional `step` function and uses it to pump all the data from the source to the sink. We can now use everything we have to write a program that reads a binary file from disk and stores it in another file, after encoding it to the Base64 transfer content encoding: +```lua +local load = source.chain( + source.file(io.open("input.bin", "rb")), + encode("base64") +) +local store = sink.chain( + wrap(76), + sink.file(io.open("output.b64", "w")), +) +pump.all(load, store) +``` + +The way we split the filters here is not intuitive, on purpose. Alternatively, we could have chained the Base64 encode filter and the line-wrap filter together, and then chain the resulting filter with either the file source or the file sink. It doesn't really matter. + +## One last important change + +Turns out we still have a problem. When David Burgess was writing his gzip filter, he noticed that the decompression filter can explode a small input chunk into a huge amount of data. Although we wished we could ignore this problem, we soon agreed we couldn't. The only solution is to allow filters to return partial results, and that is what we chose to do. After invoking the filter to pass input data, the user now has to loop invoking the filter to find out if it has more output data to return. Note that these extra calls can't pass more data to the filter. + +More specifically, after passing a chunk of input data to a filter and collecting the first chunk of output data, the user invokes the filter repeatedly, passing the empty string, to get extra output chunks. When the filter itself returns an empty string, the user knows there is no more output data, and can proceed to pass the next input chunk. In the end, after the user passes a `nil` notifying the filter that there is no more input data, the filter might still have produced too much output data to return in a single chunk. The user has to loop again, this time passing `nil` each time, until the filter itself returns `nil` to notify the user it is finally done. + +Most filters won't need this extra freedom. Fortunately, the new filter interface is easy to implement. In fact, the end-of-line translation filter we created in the introduction already conforms to it. On the other hand, the chaining function becomes much more complicated. If it wasn't for coroutines, I wouldn't be happy to implement it. Let me know if you can find a simpler implementation that does not use coroutines! +```lua +local function chain2(f1, f2) + local co = coroutine.create(function(chunk) + while true do + local filtered1 = f1(chunk) + local filtered2 = f2(filtered1) + local done2 = filtered1 and "" + while true do + if filtered2 == "" or filtered2 == nil then break end + coroutine.yield(filtered2) + filtered2 = f2(done2) + end + if filtered1 == "" then chunk = coroutine.yield(filtered1) + elseif filtered1 == nil then return nil + else chunk = chunk and "" end + end + end) + return function(chunk) + local _, res = coroutine.resume(co, chunk) + return res + end +end +``` + +Chaining sources also becomes more complicated, but a similar solution is possible with coroutines. Chaining sinks is just as simple as it has always been. Interestingly, these modifications do not have a measurable negative impact in the the performance of filters that didn't need the added flexibility. They do severely improve the efficiency of filters like the gzip filter, though, and that is why we are keeping them. + +## Final considerations + +These ideas were created during the development of [LuaSocket](https://github.com/lunarmodules/luasocket) 2.0, and are available as the LTN12 module. As a result, [LuaSocket](https://github.com/lunarmodules/luasocket) implementation was greatly simplified and became much more powerful. The MIME module is especially integrated to LTN12 and provides many other filters. We felt these concepts deserved to be made public even to those that don't care about [LuaSocket](https://github.com/lunarmodules/luasocket), hence the LTN. + +One extra application that deserves mentioning makes use of an identity filter. Suppose you want to provide some feedback to the user while a file is being downloaded into a sink. Chaining the sink with an identity filter (a filter that simply returns the received data unaltered), you can update a progress counter on the fly. The original sink doesn't have to be modified. Another interesting idea is that of a T sink: A sink that sends data to two other sinks. In summary, there appears to be enough room for many other interesting ideas. + +In this technical note we introduced filters, sources, sinks, and pumps. These are useful tools for data processing in general. Sources provide a simple abstraction for data acquisition. Sinks provide an abstraction for final data destinations. Filters define an interface for data transformations. The chaining of filters, sources and sinks provides an elegant way to create arbitrarily complex data transformation from simpler transformations. Pumps just put the machinery to work. diff --git a/ltn012.wiki b/ltn012.wiki deleted file mode 100644 index 96b13ae..0000000 --- a/ltn012.wiki +++ /dev/null @@ -1,393 +0,0 @@ -===Filters, sources and sinks: design, motivation and examples=== -==or Functional programming for the rest of us== -by DiegoNehab - -{{{ - -}}} - -===Abstract=== -Certain operations can be implemented in the form of filters. A filter is a function that processes data received in consecutive function calls, returning partial results chunk by chunk. Examples of operations that can be implemented as filters include the end-of-line normalization for text, Base64 and Quoted-Printable transfer content encodings, the breaking of text into lines, SMTP byte stuffing, and there are many others. Filters become even more powerful when we allow them to be chained together to create composite filters. Filters can be seen as middle nodes in a chain of data transformations. Sources an sinks are the corresponding end points of these chains. A source is a function that produces data, chunk by chunk, and a sink is a function that takes data, chunk by chunk. In this technical note, we define an elegant interface for filters, sources, sinks and chaining. We evolve our interface progressively, until we reach a high degree of generality. We discuss difficulties that arise during the implementation of this interface and we provide solutions and examples. - -===Introduction=== - -Applications sometimes have too much information to process to fit in memory and are thus forced to process data in smaller parts. Even when there is enough memory, processing all the data atomically may take long enough to frustrate a user that wants to interact with the application. Furthermore, complex transformations can often be defined as series of simpler operations. Several different complex transformations might share the same simpler operations, so that an uniform interface to combine them is desirable. The following concepts constitute our solution to these problems. - -''Filters'' are functions that accept successive chunks of input, and produce successive chunks of output. Furthermore, the result of concatenating all the output data is the same as the result of applying the filter over the concatenation of the input data. As a consequence, boundaries are irrelevant: filters have to handle input data split arbitrarily by the user. - -A ''chain'' is a function that combines the effect of two (or more) other functions, but whose interface is indistinguishable from the interface of one of its components. Thus, a chained filter can be used wherever an atomic filter can be used. However, its effect on data is the combined effect of its component filters. Note that, as a consequence, chains can be chained themselves to create arbitrarily complex operations that can be used just like atomic operations. - -Filters can be seen as internal nodes in a network through which data flows, potentially being transformed along its way. Chains connect these nodes together. To complete the picture, we need ''sources'' and ''sinks'' as initial and final nodes of the network, respectively. Less abstractly, a source is a function that produces new data every time it is called. On the other hand, sinks are functions that give a final destination to the data they receive. Naturally, sources and sinks can be chained with filters. - -Finally, filters, chains, sources, and sinks are all passive entities: they need to be repeatedly called in order for something to happen. ''Pumps'' provide the driving force that pushes data through the network, from a source to a sink. - - Hopefully, these concepts will become clear with examples. In the following sections, we start with simplified interfaces, which we improve several times until we can find no obvious shortcomings. The evolution we present is not contrived: it follows the steps we followed ourselves as we consolidated our understanding of these concepts. - -== A concrete example == - -Some data transformations are easier to implement as filters than others. Examples of operations that can be implemented as filters include the end-of-line normalization for text, the Base64 and Quoted-Printable transfer content encodings, the breaking of text into lines, SMTP byte stuffing, and many others. Let's use the end-of-line normalization as an example to define our initial filter interface. We later discuss why the implementation might not be trivial. - -Assume we are given text in an unknown end-of-line convention (including possibly mixed conventions) out of the commonly found Unix (LF), Mac OS (CR), and DOS (CRLF) conventions. We would like to be able to write code like the following: - {{{ -input = source.chain(source.file(io.stdin), normalize("\r\n")) -output = sink.file(io.stdout) -pump(input, output) -}}} - -This program should read data from the standard input stream and normalize the end-of-line markers to the canonic CRLF marker defined by the MIME standard, finally sending the results to the standard output stream. For that, we use a ''file source'' to produce data from standard input, and chain it with a filter that normalizes the data. The pump then repeatedly gets data from the source, and moves it to the ''file sink'' that sends it to standard output. - -To make the discussion even more concrete, we start by discussing the implementation of the normalization filter. The {{normalize}} ''factory'' is a function that creates such a filter. Our initial filter interface is as follows: the filter receives a chunk of input data, and returns a chunk of processed data. When there is no more input data, the user notifies the filter by invoking it with a {{nil}} chunk. The filter then returns the final chunk of processed data. - -Although the interface is extremely simple, the implementation doesn't seem so obvious. Any filter respecting this interface needs to keep some kind of context between calls. This is because chunks can be broken between the CR and LF characters marking the end of a line. This need for context storage is what motivates the use of factories: each time the factory is called, it returns a filter with its own context so that we can have several independent filters being used at the same time. For the normalization filter, we know that the obvious solution (i.e. concatenating all the input into the context before producing any output) is not good enough, so we will have to find another way. - -We will break the implementation in two parts: a low-level filter, and a factory of high-level filters. The low-level filter will be implemented in C and will not carry any context between function calls. The high-level filter factory, implemented in Lua, will create and return a high-level filter that keeps whatever context the low-level filter needs, but isolates the user from its internal details. That way, we take advantage of C's efficiency to perform the dirty work, and take advantage of Lua's simplicity for the bookkeeping. - -==The Lua part of the implementation== - -Below is the implementation of the factory of high-level end-of-line normalization filters: - {{{ -function filter.cycle(low, ctx, extra) - return function(chunk) - local ret - ret, ctx = low(ctx, chunk, extra) - return ret - end -end - -function normalize(marker) - return cycle(eol, 0, marker) -end -}}} - -The {{normalize}} factory simply calls a more generic factory, the {{cycle}} factory. This factory receives a low-level filter, an initial context and some extra value and returns the corresponding high-level filter. Each time the high level filer is called with a new chunk, it calls the low-level filter passing the previous context, the new chunk and the extra argument. The low-level filter produces the chunk of processed data and a new context. Finally, the high-level filter updates its internal context and returns the processed chunk of data to the user. It is the low-level filter that does all the work. Notice that this implementation takes advantage of the Lua 5.0 lexical scoping rules to store the context locally, between function calls. - -Moving to the low-level filter, we notice there is no perfect solution to the end-of-line marker normalization problem itself. The difficulty comes from an inherent ambiguity on the definition of empty lines within mixed input. However, the following solution works well for any consistent input, as well as for non-empty lines in mixed input. It also does a reasonable job with empty lines and serves as a good example of how to implement a low-level filter. - -Here is what we do: CR and LF are considered candidates for line break. We issue ''one'' end-of-line line marker if one of the candidates is seen alone, or followed by a ''different'' candidate. That is, CR CR and LF LF issue two end of line markers each, but CR LF and LF CR issue only one marker. This idea takes care of Mac OS, Mac OS X, VMS and Unix, DOS and MIME, as well as probably other more obscure conventions. - -==The C part of the implementation== - -The low-level filter is divided into two simple functions. The inner function actually does the conversion. It takes each input character in turn, deciding what to output and how to modify the context. The context tells if the last character seen was a candidate and, if so, which candidate it was. - {{{ -#define candidate(c) (c == CR || c == LF) -static int process(int c, int last, const char *marker, luaL_Buffer *buffer) { - if (candidate(c)) { - if (candidate(last)) { - if (c == last) luaL_addstring(buffer, marker); - return 0; - } else { - luaL_addstring(buffer, marker); - return c; - } - } else { - luaL_putchar(buffer, c); - return 0; - } -} -}}} - -The inner function makes use of Lua's auxiliary library's buffer interface for its efficiency and ease of use. The outer function simply interfaces with Lua. It receives the context and the input chunk (as well as an optional end-of-line marker), and returns the transformed output and the new context. - {{{ -static int eol(lua_State *L) { - int ctx = luaL_checkint(L, 1); - size_t isize = 0; - const char *input = luaL_optlstring(L, 2, NULL, &isize); - const char *last = input + isize; - const char *marker = luaL_optstring(L, 3, CRLF); - luaL_Buffer buffer; - luaL_buffinit(L, &buffer); - if (!input) { - lua_pushnil(L); - lua_pushnumber(L, 0); - return 2; - } - while (input < last) - ctx = process(*input++, ctx, marker, &buffer); - luaL_pushresult(&buffer); - lua_pushnumber(L, ctx); - return 2; -} -}}} - -Notice that if the input chunk is {{nil}}, the operation is considered to be finished. In that case, the loop will not execute a single time and the context is reset to the initial state. This allows the filter to be reused indefinitely. It is a good idea to write filters like this, when possible. - -Besides the end-of-line normalization filter shown above, many other filters can be implemented with the same ideas. Examples include Base64 and Quoted-Printable transfer content encodings, the breaking of text into lines, SMTP byte stuffing etc. The challenging part is to decide what will be the context. For line breaking, for instance, it could be the number of bytes left in the current line. For Base64 encoding, it could be the bytes that remain in the division of the input into 3-byte atoms. - -===Chaining=== - -Filters become more powerful when the concept of chaining is introduced. Suppose you have a filter for Quoted-Printable encoding and you want to encode some text. According to the standard, the text has to be normalized into its canonic form prior to encoding. A nice interface that simplifies this task is a factory that creates a composite filter that passes data through multiple filters, but that can be used wherever a primitive filter is used. - {{{ -local function chain2(f1, f2) - return function(chunk) - local ret = f2(f1(chunk)) - if chunk then return ret - else return ret .. f2() end - end -end - -function filter.chain(...) - local arg = {...} - local f = arg[1] - for i = 2, #arg do - f = chain2(f, arg[i]) - end - return f -end - -local chain = filter.chain(normalize("\r\n"), encode("quoted-printable")) -while 1 do - local chunk = io.read(2048) - io.write(chain(chunk)) - if not chunk then break end -end -}}} - -The chaining factory is very simple. All it does is return a function that passes data through all filters and returns the result to the user. It uses the simpler auxiliary function that knows how to chain two filters together. In the auxiliary function, special care must be taken if the chunk is final. This is because the final chunk notification has to be pushed through both filters in turn. Thanks to the chain factory, it is easy to perform the Quoted-Printable conversion, as the above example shows. - -===Sources, sinks, and pumps=== - -As we noted in the introduction, the filters we introduced so far act as the internal nodes in a network of transformations. Information flows from node to node (or rather from one filter to the next) and is transformed on its way out. Chaining filters together is the way we found to connect nodes in the network. But what about the end nodes? In the beginning of the network, we need a node that provides the data, a source. In the end of the network, we need a node that takes in the data, a sink. - -==Sources== - -We start with two simple sources. The first is the {{empty}} source: It simply returns no data, possibly returning an error message. The second is the {{file}} source, which produces the contents of a file in a chunk by chunk fashion, closing the file handle when done. - {{{ -function source.empty(err) - return function() - return nil, err - end -end - -function source.file(handle, io_err) - if handle then - return function() - local chunk = handle:read(2048) - if not chunk then handle:close() end - return chunk - end - else return source.empty(io_err or "unable to open file") end -end -}}} - -A source returns the next chunk of data each time it is called. When there is no more data, it just returns {{nil}}. If there is an error, the source can inform the caller by returning {{nil}} followed by an error message. Adrian Sietsma noticed that, although not on purpose, the interface for sources is compatible with the idea of iterators in Lua 5.0. That is, a data source can be nicely used in conjunction with {{for}} loops. Using our file source as an iterator, we can rewrite our first example: - {{{ -local process = normalize("\r\n") -for chunk in source.file(io.stdin) do - io.write(process(chunk)) -end -io.write(process(nil)) -}}} - -Notice that the last call to the filter obtains the last chunk of processed data. The loop terminates when the source returns {{nil}} and therefore we need that final call outside of the loop. - -==Maintaining state between calls== - -It is often the case that a source needs to change its behavior after some event. One simple example would be a file source that wants to make sure it returns {{nil}} regardless of how many times it is called after the end of file, avoiding attempts to read past the end of the file. Another example would be a source that returns the contents of several files, as if they were concatenated, moving from one file to the next until the end of the last file is reached. - -One way to implement this kind of source is to have the factory declare extra state variables that the source can use via lexical scoping. Our file source could set the file handle itself to {{nil}} when it detects the end-of-file. Then, every time the source is called, it could check if the handle is still valid and act accordingly: - {{{ -function source.file(handle, io_err) - if handle then - return function() - if not handle then return nil end - local chunk = handle:read(2048) - if not chunk then - handle:close() - handle = nil - end - return chunk - end - else return source.empty(io_err or "unable to open file") end -end -}}} - -Another way to implement this behavior involves a change in the source interface to makes it more flexible. Let's allow a source to return a second value, besides the next chunk of data. If the returned chunk is {{nil}}, the extra return value tells us what happened. A second {{nil}} means that there is just no more data and the source is empty. Any other value is considered to be an error message. On the other hand, if the chunk was ''not'' {{nil}}, the second return value tells us whether the source wants to be replaced. If it is {{nil}}, we should proceed using the same source. Otherwise it has to be another source, which we have to use from then on, to get the remaining data. - -This extra freedom is good for someone writing a source function, but it is a pain for those that have to use it. Fortunately, given one of these ''fancy'' sources, we can transform it into a simple source that never needs to be replaced, using the following factory. - {{{ -function source.simplify(src) - return function() - local chunk, err_or_new = src() - src = err_or_new or src - if not chunk then return nil, err_or_new - else return chunk end - end -end -}}} - -The simplification factory allows us to write fancy sources and use them as if they were simple. Therefore, our next functions will only produce simple sources, and functions that take sources will assume they are simple. - -Going back to our file source, the extended interface allows for a more elegant implementation. The new source just asks to be replaced by an empty source as soon as there is no more data. There is no repeated checking of the handle. To make things simpler to the user, the factory itself simplifies the the fancy file source before returning it to the user: - {{{ -function source.file(handle, io_err) - if handle then - return source.simplify(function() - local chunk = handle:read(2048) - if not chunk then - handle:close() - return "", source.empty() - end - return chunk - end) - else return source.empty(io_err or "unable to open file") end -end -}}} - -We can make these ideas even more powerful if we use a new feature of Lua 5.0: coroutines. Coroutines suffer from a great lack of advertisement, and I am going to play my part here. Just like lexical scoping, coroutines taste odd at first, but once you get used with the concept, it can save your day. I have to admit that using coroutines to implement our file source would be overkill, so let's implement a concatenated source factory instead. - {{{ -function source.cat(...) - local arg = {...} - local co = coroutine.create(function() - local i = 1 - while i <= #arg do - local chunk, err = arg[i]() - if chunk then coroutine.yield(chunk) - elseif err then return nil, err - else i = i + 1 end - end - end) - return function() - return shift(coroutine.resume(co)) - end -end -}}} - -The factory creates two functions. The first is an auxiliary that does all the work, in the form of a coroutine. It reads a chunk from one of the sources. If the chunk is {{nil}}, it moves to the next source, otherwise it just yields returning the chunk. When it is resumed, it continues from where it stopped and tries to read the next chunk. The second function is the source itself, and just resumes the execution of the auxiliary coroutine, returning to the user whatever chunks it returns (skipping the first result that tells us if the coroutine terminated). Imagine writing the same function without coroutines and you will notice the simplicity of this implementation. We will use coroutines again when we make the filter interface more powerful. - -==Chaining Sources== - -What does it mean to chain a source with a filter? The most useful interpretation is that the combined source-filter is a new source that produces data and passes it through the filter before returning it. Here is a factory that does it: - {{{ -function source.chain(src, f) - return source.simplify(function() - local chunk, err = src() - if not chunk then return f(nil), source.empty(err) - else return f(chunk) end - end) -end -}}} - -Our motivating example in the introduction chains a source with a filter. The idea of chaining a source with a filter is useful when one thinks about functions that might get their input data from a source. By chaining a simple source with one or more filters, the same function can be provided with filtered data even though it is unaware of the filtering that is happening behind its back. - -==Sinks== - -Just as we defined an interface for an initial source of data, we can also define an interface for a final destination of data. We call any function respecting that interface a ''sink''. Below are two simple factories that return sinks. The table factory creates a sink that stores all obtained data into a table. The data can later be efficiently concatenated into a single string with the {{table.concat}} library function. As another example, we introduce the {{null}} sink: A sink that simply discards the data it receives. - {{{ -function sink.table(t) - t = t or {} - local f = function(chunk, err) - if chunk then table.insert(t, chunk) end - return 1 - end - return f, t -end - -local function null() - return 1 -end - -function sink.null() - return null -end -}}} - -Sinks receive consecutive chunks of data, until the end of data is notified with a {{nil}} chunk. An error is notified by an extra argument giving an error message after the {{nil}} chunk. If a sink detects an error itself and wishes not to be called again, it should return {{nil}}, optionally followed by an error message. A return value that is not {{nil}} means the source will accept more data. Finally, just as sources can choose to be replaced, so can sinks, following the same interface. Once again, it is easy to implement a {{sink.simplify}} factory that transforms a fancy sink into a simple sink. - -As an example, let's create a source that reads from the standard input, then chain it with a filter that normalizes the end-of-line convention and let's use a sink to place all data into a table, printing the result in the end. - {{{ -local load = source.chain(source.file(io.stdin), normalize("\r\n")) -local store, t = sink.table() -while 1 do - local chunk = load() - store(chunk) - if not chunk then break end -end -print(table.concat(t)) -}}} - -Again, just as we created a factory that produces a chained source-filter from a source and a filter, it is easy to create a factory that produces a new sink given a sink and a filter. The new sink passes all data it receives through the filter before handing it in to the original sink. Here is the implementation: - {{{ -function sink.chain(f, snk) - return function(chunk, err) - local r, e = snk(f(chunk)) - if not r then return nil, e end - if not chunk then return snk(nil, err) end - return 1 - end -end -}}} - -==Pumps== - -There is a while loop that has been around for too long in our examples. It's always there because everything that we designed so far is passive. Sources, sinks, filters: None of them will do anything on their own. The operation of pumping all data a source can provide into a sink is so common that we will provide a couple helper functions to do that for us. - {{{ -function pump.step(src, snk) - local chunk, src_err = src() - local ret, snk_err = snk(chunk, src_err) - return chunk and ret and not src_err and not snk_err, src_err or snk_err -end - -function pump.all(src, snk, step) - step = step or pump.step - while true do - local ret, err = step(src, snk) - if not ret then return not err, err end - end -end -}}} - -The {{pump.step}} function moves one chunk of data from the source to the sink. The {{pump.all}} function takes an optional {{step}} function and uses it to pump all the data from the source to the sink. We can now use everything we have to write a program that reads a binary file from disk and stores it in another file, after encoding it to the Base64 transfer content encoding: - {{{ -local load = source.chain( - source.file(io.open("input.bin", "rb")), - encode("base64") -) -local store = sink.chain( - wrap(76), - sink.file(io.open("output.b64", "w")), -) -pump.all(load, store) -}}} - -The way we split the filters here is not intuitive, on purpose. Alternatively, we could have chained the Base64 encode filter and the line-wrap filter together, and then chain the resulting filter with either the file source or the file sink. It doesn't really matter. - -===One last important change=== - -Turns out we still have a problem. When David Burgess was writing his gzip filter, he noticed that the decompression filter can explode a small input chunk into a huge amount of data. Although we wished we could ignore this problem, we soon agreed we couldn't. The only solution is to allow filters to return partial results, and that is what we chose to do. After invoking the filter to pass input data, the user now has to loop invoking the filter to find out if it has more output data to return. Note that these extra calls can't pass more data to the filter. - -More specifically, after passing a chunk of input data to a filter and collecting the first chunk of output data, the user invokes the filter repeatedly, passing the empty string, to get extra output chunks. When the filter itself returns an empty string, the user knows there is no more output data, and can proceed to pass the next input chunk. In the end, after the user passes a {{nil}} notifying the filter that there is no more input data, the filter might still have produced too much output data to return in a single chunk. The user has to loop again, this time passing {{nil}} each time, until the filter itself returns {{nil}} to notify the user it is finally done. - -Most filters won't need this extra freedom. Fortunately, the new filter interface is easy to implement. In fact, the end-of-line translation filter we created in the introduction already conforms to it. On the other hand, the chaining function becomes much more complicated. If it wasn't for coroutines, I wouldn't be happy to implement it. Let me know if you can find a simpler implementation that does not use coroutines! - {{{ -local function chain2(f1, f2) - local co = coroutine.create(function(chunk) - while true do - local filtered1 = f1(chunk) - local filtered2 = f2(filtered1) - local done2 = filtered1 and "" - while true do - if filtered2 == "" or filtered2 == nil then break end - coroutine.yield(filtered2) - filtered2 = f2(done2) - end - if filtered1 == "" then chunk = coroutine.yield(filtered1) - elseif filtered1 == nil then return nil - else chunk = chunk and "" end - end - end) - return function(chunk) - local _, res = coroutine.resume(co, chunk) - return res - end -end -}}} - -Chaining sources also becomes more complicated, but a similar solution is possible with coroutines. Chaining sinks is just as simple as it has always been. Interestingly, these modifications do not have a measurable negative impact in the the performance of filters that didn't need the added flexibility. They do severely improve the efficiency of filters like the gzip filter, though, and that is why we are keeping them. - -===Final considerations=== - -These ideas were created during the development of {{LuaSocket}}[http://www.tecgraf.puc-rio.br/luasocket] 2.0, and are available as the LTN12 module. As a result, {{LuaSocket}}[http://www.tecgraf.puc-rio.br/luasocket] implementation was greatly simplified and became much more powerful. The MIME module is especially integrated to LTN12 and provides many other filters. We felt these concepts deserved to be made public even to those that don't care about {{LuaSocket}}[http://www.tecgraf.puc-rio.br/luasocket], hence the LTN. - -One extra application that deserves mentioning makes use of an identity filter. Suppose you want to provide some feedback to the user while a file is being downloaded into a sink. Chaining the sink with an identity filter (a filter that simply returns the received data unaltered), you can update a progress counter on the fly. The original sink doesn't have to be modified. Another interesting idea is that of a T sink: A sink that sends data to two other sinks. In summary, there appears to be enough room for many other interesting ideas. - -In this technical note we introduced filters, sources, sinks, and pumps. These are useful tools for data processing in general. Sources provide a simple abstraction for data acquisition. Sinks provide an abstraction for final data destinations. Filters define an interface for data transformations. The chaining of filters, sources and sinks provides an elegant way to create arbitrarily complex data transformation from simpler transformations. Pumps just put the machinery to work. diff --git a/ltn013.md b/ltn013.md new file mode 100644 index 0000000..9c56805 --- /dev/null +++ b/ltn013.md @@ -0,0 +1,191 @@ +# Using finalized exceptions +### or How to get rid of all those if statements +by DiegoNehab + + +## Abstract +This little LTN describes a simple exception scheme that greatly simplifies error checking in Lua programs. All the needed functionality ships standard with Lua, but is hidden between the `assert` and `pcall` functions. To make it more evident, we stick to a convenient standard (you probably already use anyways) for Lua function return values, and define two very simple helper functions (either in C or in Lua itself). + +## Introduction + +Most Lua functions return `nil` in case of error, followed by a message describing the error. If you don't use this convention, you probably have good reasons. Hopefully, after reading on, you will realize your reasons are not good enough. + +If you are like me, you hate error checking. Most nice little code snippets that look beautiful when you first write them lose some of their charm when you add all that error checking code. Yet, error checking is as important as the rest of the code. How sad. + +Even if you stick to a return convention, any complex task involving several function calls makes error checking both boring and error-prone (do you see the "error" below?) +```lua +function task(arg1, arg2, ...) + local ret1, err = task1(arg1) + if not ret1 then + cleanup1() + return nil, error + end + local ret2, err = task2(arg2) + if not ret then + cleanup2() + return nil, error + end + ... +end +``` + +The standard `assert` function provides an interesting alternative. To use it, simply nest every function call to be error checked with a call to `assert`. The `assert` function checks the value of its first argument. If it is `nil`, `assert` throws the second argument as an error message. Otherwise, `assert` lets all arguments through as if had not been there. The idea greatly simplifies error checking: +```lua +function task(arg1, arg2, ...) + local ret1 = assert(task1(arg1)) + local ret2 = assert(task2(arg2)) + ... +end +``` + +If any task fails, the execution is aborted by `assert` and the error message is displayed to the user as the cause of the problem. If no error happens, the task completes as before. There isn't a single `if` statement and this is great. However, there are some problems with the idea. + +First, the topmost `task` function doesn't respect the protocol followed by the lower-level tasks: It raises an error instead of returning `nil` followed by the error messages. Here is where the standard `pcall` comes in handy. +```lua +function xtask(arg1, arg2, ...) + local ret1 = assert(task1(arg1)) + local ret2 = assert(task2(arg2)) + ... +end + +function task(arg1, arg2, ...) + local ok, ret_or_err = pcall(xtask, arg1, arg2, ...) + if ok then return ret_or_err + else return nil, ret_or_err end +end +``` + +Our new `task` function is well behaved. `Pcall` catches any error raised by the calls to `assert` and returns it after the status code. That way, errors don't get propagated to the user of the high level `task` function. + +These are the main ideas for our exception scheme, but there are still a few glitches to fix: + +* Directly using `pcall` ruined the simplicity of the code; +* What happened to the cleanup function calls? What if we have to, say, close a file? +* `Assert` messes with the error message before raising the error (it adds line number information). + +Fortunately, all these problems are very easy to solve and that's what we do in the following sections. + +## Introducing the `protect` factory + +We used the `pcall` function to shield the user from errors that could be raised by the underlying implementation. Instead of directly using `pcall` (and thus duplicating code) every time we prefer a factory that does the same job: +```lua +local function pack(ok, ...) + return ok, {...} +end + +function protect(f) + return function(...) + local ok, ret = pack(pcall(f, ...)) + if ok then return unpack(ret) + else return nil, ret[1] end + end +end +``` + +The `protect` factory receives a function that might raise exceptions and returns a function that respects our return value convention. Now we can rewrite the top-level `task` function in a much cleaner way: +```lua +task = protect(function(arg1, arg2, ...) + local ret1 = assert(task1(arg1)) + local ret2 = assert(task2(arg2)) + ... +end) +``` + +The Lua implementation of the `protect` factory suffers with the creation of tables to hold multiple arguments and return values. It is possible (and easy) to implement the same function in C, without any table creation. +```c +static int safecall(lua_State *L) { + lua_pushvalue(L, lua_upvalueindex(1)); + lua_insert(L, 1); + if (lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0) != 0) { + lua_pushnil(L); + lua_insert(L, 1); + return 2; + } else return lua_gettop(L); +} + +static int protect(lua_State *L) { + lua_pushcclosure(L, safecall, 1); + return 1; +} +``` + +## The `newtry` factory + +Let's solve the two remaining issues with a single shot and use a concrete example to illustrate the proposed solution. Suppose you want to write a function to download an HTTP document. You have to connect, send the request and read the reply. Each of these tasks can fail, but if something goes wrong after you connected, you have to close the connection before returning the error message. +```lua +get = protect(function(host, path) + local c + -- create a try function with a finalizer to close the socket + local try = newtry(function() + if c then c:close() end + end) + -- connect and send request + c = try(connect(host, 80)) + try(c:send("GET " .. path .. " HTTP/1.0\r\n\r\n")) + -- get headers + local h = {} + while 1 do + l = try(c:receive()) + if l == "" then break end + table.insert(h, l) + end + -- get body + local b = try(c:receive("*a")) + c:close() + return b, h +end) +``` + +The `newtry` factory returns a function that works just like `assert`. The differences are that the `try` function doesn't mess with the error message and it calls an optional "finalizer" before raising the error. In our example, the finalizer simply closes the socket. + +Even with a simple example like this, we see that the finalized exceptions simplified our life. Let's see what we gain in general, not just in this example: + +* We don't need to declare dummy variables to hold error messages in case any ever shows up; +* We avoid using a variable to hold something that could either be a return value or an error message; +* We didn't have to use several "if" statements to check for errors; +* If an error happens, we know our finalizer is going to be invoked automatically; +* Exceptions get propagated, so we don't repeat these "if" statements until the error reaches the user. + +Try writing the same function without the tricks we used above and you will see that the code gets ugly. Longer sequences of operations with error checking would get even uglier. So let's implement the `newtry` function in Lua: +```lua +function newtry(f) + return function(...) + if not arg[1] then + if f then f() end + error(arg[2], 0) + else + return ... + end + end +end +``` + +Again, the implementation suffers from the creation of tables at each function call, so we prefer the C version: +```lua +static int finalize(lua_State *L) { + if (!lua_toboolean(L, 1)) { + lua_pushvalue(L, lua_upvalueindex(1)); + lua_pcall(L, 0, 0, 0); + lua_settop(L, 2); + lua_error(L); + return 0; + } else return lua_gettop(L); +} + +static int do_nothing(lua_State *L) { + (void) L; + return 0; +} + +static int newtry(lua_State *L) { + lua_settop(L, 1); + if (lua_isnil(L, 1)) + lua_pushcfunction(L, do_nothing); + lua_pushcclosure(L, finalize, 1); + return 1; +} +``` + +## Final considerations + +The `protect` and `newtry` functions saved a "lot" of work in the implementation of [LuaSocket](https://github.com/lunarmodules/luasocket). The size of some modules was cut in half by the these ideas. It's true the scheme is not as generic as the exception mechanism of programming languages like C++ or Java, but the power/simplicity ratio is favorable and I hope it serves you as well as it served [LuaSocket](https://github.com/lunarmodules/luasocket). diff --git a/ltn013.wiki b/ltn013.wiki deleted file mode 100644 index a622424..0000000 --- a/ltn013.wiki +++ /dev/null @@ -1,194 +0,0 @@ -===Using finalized exceptions=== -==or How to get rid of all those if statements== -by DiegoNehab - -{{{ - -}}} - -===Abstract=== -This little LTN describes a simple exception scheme that greatly simplifies error checking in Lua programs. All the needed functionality ships standard with Lua, but is hidden between the {{assert}} and {{pcall}} functions. To make it more evident, we stick to a convenient standard (you probably already use anyways) for Lua function return values, and define two very simple helper functions (either in C or in Lua itself). - -===Introduction=== - -Most Lua functions return {{nil}} in case of error, followed by a message describing the error. If you don't use this convention, you probably have good reasons. Hopefully, after reading on, you will realize your reasons are not good enough. - -If you are like me, you hate error checking. Most nice little code snippets that look beautiful when you first write them lose some of their charm when you add all that error checking code. Yet, error checking is as important as the rest of the code. How sad. - -Even if you stick to a return convention, any complex task involving several function calls makes error checking both boring and error-prone (do you see the ''error'' below?) - {{{ -function task(arg1, arg2, ...) - local ret1, err = task1(arg1) - if not ret1 then - cleanup1() - return nil, error - end - local ret2, err = task2(arg2) - if not ret then - cleanup2() - return nil, error - end - ... -end -}}} - -The standard {{assert}} function provides an interesting alternative. To use it, simply nest every function call to be error checked with a call to {{assert}}. The {{assert}} function checks the value of its first argument. If it is {{nil}}, {{assert}} throws the second argument as an error message. Otherwise, {{assert}} lets all arguments through as if had not been there. The idea greatly simplifies error checking: - {{{ -function task(arg1, arg2, ...) - local ret1 = assert(task1(arg1)) - local ret2 = assert(task2(arg2)) - ... -end -}}} - -If any task fails, the execution is aborted by {{assert}} and the error message is displayed to the user as the cause of the problem. If no error happens, the task completes as before. There isn't a single {{if}} statement and this is great. However, there are some problems with the idea. - -First, the topmost {{task}} function doesn't respect the protocol followed by the lower-level tasks: It raises an error instead of returning {{nil}} followed by the error messages. Here is where the standard {{pcall}} comes in handy. - {{{ -function xtask(arg1, arg2, ...) - local ret1 = assert(task1(arg1)) - local ret2 = assert(task2(arg2)) - ... -end - -function task(arg1, arg2, ...) - local ok, ret_or_err = pcall(xtask, arg1, arg2, ...) - if ok then return ret_or_err - else return nil, ret_or_err end -end -}}} - -Our new {{task}} function is well behaved. {{Pcall}} catches any error raised by the calls to {{assert}} and returns it after the status code. That way, errors don't get propagated to the user of the high level {{task}} function. - -These are the main ideas for our exception scheme, but there are still a few glitches to fix: - - * Directly using {{pcall}} ruined the simplicity of the code; - * What happened to the cleanup function calls? What if we have to, say, close a file? - * {{Assert}} messes with the error message before raising the error (it adds line number information). - -Fortunately, all these problems are very easy to solve and that's what we do in the following sections. - -== Introducing the {{protect}} factory == - -We used the {{pcall}} function to shield the user from errors that could be raised by the underlying implementation. Instead of directly using {{pcall}} (and thus duplicating code) every time we prefer a factory that does the same job: - {{{ -local function pack(ok, ...) - return ok, {...} -end - -function protect(f) - return function(...) - local ok, ret = pack(pcall(f, ...)) - if ok then return unpack(ret) - else return nil, ret[1] end - end -end -}}} - -The {{protect}} factory receives a function that might raise exceptions and returns a function that respects our return value convention. Now we can rewrite the top-level {{task}} function in a much cleaner way: - {{{ -task = protect(function(arg1, arg2, ...) - local ret1 = assert(task1(arg1)) - local ret2 = assert(task2(arg2)) - ... -end) -}}} - -The Lua implementation of the {{protect}} factory suffers with the creation of tables to hold multiple arguments and return values. It is possible (and easy) to implement the same function in C, without any table creation. - {{{ -static int safecall(lua_State *L) { - lua_pushvalue(L, lua_upvalueindex(1)); - lua_insert(L, 1); - if (lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0) != 0) { - lua_pushnil(L); - lua_insert(L, 1); - return 2; - } else return lua_gettop(L); -} - -static int protect(lua_State *L) { - lua_pushcclosure(L, safecall, 1); - return 1; -} -}}} - -===The {{newtry}} factory=== - -Let's solve the two remaining issues with a single shot and use a concrete example to illustrate the proposed solution. Suppose you want to write a function to download an HTTP document. You have to connect, send the request and read the reply. Each of these tasks can fail, but if something goes wrong after you connected, you have to close the connection before returning the error message. - {{{ -get = protect(function(host, path) - local c - -- create a try function with a finalizer to close the socket - local try = newtry(function() - if c then c:close() end - end) - -- connect and send request - c = try(connect(host, 80)) - try(c:send("GET " .. path .. " HTTP/1.0\r\n\r\n")) - -- get headers - local h = {} - while 1 do - l = try(c:receive()) - if l == "" then break end - table.insert(h, l) - end - -- get body - local b = try(c:receive("*a")) - c:close() - return b, h -end) -}}} - -The {{newtry}} factory returns a function that works just like {{assert}}. The differences are that the {{try}} function doesn't mess with the error message and it calls an optional ''finalizer'' before raising the error. In our example, the finalizer simply closes the socket. - -Even with a simple example like this, we see that the finalized exceptions simplified our life. Let's see what we gain in general, not just in this example: - - * We don't need to declare dummy variables to hold error messages in case any ever shows up; - * We avoid using a variable to hold something that could either be a return value or an error message; - * We didn't have to use several ''if'' statements to check for errors; - * If an error happens, we know our finalizer is going to be invoked automatically; - * Exceptions get propagated, so we don't repeat these ''if'' statements until the error reaches the user. - -Try writing the same function without the tricks we used above and you will see that the code gets ugly. Longer sequences of operations with error checking would get even uglier. So let's implement the {{newtry}} function in Lua: - {{{ -function newtry(f) - return function(...) - if not arg[1] then - if f then f() end - error(arg[2], 0) - else - return ... - end - end -end -}}} - -Again, the implementation suffers from the creation of tables at each function call, so we prefer the C version: - {{{ -static int finalize(lua_State *L) { - if (!lua_toboolean(L, 1)) { - lua_pushvalue(L, lua_upvalueindex(1)); - lua_pcall(L, 0, 0, 0); - lua_settop(L, 2); - lua_error(L); - return 0; - } else return lua_gettop(L); -} - -static int do_nothing(lua_State *L) { - (void) L; - return 0; -} - -static int newtry(lua_State *L) { - lua_settop(L, 1); - if (lua_isnil(L, 1)) - lua_pushcfunction(L, do_nothing); - lua_pushcclosure(L, finalize, 1); - return 1; -} -}}} - -===Final considerations=== - -The {{protect}} and {{newtry}} functions saved a ''lot'' of work in the implementation of {{LuaSocket}}[http://www.tecgraf.puc-rio.br/luasocket]. The size of some modules was cut in half by the these ideas. It's true the scheme is not as generic as the exception mechanism of programming languages like C++ or Java, but the power/simplicity ratio is favorable and I hope it serves you as well as it served {{LuaSocket}}. diff --git a/luasocket-scm-0.rockspec b/luasocket-scm-0.rockspec deleted file mode 100644 index 352a497..0000000 --- a/luasocket-scm-0.rockspec +++ /dev/null @@ -1,101 +0,0 @@ -package = "LuaSocket" -version = "scm-0" -source = { - url = "https://github.com/diegonehab/luasocket/archive/master.zip", - dir = "luasocket-master", -} -description = { - summary = "Network support for the Lua language", - detailed = [[ - LuaSocket is a Lua extension library that is composed by two parts: a C core - that provides support for the TCP and UDP transport layers, and a set of Lua - modules that add support for functionality commonly needed by applications - that deal with the Internet. - ]], - homepage = "http://luaforge.net/projects/luasocket/", - license = "MIT" -} -dependencies = { - "lua >= 5.1" -} - -local function make_plat(plat) - local defines = { - unix = { - "LUASOCKET_DEBUG", - "LUASOCKET_API=__attribute__((visibility(\"default\")))", - "UNIX_API=__attribute__((visibility(\"default\")))", - "MIME_API=__attribute__((visibility(\"default\")))" - }, - macosx = { - "LUASOCKET_DEBUG", - "UNIX_HAS_SUN_LEN", - "LUASOCKET_API=__attribute__((visibility(\"default\")))", - "UNIX_API=__attribute__((visibility(\"default\")))", - "MIME_API=__attribute__((visibility(\"default\")))" - }, - win32 = { - "LUASOCKET_DEBUG", - "NDEBUG", - "LUASOCKET_API=__declspec(dllexport)", - "MIME_API=__declspec(dllexport)" - }, - mingw32 = { - "LUASOCKET_DEBUG", - "LUASOCKET_INET_PTON", - "WINVER=0x0501", - "LUASOCKET_API=__declspec(dllexport)", - "MIME_API=__declspec(dllexport)" - } - } - local modules = { - ["socket.core"] = { - sources = { "src/luasocket.c", "src/timeout.c", "src/buffer.c", "src/io.c", "src/auxiliar.c", "src/options.c", "src/inet.c", "src/except.c", "src/select.c", "src/tcp.c", "src/udp.c", "src/compat.c" }, - defines = defines[plat], - incdir = "/src" - }, - ["mime.core"] = { - sources = { "src/mime.c", "src/compat.c" }, - defines = defines[plat], - incdir = "/src" - }, - ["socket.http"] = "src/http.lua", - ["socket.url"] = "src/url.lua", - ["socket.tp"] = "src/tp.lua", - ["socket.ftp"] = "src/ftp.lua", - ["socket.headers"] = "src/headers.lua", - ["socket.smtp"] = "src/smtp.lua", - ltn12 = "src/ltn12.lua", - socket = "src/socket.lua", - mime = "src/mime.lua" - } - if plat == "unix" or plat == "macosx" then - modules["socket.core"].sources[#modules["socket.core"].sources+1] = "src/usocket.c" - modules["socket.unix"] = { - sources = { "src/buffer.c", "src/auxiliar.c", "src/options.c", "src/timeout.c", "src/io.c", "src/usocket.c", "src/unix.c" }, - defines = defines[plat], - incdir = "/src" - } - modules["socket.serial"] = { - sources = { "src/buffer.c", "src/auxiliar.c", "src/options.c", "src/timeout.c", "src/io.c", "src/usocket.c", "src/serial.c" }, - defines = defines[plat], - incdir = "/src" - } - end - if plat == "win32" or plat == "mingw32" then - modules["socket.core"].sources[#modules["socket.core"].sources+1] = "src/wsocket.c" - modules["socket.core"].libraries = { "ws2_32" } - end - return { modules = modules } -end - -build = { - type = "builtin", - platforms = { - unix = make_plat("unix"), - macosx = make_plat("macosx"), - win32 = make_plat("win32"), - mingw32 = make_plat("mingw32") - }, - copy_directories = { "doc", "samples", "etc", "test" } -} diff --git a/luasocket-scm-3.rockspec b/luasocket-scm-3.rockspec new file mode 100644 index 0000000..5a5f7b5 --- /dev/null +++ b/luasocket-scm-3.rockspec @@ -0,0 +1,134 @@ +package = "LuaSocket" +version = "scm-3" +source = { + url = "git+https://github.com/lunarmodules/luasocket.git", + branch = "master" +} +description = { + summary = "Network support for the Lua language", + detailed = [[ + LuaSocket is a Lua extension library composed of two parts: a set of C + modules that provide support for the TCP and UDP transport layers, and a + set of Lua modules that provide functions commonly needed by applications + that deal with the Internet. + ]], + homepage = "https://github.com/lunarmodules/luasocket", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} + +local function make_plat(plat) + local defines = { + unix = { + "LUASOCKET_DEBUG" + }, + macosx = { + "LUASOCKET_DEBUG", + "UNIX_HAS_SUN_LEN" + }, + win32 = { + "LUASOCKET_DEBUG", + "NDEBUG" + }, + mingw32 = { + "LUASOCKET_DEBUG", + -- "LUASOCKET_INET_PTON", + "WINVER=0x0501" + } + } + local modules = { + ["socket.core"] = { + sources = { + "src/luasocket.c" + , "src/timeout.c" + , "src/buffer.c" + , "src/io.c" + , "src/auxiliar.c" + , "src/options.c" + , "src/inet.c" + , "src/except.c" + , "src/select.c" + , "src/tcp.c" + , "src/udp.c" + , "src/compat.c" }, + defines = defines[plat], + incdir = "/src" + }, + ["mime.core"] = { + sources = { "src/mime.c", "src/compat.c" }, + defines = defines[plat], + incdir = "/src" + }, + ["socket.http"] = "src/http.lua", + ["socket.url"] = "src/url.lua", + ["socket.tp"] = "src/tp.lua", + ["socket.ftp"] = "src/ftp.lua", + ["socket.headers"] = "src/headers.lua", + ["socket.smtp"] = "src/smtp.lua", + ltn12 = "src/ltn12.lua", + socket = "src/socket.lua", + mime = "src/mime.lua" + } + if plat == "unix" + or plat == "macosx" + or plat == "haiku" + then + modules["socket.core"].sources[#modules["socket.core"].sources+1] = "src/usocket.c" + if plat == "haiku" then + modules["socket.core"].libraries = {"network"} + end + modules["socket.unix"] = { + sources = { + "src/buffer.c" + , "src/compat.c" + , "src/auxiliar.c" + , "src/options.c" + , "src/timeout.c" + , "src/io.c" + , "src/usocket.c" + , "src/unix.c" + , "src/unixdgram.c" + , "src/unixstream.c" }, + defines = defines[plat], + incdir = "/src" + } + modules["socket.serial"] = { + sources = { + "src/buffer.c" + , "src/compat.c" + , "src/auxiliar.c" + , "src/options.c" + , "src/timeout.c" + , "src/io.c" + , "src/usocket.c" + , "src/serial.c" }, + defines = defines[plat], + incdir = "/src" + } + end + if plat == "win32" + or plat == "mingw32" + then + modules["socket.core"].sources[#modules["socket.core"].sources+1] = "src/wsocket.c" + modules["socket.core"].libraries = { "ws2_32" } + modules["socket.core"].libdirs = {} + end + return { modules = modules } +end + +build = { + type = "builtin", + platforms = { + unix = make_plat("unix"), + macosx = make_plat("macosx"), + haiku = make_plat("haiku"), + win32 = make_plat("win32"), + mingw32 = make_plat("mingw32") + }, + copy_directories = { + "docs" + , "samples" + , "test" } +} diff --git a/macosx.cmd b/macosx.cmd index 46a0709..dd5d8cf 100644 --- a/macosx.cmd +++ b/macosx.cmd @@ -1 +1 @@ -make DEBUG=DEBUG PLAT=macosx LUAINC_macosx_base=/Users/diego/build/macosx/include LUAPREFIX_macosx=/Users/diego/build/macosx install-both +make DEBUG=DEBUG PLAT=macosx LUAINC_macosx_base=/Users/$USER/build/macosx/include LUAPREFIX_macosx=/Users/$USER/build/macosx install-both diff --git a/makefile b/makefile old mode 100644 new mode 100755 index e34f5a9..63d9985 --- a/makefile +++ b/makefile @@ -10,7 +10,7 @@ # print print the build settings PLAT?= linux -PLATS= macosx linux win32 mingw freebsd +PLATS= macosx linux win32 win64 mingw freebsd solaris all: $(PLAT) @@ -33,6 +33,9 @@ install-both: $(MAKE) clean @cd src; $(MAKE) $(PLAT) LUAV=5.3 @cd src; $(MAKE) install LUAV=5.3 + $(MAKE) clean + @cd src; $(MAKE) $(PLAT) LUAV=5.4 + @cd src; $(MAKE) install LUAV=5.4 install-both-unix: $(MAKE) clean @@ -44,6 +47,9 @@ install-both-unix: $(MAKE) clean @cd src; $(MAKE) $(PLAT) LUAV=5.3 @cd src; $(MAKE) install-unix LUAV=5.3 + $(MAKE) clean + @cd src; $(MAKE) $(PLAT) LUAV=5.4 + @cd src; $(MAKE) install-unix LUAV=5.4 .PHONY: test diff --git a/makefile.dist b/makefile.dist index 45a8866..5ef44d3 100644 --- a/makefile.dist +++ b/makefile.dist @@ -1,7 +1,7 @@ #-------------------------------------------------------------------------- # Distribution makefile #-------------------------------------------------------------------------- -DIST = luasocket-3.0-rc1 +DIST = luasocket-3.0.0 TEST = \ test/README \ @@ -22,20 +22,17 @@ SAMPLES = \ samples/lpr.lua \ samples/talker.lua \ samples/tinyirc.lua - -ETC = \ - etc/README \ - etc/b64.lua \ - etc/check-links.lua \ - etc/check-memory.lua \ - etc/dict.lua \ - etc/dispatch.lua \ - etc/eol.lua \ - etc/forward.lua \ - etc/get.lua \ - etc/lp.lua \ - etc/qp.lua \ - etc/tftp.lua + samples/b64.lua \ + samples/check-links.lua \ + samples/check-memory.lua \ + samples/dict.lua \ + samples/dispatch.lua \ + samples/eol.lua \ + samples/forward.lua \ + samples/get.lua \ + samples/lp.lua \ + samples/qp.lua \ + samples/tftp.lua SRC = \ src/makefile \ @@ -92,39 +89,36 @@ MAKE = \ socket.vcxproj \ mime.vcxproj -DOC = \ - doc/dns.html \ - doc/ftp.html \ - doc/index.html \ - doc/http.html \ - doc/installation.html \ - doc/introduction.html \ - doc/ltn12.html \ - doc/luasocket.png \ - doc/mime.html \ - doc/reference.css \ - doc/reference.html \ - doc/smtp.html \ - doc/socket.html \ - doc/tcp.html \ - doc/udp.html \ - doc/url.html +DOCS = \ + docs/dns.html \ + docs/ftp.html \ + docs/index.html \ + docs/http.html \ + docs/installation.html \ + docs/introduction.html \ + docs/ltn12.html \ + docs/luasocket.png \ + docs/mime.html \ + docs/reference.css \ + docs/reference.html \ + docs/smtp.html \ + docs/socket.html \ + docs/tcp.html \ + docs/udp.html \ + docs/url.html dist: mkdir -p $(DIST) - cp -vf NEW $(DIST) + cp -vf CHANGELOG.md $(DIST) cp -vf LICENSE $(DIST) - cp -vf README $(DIST) + cp -vf README.md $(DIST) cp -vf $(MAKE) $(DIST) - mkdir -p $(DIST)/etc - cp -vf $(ETC) $(DIST)/etc - mkdir -p $(DIST)/src cp -vf $(SRC) $(DIST)/src - mkdir -p $(DIST)/doc - cp -vf $(DOC) $(DIST)/doc + mkdir -p $(DIST)/docs + cp -vf $(DOCS) $(DIST)/docs mkdir -p $(DIST)/samples cp -vf $(SAMPLES) $(DIST)/samples diff --git a/mime.vcxproj b/mime.vcxproj index c77d611..575f985 100755 --- a/mime.vcxproj +++ b/mime.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -20,19 +20,7 @@- - - - +Document -copy %(FullPath) $(LUABIN_PATH)$(Configuration) -$(LUABIN_PATH)$(Configuration)\%(Filename)%(Extension) -copy %(FullPath) $(LUABIN_PATH)$(Configuration) -$(LUABIN_PATH)$(Configuration)\%(Filename)%(Extension) -copy %(FullPath) $(LUALIB_PATH)$(Platform)\$(Configuration) -copy %(FullPath) $(LUALIB_PATH)$(Platform)\$(Configuration) -$(LUABIN_PATH)$(Platform)\$(Configuration)\%(Filename)%(Extension) -$(LUABIN_PATH)$(Platform)\$(Configuration)\%(Filename)%(Extension) -{128E8BD0-174A-48F0-8771-92B1E8D18713} @@ -41,22 +29,22 @@DynamicLibrary -v110 +v141 MultiByte DynamicLibrary -v110 +v141 MultiByte DynamicLibrary -v110 +v141 MultiByte DynamicLibrary -v110 +v141 MultiByte @@ -87,7 +75,7 @@ <_ProjectFileVersion>11.0.50727.1 - $(LUABIN_PATH)$(Configuration)\mime\ +$(Configuration)\mime\ $(Configuration)\ true core @@ -95,23 +83,23 @@true core -$(LUABIN_PATH)$(Platform)\$(Configuration)\mime\ +$(Platform)\$(Configuration)\mime\ - $(LUABIN_PATH)$(Configuration)\mime\ +$(Configuration)\mime\ $(Configuration)\ false core false -$(LUABIN_PATH)$(Platform)\$(Configuration)\mime\ +$(Platform)\$(Configuration)\mime\ core - Disabled -$(LUAINC_PATH);%(AdditionalIncludeDirectories) +$(LUAINC);%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;MIME_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true EnableFastChecks @@ -122,9 +110,9 @@$(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb $(LUALIB);%(AdditionalDependencies) +$(LUALIBNAME);%(AdditionalDependencies) $(OutDir)$(TargetName).dll -$(LUALIB_PATH)$(Configuration);%(AdditionalLibraryDirectories) +$(LUALIB);%(AdditionalLibraryDirectories) true $(OutDir)mime.pdb Windows @@ -138,7 +126,7 @@- Disabled -$(LUAINC_PATH);%(AdditionalIncludeDirectories) +$(LUAINC);%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;MIME_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL @@ -149,9 +137,9 @@$(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb $(LUALIB);%(AdditionalDependencies) +$(LUALIBNAME);%(AdditionalDependencies) $(OutDir)$(TargetName).dll -$(LUALIB_PATH)$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) +$(LUALIB);%(AdditionalLibraryDirectories) true $(OutDir)mime.pdb Windows @@ -163,7 +151,7 @@- -$(LUAINC_PATH);%(AdditionalIncludeDirectories) +$(LUAINC);%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;MIME_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreadedDLL @@ -172,9 +160,9 @@ $(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb $(LUALIB);%(AdditionalDependencies) +$(LUALIBNAME);%(AdditionalDependencies) $(OutDir)$(TargetName).dll -$(LUALIB_PATH)$(Configuration);%(AdditionalLibraryDirectories) +$(LUALIB);%(AdditionalLibraryDirectories) true Windows true @@ -187,7 +175,7 @@- -$(LUAINC_PATH);%(AdditionalIncludeDirectories) +$(LUAINC);%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;MIME_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreadedDLL @@ -198,9 +186,9 @@ $(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb $(LUALIB);%(AdditionalDependencies) +$(LUALIBNAME);%(AdditionalDependencies) $(OutDir)$(TargetName).dll -$(LUALIB_PATH)$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) +$(LUALIB);%(AdditionalLibraryDirectories) true Windows true diff --git a/mime.vcxproj.filters b/mime.vcxproj.filters deleted file mode 100644 index 621215b..0000000 --- a/mime.vcxproj.filters +++ /dev/null @@ -1,16 +0,0 @@ - -- \ No newline at end of file diff --git a/rockspecs/luasocket-3.0.0-1.rockspec b/rockspecs/luasocket-3.0.0-1.rockspec new file mode 100644 index 0000000..4cf4acf --- /dev/null +++ b/rockspecs/luasocket-3.0.0-1.rockspec @@ -0,0 +1,134 @@ +package = "LuaSocket" +version = "3.0.0-1" +source = { + url = "git+https://github.com/lunarmodules/luasocket.git", + tag = "v3.0.0" +} +description = { + summary = "Network support for the Lua language", + detailed = [[ + LuaSocket is a Lua extension library composed of two parts: a set of C + modules that provide support for the TCP and UDP transport layers, and a + set of Lua modules that provide functions commonly needed by applications + that deal with the Internet. + ]], + homepage = "https://github.com/lunarmodules/luasocket", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} + +local function make_plat(plat) + local defines = { + unix = { + "LUASOCKET_DEBUG" + }, + macosx = { + "LUASOCKET_DEBUG", + "UNIX_HAS_SUN_LEN" + }, + win32 = { + "LUASOCKET_DEBUG", + "NDEBUG" + }, + mingw32 = { + "LUASOCKET_DEBUG", + "LUASOCKET_INET_PTON", + "WINVER=0x0501" + } + } + local modules = { + ["socket.core"] = { + sources = { + "src/luasocket.c" + , "src/timeout.c" + , "src/buffer.c" + , "src/io.c" + , "src/auxiliar.c" + , "src/options.c" + , "src/inet.c" + , "src/except.c" + , "src/select.c" + , "src/tcp.c" + , "src/udp.c" + , "src/compat.c" }, + defines = defines[plat], + incdir = "/src" + }, + ["mime.core"] = { + sources = { "src/mime.c", "src/compat.c" }, + defines = defines[plat], + incdir = "/src" + }, + ["socket.http"] = "src/http.lua", + ["socket.url"] = "src/url.lua", + ["socket.tp"] = "src/tp.lua", + ["socket.ftp"] = "src/ftp.lua", + ["socket.headers"] = "src/headers.lua", + ["socket.smtp"] = "src/smtp.lua", + ltn12 = "src/ltn12.lua", + socket = "src/socket.lua", + mime = "src/mime.lua" + } + if plat == "unix" + or plat == "macosx" + or plat == "haiku" + then + modules["socket.core"].sources[#modules["socket.core"].sources+1] = "src/usocket.c" + if plat == "haiku" then + modules["socket.core"].libraries = {"network"} + end + modules["socket.unix"] = { + sources = { + "src/buffer.c" + , "src/compat.c" + , "src/auxiliar.c" + , "src/options.c" + , "src/timeout.c" + , "src/io.c" + , "src/usocket.c" + , "src/unix.c" + , "src/unixdgram.c" + , "src/unixstream.c" }, + defines = defines[plat], + incdir = "/src" + } + modules["socket.serial"] = { + sources = { + "src/buffer.c" + , "src/compat.c" + , "src/auxiliar.c" + , "src/options.c" + , "src/timeout.c" + , "src/io.c" + , "src/usocket.c" + , "src/serial.c" }, + defines = defines[plat], + incdir = "/src" + } + end + if plat == "win32" + or plat == "mingw32" + then + modules["socket.core"].sources[#modules["socket.core"].sources+1] = "src/wsocket.c" + modules["socket.core"].libraries = { "ws2_32" } + end + return { modules = modules } +end + +build = { + type = "builtin", + platforms = { + unix = make_plat("unix"), + macosx = make_plat("macosx"), + haiku = make_plat("haiku"), + win32 = make_plat("win32"), + mingw32 = make_plat("mingw32") + }, + copy_directories = { + "docs" + , "samples" + , "etc" + , "test" } +} diff --git a/rockspecs/luasocket-3.0rc1-2.rockspec b/rockspecs/luasocket-3.0rc1-2.rockspec new file mode 100644 index 0000000..3cb6524 --- /dev/null +++ b/rockspecs/luasocket-3.0rc1-2.rockspec @@ -0,0 +1,108 @@ +package = "LuaSocket" +version = "3.0rc1-2" +source = { + url = "https://github.com/diegonehab/luasocket/archive/v3.0-rc1.zip", + dir = "luasocket-3.0-rc1", +} +description = { + summary = "Network support for the Lua language", + detailed = [[ + LuaSocket is a Lua extension library that is composed by two parts: a C core + that provides support for the TCP and UDP transport layers, and a set of Lua + modules that add support for functionality commonly needed by applications + that deal with the Internet. + ]], + homepage = "http://luaforge.net/projects/luasocket/", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} + +local function make_plat(plat) + local defines = { + unix = { + "LUA_COMPAT_APIINTCASTS", + "LUASOCKET_DEBUG", + "LUASOCKET_API=__attribute__((visibility(\"default\")))", + "UNIX_API=__attribute__((visibility(\"default\")))", + "MIME_API=__attribute__((visibility(\"default\")))" + }, + macosx = { + "LUA_COMPAT_APIINTCASTS", + "LUASOCKET_DEBUG", + "UNIX_HAS_SUN_LEN", + "LUASOCKET_API=__attribute__((visibility(\"default\")))", + "UNIX_API=__attribute__((visibility(\"default\")))", + "MIME_API=__attribute__((visibility(\"default\")))" + }, + win32 = { + "LUA_COMPAT_APIINTCASTS", + "LUASOCKET_DEBUG", + "NDEBUG", + "LUASOCKET_API=__declspec(dllexport)", + "MIME_API=__declspec(dllexport)" + }, + mingw32 = { + "LUA_COMPAT_APIINTCASTS", + "LUASOCKET_DEBUG", + "LUASOCKET_INET_PTON", + "WINVER=0x0501", + "LUASOCKET_API=__declspec(dllexport)", + "MIME_API=__declspec(dllexport)" + } + } + local modules = { + ["socket.core"] = { + sources = { "src/luasocket.c", "src/timeout.c", "src/buffer.c", "src/io.c", "src/auxiliar.c", + "src/options.c", "src/inet.c", "src/except.c", "src/select.c", "src/tcp.c", "src/udp.c" }, + defines = defines[plat], + incdir = "src" + }, + ["mime.core"] = { + sources = { "src/mime.c" }, + defines = defines[plat], + incdir = "src" + }, + ["socket.http"] = "src/http.lua", + ["socket.url"] = "src/url.lua", + ["socket.tp"] = "src/tp.lua", + ["socket.ftp"] = "src/ftp.lua", + ["socket.headers"] = "src/headers.lua", + ["socket.smtp"] = "src/smtp.lua", + ltn12 = "src/ltn12.lua", + socket = "src/socket.lua", + mime = "src/mime.lua" + } + if plat == "unix" or plat == "macosx" then + modules["socket.core"].sources[#modules["socket.core"].sources+1] = "src/usocket.c" + modules["socket.unix"] = { + sources = { "src/buffer.c", "src/auxiliar.c", "src/options.c", "src/timeout.c", "src/io.c", + "src/usocket.c", "src/unix.c" }, + defines = defines[plat], + incdir = "/src" + } + modules["socket.serial"] = { + sources = { "src/buffer.c", "src/auxiliar.c", "src/options.c", "src/timeout.c", + "src/io.c", "src/usocket.c", "src/serial.c" }, + defines = defines[plat], + incdir = "/src" + } + end + if plat == "win32" or plat == "mingw32" then + modules["socket.core"].sources[#modules["socket.core"].sources+1] = "src/wsocket.c" + modules["socket.core"].libraries = { "ws2_32" } + end + return { modules = modules } +end + +build = { + type = "builtin", + platforms = { + unix = make_plat("unix"), + macosx = make_plat("macosx"), + win32 = make_plat("win32"), + mingw32 = make_plat("mingw32") + }, + copy_directories = { "doc", "samples", "etc", "test" } +} diff --git a/rockspecs/luasocket-3.1.0-1.rockspec b/rockspecs/luasocket-3.1.0-1.rockspec new file mode 100644 index 0000000..2d724ac --- /dev/null +++ b/rockspecs/luasocket-3.1.0-1.rockspec @@ -0,0 +1,135 @@ +package = "LuaSocket" +version = "3.1.0-1" +source = { + url = "git+https://github.com/lunarmodules/luasocket.git", + tag = "v3.1.0" +} +description = { + summary = "Network support for the Lua language", + detailed = [[ + LuaSocket is a Lua extension library composed of two parts: a set of C + modules that provide support for the TCP and UDP transport layers, and a + set of Lua modules that provide functions commonly needed by applications + that deal with the Internet. + ]], + homepage = "https://github.com/lunarmodules/luasocket", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} + +local function make_plat(plat) + local defines = { + unix = { + "LUASOCKET_DEBUG" + }, + macosx = { + "LUASOCKET_DEBUG", + "UNIX_HAS_SUN_LEN" + }, + win32 = { + "LUASOCKET_DEBUG", + "NDEBUG" + }, + mingw32 = { + "LUASOCKET_DEBUG", + -- "LUASOCKET_INET_PTON", + "WINVER=0x0501" + } + } + local modules = { + ["socket.core"] = { + sources = { + "src/luasocket.c" + , "src/timeout.c" + , "src/buffer.c" + , "src/io.c" + , "src/auxiliar.c" + , "src/options.c" + , "src/inet.c" + , "src/except.c" + , "src/select.c" + , "src/tcp.c" + , "src/udp.c" + , "src/compat.c" }, + defines = defines[plat], + incdir = "/src" + }, + ["mime.core"] = { + sources = { "src/mime.c", "src/compat.c" }, + defines = defines[plat], + incdir = "/src" + }, + ["socket.http"] = "src/http.lua", + ["socket.url"] = "src/url.lua", + ["socket.tp"] = "src/tp.lua", + ["socket.ftp"] = "src/ftp.lua", + ["socket.headers"] = "src/headers.lua", + ["socket.smtp"] = "src/smtp.lua", + ltn12 = "src/ltn12.lua", + socket = "src/socket.lua", + mime = "src/mime.lua" + } + if plat == "unix" + or plat == "macosx" + or plat == "haiku" + then + modules["socket.core"].sources[#modules["socket.core"].sources+1] = "src/usocket.c" + if plat == "haiku" then + modules["socket.core"].libraries = {"network"} + end + modules["socket.unix"] = { + sources = { + "src/buffer.c" + , "src/compat.c" + , "src/auxiliar.c" + , "src/options.c" + , "src/timeout.c" + , "src/io.c" + , "src/usocket.c" + , "src/unix.c" + , "src/unixdgram.c" + , "src/unixstream.c" }, + defines = defines[plat], + incdir = "/src" + } + modules["socket.serial"] = { + sources = { + "src/buffer.c" + , "src/compat.c" + , "src/auxiliar.c" + , "src/options.c" + , "src/timeout.c" + , "src/io.c" + , "src/usocket.c" + , "src/serial.c" }, + defines = defines[plat], + incdir = "/src" + } + end + if plat == "win32" + or plat == "mingw32" + then + modules["socket.core"].sources[#modules["socket.core"].sources+1] = "src/wsocket.c" + modules["socket.core"].libraries = { "ws2_32" } + modules["socket.core"].libdirs = {} + end + return { modules = modules } +end + +build = { + type = "builtin", + platforms = { + unix = make_plat("unix"), + macosx = make_plat("macosx"), + haiku = make_plat("haiku"), + win32 = make_plat("win32"), + mingw32 = make_plat("mingw32") + }, + copy_directories = { + "docs" + , "samples" + , "etc" + , "test" } +} diff --git a/samples/README b/samples/README index e63a6f5..4ee06b6 100644 --- a/samples/README +++ b/samples/README @@ -1,11 +1,95 @@ This directory contains some sample programs using LuaSocket. This code is not supported. + tftp.lua -- Trivial FTP client + +This module implements file retrieval by the TFTP protocol. +Its main use was to test the UDP code, but since someone +found it usefull, I turned it into a module that is almost +official (no uploads, yet). + + dict.lua -- Dict client + +The dict.lua module started with a cool simple client +for the DICT protocol, written by Luiz Henrique Figueiredo. +This new version has been converted into a library, similar +to the HTTP and FTP libraries, that can be used from within +any luasocket application. Take a look on the source code +and you will be able to figure out how to use it. + + lp.lua -- LPD client library + +The lp.lua module implements the client part of the Line +Printer Daemon protocol, used to print files on Unix +machines. It is courtesy of David Burgess! See the source +code and the lpr.lua in the examples directory. + + b64.lua + qp.lua + eol.lua + +These are tiny programs that perform Base64, +Quoted-Printable and end-of-line marker conversions. + + get.lua -- file retriever + +This little program is a client that uses the FTP and +HTTP code to implement a command line file graber. Just +run + + lua get.lua- -- - -- -{fad87a86-297c-4881-a114-73b967bb3c92} -- -- -cdir -[ ] + +to download a remote file (either ftp:// or http://) to +the specified local file. The program also prints the +download throughput, elapsed time, bytes already downloaded +etc during download. + + check-memory.lua -- checks memory consumption + +This is just to see how much memory each module uses. + + dispatch.lua -- coroutine based dispatcher + +This is a first try at a coroutine based non-blocking +dispatcher for LuaSocket. Take a look at 'check-links.lua' +and at 'forward.lua' to see how to use it. + + check-links.lua -- HTML link checker program + +This little program scans a HTML file and checks for broken +links. It is similar to check-links.pl by Jamie Zawinski, +but uses all facilities of the LuaSocket library and the Lua +language. It has not been thoroughly tested, but it should +work. Just run + + lua check-links.lua [-n] { } > output + +and open the result to see a list of broken links. Make sure +you check the '-n' switch. It runs in non-blocking mode, +using coroutines, and is MUCH faster! + + forward.lua -- coroutine based forward server + +This is a forward server that can accept several connections +and transfers simultaneously using non-blocking I/O and the +coroutine-based dispatcher. You can run, for example + + lua forward.lua 8080:proxy.com:3128 + +to redirect all local conections to port 8080 to the host +'proxy.com' at port 3128. + + unix.c and unix.h + +This is an implementation of Unix local domain sockets and +demonstrates how to extend LuaSocket with a new type of +transport. It has been tested on Linux and on Mac OS X. + listener.lua -- socket to stdout talker.lua -- stdin to socket listener.lua and talker.lua are about the simplest -applications you can write using LuaSocket. Run +applications you can write using LuaSocket. Run 'lua listener.lua' and 'lua talker.lua' @@ -17,13 +101,13 @@ be printed by listen.lua. This is a cool program written by David Burgess to print files using the Line Printer Daemon protocol, widely used in Unix machines. It uses the lp.lua implementation, in the -etc directory. Just run 'lua lpr.lua +samples directory. Just run 'lua lpr.lua queue= ' and the file will print! cddb.lua -- CDDB client This is the first try on a simple CDDB client. Not really -useful, but one day it might become a module. +useful, but one day it might become a module. daytimeclnt.lua -- day time client diff --git a/etc/b64.lua b/samples/b64.lua similarity index 100% rename from etc/b64.lua rename to samples/b64.lua diff --git a/samples/cddb.lua b/samples/cddb.lua index 49a1871..59d5a44 100644 --- a/samples/cddb.lua +++ b/samples/cddb.lua @@ -26,7 +26,7 @@ function parse(body) data[key] = value end end - return data, code, message + return data, code, message end local host = socket.dns.gethostname() diff --git a/etc/check-links.lua b/samples/check-links.lua similarity index 100% rename from etc/check-links.lua rename to samples/check-links.lua diff --git a/etc/check-memory.lua b/samples/check-memory.lua similarity index 100% rename from etc/check-memory.lua rename to samples/check-memory.lua diff --git a/etc/cookie.lua b/samples/cookie.lua similarity index 82% rename from etc/cookie.lua rename to samples/cookie.lua index 4adb403..fec10a1 100644 --- a/etc/cookie.lua +++ b/samples/cookie.lua @@ -5,7 +5,7 @@ local ltn12 = require"ltn12" local token_class = '[^%c%s%(%)%<%>%@%,%;%:%\\%"%/%[%]%?%=%{%}]' -local function unquote(t, quoted) +local function unquote(t, quoted) local n = string.match(t, "%$(%d+)$") if n then n = tonumber(n) end if quoted[n] then return quoted[n] @@ -14,19 +14,19 @@ end local function parse_set_cookie(c, quoted, cookie_table) c = c .. ";$last=last;" - local _, __, n, v, i = string.find(c, "(" .. token_class .. + local _, _, n, v, i = string.find(c, "(" .. token_class .. "+)%s*=%s*(.-)%s*;%s*()") local cookie = { - name = n, - value = unquote(v, quoted), + name = n, + value = unquote(v, quoted), attributes = {} } while 1 do - _, __, n, v, i = string.find(c, "(" .. token_class .. + _, _, n, v, i = string.find(c, "(" .. token_class .. "+)%s*=?%s*(.-)%s*;%s*()", i) if not n or n == "$last" then break end cookie.attributes[#cookie.attributes+1] = { - name = n, + name = n, value = unquote(v, quoted) } end @@ -46,8 +46,8 @@ local function split_set_cookie(s, cookie_table) -- split into individual cookies i = 1 while 1 do - local _, __, cookie, next_token - _, __, cookie, i, next_token = string.find(s, "(.-)%s*%,%s*()(" .. + local _, _, cookie, next_token + _, _, cookie, i, next_token = string.find(s, "(.-)%s*%,%s*()(" .. token_class .. "+)%s*=", i) if not next_token then break end parse_set_cookie(cookie, quoted, cookie_table) @@ -62,12 +62,12 @@ local function quote(s) end local _empty = {} -local function build_cookies(cookies) +local function build_cookies(cookies) s = "" for i,v in ipairs(cookies or _empty) do if v.name then s = s .. v.name - if v.value and v.value ~= "" then + if v.value and v.value ~= "" then s = s .. '=' .. quote(v.value) end end @@ -83,6 +83,6 @@ local function build_cookies(cookies) end if i < #cookies then s = s .. ", " end end - return s + return s end diff --git a/etc/dict.lua b/samples/dict.lua similarity index 100% rename from etc/dict.lua rename to samples/dict.lua diff --git a/etc/dispatch.lua b/samples/dispatch.lua similarity index 100% rename from etc/dispatch.lua rename to samples/dispatch.lua diff --git a/etc/eol.lua b/samples/eol.lua similarity index 100% rename from etc/eol.lua rename to samples/eol.lua diff --git a/etc/forward.lua b/samples/forward.lua similarity index 100% rename from etc/forward.lua rename to samples/forward.lua diff --git a/etc/get.lua b/samples/get.lua similarity index 98% rename from etc/get.lua rename to samples/get.lua index 9edc235..d53c465 100644 --- a/etc/get.lua +++ b/samples/get.lua @@ -71,7 +71,7 @@ function stats(size) local current = socket.gettime() if chunk then -- total bytes received - got = got + string.len(chunk) + got = got + string.len(chunk) -- not enough time for estimate if current - last > 1 then io.stderr:write("\r", gauge(got, current - start, size)) diff --git a/etc/links b/samples/links similarity index 100% rename from etc/links rename to samples/links diff --git a/etc/lp.lua b/samples/lp.lua similarity index 100% rename from etc/lp.lua rename to samples/lp.lua diff --git a/etc/qp.lua b/samples/qp.lua similarity index 100% rename from etc/qp.lua rename to samples/qp.lua diff --git a/etc/tftp.lua b/samples/tftp.lua similarity index 100% rename from etc/tftp.lua rename to samples/tftp.lua diff --git a/socket.vcxproj b/socket.vcxproj index 16daeef..51ebc68 100755 --- a/socket.vcxproj +++ b/socket.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -21,6 +21,7 @@- + @@ -32,98 +33,6 @@ - -- -Document -copy %(FullPath) $(LUABIN_PATH)$(Configuration) -copy %(FullPath) $(LUABIN_PATH)$(Configuration) -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration) -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration) -$(LUABIN_PATH)$(Configuration)\%(Filename)%(Extension) -$(LUABIN_PATH)$(Configuration)\%(Filename)%(Extension) -$(LUABIN_PATH)$(Platform)\$(Configuration)\%(Filename)%(Extension) -$(LUABIN_PATH)$(Platform)\$(Configuration)\%(Filename)%(Extension) -- -Document -copy %(FullPath) $(LUABIN_PATH)$(Configuration) -copy %(FullPath) $(LUABIN_PATH)$(Configuration) -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration) -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration) -$(LUABIN_PATH)$(Configuration)\%(Filename)%(Extension) -$(LUABIN_PATH)$(Configuration)\%(Filename)%(Extension) -$(LUABIN_PATH)$(Platform)\$(Configuration)\%(Filename)%(Extension) -$(LUABIN_PATH)$(Platform)\$(Configuration)\%(Filename)%(Extension) -- - -Document -$(LUABIN_PATH)$(Platform)\$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Platform)\$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Configuration)\socket\%(Filename)%(Extension) -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Configuration)\socket -- -Document -$(LUABIN_PATH)$(Platform)\$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Platform)\$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Configuration)\socket\%(Filename)%(Extension) -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Configuration)\socket -- -Document -$(LUABIN_PATH)$(Platform)\$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Platform)\$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Configuration)\socket\%(Filename)%(Extension) -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Configuration)\socket -- -Document -$(LUABIN_PATH)$(Platform)\$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Platform)\$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Configuration)\socket\%(Filename)%(Extension) -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Configuration)\socket -- -Document -$(LUABIN_PATH)$(Platform)\$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Platform)\$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Configuration)\socket\%(Filename)%(Extension) -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Configuration)\socket -- -Document -$(LUABIN_PATH)$(Platform)\$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Platform)\$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Configuration)\socket\%(Filename)%(Extension) -$(LUABIN_PATH)$(Configuration)\socket\%(Filename)%(Extension) -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Platform)\$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Configuration)\socket -copy %(FullPath) $(LUABIN_PATH)$(Configuration)\socket -{66E3CE14-884D-4AEA-9F20-15A0BEAF8C5A} Win32Proj @@ -131,22 +40,22 @@DynamicLibrary -v110 +v141 MultiByte DynamicLibrary -v110 +v141 MultiByte DynamicLibrary -v110 +v141 MultiByte DynamicLibrary -v110 +v141 MultiByte @@ -177,7 +86,7 @@ <_ProjectFileVersion>11.0.50727.1 - $(LUALIB_PATH)$(Configuration)\socket\ +$(Configuration)\socket\ $(Configuration)\ true core @@ -185,23 +94,23 @@true core -$(LUABIN_PATH)$(Platform)\$(Configuration)\socket\ +$(Platform)\$(Configuration)\socket\ - $(LUALIB_PATH)$(Configuration)\socket\ +$(Configuration)\socket\ $(Configuration)\ false core false -$(LUABIN_PATH)$(Platform)\$(Configuration)\socket\ +$(Platform)\$(Configuration)\socket\ core - Disabled -$(LUAINC_PATH);%(AdditionalIncludeDirectories) +$(LUAINC);%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;LUASOCKET_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;LUASOCKET_DEBUG;%(PreprocessorDefinitions) true EnableFastChecks @@ -212,9 +121,9 @@$(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb $(LUALIB);ws2_32.lib;%(AdditionalDependencies) +$(LUALIBNAME);ws2_32.lib;%(AdditionalDependencies) $(OutDir)$(TargetName).dll -$(LUALIB_PATH)$(Configuration);%(AdditionalLibraryDirectories) +$(LUALIB);%(AdditionalLibraryDirectories) true $(OutDir)mime.pdb Windows @@ -228,7 +137,7 @@- Disabled -$(LUAINC_PATH);%(AdditionalIncludeDirectories) +$(LUAINC);%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;LUASOCKET_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;LUASOCKET_DEBUG;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL @@ -239,9 +148,9 @@$(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb $(LUALIB);ws2_32.lib;%(AdditionalDependencies) +$(LUALIBNAME);ws2_32.lib;%(AdditionalDependencies) $(OutDir)$(TargetName).dll -$(LUALIB_PATH)$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) +$(LUALIB);%(AdditionalLibraryDirectories) true $(OutDir)mime.pdb Windows @@ -253,7 +162,7 @@- -$(LUAINC_PATH);%(AdditionalIncludeDirectories) +$(LUAINC);%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;LUASOCKET_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreadedDLL @@ -262,9 +171,9 @@ $(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb $(LUALIB);ws2_32.lib;%(AdditionalDependencies) +$(LUALIBNAME);ws2_32.lib;%(AdditionalDependencies) $(OutDir)$(TargetName).dll -$(LUALIB_PATH)$(Configuration);%(AdditionalLibraryDirectories) +$(LUALIB);%(AdditionalLibraryDirectories) true Windows true @@ -277,7 +186,7 @@- -$(LUAINC_PATH);%(AdditionalIncludeDirectories) +$(LUAINC);%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;LUASOCKET_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreadedDLL @@ -288,9 +197,9 @@ $(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb $(LUALIB);ws2_32.lib;%(AdditionalDependencies) +$(LUALIBNAME);ws2_32.lib;%(AdditionalDependencies) $(OutDir)$(TargetName).dll -$(LUALIB_PATH)$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) +$(LUALIB);%(AdditionalLibraryDirectories) true Windows true diff --git a/socket.vcxproj.filters b/socket.vcxproj.filters deleted file mode 100644 index 38f2f07..0000000 --- a/socket.vcxproj.filters +++ /dev/null @@ -1,51 +0,0 @@ - -- \ No newline at end of file diff --git a/src/auxiliar.c b/src/auxiliar.c index 18fa8e4..93a66a0 100644 --- a/src/auxiliar.c +++ b/src/auxiliar.c @@ -2,14 +2,11 @@ * Auxiliar routines for class hierarchy manipulation * LuaSocket toolkit \*=========================================================================*/ +#include "luasocket.h" +#include "auxiliar.h" #include- -- - - - - - - - - - - - - -- -cdir -- -cdir -- -ldir -- -ldir -- -ldir -- -ldir -- -ldir -- -ldir -- -- -{b053460d-5439-4e3a-a2eb-c31a95b5691f} -- -{b301b82c-37cb-4e05-9333-194e92ed7a62} -#include -#include "auxiliar.h" - -/*=========================================================================*\ -* Exported functions -\*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Initializes the module \*-------------------------------------------------------------------------*/ @@ -143,7 +140,7 @@ void *auxiliar_getgroupudata(lua_State *L, const char *groupname, int objidx) { * otherwise \*-------------------------------------------------------------------------*/ void *auxiliar_getclassudata(lua_State *L, const char *classname, int objidx) { - return luaL_checkudata(L, objidx, classname); + return luaL_testudata(L, objidx, classname); } /*-------------------------------------------------------------------------*\ @@ -155,4 +152,3 @@ int auxiliar_typeerror (lua_State *L, int narg, const char *tname) { luaL_typename(L, narg)); return luaL_argerror(L, narg, msg); } - diff --git a/src/auxiliar.h b/src/auxiliar.h index 65511d4..e8c3ead 100644 --- a/src/auxiliar.h +++ b/src/auxiliar.h @@ -29,20 +29,26 @@ * reverse mapping are done using lauxlib. \*=========================================================================*/ -#include "lua.h" -#include "lauxlib.h" -#include "compat.h" +#include "luasocket.h" + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif int auxiliar_open(lua_State *L); void auxiliar_newclass(lua_State *L, const char *classname, luaL_Reg *func); +int auxiliar_tostring(lua_State *L); void auxiliar_add2group(lua_State *L, const char *classname, const char *group); -void auxiliar_setclass(lua_State *L, const char *classname, int objidx); +int auxiliar_checkboolean(lua_State *L, int objidx); void *auxiliar_checkclass(lua_State *L, const char *classname, int objidx); void *auxiliar_checkgroup(lua_State *L, const char *groupname, int objidx); -void *auxiliar_getclassudata(lua_State *L, const char *groupname, int objidx); +void auxiliar_setclass(lua_State *L, const char *classname, int objidx); void *auxiliar_getgroupudata(lua_State *L, const char *groupname, int objidx); -int auxiliar_checkboolean(lua_State *L, int objidx); -int auxiliar_tostring(lua_State *L); +void *auxiliar_getclassudata(lua_State *L, const char *groupname, int objidx); int auxiliar_typeerror(lua_State *L, int narg, const char *tname); +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + #endif /* AUXILIAR_H */ diff --git a/src/buffer.c b/src/buffer.c index fff1634..7148be3 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -2,10 +2,7 @@ * Input/Output interface for Lua programs * LuaSocket toolkit \*=========================================================================*/ -#include "lua.h" -#include "lauxlib.h" -#include "compat.h" - +#include "luasocket.h" #include "buffer.h" /*=========================================================================*\ @@ -106,11 +103,14 @@ int buffer_meth_send(lua_State *L, p_buffer buf) { * object:receive() interface \*-------------------------------------------------------------------------*/ int buffer_meth_receive(lua_State *L, p_buffer buf) { - int err = IO_DONE, top = lua_gettop(L); + int err = IO_DONE, top; luaL_Buffer b; size_t size; const char *part = luaL_optlstring(L, 3, "", &size); timeout_markstart(buf->tm); + /* make sure we don't confuse buffer stuff with arguments */ + lua_settop(L, 3); + top = lua_gettop(L); /* initialize buffer with optional extra prefix * (useful for concatenating previous partial results) */ luaL_buffinit(L, &b); diff --git a/src/buffer.h b/src/buffer.h index 1281bb3..a0901fc 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -15,8 +15,7 @@ * The module is built on top of the I/O abstraction defined in io.h and the * timeout management is done with the timeout.h interface. \*=========================================================================*/ -#include "lua.h" - +#include "luasocket.h" #include "io.h" #include "timeout.h" @@ -34,12 +33,20 @@ typedef struct t_buffer_ { } t_buffer; typedef t_buffer *p_buffer; +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + int buffer_open(lua_State *L); void buffer_init(p_buffer buf, p_io io, p_timeout tm); -int buffer_meth_send(lua_State *L, p_buffer buf); -int buffer_meth_receive(lua_State *L, p_buffer buf); int buffer_meth_getstats(lua_State *L, p_buffer buf); int buffer_meth_setstats(lua_State *L, p_buffer buf); +int buffer_meth_send(lua_State *L, p_buffer buf); +int buffer_meth_receive(lua_State *L, p_buffer buf); int buffer_isempty(p_buffer buf); +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + #endif /* BUF_H */ diff --git a/src/compat.c b/src/compat.c index c2d99cb..34ffdaf 100644 --- a/src/compat.c +++ b/src/compat.c @@ -1,10 +1,12 @@ +#include "luasocket.h" #include "compat.h" #if LUA_VERSION_NUM==501 + /* ** Adapted from Lua 5.2 */ -void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { +void luasocket_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { luaL_checkstack(L, nup+1, "too many upvalues"); for (; l->name != NULL; l++) { /* fill the table with given functions */ int i; @@ -16,4 +18,22 @@ void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { } lua_pop(L, nup); /* remove upvalues */ } + +/* +** Duplicated from Lua 5.2 +*/ +void *luasocket_testudata (lua_State *L, int ud, const char *tname) { + void *p = lua_touserdata(L, ud); + if (p != NULL) { /* value is a userdata? */ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + luaL_getmetatable(L, tname); /* get correct metatable */ + if (!lua_rawequal(L, -1, -2)) /* not the same? */ + p = NULL; /* value is a userdata with wrong metatable */ + lua_pop(L, 2); /* remove both metatables */ + return p; + } + } + return NULL; /* value is not a userdata with a metatable */ +} + #endif diff --git a/src/compat.h b/src/compat.h index 7bf8010..fa2d7d7 100644 --- a/src/compat.h +++ b/src/compat.h @@ -1,11 +1,22 @@ #ifndef COMPAT_H #define COMPAT_H -#include "lua.h" -#include "lauxlib.h" - #if LUA_VERSION_NUM==501 -void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup); + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +void luasocket_setfuncs (lua_State *L, const luaL_Reg *l, int nup); +void *luasocket_testudata ( lua_State *L, int arg, const char *tname); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#define luaL_setfuncs luasocket_setfuncs +#define luaL_testudata luasocket_testudata + #endif #endif diff --git a/src/except.c b/src/except.c index 261ac98..9c3317f 100644 --- a/src/except.c +++ b/src/except.c @@ -2,17 +2,13 @@ * Simple exception support * LuaSocket toolkit \*=========================================================================*/ -#include - -#include "lua.h" -#include "lauxlib.h" -#include "compat.h" - +#include "luasocket.h" #include "except.h" +#include #if LUA_VERSION_NUM < 502 #define lua_pcallk(L, na, nr, err, ctx, cont) \ - ((void)ctx,(void)cont,lua_pcall(L, na, nr, err)) + (((void)ctx),((void)cont),lua_pcall(L, na, nr, err)) #endif #if LUA_VERSION_NUM < 503 @@ -39,18 +35,17 @@ static luaL_Reg func[] = { * Try factory \*-------------------------------------------------------------------------*/ static void wrap(lua_State *L) { - lua_newtable(L); - lua_pushnumber(L, 1); - lua_pushvalue(L, -3); - lua_settable(L, -3); - lua_insert(L, -2); - lua_pop(L, 1); + lua_createtable(L, 1, 0); + lua_pushvalue(L, -2); + lua_rawseti(L, -2, 1); + lua_pushvalue(L, lua_upvalueindex(1)); + lua_setmetatable(L, -2); } static int finalize(lua_State *L) { if (!lua_toboolean(L, 1)) { - lua_pushvalue(L, lua_upvalueindex(1)); - lua_pcall(L, 0, 0, 0); + lua_pushvalue(L, lua_upvalueindex(2)); + lua_call(L, 0, 0); lua_settop(L, 2); wrap(L); lua_error(L); @@ -58,15 +53,17 @@ static int finalize(lua_State *L) { } else return lua_gettop(L); } -static int do_nothing(lua_State *L) { +static int do_nothing(lua_State *L) { (void) L; - return 0; + return 0; } static int global_newtry(lua_State *L) { lua_settop(L, 1); if (lua_isnil(L, 1)) lua_pushcfunction(L, do_nothing); - lua_pushcclosure(L, finalize, 1); + lua_pushvalue(L, lua_upvalueindex(1)); + lua_insert(L, -2); + lua_pushcclosure(L, finalize, 2); return 1; } @@ -74,13 +71,16 @@ static int global_newtry(lua_State *L) { * Protect factory \*-------------------------------------------------------------------------*/ static int unwrap(lua_State *L) { - if (lua_istable(L, -1)) { - lua_pushnumber(L, 1); - lua_gettable(L, -2); - lua_pushnil(L); - lua_insert(L, -2); - return 1; - } else return 0; + if (lua_istable(L, -1) && lua_getmetatable(L, -1)) { + int r = lua_rawequal(L, -1, lua_upvalueindex(1)); + lua_pop(L, 1); + if (r) { + lua_pushnil(L); + lua_rawgeti(L, -2, 1); + return 1; + } + } + return 0; } static int protected_finish(lua_State *L, int status, lua_KContext ctx) { @@ -103,14 +103,17 @@ static int protected_cont(lua_State *L) { static int protected_(lua_State *L) { int status; - lua_pushvalue(L, lua_upvalueindex(1)); + lua_pushvalue(L, lua_upvalueindex(2)); lua_insert(L, 1); status = lua_pcallk(L, lua_gettop(L) - 1, LUA_MULTRET, 0, 0, protected_cont); return protected_finish(L, status, 0); } static int global_protect(lua_State *L) { - lua_pushcclosure(L, protected_, 1); + lua_settop(L, 1); + lua_pushvalue(L, lua_upvalueindex(1)); + lua_insert(L, 1); + lua_pushcclosure(L, protected_, 2); return 1; } @@ -118,6 +121,9 @@ static int global_protect(lua_State *L) { * Init module \*-------------------------------------------------------------------------*/ int except_open(lua_State *L) { - luaL_setfuncs(L, func, 0); + lua_newtable(L); /* metatable for wrapped exceptions */ + lua_pushboolean(L, 0); + lua_setfield(L, -2, "__metatable"); + luaL_setfuncs(L, func, 1); return 0; } diff --git a/src/except.h b/src/except.h index 1e7a245..71c31fd 100644 --- a/src/except.h +++ b/src/except.h @@ -9,25 +9,38 @@ * error checking was taking a substantial amount of the coding. These * function greatly simplify the task of checking errors. * -* The main idea is that functions should return nil as its first return -* value when it finds an error, and return an error message (or value) +* The main idea is that functions should return nil as their first return +* values when they find an error, and return an error message (or value) * following nil. In case of success, as long as the first value is not nil, * the other values don't matter. * * The idea is to nest function calls with the "try" function. This function -* checks the first value, and calls "error" on the second if the first is -* nil. Otherwise, it returns all values it received. +* checks the first value, and, if it's falsy, wraps the second value in a +* table with metatable and calls "error" on it. Otherwise, it returns all +* values it received. Basically, it works like the Lua "assert" function, +* but it creates errors targeted specifically at "protect". * -* The protect function returns a new function that behaves exactly like the -* function it receives, but the new function doesn't throw exceptions: it -* returns nil followed by the error message instead. +* The "newtry" function is a factory for "try" functions that call a +* finalizer in protected mode before calling "error". * -* With these two function, it's easy to write functions that throw -* exceptions on error, but that don't interrupt the user script. +* The "protect" function returns a new function that behaves exactly like +* the function it receives, but the new function catches exceptions thrown +* by "try" functions and returns nil followed by the error message instead. +* +* With these three functions, it's easy to write functions that throw +* exceptions on error, but that don't interrupt the user script. \*=========================================================================*/ -#include "lua.h" +#include "luasocket.h" + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif int except_open(lua_State *L); +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + #endif diff --git a/src/ftp.lua b/src/ftp.lua index 917cd89..0ebc508 100644 --- a/src/ftp.lua +++ b/src/ftp.lua @@ -23,7 +23,7 @@ local _M = socket.ftp -- timeout in seconds before the program gives up on a connection _M.TIMEOUT = 60 -- default port for ftp service -_M.PORT = 21 +local PORT = 21 -- this is the default anonymous password. used when no password is -- provided in url. should be changed to your e-mail. _M.USER = "ftp" @@ -35,7 +35,7 @@ _M.PASSWORD = "anonymous@anonymous.org" local metat = { __index = {} } function _M.open(server, port, create) - local tp = socket.try(tp.connect(server, port or _M.PORT, _M.TIMEOUT, create)) + local tp = socket.try(tp.connect(server, port or PORT, _M.TIMEOUT, create)) local f = base.setmetatable({ tp = tp }, metat) -- make sure everything gets closed in an exception f.try = socket.newtry(function() f:close() end) @@ -51,12 +51,12 @@ end function metat.__index:pasvconnect() self.data = self.try(socket.tcp()) self.try(self.data:settimeout(_M.TIMEOUT)) - self.try(self.data:connect(self.pasvt.ip, self.pasvt.port)) + self.try(self.data:connect(self.pasvt.address, self.pasvt.port)) end function metat.__index:login(user, password) self.try(self.tp:command("user", user or _M.USER)) - local code, reply = self.try(self.tp:check{"2..", 331}) + local code, _ = self.try(self.tp:check{"2..", 331}) if code == 331 then self.try(self.tp:command("pass", password or _M.PASSWORD)) self.try(self.tp:check("2..")) @@ -66,37 +66,70 @@ end function metat.__index:pasv() self.try(self.tp:command("pasv")) - local code, reply = self.try(self.tp:check("2..")) + local _, reply = self.try(self.tp:check("2..")) local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) self.try(a and b and c and d and p1 and p2, reply) self.pasvt = { - ip = string.format("%d.%d.%d.%d", a, b, c, d), + address = string.format("%d.%d.%d.%d", a, b, c, d), port = p1*256 + p2 } if self.server then self.server:close() self.server = nil end - return self.pasvt.ip, self.pasvt.port + return self.pasvt.address, self.pasvt.port end -function metat.__index:port(ip, port) +function metat.__index:epsv() + self.try(self.tp:command("epsv")) + local _, reply = self.try(self.tp:check("229")) + local pattern = "%((.)(.-)%1(.-)%1(.-)%1%)" + local _, _, _, port = string.match(reply, pattern) + self.try(port, "invalid epsv response") + self.pasvt = { + address = self.tp:getpeername(), + port = port + } + if self.server then + self.server:close() + self.server = nil + end + return self.pasvt.address, self.pasvt.port +end + + +function metat.__index:port(address, port) self.pasvt = nil - if not ip then - ip, port = self.try(self.tp:getcontrol():getsockname()) - self.server = self.try(socket.bind(ip, 0)) - ip, port = self.try(self.server:getsockname()) + if not address then + address = self.try(self.tp:getsockname()) + self.server = self.try(socket.bind(address, 0)) + address, port = self.try(self.server:getsockname()) self.try(self.server:settimeout(_M.TIMEOUT)) end local pl = math.mod(port, 256) local ph = (port - pl)/256 - local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") + local arg = string.gsub(string.format("%s,%d,%d", address, ph, pl), "%.", ",") self.try(self.tp:command("port", arg)) self.try(self.tp:check("2..")) return 1 end +function metat.__index:eprt(family, address, port) + self.pasvt = nil + if not address then + address = self.try(self.tp:getsockname()) + self.server = self.try(socket.bind(address, 0)) + address, port = self.try(self.server:getsockname()) + self.try(self.server:settimeout(_M.TIMEOUT)) + end + local arg = string.format("|%s|%s|%d|", family, address, port) + self.try(self.tp:command("eprt", arg)) + self.try(self.tp:check("2..")) + return 1 +end + + function metat.__index:send(sendt) self.try(self.pasvt or self.server, "need port or pasv first") -- if there is a pasvt table, we already sent a PASV command @@ -109,13 +142,13 @@ function metat.__index:send(sendt) local command = sendt.command or "stor" -- send the transfer command and check the reply self.try(self.tp:command(command, argument)) - local code, reply = self.try(self.tp:check{"2..", "1.."}) - -- if there is not a a pasvt table, then there is a server + local code, _ = self.try(self.tp:check{"2..", "1.."}) + -- if there is not a pasvt table, then there is a server -- and we already sent a PORT command if not self.pasvt then self:portconnect() end -- get the sink, source and step for the transfer local step = sendt.step or ltn12.pump.step - local readt = {self.tp.c} + local readt = { self.tp } local checkstep = function(src, snk) -- check status in control connection while downloading local readyt = socket.select(readt, nil, 0) @@ -207,7 +240,7 @@ local function tput(putt) f:greet() f:login(putt.user, putt.password) if putt.type then f:type(putt.type) end - f:pasv() + f:epsv() local sent = f:send(putt) f:quit() f:close() @@ -219,7 +252,7 @@ local default = { scheme = "ftp" } -local function parse(u) +local function genericform(u) local t = socket.try(url.parse(u, default)) socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") socket.try(t.host, "missing hostname") @@ -232,8 +265,10 @@ local function parse(u) return t end +_M.genericform = genericform + local function sput(u, body) - local putt = parse(u) + local putt = genericform(u) putt.source = ltn12.source.string(body) return tput(putt) end @@ -250,14 +285,14 @@ local function tget(gett) f:greet() f:login(gett.user, gett.password) if gett.type then f:type(gett.type) end - f:pasv() + f:epsv() f:receive(gett) f:quit() return f:close() end local function sget(u) - local gett = parse(u) + local gett = genericform(u) local t = {} gett.sink = ltn12.sink.table(t) tget(gett) @@ -271,8 +306,17 @@ _M.command = socket.protect(function(cmdt) local f = _M.open(cmdt.host, cmdt.port, cmdt.create) f:greet() f:login(cmdt.user, cmdt.password) - f.try(f.tp:command(cmdt.command, cmdt.argument)) - if cmdt.check then f.try(f.tp:check(cmdt.check)) end + if type(cmdt.command) == "table" then + local argument = cmdt.argument or {} + local check = cmdt.check or {} + for i,cmd in ipairs(cmdt.command) do + f.try(f.tp:command(cmd, argument[i])) + if check[i] then f.try(f.tp:check(check[i])) end + end + else + f.try(f.tp:command(cmdt.command, cmdt.argument)) + if cmdt.check then f.try(f.tp:check(cmdt.check)) end + end f:quit() return f:close() end) @@ -282,4 +326,4 @@ _M.get = socket.protect(function(gett) else return tget(gett) end end) -return _M \ No newline at end of file +return _M diff --git a/src/http.lua b/src/http.lua index 45ffa15..fbd5ff6 100644 --- a/src/http.lua +++ b/src/http.lua @@ -23,11 +23,24 @@ local _M = socket.http ----------------------------------------------------------------------------- -- connection timeout in seconds _M.TIMEOUT = 60 --- default port for document retrieval -_M.PORT = 80 -- user agent field sent in request _M.USERAGENT = socket._VERSION +-- supported schemes and their particulars +local SCHEMES = { + http = { + port = 80 + , create = function(t) + return socket.tcp end } + , https = { + port = 443 + , create = function(t) + local https = assert( + require("ssl.https"), 'LuaSocket: LuaSec not found') + local tcp = assert( + https.tcp, 'LuaSocket: Function tcp() not available from LuaSec') + return tcp(t) end }} + ----------------------------------------------------------------------------- -- Reads MIME headers from a connection, unfolding where needed ----------------------------------------------------------------------------- @@ -76,7 +89,7 @@ socket.sourcet["http-chunked"] = function(sock, headers) -- was it the last chunk? if size > 0 then -- if not, get chunk and skip terminating CRLF - local chunk, err, part = sock:receive(size) + local chunk, err, _ = sock:receive(size) if chunk then sock:receive() end return chunk, err else @@ -108,13 +121,13 @@ local metat = { __index = {} } function _M.open(host, port, create) -- create socket with user connect function, or with default - local c = socket.try((create or socket.tcp)()) + local c = socket.try(create()) local h = base.setmetatable({ c = c }, metat) -- create finalized try h.try = socket.newtry(function() h:close() end) -- set timeout before connecting h.try(c:settimeout(_M.TIMEOUT)) - h.try(c:connect(host, port or _M.PORT)) + h.try(c:connect(host, port)) -- here everything worked return h end @@ -144,10 +157,15 @@ function metat.__index:sendbody(headers, source, step) end function metat.__index:receivestatusline() - local status = self.try(self.c:receive(5)) + local status,ec = self.try(self.c:receive(5)) -- identify HTTP/0.9 responses, which do not contain a status line -- this is just a heuristic, but is what the RFC recommends - if status ~= "HTTP/" then return nil, status end + if status ~= "HTTP/" then + if ec == "timeout" then + return 408 + end + return nil, status + end -- otherwise proceed reading a status line status = self.try(self.c:receive("*l", status)) local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) @@ -209,7 +227,10 @@ end local function adjustheaders(reqt) -- default headers - local host = string.gsub(reqt.authority, "^.-@", "") + local host = reqt.host + local port = tostring(reqt.port) + if port ~= tostring(SCHEMES[reqt.scheme].port) then + host = host .. ':' .. port end local lower = { ["user-agent"] = _M.USERAGENT, ["host"] = host, @@ -218,15 +239,16 @@ local function adjustheaders(reqt) } -- if we have authentication information, pass it along if reqt.user and reqt.password then - lower["authorization"] = - "Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password)) + lower["authorization"] = + "Basic " .. (mime.b64(reqt.user .. ":" .. + url.unescape(reqt.password))) end -- if we have proxy authentication information, pass it along local proxy = reqt.proxy or _M.PROXY if proxy then proxy = url.parse(proxy) if proxy.user and proxy.password then - lower["proxy-authorization"] = + lower["proxy-authorization"] = "Basic " .. (mime.b64(proxy.user .. ":" .. proxy.password)) end end @@ -239,10 +261,8 @@ end -- default url parts local default = { - host = "", - port = _M.PORT, - path ="/", - scheme = "http" + path ="/" + , scheme = "http" } local function adjustrequest(reqt) @@ -250,25 +270,48 @@ local function adjustrequest(reqt) local nreqt = reqt.url and url.parse(reqt.url, default) or {} -- explicit components override url for i,v in base.pairs(reqt) do nreqt[i] = v end - if nreqt.port == "" then nreqt.port = 80 end - socket.try(nreqt.host and nreqt.host ~= "", - "invalid host '" .. base.tostring(nreqt.host) .. "'") + -- default to scheme particulars + local schemedefs, host, port, method + = SCHEMES[nreqt.scheme], nreqt.host, nreqt.port, nreqt.method + if not nreqt.create then nreqt.create = schemedefs.create(nreqt) end + if not (port and port ~= '') then nreqt.port = schemedefs.port end + if not (method and method ~= '') then nreqt.method = 'GET' end + if not (host and host ~= "") then + socket.try(nil, "invalid host '" .. base.tostring(nreqt.host) .. "'") + end -- compute uri if user hasn't overriden nreqt.uri = reqt.uri or adjusturi(nreqt) -- adjust headers in request nreqt.headers = adjustheaders(nreqt) + if nreqt.source + and not nreqt.headers["content-length"] + and not nreqt.headers["transfer-encoding"] + then + nreqt.headers["transfer-encoding"] = "chunked" + end + -- ajust host and port if there is a proxy nreqt.host, nreqt.port = adjustproxy(nreqt) return nreqt end local function shouldredirect(reqt, code, headers) - return headers.location and - string.gsub(headers.location, "%s", "") ~= "" and - (reqt.redirect ~= false) and + local location = headers.location + if not location then return false end + location = string.gsub(location, "%s", "") + if location == "" then return false end + -- the RFC says the redirect URL may be relative + location = url.absolute(reqt.url, location) + local scheme = url.parse(location).scheme + if scheme and (not SCHEMES[scheme]) then return false end + -- avoid https downgrades + if ('https' == reqt.scheme) and ('https' ~= scheme) then return false end + return (reqt.redirect ~= false) and (code == 301 or code == 302 or code == 303 or code == 307) and (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") - and (not reqt.nredirects or reqt.nredirects < 5) + and ((false == reqt.maxredirects) + or ((reqt.nredirects or 0) + < (reqt.maxredirects or 5))) end local function shouldreceivebody(reqt, code) @@ -282,17 +325,23 @@ end local trequest, tredirect --[[local]] function tredirect(reqt, location) + -- the RFC says the redirect URL may be relative + local newurl = url.absolute(reqt.url, location) + -- if switching schemes, reset port and create function + if url.parse(newurl).scheme ~= reqt.scheme then + reqt.port = nil + reqt.create = nil end + -- make new request local result, code, headers, status = trequest { - -- the RFC says the redirect URL has to be absolute, but some - -- servers do not respect that - url = url.absolute(reqt.url, location), + url = newurl, source = reqt.source, sink = reqt.sink, headers = reqt.headers, - proxy = reqt.proxy, + proxy = reqt.proxy, + maxredirects = reqt.maxredirects, nredirects = (reqt.nredirects or 0) + 1, create = reqt.create - } + } -- pass location header back as a hint we redirected headers = headers or {} headers.location = headers.location or location @@ -309,23 +358,25 @@ end h:sendheaders(nreqt.headers) -- if there is a body, send it if nreqt.source then - h:sendbody(nreqt.headers, nreqt.source, nreqt.step) + h:sendbody(nreqt.headers, nreqt.source, nreqt.step) end local code, status = h:receivestatusline() -- if it is an HTTP/0.9 server, simply get the body and we are done if not code then h:receive09body(status, nreqt.sink, nreqt.step) return 1, 200 + elseif code == 408 then + return 1, code end local headers -- ignore any 100-continue messages - while code == 100 do - headers = h:receiveheaders() + while code == 100 do + h:receiveheaders() code, status = h:receivestatusline() end headers = h:receiveheaders() -- at this point we should have a honest reply from the server - -- we can't redirect if we already used the source, so we report the error + -- we can't redirect if we already used the source, so we report the error if shouldredirect(nreqt, code, headers) and not nreqt.source then h:close() return tredirect(reqt, headers.location) @@ -338,11 +389,13 @@ end return 1, code, headers, status end -local function srequest(u, b) +-- turns an url and a body into a generic request +local function genericform(u, b) local t = {} local reqt = { url = u, - sink = ltn12.sink.table(t) + sink = ltn12.sink.table(t), + target = t } if b then reqt.source = ltn12.source.string(b) @@ -352,8 +405,15 @@ local function srequest(u, b) } reqt.method = "POST" end - local code, headers, status = socket.skip(1, trequest(reqt)) - return table.concat(t), code, headers, status + return reqt +end + +_M.genericform = genericform + +local function srequest(u, b) + local reqt = genericform(u, b) + local _, code, headers, status = trequest(reqt) + return table.concat(reqt.target), code, headers, status end _M.request = socket.protect(function(reqt, body) @@ -361,4 +421,5 @@ _M.request = socket.protect(function(reqt, body) else return trequest(reqt) end end) +_M.schemes = SCHEMES return _M diff --git a/src/inet.c b/src/inet.c old mode 100644 new mode 100755 index 331b800..138c9ab --- a/src/inet.c +++ b/src/inet.c @@ -2,16 +2,13 @@ * Internet domain functions * LuaSocket toolkit \*=========================================================================*/ +#include "luasocket.h" +#include "inet.h" + #include #include #include -#include "lua.h" -#include "lauxlib.h" -#include "compat.h" - -#include "inet.h" - /*=========================================================================*\ * Internal function prototypes. \*=========================================================================*/ @@ -32,9 +29,6 @@ static luaL_Reg func[] = { { NULL, NULL} }; -/*=========================================================================*\ -* Exported functions -\*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ @@ -259,7 +253,7 @@ int inet_meth_getpeername(lua_State *L, p_socket ps, int family) port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); if (err) { lua_pushnil(L); - lua_pushstring(L, gai_strerror(err)); + lua_pushstring(L, LUA_GAI_STRERROR(err)); return 2; } lua_pushstring(L, name); @@ -292,7 +286,7 @@ int inet_meth_getsockname(lua_State *L, p_socket ps, int family) name, INET6_ADDRSTRLEN, port, 6, NI_NUMERICHOST | NI_NUMERICSERV); if (err) { lua_pushnil(L); - lua_pushstring(L, gai_strerror(err)); + lua_pushstring(L, LUA_GAI_STRERROR(err)); return 2; } lua_pushstring(L, name); @@ -423,8 +417,8 @@ const char *inet_tryconnect(p_socket ps, int *family, const char *address, /* try connecting to remote address */ err = socket_strerror(socket_connect(ps, (SA *) iterator->ai_addr, (socklen_t) iterator->ai_addrlen, tm)); - /* if success, break out of loop */ - if (err == NULL) { + /* if success or timeout is zero, break out of loop */ + if (err == NULL || timeout_iszero(tm)) { *family = current_family; break; } diff --git a/src/inet.h b/src/inet.h index feb3541..5618b61 100644 --- a/src/inet.h +++ b/src/inet.h @@ -14,7 +14,7 @@ * * The Lua functions toip and tohostname are also implemented here. \*=========================================================================*/ -#include "lua.h" +#include "luasocket.h" #include "socket.h" #include "timeout.h" @@ -22,21 +22,23 @@ #define LUASOCKET_INET_ATON #endif +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + int inet_open(lua_State *L); -const char *inet_trycreate(p_socket ps, int family, int type, int protocol); -const char *inet_tryconnect(p_socket ps, int *family, const char *address, - const char *serv, p_timeout tm, struct addrinfo *connecthints); -const char *inet_trybind(p_socket ps, int *family, const char *address, - const char *serv, struct addrinfo *bindhints); -const char *inet_trydisconnect(p_socket ps, int family, p_timeout tm); -const char *inet_tryaccept(p_socket server, int family, p_socket client, p_timeout tm); +int inet_optfamily(lua_State* L, int narg, const char* def); +int inet_optsocktype(lua_State* L, int narg, const char* def); int inet_meth_getpeername(lua_State *L, p_socket ps, int family); int inet_meth_getsockname(lua_State *L, p_socket ps, int family); -int inet_optfamily(lua_State* L, int narg, const char* def); -int inet_optsocktype(lua_State* L, int narg, const char* def); +const char *inet_trycreate(p_socket ps, int family, int type, int protocol); +const char *inet_trydisconnect(p_socket ps, int family, p_timeout tm); +const char *inet_tryconnect(p_socket ps, int *family, const char *address, const char *serv, p_timeout tm, struct addrinfo *connecthints); +const char *inet_tryaccept(p_socket server, int family, p_socket client, p_timeout tm); +const char *inet_trybind(p_socket ps, int *family, const char *address, const char *serv, struct addrinfo *bindhints); #ifdef LUASOCKET_INET_ATON int inet_aton(const char *cp, struct in_addr *inp); @@ -47,4 +49,8 @@ const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt); int inet_pton(int af, const char *src, void *dst); #endif +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + #endif /* INET_H */ diff --git a/src/io.c b/src/io.c index a4230ce..5ad4b3a 100644 --- a/src/io.c +++ b/src/io.c @@ -2,11 +2,9 @@ * Input/Output abstraction * LuaSocket toolkit \*=========================================================================*/ +#include "luasocket.h" #include "io.h" -/*=========================================================================*\ -* Exported functions -\*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Initializes C structure \*-------------------------------------------------------------------------*/ diff --git a/src/io.h b/src/io.h index 8cca08a..b8a54df 100644 --- a/src/io.h +++ b/src/io.h @@ -12,9 +12,7 @@ * The module socket.h implements this interface, and thus the module tcp.h * is very simple. \*=========================================================================*/ -#include -#include "lua.h" - +#include "luasocket.h" #include "timeout.h" /* IO error codes */ @@ -58,8 +56,15 @@ typedef struct t_io_ { } t_io; typedef t_io *p_io; +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + void io_init(p_io io, p_send send, p_recv recv, p_error error, void *ctx); const char *io_strerror(int err); -#endif /* IO_H */ +#ifndef _WIN32 +#pragma GCC visibility pop +#endif +#endif /* IO_H */ diff --git a/src/ltn12.lua b/src/ltn12.lua index 1014de2..4cb17f5 100644 --- a/src/ltn12.lua +++ b/src/ltn12.lua @@ -9,10 +9,13 @@ ----------------------------------------------------------------------------- local string = require("string") local table = require("table") +local unpack = unpack or table.unpack local base = _G +local select = select + local _M = {} if module then -- heuristic for exporting a global package table - ltn12 = _M + ltn12 = _M -- luacheck: ignore end local filter,source,sink,pump = {},{},{},{} @@ -124,6 +127,16 @@ function source.string(s) else return source.empty() end end +-- creates table source +function source.table(t) + base.assert('table' == type(t)) + local i = 0 + return function() + i = i + 1 + return t[i] + end +end + -- creates rewindable source function source.rewind(src) base.assert(src) diff --git a/src/luasocket.c b/src/luasocket.c old mode 100644 new mode 100755 index 7d9c802..0fd99f7 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -12,16 +12,6 @@ * standard Lua read and write functions. \*=========================================================================*/ -/*=========================================================================*\ -* Standard include files -\*=========================================================================*/ -#include "lua.h" -#include "lauxlib.h" -#include "compat.h" - -/*=========================================================================*\ -* LuaSocket includes -\*=========================================================================*/ #include "luasocket.h" #include "auxiliar.h" #include "except.h" @@ -64,7 +54,7 @@ static luaL_Reg func[] = { * Skip a few arguments \*-------------------------------------------------------------------------*/ static int global_skip(lua_State *L) { - int amount = luaL_checkinteger(L, 1); + int amount = (int) luaL_checkinteger(L, 1); int ret = lua_gettop(L) - amount - 1; return ret >= 0 ? ret : 0; } diff --git a/src/luasocket.h b/src/luasocket.h index f75d21f..1017fba 100644 --- a/src/luasocket.h +++ b/src/luasocket.h @@ -6,20 +6,27 @@ * Diego Nehab * 9/11/1999 \*=========================================================================*/ -#include "lua.h" -/*-------------------------------------------------------------------------*\ +/*-------------------------------------------------------------------------* \ * Current socket library version \*-------------------------------------------------------------------------*/ -#define LUASOCKET_VERSION "LuaSocket 3.0-rc1" +#define LUASOCKET_VERSION "LuaSocket 3.0.0" #define LUASOCKET_COPYRIGHT "Copyright (C) 1999-2013 Diego Nehab" /*-------------------------------------------------------------------------*\ * This macro prefixes all exported API functions \*-------------------------------------------------------------------------*/ #ifndef LUASOCKET_API -#define LUASOCKET_API extern +#ifdef _WIN32 +#define LUASOCKET_API __declspec(dllexport) +#else +#define LUASOCKET_API __attribute__ ((visibility ("default"))) #endif +#endif + +#include "lua.h" +#include "lauxlib.h" +#include "compat.h" /*-------------------------------------------------------------------------*\ * Initializes the library. diff --git a/src/makefile b/src/makefile old mode 100644 new mode 100755 index 2704a92..06f4d19 --- a/src/makefile +++ b/src/makefile @@ -12,34 +12,31 @@ # # make PLAT=linux DEBUG=DEBUG LUAV=5.2 prefix=/sw -# PLAT: linux macosx win32 mingw +# PLAT: linux macosx win32 win64 mingw # platform to build for PLAT?=linux -# LUAV: 5.1 5.2 +# LUAV: 5.1 5.2 5.3 5.4 # lua version to build against LUAV?=5.1 # MYCFLAGS: to be set by user if needed -MYCFLAGS= +MYCFLAGS?= # MYLDFLAGS: to be set by user if needed -MYLDFLAGS= +MYLDFLAGS?= # DEBUG: NODEBUG DEBUG # debug mode causes luasocket to collect and returns timing information useful # for testing and debugging luasocket itself DEBUG?=NODEBUG -# COMPAT: COMPAT NOCOMPAT -# when compiling for 5.2, use LUA_COMPAT_MODULE -COMPAT?=NOCOMPAT - # where lua headers are found for macosx builds # LUAINC_macosx: # /opt/local/include LUAINC_macosx_base?=/opt/local/include -LUAINC_macosx?=$(LUAINC_macosx_base)/lua/$(LUAV) +LUAINC_macosx?=$(LUAINC_macosx_base)/lua/$(LUAV) $(LUAINC_macosx_base)/lua$(LUAV) $(LUAINC_macosx_base)/lua-$(LUAV) + # FIXME default should this default to fink or to macports? # What happens when more than one Lua version is installed? LUAPREFIX_macosx?=/opt/local @@ -52,16 +49,16 @@ LDIR_macosx?=share/lua/$(LUAV) # /usr/local/include/lua$(LUAV) # where lua headers are found for linux builds LUAINC_linux_base?=/usr/include -LUAINC_linux?=$(LUAINC_linux_base)/lua/$(LUAV) +LUAINC_linux?=$(LUAINC_linux_base)/lua/$(LUAV) $(LUAINC_linux_base)/lua$(LUAV) LUAPREFIX_linux?=/usr/local CDIR_linux?=lib/lua/$(LUAV) LDIR_linux?=share/lua/$(LUAV) # LUAINC_freebsd: # /usr/local/include/lua$(LUAV) -# where lua headers are found for linux builds +# where lua headers are found for freebsd builds LUAINC_freebsd_base?=/usr/local/include/ -LUAINC_freebsd?=$(LUAINC_freebsd_base)/lua$(LUAV) +LUAINC_freebsd?=$(LUAINC_freebsd_base)/lua/$(LUAV) $(LUAINC_freebsd_base)/lua$(LUAV) LUAPREFIX_freebsd?=/usr/local/ CDIR_freebsd?=lib/lua/$(LUAV) LDIR_freebsd?=share/lua/$(LUAV) @@ -70,7 +67,7 @@ LDIR_freebsd?=share/lua/$(LUAV) # LUAINC_mingw: # /opt/local/include LUAINC_mingw_base?=/usr/include -LUAINC_mingw?=$(LUAINC_mingw_base)/lua/$(LUAV) +LUAINC_mingw?=$(LUAINC_mingw_base)/lua/$(LUAV) $(LUAINC_mingw_base)/lua$(LUAV) LUALIB_mingw_base?=/usr/bin LUALIB_mingw?=$(LUALIB_mingw_base)/lua/$(LUAV)/lua$(subst .,,$(LUAV)).dll LUAPREFIX_mingw?=/usr @@ -81,13 +78,32 @@ LDIR_mingw?=lua/$(LUAV)/lua # LUAINC_win32: # LUALIB_win32: # where lua headers and libraries are found for win32 builds -LUAINC_win32_base?= -LUAINC_win32?=$(LUAINC_win32_base)/lua/$(LUAV) -PLATFORM_win32?=Release LUAPREFIX_win32?= -CDIR_win32?=lua/$(LUAV)/$(PLATFORM_win32) -LDIR_win32?=lua/$(LUAV)/$(PLATFORM_win32)/lua -LUALIB_win32?=$(LUAPREFIX_win32)/lua/$(LUAV)/$(PLATFORM_win32) +LUAINC_win32?=$(LUAPREFIX_win32)/include/lua/$(LUAV) $(LUAPREFIX_win32)/include/lua$(LUAV) +PLATFORM_win32?=Release +CDIR_win32?=bin/lua/$(LUAV)/$(PLATFORM_win32) +LDIR_win32?=bin/lua/$(LUAV)/$(PLATFORM_win32)/lua +LUALIB_win32?=$(LUAPREFIX_win32)/lib/lua/$(LUAV)/$(PLATFORM_win32) +LUALIBNAME_win32?=lua$(subst .,,$(LUAV)).lib + +# LUAINC_win64: +# LUALIB_win64: +# where lua headers and libraries are found for win64 builds +LUAPREFIX_win64?= +LUAINC_win64?=$(LUAPREFIX_win64)/include/lua/$(LUAV) $(LUAPREFIX_win64)/include/lua$(LUAV) +PLATFORM_win64?=x64/Release +CDIR_win64?=bin/lua/$(LUAV)/$(PLATFORM_win64) +LDIR_win64?=bin/lua/$(LUAV)/$(PLATFORM_win64)/lua +LUALIB_win64?=$(LUAPREFIX_win64)/lib/lua/$(LUAV)/$(PLATFORM_win64) +LUALIBNAME_win64?=lua$(subst .,,$(LUAV)).lib + + +# LUAINC_solaris: +LUAINC_solaris_base?=/usr/include +LUAINC_solaris?=$(LUAINC_solaris_base)/lua/$(LUAV) $(LUAINC_solaris_base)/lua$(LUAV) +LUAPREFIX_solaris?=/usr/local +CDIR_solaris?=lib/lua/$(LUAV) +LDIR_solaris?=share/lua/$(LUAV) # prefix: /usr/local /usr /opt/local /sw # the top of the default install tree @@ -98,7 +114,7 @@ LDIR?=$(LDIR_$(PLAT)) # DESTDIR: (no default) # used by package managers to install into a temporary destination -DESTDIR= +DESTDIR?= #------ # Definitions below can be overridden on the make command line, but @@ -131,11 +147,13 @@ print: @echo LUALIB_$(PLAT)=$(LUALIB_$(PLAT)) @echo INSTALL_TOP_CDIR=$(INSTALL_TOP_CDIR) @echo INSTALL_TOP_LDIR=$(INSTALL_TOP_LDIR) + @echo CFLAGS=$(CFLAGS) + @echo LDFLAGS=$(LDFLAGS) #------ # Supported platforms # -PLATS= macosx linux win32 mingw +PLATS= macosx linux win32 win64 mingw solaris #------ # Compiler and linker settings @@ -143,14 +161,10 @@ PLATS= macosx linux win32 mingw SO_macosx=so O_macosx=o CC_macosx=gcc -DEF_macosx= -DLUASOCKET_$(DEBUG) -DUNIX_HAS_SUN_LEN -DLUA_$(COMPAT)_MODULE \ - -DLUASOCKET_API='__attribute__((visibility("default")))' \ - -DUNIX_API='__attribute__((visibility("default")))' \ - -DMIME_API='__attribute__((visibility("default")))' -CFLAGS_macosx= -I$(LUAINC) $(DEF) -Wall -O2 -fno-common \ - -fvisibility=hidden -LDFLAGS_macosx= -bundle -undefined dynamic_lookup -o -LD_macosx= export MACOSX_DEPLOYMENT_TARGET="10.3"; gcc +DEF_macosx= -DLUASOCKET_$(DEBUG) -DUNIX_HAS_SUN_LEN +CFLAGS_macosx=$(LUAINC:%=-I%) $(DEF) -Wall -O2 -fno-common +LDFLAGS_macosx= -bundle -undefined dynamic_lookup -o +LD_macosx=gcc SOCKET_macosx=usocket.o #------ @@ -159,13 +173,10 @@ SOCKET_macosx=usocket.o SO_linux=so O_linux=o CC_linux=gcc -DEF_linux=-DLUASOCKET_$(DEBUG) -DLUA_$(COMPAT)_MODULE \ - -DLUASOCKET_API='__attribute__((visibility("default")))' \ - -DUNIX_API='__attribute__((visibility("default")))' \ - -DMIME_API='__attribute__((visibility("default")))' -CFLAGS_linux= -I$(LUAINC) $(DEF) -Wall -Wshadow -Wextra \ - -Wimplicit -O2 -ggdb3 -fpic -fvisibility=hidden -LDFLAGS_linux=-O -shared -fpic -o +DEF_linux=-DLUASOCKET_$(DEBUG) +CFLAGS_linux=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ + -Wimplicit -O2 -ggdb3 -fpic +LDFLAGS_linux=-O -shared -fpic -o LD_linux=gcc SOCKET_linux=usocket.o @@ -175,28 +186,36 @@ SOCKET_linux=usocket.o SO_freebsd=so O_freebsd=o CC_freebsd=gcc -DEF_freebsd=-DLUASOCKET_$(DEBUG) -DLUA_$(COMPAT)_MODULE \ - -DLUASOCKET_API='__attribute__((visibility("default")))' \ - -DUNIX_API='__attribute__((visibility("default")))' \ - -DMIME_API='__attribute__((visibility("default")))' -CFLAGS_freebsd= -I$(LUAINC) $(DEF) -Wall -Wshadow -Wextra \ - -Wimplicit -O2 -ggdb3 -fpic -fvisibility=hidden -LDFLAGS_freebsd=-O -shared -fpic -o +DEF_freebsd=-DLUASOCKET_$(DEBUG) -DUNIX_HAS_SUN_LEN +CFLAGS_freebsd=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ + -Wimplicit -O2 -ggdb3 -fpic +LDFLAGS_freebsd=-O -shared -fpic -o LD_freebsd=gcc SOCKET_freebsd=usocket.o +#------ +# Compiler and linker settings +# for Solaris +SO_solaris=so +O_solaris=o +CC_solaris=gcc +DEF_solaris=-DLUASOCKET_$(DEBUG) +CFLAGS_solaris=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ + -Wimplicit -O2 -ggdb3 -fpic +LDFLAGS_solaris=-lnsl -lsocket -lresolv -O -shared -fpic -o +LD_solaris=gcc +SOCKET_solaris=usocket.o + #------ # Compiler and linker settings # for MingW SO_mingw=dll O_mingw=o CC_mingw=gcc -DEF_mingw= -DLUASOCKET_INET_PTON -DLUASOCKET_$(DEBUG) -DLUA_$(COMPAT)_MODULE \ - -DWINVER=0x0501 -DLUASOCKET_API='__declspec(dllexport)' \ - -DMIME_API='__declspec(dllexport)' -CFLAGS_mingw= -I$(LUAINC) $(DEF) -Wall -O2 -fno-common \ - -fvisibility=hidden -LDFLAGS_mingw= $(LUALIB) -shared -Wl,-s -lws2_32 -o +DEF_mingw= -DLUASOCKET_$(DEBUG) \ + -DWINVER=0x0501 +CFLAGS_mingw=$(LUAINC:%=-I%) $(DEF) -Wall -O2 -fno-common +LDFLAGS_mingw= $(LUALIB) -shared -Wl,-s -lws2_32 -o LD_mingw=gcc SOCKET_mingw=wsocket.o @@ -208,34 +227,55 @@ SO_win32=dll O_win32=obj CC_win32=cl DEF_win32= //D "WIN32" //D "NDEBUG" //D "_WINDOWS" //D "_USRDLL" \ - //D "LUASOCKET_API=__declspec(dllexport)" //D "_CRT_SECURE_NO_WARNINGS" \ - //D "_WINDLL" //D "LUA_$(COMPAT)_MODULE" \ - //D "MIME_API=__declspec(dllexport)" \ - //D "LUASOCKET_$(DEBUG)" -CFLAGS_win32=//I "$(LUAINC)" $(DEF) //O2 //Ot //MD //W3 //nologo + //D "_CRT_SECURE_NO_WARNINGS" \ + //D "_WINDLL" \ + //D "LUASOCKET_$(DEBUG)" +CFLAGS_win32=$(LUAINC:%=//I "%") $(DEF) //O2 //Ot //MD //W3 //nologo LDFLAGS_win32= //nologo //link //NOLOGO //DLL //INCREMENTAL:NO \ //MANIFEST //MANIFESTFILE:"intermediate.manifest" \ - //MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ + /MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ //SUBSYSTEM:WINDOWS //OPT:REF //OPT:ICF //DYNAMICBASE:NO \ - //MACHINE:X86 /LIBPATH:"$(shell cmd //c echo $(LUALIB))" \ - lua$(subst .,,$(LUAV)).lib ws2_32.lib //OUT: + //MACHINE:X86 /LIBPATH:"$(LUALIB)" \ + $(LUALIBNAME_win32) ws2_32.lib //OUT: + LD_win32=cl SOCKET_win32=wsocket.obj +#------ +# Compiler and linker settings +# for Win64 +SO_win64=dll +O_win64=obj +CC_win64=cl +DEF_win64= //D "WIN32" //D "NDEBUG" //D "_WINDOWS" //D "_USRDLL" \ + //D "_CRT_SECURE_NO_WARNINGS" \ + //D "_WINDLL" \ + //D "LUASOCKET_$(DEBUG)" +CFLAGS_win64=$(LUAINC:%=//I "%") $(DEF) //O2 //Ot //MD //W3 //nologo +LDFLAGS_win64= //nologo //link //NOLOGO //DLL //INCREMENTAL:NO \ + //MANIFEST //MANIFESTFILE:"intermediate.manifest" \ + /MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ + //SUBSYSTEM:WINDOWS //OPT:REF //OPT:ICF //DYNAMICBASE:NO \ + /LIBPATH:"$(LUALIB)" \ + $(LUALIBNAME_win64) ws2_32.lib //OUT: + +LD_win64=cl +SOCKET_win64=wsocket.obj + .SUFFIXES: .obj .c.obj: - $(CC) $(LUASOCKET_CFLAGS) //Fo"$@" //c $< + $(CC) $(CFLAGS) //Fo"$@" //c $< #------ # Output file names # SO=$(SO_$(PLAT)) O=$(O_$(PLAT)) -SOCKET_V=3.0-rc1 +SOCKET_V=3.0.0 MIME_V=1.0.3 -SOCKET_SO=socket.$(SO).$(SOCKET_V) -MIME_SO=mime.$(SO).$(MIME_V) +SOCKET_SO=socket-$(SOCKET_V).$(SO) +MIME_SO=mime-$(MIME_V).$(SO) UNIX_SO=unix.$(SO) SERIAL_SO=serial.$(SO) SOCKET=$(SOCKET_$(PLAT)) @@ -286,6 +326,9 @@ UNIX_OBJS=\ timeout.$(O) \ io.$(O) \ usocket.$(O) \ + unixstream.$(O) \ + unixdgram.$(O) \ + compat.$(O) \ unix.$(O) #------ @@ -293,6 +336,7 @@ UNIX_OBJS=\ # SERIAL_OBJS=\ buffer.$(O) \ + compat.$(O) \ auxiliar.$(O) \ options.$(O) \ timeout.$(O) \ @@ -331,12 +375,18 @@ macosx: win32: $(MAKE) all PLAT=win32 +win64: + $(MAKE) all PLAT=win64 + linux: $(MAKE) all-unix PLAT=linux mingw: $(MAKE) all PLAT=mingw +solaris: + $(MAKE) all-unix PLAT=solaris + none: @echo "Please run" @echo " make PLATFORM" @@ -359,7 +409,7 @@ $(UNIX_SO): $(UNIX_OBJS) $(SERIAL_SO): $(SERIAL_OBJS) $(LD) $(SERIAL_OBJS) $(LDFLAGS)$@ -install: +install: $(INSTALL_DIR) $(INSTALL_TOP_LDIR) $(INSTALL_DATA) $(TO_TOP_LDIR) $(INSTALL_TOP_LDIR) $(INSTALL_DIR) $(INSTALL_SOCKET_LDIR) diff --git a/src/mbox.lua b/src/mbox.lua index 7724ae2..12823b0 100644 --- a/src/mbox.lua +++ b/src/mbox.lua @@ -1,8 +1,8 @@ local _M = {} if module then - mbox = _M -end + mbox = _M -- luacheck: ignore +end function _M.split_message(message_s) local message = {} @@ -29,7 +29,7 @@ end function _M.parse_header(header_s) header_s = string.gsub(header_s, "\n[ ]+", " ") header_s = string.gsub(header_s, "\n+", "") - local _, __, name, value = string.find(header_s, "([^%s:]-):%s*(.*)") + local _, _, name, value = string.find(header_s, "([^%s:]-):%s*(.*)") return name, value end @@ -49,9 +49,9 @@ function _M.parse_headers(headers_s) end function _M.parse_from(from) - local _, __, name, address = string.find(from, "^%s*(.-)%s*%<(.-)%>") + local _, _, name, address = string.find(from, "^%s*(.-)%s*%<(.-)%>") if not address then - _, __, address = string.find(from, "%s*(.+)%s*") + _, _, address = string.find(from, "%s*(.+)%s*") end name = name or "" address = address or "" @@ -61,9 +61,10 @@ function _M.parse_from(from) end function _M.split_mbox(mbox_s) - mbox = {} + local mbox = {} mbox_s = string.gsub(mbox_s, "\r\n", "\n") .."\n\nFrom \n" - local nj, i, j = 1, 1, 1 + local nj, i + local j = 1 while 1 do i, nj = string.find(mbox_s, "\n\nFrom .-\n", j) if not i then break end diff --git a/src/mime.c b/src/mime.c old mode 100644 new mode 100755 index ed44104..05602f5 --- a/src/mime.c +++ b/src/mime.c @@ -2,13 +2,10 @@ * MIME support functions * LuaSocket toolkit \*=========================================================================*/ -#include - -#include "lua.h" -#include "lauxlib.h" -#include "compat.h" - +#include "luasocket.h" #include "mime.h" +#include +#include /*=========================================================================*\ * Don't want to trust escape character constants @@ -30,12 +27,12 @@ static int mime_global_eol(lua_State *L); static int mime_global_dot(lua_State *L); static size_t dot(int c, size_t state, luaL_Buffer *buffer); -static void b64setup(UC *base); +/*static void b64setup(UC *base);*/ static size_t b64encode(UC c, UC *input, size_t size, luaL_Buffer *buffer); static size_t b64pad(const UC *input, size_t size, luaL_Buffer *buffer); static size_t b64decode(UC c, UC *input, size_t size, luaL_Buffer *buffer); -static void qpsetup(UC *class, UC *unbase); +/*static void qpsetup(UC *class, UC *unbase);*/ static void qpquote(UC c, luaL_Buffer *buffer); static size_t qpdecode(UC c, UC *input, size_t size, luaL_Buffer *buffer); static size_t qpencode(UC c, UC *input, size_t size, @@ -58,17 +55,111 @@ static luaL_Reg func[] = { /*-------------------------------------------------------------------------*\ * Quoted-printable globals \*-------------------------------------------------------------------------*/ -static UC qpclass[256]; -static UC qpbase[] = "0123456789ABCDEF"; -static UC qpunbase[256]; enum {QP_PLAIN, QP_QUOTED, QP_CR, QP_IF_LAST}; +static const UC qpclass[] = { + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_IF_LAST, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_CR, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_IF_LAST, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_QUOTED, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED +}; + +static const UC qpbase[] = "0123456789ABCDEF"; + +static const UC qpunbase[] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, + 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255 +}; + /*-------------------------------------------------------------------------*\ * Base64 globals \*-------------------------------------------------------------------------*/ static const UC b64base[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -static UC b64unbase[256]; + +static const UC b64unbase[] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, + 255, 255, 255, 0, 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, 255, 255, + 255, 255, 255, 255, 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, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255 +}; /*=========================================================================*\ * Exported functions @@ -76,7 +167,7 @@ static UC b64unbase[256]; /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ -MIME_API int luaopen_mime_core(lua_State *L) +LUASOCKET_API int luaopen_mime_core(lua_State *L) { lua_newtable(L); luaL_setfuncs(L, func, 0); @@ -85,8 +176,8 @@ MIME_API int luaopen_mime_core(lua_State *L) lua_pushstring(L, MIME_VERSION); lua_rawset(L, -3); /* initialize lookup tables */ - qpsetup(qpclass, qpunbase); - b64setup(b64unbase); + /*qpsetup(qpclass, qpunbase);*/ + /*b64setup(b64unbase);*/ return 1; } @@ -142,6 +233,7 @@ static int mime_global_wrp(lua_State *L) return 2; } +#if 0 /*-------------------------------------------------------------------------*\ * Fill base64 decode map. \*-------------------------------------------------------------------------*/ @@ -151,7 +243,14 @@ static void b64setup(UC *unbase) for (i = 0; i <= 255; i++) unbase[i] = (UC) 255; for (i = 0; i < 64; i++) unbase[b64base[i]] = (UC) i; unbase['='] = 0; + + printf("static const UC b64unbase[] = {\n"); + for (int i = 0; i < 256; i++) { + printf("%d, ", unbase[i]); + } + printf("\n}\n;"); } +#endif /*-------------------------------------------------------------------------*\ * Acumulates bytes in input buffer until 3 bytes are available. @@ -345,12 +444,14 @@ static int mime_global_unb64(lua_State *L) * To encode one byte, we need to see the next two. * Worst case is when we see a space, and wonder if a CRLF is comming \*-------------------------------------------------------------------------*/ +#if 0 /*-------------------------------------------------------------------------*\ * Split quoted-printable characters into classes * Precompute reverse map for encoding \*-------------------------------------------------------------------------*/ static void qpsetup(UC *cl, UC *unbase) { + int i; for (i = 0; i < 256; i++) cl[i] = QP_QUOTED; for (i = 33; i <= 60; i++) cl[i] = QP_PLAIN; @@ -367,7 +468,37 @@ static void qpsetup(UC *cl, UC *unbase) unbase['c'] = 12; unbase['D'] = 13; unbase['d'] = 13; unbase['E'] = 14; unbase['e'] = 14; unbase['F'] = 15; unbase['f'] = 15; + +printf("static UC qpclass[] = {"); + for (int i = 0; i < 256; i++) { + if (i % 6 == 0) { + printf("\n "); + } + switch(cl[i]) { + case QP_QUOTED: + printf("QP_QUOTED, "); + break; + case QP_PLAIN: + printf("QP_PLAIN, "); + break; + case QP_CR: + printf("QP_CR, "); + break; + case QP_IF_LAST: + printf("QP_IF_LAST, "); + break; + } + } +printf("\n};\n"); + +printf("static const UC qpunbase[] = {"); + for (int i = 0; i < 256; i++) { + int c = qpunbase[i]; + printf("%d, ", c); + } +printf("\";\n"); } +#endif /*-------------------------------------------------------------------------*\ * Output one character in form =XX @@ -447,7 +578,6 @@ static size_t qppad(UC *input, size_t size, luaL_Buffer *buffer) \*-------------------------------------------------------------------------*/ static int mime_global_qp(lua_State *L) { - size_t asize = 0, isize = 0; UC atom[3]; const UC *input = (const UC *) luaL_optlstring(L, 1, NULL, &isize); @@ -654,7 +784,7 @@ static int eolprocess(int c, int last, const char *marker, \*-------------------------------------------------------------------------*/ static int mime_global_eol(lua_State *L) { - int ctx = luaL_checkinteger(L, 1); + int ctx = (int) luaL_checkinteger(L, 1); size_t isize = 0; const char *input = luaL_optlstring(L, 2, NULL, &isize); const char *last = input + isize; @@ -689,6 +819,7 @@ static size_t dot(int c, size_t state, luaL_Buffer *buffer) case '.': if (state == 2) luaL_addchar(buffer, '.'); + /* Falls through. */ default: return 0; } diff --git a/src/mime.h b/src/mime.h index 99968a5..4d938f4 100644 --- a/src/mime.h +++ b/src/mime.h @@ -8,7 +8,7 @@ * and formatting conforming to RFC 2045. It is used by mime.lua, which * provide a higher level interface to this functionality. \*=========================================================================*/ -#include "lua.h" +#include "luasocket.h" /*-------------------------------------------------------------------------*\ * Current MIME library version @@ -17,13 +17,6 @@ #define MIME_COPYRIGHT "Copyright (C) 2004-2013 Diego Nehab" #define MIME_AUTHORS "Diego Nehab" -/*-------------------------------------------------------------------------*\ -* This macro prefixes all exported API functions -\*-------------------------------------------------------------------------*/ -#ifndef MIME_API -#define MIME_API extern -#endif - -MIME_API int luaopen_mime_core(lua_State *L); +LUASOCKET_API int luaopen_mime_core(lua_State *L); #endif /* MIME_H */ diff --git a/src/mime.lua b/src/mime.lua index 642cd9c..93539de 100644 --- a/src/mime.lua +++ b/src/mime.lua @@ -10,8 +10,6 @@ local base = _G local ltn12 = require("ltn12") local mime = require("mime.core") -local io = require("io") -local string = require("string") local _M = mime -- encode, decode and wrap algorithm tables @@ -19,7 +17,7 @@ local encodet, decodet, wrapt = {},{},{} _M.encodet = encodet _M.decodet = decodet -_M.wrapt = wrapt +_M.wrapt = wrapt -- creates a function that chooses a filter by name from a given table local function choose(table) @@ -28,7 +26,7 @@ local function choose(table) name, opt1, opt2 = "default", name, opt1 end local f = table[name or "nil"] - if not f then + if not f then base.error("unknown key (" .. base.tostring(name) .. ")", 3) else return f(opt1, opt2) end end @@ -53,13 +51,6 @@ decodet['quoted-printable'] = function() return ltn12.filter.cycle(_M.unqp, "") end -local function format(chunk) - if chunk then - if chunk == "" then return "''" - else return string.len(chunk) end - else return "nil" end -end - -- define the line-wrap filters wrapt['text'] = function(length) length = length or 76 @@ -87,4 +78,4 @@ function _M.stuff() return ltn12.filter.cycle(_M.dot, 2) end -return _M \ No newline at end of file +return _M diff --git a/src/options.c b/src/options.c index 20f4c28..9dea6bd 100644 --- a/src/options.c +++ b/src/options.c @@ -2,14 +2,11 @@ * Common option interface * LuaSocket toolkit \*=========================================================================*/ -#include - -#include "lauxlib.h" - +#include "luasocket.h" #include "auxiliar.h" #include "options.h" #include "inet.h" - +#include /*=========================================================================*\ * Internal functions prototypes @@ -37,7 +34,7 @@ int opt_meth_setoption(lua_State *L, p_opt opt, p_socket ps) while (opt->name && strcmp(name, opt->name)) opt++; if (!opt->func) { - char msg[45]; + char msg[57]; sprintf(msg, "unsupported option `%.35s'", name); luaL_argerror(L, 2, msg); } @@ -50,13 +47,41 @@ int opt_meth_getoption(lua_State *L, p_opt opt, p_socket ps) while (opt->name && strcmp(name, opt->name)) opt++; if (!opt->func) { - char msg[45]; + char msg[57]; sprintf(msg, "unsupported option `%.35s'", name); luaL_argerror(L, 2, msg); } return opt->func(L, ps); } +/*------------------------------------------------------*/ +/* binds socket to network interface */ +int opt_set_bindtodevice(lua_State *L, p_socket ps) +{ +#ifndef SO_BINDTODEVICE + return luaL_error(L, "SO_BINDTODEVICE is not supported on this operating system"); +#else + const char *dev = luaL_checkstring(L, 3); + return opt_set(L, ps, SOL_SOCKET, SO_BINDTODEVICE, (char*)dev, strlen(dev)+1); +#endif +} + +int opt_get_bindtodevice(lua_State *L, p_socket ps) +{ +#ifndef SO_BINDTODEVICE + return luaL_error(L, "SO_BINDTODEVICE is not supported on this operating system"); +#else + char dev[IFNAMSIZ]; + int len = sizeof(dev); + int err = opt_get(L, ps, SOL_SOCKET, SO_BINDTODEVICE, &dev, &len); + if (err) + return err; + lua_pushstring(L, dev); + return 1; +#endif +} + +/*------------------------------------------------------*/ /* enables reuse of local address */ int opt_set_reuseaddr(lua_State *L, p_socket ps) { @@ -68,6 +93,7 @@ int opt_get_reuseaddr(lua_State *L, p_socket ps) return opt_getboolean(L, ps, SOL_SOCKET, SO_REUSEADDR); } +/*------------------------------------------------------*/ /* enables reuse of local port */ int opt_set_reuseport(lua_State *L, p_socket ps) { @@ -79,7 +105,8 @@ int opt_get_reuseport(lua_State *L, p_socket ps) return opt_getboolean(L, ps, SOL_SOCKET, SO_REUSEPORT); } -/* disables the Naggle algorithm */ +/*------------------------------------------------------*/ +/* disables the Nagle algorithm */ int opt_set_tcp_nodelay(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, IPPROTO_TCP, TCP_NODELAY); @@ -90,6 +117,52 @@ int opt_get_tcp_nodelay(lua_State *L, p_socket ps) return opt_getboolean(L, ps, IPPROTO_TCP, TCP_NODELAY); } +/*------------------------------------------------------*/ +#ifdef TCP_KEEPIDLE + +int opt_get_tcp_keepidle(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, IPPROTO_TCP, TCP_KEEPIDLE); +} + +int opt_set_tcp_keepidle(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_TCP, TCP_KEEPIDLE); +} + +#endif + +/*------------------------------------------------------*/ +#ifdef TCP_KEEPCNT + +int opt_get_tcp_keepcnt(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, IPPROTO_TCP, TCP_KEEPCNT); +} + +int opt_set_tcp_keepcnt(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_TCP, TCP_KEEPCNT); +} + +#endif + +/*------------------------------------------------------*/ +#ifdef TCP_KEEPINTVL + +int opt_get_tcp_keepintvl(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, IPPROTO_TCP, TCP_KEEPINTVL); +} + +int opt_set_tcp_keepintvl(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_TCP, TCP_KEEPINTVL); +} + +#endif + +/*------------------------------------------------------*/ int opt_set_keepalive(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, SOL_SOCKET, SO_KEEPALIVE); @@ -100,6 +173,7 @@ int opt_get_keepalive(lua_State *L, p_socket ps) return opt_getboolean(L, ps, SOL_SOCKET, SO_KEEPALIVE); } +/*------------------------------------------------------*/ int opt_set_dontroute(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, SOL_SOCKET, SO_DONTROUTE); @@ -110,6 +184,7 @@ int opt_get_dontroute(lua_State *L, p_socket ps) return opt_getboolean(L, ps, SOL_SOCKET, SO_DONTROUTE); } +/*------------------------------------------------------*/ int opt_set_broadcast(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, SOL_SOCKET, SO_BROADCAST); @@ -120,6 +195,54 @@ int opt_get_broadcast(lua_State *L, p_socket ps) return opt_getboolean(L, ps, SOL_SOCKET, SO_BROADCAST); } +/*------------------------------------------------------*/ +int opt_set_recv_buf_size(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, SOL_SOCKET, SO_RCVBUF); +} + +int opt_get_recv_buf_size(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, SOL_SOCKET, SO_RCVBUF); +} + +/*------------------------------------------------------*/ +int opt_get_send_buf_size(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, SOL_SOCKET, SO_SNDBUF); +} + +int opt_set_send_buf_size(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, SOL_SOCKET, SO_SNDBUF); +} + +/*------------------------------------------------------*/ + +#ifdef TCP_FASTOPEN +int opt_set_tcp_fastopen(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_TCP, TCP_FASTOPEN); +} +#endif + +#ifdef TCP_FASTOPEN_CONNECT +int opt_set_tcp_fastopen_connect(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_TCP, TCP_FASTOPEN_CONNECT); +} +#endif + +/*------------------------------------------------------*/ + +#ifdef TCP_DEFER_ACCEPT +int opt_set_tcp_defer_accept(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_TCP, TCP_DEFER_ACCEPT); +} +#endif + +/*------------------------------------------------------*/ int opt_set_ip6_unicast_hops(lua_State *L, p_socket ps) { return opt_setint(L, ps, IPPROTO_IPV6, IPV6_UNICAST_HOPS); @@ -130,6 +253,7 @@ int opt_get_ip6_unicast_hops(lua_State *L, p_socket ps) return opt_getint(L, ps, IPPROTO_IPV6, IPV6_UNICAST_HOPS); } +/*------------------------------------------------------*/ int opt_set_ip6_multicast_hops(lua_State *L, p_socket ps) { return opt_setint(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_HOPS); @@ -140,6 +264,7 @@ int opt_get_ip6_multicast_hops(lua_State *L, p_socket ps) return opt_getint(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_HOPS); } +/*------------------------------------------------------*/ int opt_set_ip_multicast_loop(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, IPPROTO_IP, IP_MULTICAST_LOOP); @@ -150,6 +275,7 @@ int opt_get_ip_multicast_loop(lua_State *L, p_socket ps) return opt_getboolean(L, ps, IPPROTO_IP, IP_MULTICAST_LOOP); } +/*------------------------------------------------------*/ int opt_set_ip6_multicast_loop(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); @@ -160,6 +286,7 @@ int opt_get_ip6_multicast_loop(lua_State *L, p_socket ps) return opt_getboolean(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); } +/*------------------------------------------------------*/ int opt_set_linger(lua_State *L, p_socket ps) { struct linger li; /* obj, name, table */ @@ -192,11 +319,13 @@ int opt_get_linger(lua_State *L, p_socket ps) return 1; } +/*------------------------------------------------------*/ int opt_set_ip_multicast_ttl(lua_State *L, p_socket ps) { return opt_setint(L, ps, IPPROTO_IP, IP_MULTICAST_TTL); } +/*------------------------------------------------------*/ int opt_set_ip_multicast_if(lua_State *L, p_socket ps) { const char *address = luaL_checkstring(L, 3); /* obj, name, ip */ @@ -221,6 +350,7 @@ int opt_get_ip_multicast_if(lua_State *L, p_socket ps) return 1; } +/*------------------------------------------------------*/ int opt_set_ip_add_membership(lua_State *L, p_socket ps) { return opt_setmembership(L, ps, IPPROTO_IP, IP_ADD_MEMBERSHIP); @@ -231,6 +361,7 @@ int opt_set_ip_drop_membersip(lua_State *L, p_socket ps) return opt_setmembership(L, ps, IPPROTO_IP, IP_DROP_MEMBERSHIP); } +/*------------------------------------------------------*/ int opt_set_ip6_add_membership(lua_State *L, p_socket ps) { return opt_ip6_setmembership(L, ps, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP); @@ -241,6 +372,7 @@ int opt_set_ip6_drop_membersip(lua_State *L, p_socket ps) return opt_ip6_setmembership(L, ps, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP); } +/*------------------------------------------------------*/ int opt_get_ip6_v6only(lua_State *L, p_socket ps) { return opt_getboolean(L, ps, IPPROTO_IPV6, IPV6_V6ONLY); @@ -251,6 +383,20 @@ int opt_set_ip6_v6only(lua_State *L, p_socket ps) return opt_setboolean(L, ps, IPPROTO_IPV6, IPV6_V6ONLY); } +/*------------------------------------------------------*/ +int opt_get_error(lua_State *L, p_socket ps) +{ + int val = 0; + socklen_t len = sizeof(val); + if (getsockopt(*ps, SOL_SOCKET, SO_ERROR, (char *) &val, &len) < 0) { + lua_pushnil(L); + lua_pushstring(L, "getsockopt failed"); + return 2; + } + lua_pushstring(L, socket_strerror(val)); + return 1; +} + /*=========================================================================*\ * Auxiliar functions \*=========================================================================*/ @@ -337,19 +483,6 @@ static int opt_getboolean(lua_State *L, p_socket ps, int level, int name) return 1; } -int opt_get_error(lua_State *L, p_socket ps) -{ - int val = 0; - socklen_t len = sizeof(val); - if (getsockopt(*ps, SOL_SOCKET, SO_ERROR, (char *) &val, &len) < 0) { - lua_pushnil(L); - lua_pushstring(L, "getsockopt failed"); - return 2; - } - lua_pushstring(L, socket_strerror(val)); - return 1; -} - static int opt_setboolean(lua_State *L, p_socket ps, int level, int name) { int val = auxiliar_checkboolean(L, 3); /* obj, name, bool */ diff --git a/src/options.h b/src/options.h index ad1b00d..26d6f02 100644 --- a/src/options.h +++ b/src/options.h @@ -8,7 +8,7 @@ * modules UDP and TCP. \*=========================================================================*/ -#include "lua.h" +#include "luasocket.h" #include "socket.h" /* option registry */ @@ -18,45 +18,99 @@ typedef struct t_opt { } t_opt; typedef t_opt *p_opt; -/* supported options for setoption */ -int opt_set_dontroute(lua_State *L, p_socket ps); -int opt_set_broadcast(lua_State *L, p_socket ps); -int opt_set_reuseaddr(lua_State *L, p_socket ps); -int opt_set_tcp_nodelay(lua_State *L, p_socket ps); -int opt_set_keepalive(lua_State *L, p_socket ps); -int opt_set_linger(lua_State *L, p_socket ps); -int opt_set_reuseaddr(lua_State *L, p_socket ps); -int opt_set_reuseport(lua_State *L, p_socket ps); -int opt_set_ip_multicast_if(lua_State *L, p_socket ps); -int opt_set_ip_multicast_ttl(lua_State *L, p_socket ps); -int opt_set_ip_multicast_loop(lua_State *L, p_socket ps); -int opt_set_ip_add_membership(lua_State *L, p_socket ps); -int opt_set_ip_drop_membersip(lua_State *L, p_socket ps); -int opt_set_ip6_unicast_hops(lua_State *L, p_socket ps); -int opt_set_ip6_multicast_hops(lua_State *L, p_socket ps); -int opt_set_ip6_multicast_loop(lua_State *L, p_socket ps); -int opt_set_ip6_add_membership(lua_State *L, p_socket ps); -int opt_set_ip6_drop_membersip(lua_State *L, p_socket ps); -int opt_set_ip6_v6only(lua_State *L, p_socket ps); +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif -/* supported options for getoption */ -int opt_get_dontroute(lua_State *L, p_socket ps); -int opt_get_broadcast(lua_State *L, p_socket ps); -int opt_get_reuseaddr(lua_State *L, p_socket ps); -int opt_get_tcp_nodelay(lua_State *L, p_socket ps); -int opt_get_keepalive(lua_State *L, p_socket ps); -int opt_get_linger(lua_State *L, p_socket ps); -int opt_get_ip_multicast_loop(lua_State *L, p_socket ps); -int opt_get_ip_multicast_if(lua_State *L, p_socket ps); -int opt_get_error(lua_State *L, p_socket ps); -int opt_get_ip6_multicast_loop(lua_State *L, p_socket ps); -int opt_get_ip6_multicast_hops(lua_State *L, p_socket ps); -int opt_get_ip6_unicast_hops(lua_State *L, p_socket ps); -int opt_get_ip6_v6only(lua_State *L, p_socket ps); -int opt_get_reuseport(lua_State *L, p_socket ps); - -/* invokes the appropriate option handler */ int opt_meth_setoption(lua_State *L, p_opt opt, p_socket ps); int opt_meth_getoption(lua_State *L, p_opt opt, p_socket ps); +int opt_set_reuseaddr(lua_State *L, p_socket ps); +int opt_get_reuseaddr(lua_State *L, p_socket ps); + +int opt_set_reuseport(lua_State *L, p_socket ps); +int opt_get_reuseport(lua_State *L, p_socket ps); + +int opt_set_tcp_nodelay(lua_State *L, p_socket ps); +int opt_get_tcp_nodelay(lua_State *L, p_socket ps); + +#ifdef TCP_KEEPIDLE +int opt_set_tcp_keepidle(lua_State *L, p_socket ps); +int opt_get_tcp_keepidle(lua_State *L, p_socket ps); +#endif + +#ifdef TCP_KEEPCNT +int opt_set_tcp_keepcnt(lua_State *L, p_socket ps); +int opt_get_tcp_keepcnt(lua_State *L, p_socket ps); +#endif + +#ifdef TCP_KEEPINTVL +int opt_set_tcp_keepintvl(lua_State *L, p_socket ps); +int opt_get_tcp_keepintvl(lua_State *L, p_socket ps); +#endif + +#ifdef TCP_DEFER_ACCEPT +int opt_set_tcp_defer_accept(lua_State *L, p_socket ps); +#endif + +int opt_set_bindtodevice(lua_State *L, p_socket ps); +int opt_get_bindtodevice(lua_State *L, p_socket ps); + +int opt_set_keepalive(lua_State *L, p_socket ps); +int opt_get_keepalive(lua_State *L, p_socket ps); + +int opt_set_dontroute(lua_State *L, p_socket ps); +int opt_get_dontroute(lua_State *L, p_socket ps); + +int opt_set_broadcast(lua_State *L, p_socket ps); +int opt_get_broadcast(lua_State *L, p_socket ps); + +int opt_set_recv_buf_size(lua_State *L, p_socket ps); +int opt_get_recv_buf_size(lua_State *L, p_socket ps); + +int opt_set_send_buf_size(lua_State *L, p_socket ps); +int opt_get_send_buf_size(lua_State *L, p_socket ps); + +#ifdef TCP_FASTOPEN +int opt_set_tcp_fastopen(lua_State *L, p_socket ps); +#endif +#ifdef TCP_FASTOPEN_CONNECT +int opt_set_tcp_fastopen_connect(lua_State *L, p_socket ps); +#endif + +int opt_set_ip6_unicast_hops(lua_State *L, p_socket ps); +int opt_get_ip6_unicast_hops(lua_State *L, p_socket ps); + +int opt_set_ip6_multicast_hops(lua_State *L, p_socket ps); +int opt_get_ip6_multicast_hops(lua_State *L, p_socket ps); + +int opt_set_ip_multicast_loop(lua_State *L, p_socket ps); +int opt_get_ip_multicast_loop(lua_State *L, p_socket ps); + +int opt_set_ip6_multicast_loop(lua_State *L, p_socket ps); +int opt_get_ip6_multicast_loop(lua_State *L, p_socket ps); + +int opt_set_linger(lua_State *L, p_socket ps); +int opt_get_linger(lua_State *L, p_socket ps); + +int opt_set_ip_multicast_ttl(lua_State *L, p_socket ps); + +int opt_set_ip_multicast_if(lua_State *L, p_socket ps); +int opt_get_ip_multicast_if(lua_State *L, p_socket ps); + +int opt_set_ip_add_membership(lua_State *L, p_socket ps); +int opt_set_ip_drop_membersip(lua_State *L, p_socket ps); + +int opt_set_ip6_add_membership(lua_State *L, p_socket ps); +int opt_set_ip6_drop_membersip(lua_State *L, p_socket ps); + +int opt_set_ip6_v6only(lua_State *L, p_socket ps); +int opt_get_ip6_v6only(lua_State *L, p_socket ps); + +int opt_get_error(lua_State *L, p_socket ps); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + #endif diff --git a/src/select.c b/src/select.c index d14c40a..bb47c45 100644 --- a/src/select.c +++ b/src/select.c @@ -2,16 +2,14 @@ * Select implementation * LuaSocket toolkit \*=========================================================================*/ -#include - -#include "lua.h" -#include "lauxlib.h" -#include "compat.h" +#include "luasocket.h" #include "socket.h" #include "timeout.h" #include "select.h" +#include + /*=========================================================================*\ * Internal function prototypes. \*=========================================================================*/ @@ -31,15 +29,15 @@ static luaL_Reg func[] = { {NULL, NULL} }; -/*=========================================================================*\ -* Exported functions -\*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ int select_open(lua_State *L) { lua_pushstring(L, "_SETSIZE"); - lua_pushnumber(L, FD_SETSIZE); + lua_pushinteger(L, FD_SETSIZE); + lua_rawset(L, -3); + lua_pushstring(L, "_SOCKETINVALID"); + lua_pushinteger(L, SOCKET_INVALID); lua_rawset(L, -3); luaL_setfuncs(L, func, 0); return 0; @@ -214,4 +212,3 @@ static void make_assoc(lua_State *L, int tab) { i = i+1; } } - diff --git a/src/select.h b/src/select.h index 8750200..5d45fe7 100644 --- a/src/select.h +++ b/src/select.h @@ -10,6 +10,14 @@ * true if there is data ready for reading (required for buffered input). \*=========================================================================*/ +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + int select_open(lua_State *L); +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + #endif /* SELECT_H */ diff --git a/src/serial.c b/src/serial.c index 7bdb21c..21485d3 100644 --- a/src/serial.c +++ b/src/serial.c @@ -2,15 +2,14 @@ * Serial stream * LuaSocket toolkit \*=========================================================================*/ -#include - -#include "lua.h" -#include "lauxlib.h" +#include "luasocket.h" #include "auxiliar.h" #include "socket.h" #include "options.h" #include "unix.h" + +#include #include /* diff --git a/src/socket.h b/src/socket.h old mode 100644 new mode 100755 index 63573de..2555bab --- a/src/socket.h +++ b/src/socket.h @@ -16,8 +16,10 @@ \*=========================================================================*/ #ifdef _WIN32 #include "wsocket.h" +#define LUA_GAI_STRERROR gai_strerrorA #else #include "usocket.h" +#define LUA_GAI_STRERROR gai_strerror #endif /*=========================================================================*\ @@ -28,51 +30,46 @@ \*=========================================================================*/ #include "timeout.h" -/* we are lazy... */ +/* convenient shorthand */ typedef struct sockaddr SA; /*=========================================================================*\ * Functions bellow implement a comfortable platform independent * interface to sockets \*=========================================================================*/ + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int socket_waitfd(p_socket ps, int sw, p_timeout tm); int socket_open(void); int socket_close(void); void socket_destroy(p_socket ps); -void socket_shutdown(p_socket ps, int how); -int socket_sendto(p_socket ps, const char *data, size_t count, - size_t *sent, SA *addr, socklen_t addr_len, p_timeout tm); -int socket_recvfrom(p_socket ps, char *data, size_t count, - size_t *got, SA *addr, socklen_t *addr_len, p_timeout tm); - -void socket_setnonblocking(p_socket ps); -void socket_setblocking(p_socket ps); - -int socket_waitfd(p_socket ps, int sw, p_timeout tm); -int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds, - p_timeout tm); - -int socket_connect(p_socket ps, SA *addr, socklen_t addr_len, p_timeout tm); +int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds, p_timeout tm); int socket_create(p_socket ps, int domain, int type, int protocol); int socket_bind(p_socket ps, SA *addr, socklen_t addr_len); int socket_listen(p_socket ps, int backlog); -int socket_accept(p_socket ps, p_socket pa, SA *addr, - socklen_t *addr_len, p_timeout tm); - -const char *socket_hoststrerror(int err); -const char *socket_gaistrerror(int err); -const char *socket_strerror(int err); - -/* these are perfect to use with the io abstraction module - and the buffered input module */ -int socket_send(p_socket ps, const char *data, size_t count, - size_t *sent, p_timeout tm); +void socket_shutdown(p_socket ps, int how); +int socket_connect(p_socket ps, SA *addr, socklen_t addr_len, p_timeout tm); +int socket_accept(p_socket ps, p_socket pa, SA *addr, socklen_t *addr_len, p_timeout tm); +int socket_send(p_socket ps, const char *data, size_t count, size_t *sent, p_timeout tm); +int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, SA *addr, socklen_t addr_len, p_timeout tm); int socket_recv(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm); -int socket_write(p_socket ps, const char *data, size_t count, - size_t *sent, p_timeout tm); +int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got, SA *addr, socklen_t *addr_len, p_timeout tm); +int socket_write(p_socket ps, const char *data, size_t count, size_t *sent, p_timeout tm); int socket_read(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm); -const char *socket_ioerror(p_socket ps, int err); - +void socket_setblocking(p_socket ps); +void socket_setnonblocking(p_socket ps); int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp); int socket_gethostbyname(const char *addr, struct hostent **hp); +const char *socket_hoststrerror(int err); +const char *socket_strerror(int err); +const char *socket_ioerror(p_socket ps, int err); +const char *socket_gaistrerror(int err); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif #endif /* SOCKET_H */ diff --git a/src/tcp.c b/src/tcp.c index 7bf1af5..f001206 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -2,11 +2,7 @@ * TCP object * LuaSocket toolkit \*=========================================================================*/ -#include - -#include "lua.h" -#include "lauxlib.h" -#include "compat.h" +#include "luasocket.h" #include "auxiliar.h" #include "socket.h" @@ -14,6 +10,8 @@ #include "options.h" #include "tcp.h" +#include + /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ @@ -36,6 +34,7 @@ static int meth_accept(lua_State *L); static int meth_close(lua_State *L); static int meth_getoption(lua_State *L); static int meth_setoption(lua_State *L); +static int meth_gettimeout(lua_State *L); static int meth_settimeout(lua_State *L); static int meth_getfd(lua_State *L); static int meth_setfd(lua_State *L); @@ -65,26 +64,62 @@ static luaL_Reg tcp_methods[] = { {"setpeername", meth_connect}, {"setsockname", meth_bind}, {"settimeout", meth_settimeout}, + {"gettimeout", meth_gettimeout}, {"shutdown", meth_shutdown}, {NULL, NULL} }; /* socket option handlers */ static t_opt optget[] = { + {"bindtodevice", opt_get_bindtodevice}, {"keepalive", opt_get_keepalive}, {"reuseaddr", opt_get_reuseaddr}, + {"reuseport", opt_get_reuseport}, {"tcp-nodelay", opt_get_tcp_nodelay}, +#ifdef TCP_KEEPIDLE + {"tcp-keepidle", opt_get_tcp_keepidle}, +#endif +#ifdef TCP_KEEPCNT + {"tcp-keepcnt", opt_get_tcp_keepcnt}, +#endif +#ifdef TCP_KEEPINTVL + {"tcp-keepintvl", opt_get_tcp_keepintvl}, +#endif {"linger", opt_get_linger}, {"error", opt_get_error}, + {"recv-buffer-size", opt_get_recv_buf_size}, + {"send-buffer-size", opt_get_send_buf_size}, {NULL, NULL} }; static t_opt optset[] = { + {"bindtodevice", opt_set_bindtodevice}, {"keepalive", opt_set_keepalive}, {"reuseaddr", opt_set_reuseaddr}, + {"reuseport", opt_set_reuseport}, {"tcp-nodelay", opt_set_tcp_nodelay}, +#ifdef TCP_KEEPIDLE + {"tcp-keepidle", opt_set_tcp_keepidle}, +#endif +#ifdef TCP_KEEPCNT + {"tcp-keepcnt", opt_set_tcp_keepcnt}, +#endif +#ifdef TCP_KEEPINTVL + {"tcp-keepintvl", opt_set_tcp_keepintvl}, +#endif {"ipv6-v6only", opt_set_ip6_v6only}, {"linger", opt_set_linger}, + {"recv-buffer-size", opt_set_recv_buf_size}, + {"send-buffer-size", opt_set_send_buf_size}, +#ifdef TCP_DEFER_ACCEPT + {"tcp-defer-accept", opt_set_tcp_defer_accept}, +#endif +#ifdef TCP_FASTOPEN + {"tcp-fastopen", opt_set_tcp_fastopen}, +#endif +#ifdef TCP_FASTOPEN_CONNECT + {"tcp-fastopen-connect", opt_set_tcp_fastopen_connect}, +#endif {NULL, NULL} }; @@ -348,6 +383,12 @@ static int meth_settimeout(lua_State *L) return timeout_meth_settimeout(L, &tcp->tm); } +static int meth_gettimeout(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + return timeout_meth_gettimeout(L, &tcp->tm); +} + /*=========================================================================*\ * Library functions \*=========================================================================*/ @@ -415,7 +456,7 @@ static int global_connect(lua_State *L) { bindhints.ai_family = family; bindhints.ai_flags = AI_PASSIVE; if (localaddr) { - err = inet_trybind(&tcp->sock, &tcp->family, localaddr, + err = inet_trybind(&tcp->sock, &tcp->family, localaddr, localserv, &bindhints); if (err) { lua_pushnil(L); @@ -427,7 +468,7 @@ static int global_connect(lua_State *L) { memset(&connecthints, 0, sizeof(connecthints)); connecthints.ai_socktype = SOCK_STREAM; /* make sure we try to connect only to the same family */ - connecthints.ai_family = tcp->family; + connecthints.ai_family = tcp->family; err = inet_tryconnect(&tcp->sock, &tcp->family, remoteaddr, remoteserv, &tcp->tm, &connecthints); if (err) { diff --git a/src/tcp.h b/src/tcp.h index eded620..9b282ef 100644 --- a/src/tcp.h +++ b/src/tcp.h @@ -14,7 +14,7 @@ * tcp objects either connected to some address or returned by the accept * method of a server object. \*=========================================================================*/ -#include "lua.h" +#include "luasocket.h" #include "buffer.h" #include "timeout.h" @@ -30,6 +30,14 @@ typedef struct t_tcp_ { typedef t_tcp *p_tcp; +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + int tcp_open(lua_State *L); +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + #endif /* TCP_H */ diff --git a/src/timeout.c b/src/timeout.c index 087d033..2bdc069 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -2,17 +2,15 @@ * Timeout management functions * LuaSocket toolkit \*=========================================================================*/ -#include -#include -#include - -#include "lua.h" -#include "lauxlib.h" -#include "compat.h" +#include "luasocket.h" #include "auxiliar.h" #include "timeout.h" +#include +#include +#include + #ifdef _WIN32 #include #else @@ -173,6 +171,16 @@ int timeout_meth_settimeout(lua_State *L, p_timeout tm) { return 1; } +/*-------------------------------------------------------------------------*\ +* Gets timeout values for IO operations +* Lua Output: block, total +\*-------------------------------------------------------------------------*/ +int timeout_meth_gettimeout(lua_State *L, p_timeout tm) { + lua_pushnumber(L, tm->block); + lua_pushnumber(L, tm->total); + return 2; +} + /*=========================================================================*\ * Test support functions \*=========================================================================*/ diff --git a/src/timeout.h b/src/timeout.h index 6715ca7..9e5250d 100644 --- a/src/timeout.h +++ b/src/timeout.h @@ -4,7 +4,7 @@ * Timeout management functions * LuaSocket toolkit \*=========================================================================*/ -#include "lua.h" +#include "luasocket.h" /* timeout control structure */ typedef struct t_timeout_ { @@ -14,14 +14,26 @@ typedef struct t_timeout_ { } t_timeout; typedef t_timeout *p_timeout; -int timeout_open(lua_State *L); +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + void timeout_init(p_timeout tm, double block, double total); double timeout_get(p_timeout tm); +double timeout_getstart(p_timeout tm); double timeout_getretry(p_timeout tm); p_timeout timeout_markstart(p_timeout tm); -double timeout_getstart(p_timeout tm); + double timeout_gettime(void); + +int timeout_open(lua_State *L); + int timeout_meth_settimeout(lua_State *L, p_timeout tm); +int timeout_meth_gettimeout(lua_State *L, p_timeout tm); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif #define timeout_iszero(tm) ((tm)->block == 0.0) diff --git a/src/tp.lua b/src/tp.lua index cbeff56..b8ebc56 100644 --- a/src/tp.lua +++ b/src/tp.lua @@ -46,6 +46,14 @@ end -- metatable for sock object local metat = { __index = {} } +function metat.__index:getpeername() + return self.c:getpeername() +end + +function metat.__index:getsockname() + return self.c:getpeername() +end + function metat.__index:check(ok) local code, reply = get_reply(self.c) if not code then return nil, reply end @@ -74,7 +82,7 @@ function metat.__index:command(cmd, arg) end function metat.__index:sink(snk, pat) - local chunk, err = c:receive(pat) + local chunk, err = self.c:receive(pat) return snk(chunk, err) end @@ -123,4 +131,4 @@ function _M.connect(host, port, timeout, create) return base.setmetatable({c = c}, metat) end -return _M \ No newline at end of file +return _M diff --git a/src/udp.c b/src/udp.c old mode 100644 new mode 100755 index 17d932a..712ad50 --- a/src/udp.c +++ b/src/udp.c @@ -2,12 +2,7 @@ * UDP object * LuaSocket toolkit \*=========================================================================*/ -#include -#include - -#include "lua.h" -#include "lauxlib.h" -#include "compat.h" +#include "luasocket.h" #include "auxiliar.h" #include "socket.h" @@ -15,6 +10,9 @@ #include "options.h" #include "udp.h" +#include +#include + /* min and max macros */ #ifndef MIN #define MIN(x, y) ((x) < (y) ? x : y) @@ -36,6 +34,7 @@ static int meth_receivefrom(lua_State *L); static int meth_getfamily(lua_State *L); static int meth_getsockname(lua_State *L); static int meth_getpeername(lua_State *L); +static int meth_gettimeout(lua_State *L); static int meth_setsockname(lua_State *L); static int meth_setpeername(lua_State *L); static int meth_close(lua_State *L); @@ -66,6 +65,7 @@ static luaL_Reg udp_methods[] = { {"setpeername", meth_setpeername}, {"setsockname", meth_setsockname}, {"settimeout", meth_settimeout}, + {"gettimeout", meth_gettimeout}, {NULL, NULL} }; @@ -86,6 +86,8 @@ static t_opt optset[] = { {"ipv6-add-membership", opt_set_ip6_add_membership}, {"ipv6-drop-membership", opt_set_ip6_drop_membersip}, {"ipv6-v6only", opt_set_ip6_v6only}, + {"recv-buffer-size", opt_set_recv_buf_size}, + {"send-buffer-size", opt_set_send_buf_size}, {NULL, NULL} }; @@ -102,6 +104,8 @@ static t_opt optget[] = { {"ipv6-multicast-hops", opt_get_ip6_unicast_hops}, {"ipv6-multicast-loop", opt_get_ip6_multicast_loop}, {"ipv6-v6only", opt_get_ip6_v6only}, + {"recv-buffer-size", opt_get_recv_buf_size}, + {"send-buffer-size", opt_get_send_buf_size}, {NULL, NULL} }; @@ -116,8 +120,7 @@ static luaL_Reg func[] = { /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ -int udp_open(lua_State *L) -{ +int udp_open(lua_State *L) { /* create classes */ auxiliar_newclass(L, "udp{connected}", udp_methods); auxiliar_newclass(L, "udp{unconnected}", udp_methods); @@ -128,6 +131,10 @@ int udp_open(lua_State *L) auxiliar_add2group(L, "udp{unconnected}", "select{able}"); /* define library functions */ luaL_setfuncs(L, func, 0); + /* export default UDP size */ + lua_pushliteral(L, "_DATAGRAMSIZE"); + lua_pushinteger(L, UDP_DATAGRAMSIZE); + lua_rawset(L, -3); return 0; } @@ -177,13 +184,37 @@ static int meth_sendto(lua_State *L) { memset(&aihint, 0, sizeof(aihint)); aihint.ai_family = udp->family; aihint.ai_socktype = SOCK_DGRAM; - aihint.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; + aihint.ai_flags = AI_NUMERICHOST; +#ifdef AI_NUMERICSERV + aihint.ai_flags |= AI_NUMERICSERV; +#endif err = getaddrinfo(ip, port, &aihint, &ai); if (err) { lua_pushnil(L); - lua_pushstring(L, gai_strerror(err)); + lua_pushstring(L, LUA_GAI_STRERROR(err)); return 2; } + + /* create socket if on first sendto if AF_UNSPEC was set */ + if (udp->family == AF_UNSPEC && udp->sock == SOCKET_INVALID) { + struct addrinfo *ap; + const char *errstr = NULL; + for (ap = ai; ap != NULL; ap = ap->ai_next) { + errstr = inet_trycreate(&udp->sock, ap->ai_family, SOCK_DGRAM, 0); + if (errstr == NULL) { + socket_setnonblocking(&udp->sock); + udp->family = ap->ai_family; + break; + } + } + if (errstr != NULL) { + lua_pushnil(L); + lua_pushstring(L, errstr); + freeaddrinfo(ai); + return 2; + } + } + timeout_markstart(tm); err = socket_sendto(&udp->sock, data, count, &sent, ai->ai_addr, (socklen_t) ai->ai_addrlen, tm); @@ -202,69 +233,78 @@ static int meth_sendto(lua_State *L) { \*-------------------------------------------------------------------------*/ static int meth_receive(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); - char buffer[UDP_DATAGRAMSIZE]; - size_t got, count = (size_t) luaL_optnumber(L, 2, sizeof(buffer)); + char buf[UDP_DATAGRAMSIZE]; + size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); + char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; int err; p_timeout tm = &udp->tm; - count = MIN(count, sizeof(buffer)); timeout_markstart(tm); - err = socket_recv(&udp->sock, buffer, count, &got, tm); - /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ - if (err == IO_CLOSED) - err = IO_DONE; - if (err != IO_DONE) { + if (!dgram) { lua_pushnil(L); - lua_pushstring(L, udp_strerror(err)); + lua_pushliteral(L, "out of memory"); return 2; } - lua_pushlstring(L, buffer, got); + err = socket_recv(&udp->sock, dgram, wanted, &got, tm); + /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ + if (err != IO_DONE && err != IO_CLOSED) { + lua_pushnil(L); + lua_pushstring(L, udp_strerror(err)); + if (wanted > sizeof(buf)) free(dgram); + return 2; + } + lua_pushlstring(L, dgram, got); + if (wanted > sizeof(buf)) free(dgram); return 1; } /*-------------------------------------------------------------------------*\ * Receives data and sender from a UDP socket \*-------------------------------------------------------------------------*/ -static int meth_receivefrom(lua_State *L) -{ +static int meth_receivefrom(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{unconnected}", 1); - char buffer[UDP_DATAGRAMSIZE]; - size_t got, count = (size_t) luaL_optnumber(L, 2, sizeof(buffer)); - int err; - p_timeout tm = &udp->tm; + char buf[UDP_DATAGRAMSIZE]; + size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); + char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); char addrstr[INET6_ADDRSTRLEN]; char portstr[6]; + int err; + p_timeout tm = &udp->tm; timeout_markstart(tm); - count = MIN(count, sizeof(buffer)); - err = socket_recvfrom(&udp->sock, buffer, count, &got, (SA *) &addr, + if (!dgram) { + lua_pushnil(L); + lua_pushliteral(L, "out of memory"); + return 2; + } + err = socket_recvfrom(&udp->sock, dgram, wanted, &got, (SA *) &addr, &addr_len, tm); /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ - if (err == IO_CLOSED) - err = IO_DONE; - if (err != IO_DONE) { + if (err != IO_DONE && err != IO_CLOSED) { lua_pushnil(L); lua_pushstring(L, udp_strerror(err)); + if (wanted > sizeof(buf)) free(dgram); return 2; } err = getnameinfo((struct sockaddr *)&addr, addr_len, addrstr, INET6_ADDRSTRLEN, portstr, 6, NI_NUMERICHOST | NI_NUMERICSERV); if (err) { lua_pushnil(L); - lua_pushstring(L, gai_strerror(err)); + lua_pushstring(L, LUA_GAI_STRERROR(err)); + if (wanted > sizeof(buf)) free(dgram); return 2; } - lua_pushlstring(L, buffer, got); + lua_pushlstring(L, dgram, got); lua_pushstring(L, addrstr); lua_pushinteger(L, (int) strtol(portstr, (char **) NULL, 10)); + if (wanted > sizeof(buf)) free(dgram); return 3; } /*-------------------------------------------------------------------------*\ * Returns family as string \*-------------------------------------------------------------------------*/ -static int meth_getfamily(lua_State *L) -{ +static int meth_getfamily(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); if (udp->family == AF_INET6) { lua_pushliteral(L, "inet6"); @@ -335,6 +375,11 @@ static int meth_settimeout(lua_State *L) { return timeout_meth_settimeout(L, &udp->tm); } +static int meth_gettimeout(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + return timeout_meth_gettimeout(L, &udp->tm); +} + /*-------------------------------------------------------------------------*\ * Turns a master udp object into a client object. \*-------------------------------------------------------------------------*/ diff --git a/src/udp.h b/src/udp.h index 2b831a5..07d5247 100644 --- a/src/udp.h +++ b/src/udp.h @@ -8,16 +8,15 @@ * (AF_INET, SOCK_DGRAM). * * Two classes are defined: connected and unconnected. UDP objects are -* originally unconnected. They can be "connected" to a given address +* originally unconnected. They can be "connected" to a given address * with a call to the setpeername function. The same function can be used to * break the connection. \*=========================================================================*/ -#include "lua.h" +#include "luasocket.h" #include "timeout.h" #include "socket.h" -/* can't be larger than wsocket.c MAXCHUNK!!! */ #define UDP_DATAGRAMSIZE 8192 typedef struct t_udp_ { @@ -27,6 +26,14 @@ typedef struct t_udp_ { } t_udp; typedef t_udp *p_udp; +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + int udp_open(lua_State *L); +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + #endif /* UDP_H */ diff --git a/src/unix.c b/src/unix.c index 5bc3148..268d8b2 100644 --- a/src/unix.c +++ b/src/unix.c @@ -2,329 +2,68 @@ * Unix domain socket * LuaSocket toolkit \*=========================================================================*/ -#include +#include "luasocket.h" -#include "lua.h" -#include "lauxlib.h" +#include "unixstream.h" +#include "unixdgram.h" -#include "auxiliar.h" -#include "socket.h" -#include "options.h" -#include "unix.h" -#include - -/*=========================================================================*\ -* Internal function prototypes -\*=========================================================================*/ -static int global_create(lua_State *L); -static int meth_connect(lua_State *L); -static int meth_listen(lua_State *L); -static int meth_bind(lua_State *L); -static int meth_send(lua_State *L); -static int meth_shutdown(lua_State *L); -static int meth_receive(lua_State *L); -static int meth_accept(lua_State *L); -static int meth_close(lua_State *L); -static int meth_setoption(lua_State *L); -static int meth_settimeout(lua_State *L); -static int meth_getfd(lua_State *L); -static int meth_setfd(lua_State *L); -static int meth_dirty(lua_State *L); -static int meth_getstats(lua_State *L); -static int meth_setstats(lua_State *L); - -static const char *unix_tryconnect(p_unix un, const char *path); -static const char *unix_trybind(p_unix un, const char *path); - -/* unix object methods */ -static luaL_Reg unix_methods[] = { - {"__gc", meth_close}, - {"__tostring", auxiliar_tostring}, - {"accept", meth_accept}, - {"bind", meth_bind}, - {"close", meth_close}, - {"connect", meth_connect}, - {"dirty", meth_dirty}, - {"getfd", meth_getfd}, - {"getstats", meth_getstats}, - {"setstats", meth_setstats}, - {"listen", meth_listen}, - {"receive", meth_receive}, - {"send", meth_send}, - {"setfd", meth_setfd}, - {"setoption", meth_setoption}, - {"setpeername", meth_connect}, - {"setsockname", meth_bind}, - {"settimeout", meth_settimeout}, - {"shutdown", meth_shutdown}, - {NULL, NULL} +/*-------------------------------------------------------------------------*\ +* Modules and functions +\*-------------------------------------------------------------------------*/ +static const luaL_Reg mod[] = { + {"stream", unixstream_open}, + {"dgram", unixdgram_open}, + {NULL, NULL} }; -/* socket option handlers */ -static t_opt optset[] = { - {"keepalive", opt_set_keepalive}, - {"reuseaddr", opt_set_reuseaddr}, - {"linger", opt_set_linger}, - {NULL, NULL} -}; +static void add_alias(lua_State *L, int index, const char *name, const char *target) +{ + lua_getfield(L, index, target); + lua_setfield(L, index, name); +} + +static int compat_socket_unix_call(lua_State *L) +{ + /* Look up socket.unix.stream in the socket.unix table (which is the first + * argument). */ + lua_getfield(L, 1, "stream"); + + /* Replace the stack entry for the socket.unix table with the + * socket.unix.stream function. */ + lua_replace(L, 1); + + /* Call socket.unix.stream, passing along any arguments. */ + int n = lua_gettop(L); + lua_call(L, n-1, LUA_MULTRET); + + /* Pass along the return values from socket.unix.stream. */ + n = lua_gettop(L); + return n; +} /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ -int luaopen_socket_unix(lua_State *L) { - /* create classes */ - auxiliar_newclass(L, "unix{master}", unix_methods); - auxiliar_newclass(L, "unix{client}", unix_methods); - auxiliar_newclass(L, "unix{server}", unix_methods); - /* create class groups */ - auxiliar_add2group(L, "unix{master}", "unix{any}"); - auxiliar_add2group(L, "unix{client}", "unix{any}"); - auxiliar_add2group(L, "unix{server}", "unix{any}"); - /* return the function instead of the 'socket' table */ - lua_pushcfunction(L, global_create); - return 1; -} - -/*=========================================================================*\ -* Lua methods -\*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Just call buffered IO methods -\*-------------------------------------------------------------------------*/ -static int meth_send(lua_State *L) { - p_unix un = (p_unix) auxiliar_checkclass(L, "unix{client}", 1); - return buffer_meth_send(L, &un->buf); -} - -static int meth_receive(lua_State *L) { - p_unix un = (p_unix) auxiliar_checkclass(L, "unix{client}", 1); - return buffer_meth_receive(L, &un->buf); -} - -static int meth_getstats(lua_State *L) { - p_unix un = (p_unix) auxiliar_checkclass(L, "unix{client}", 1); - return buffer_meth_getstats(L, &un->buf); -} - -static int meth_setstats(lua_State *L) { - p_unix un = (p_unix) auxiliar_checkclass(L, "unix{client}", 1); - return buffer_meth_setstats(L, &un->buf); -} - -/*-------------------------------------------------------------------------*\ -* Just call option handler -\*-------------------------------------------------------------------------*/ -static int meth_setoption(lua_State *L) { - p_unix un = (p_unix) auxiliar_checkgroup(L, "unix{any}", 1); - return opt_meth_setoption(L, optset, &un->sock); -} - -/*-------------------------------------------------------------------------*\ -* Select support methods -\*-------------------------------------------------------------------------*/ -static int meth_getfd(lua_State *L) { - p_unix un = (p_unix) auxiliar_checkgroup(L, "unix{any}", 1); - lua_pushnumber(L, (int) un->sock); - return 1; -} - -/* this is very dangerous, but can be handy for those that are brave enough */ -static int meth_setfd(lua_State *L) { - p_unix un = (p_unix) auxiliar_checkgroup(L, "unix{any}", 1); - un->sock = (t_socket) luaL_checknumber(L, 2); - return 0; -} - -static int meth_dirty(lua_State *L) { - p_unix un = (p_unix) auxiliar_checkgroup(L, "unix{any}", 1); - lua_pushboolean(L, !buffer_isempty(&un->buf)); - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Waits for and returns a client object attempting connection to the -* server object -\*-------------------------------------------------------------------------*/ -static int meth_accept(lua_State *L) { - p_unix server = (p_unix) auxiliar_checkclass(L, "unix{server}", 1); - p_timeout tm = timeout_markstart(&server->tm); - t_socket sock; - int err = socket_accept(&server->sock, &sock, NULL, NULL, tm); - /* if successful, push client socket */ - if (err == IO_DONE) { - p_unix clnt = (p_unix) lua_newuserdata(L, sizeof(t_unix)); - auxiliar_setclass(L, "unix{client}", -1); - /* initialize structure fields */ - socket_setnonblocking(&sock); - clnt->sock = sock; - io_init(&clnt->io, (p_send)socket_send, (p_recv)socket_recv, - (p_error) socket_ioerror, &clnt->sock); - timeout_init(&clnt->tm, -1, -1); - buffer_init(&clnt->buf, &clnt->io, &clnt->tm); - return 1; - } else { - lua_pushnil(L); - lua_pushstring(L, socket_strerror(err)); - return 2; - } -} - -/*-------------------------------------------------------------------------*\ -* Binds an object to an address -\*-------------------------------------------------------------------------*/ -static const char *unix_trybind(p_unix un, const char *path) { - struct sockaddr_un local; - size_t len = strlen(path); - int err; - if (len >= sizeof(local.sun_path)) return "path too long"; - memset(&local, 0, sizeof(local)); - strcpy(local.sun_path, path); - local.sun_family = AF_UNIX; -#ifdef UNIX_HAS_SUN_LEN - local.sun_len = sizeof(local.sun_family) + sizeof(local.sun_len) - + len + 1; - err = socket_bind(&un->sock, (SA *) &local, local.sun_len); - -#else - err = socket_bind(&un->sock, (SA *) &local, - sizeof(local.sun_family) + len); -#endif - if (err != IO_DONE) socket_destroy(&un->sock); - return socket_strerror(err); -} - -static int meth_bind(lua_State *L) { - p_unix un = (p_unix) auxiliar_checkclass(L, "unix{master}", 1); - const char *path = luaL_checkstring(L, 2); - const char *err = unix_trybind(un, path); - if (err) { - lua_pushnil(L); - lua_pushstring(L, err); - return 2; - } - lua_pushnumber(L, 1); - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Turns a master unix object into a client object. -\*-------------------------------------------------------------------------*/ -static const char *unix_tryconnect(p_unix un, const char *path) +LUASOCKET_API int luaopen_socket_unix(lua_State *L) { - struct sockaddr_un remote; - int err; - size_t len = strlen(path); - if (len >= sizeof(remote.sun_path)) return "path too long"; - memset(&remote, 0, sizeof(remote)); - strcpy(remote.sun_path, path); - remote.sun_family = AF_UNIX; - timeout_markstart(&un->tm); -#ifdef UNIX_HAS_SUN_LEN - remote.sun_len = sizeof(remote.sun_family) + sizeof(remote.sun_len) - + len + 1; - err = socket_connect(&un->sock, (SA *) &remote, remote.sun_len, &un->tm); -#else - err = socket_connect(&un->sock, (SA *) &remote, - sizeof(remote.sun_family) + len, &un->tm); -#endif - if (err != IO_DONE) socket_destroy(&un->sock); - return socket_strerror(err); -} + int i; + lua_newtable(L); + int socket_unix_table = lua_gettop(L); + + for (i = 0; mod[i].name; i++) + mod[i].func(L); + + /* Add backwards compatibility aliases "tcp" and "udp" for the "stream" and + * "dgram" functions. */ + add_alias(L, socket_unix_table, "tcp", "stream"); + add_alias(L, socket_unix_table, "udp", "dgram"); + + /* Add a backwards compatibility function and a metatable setup to call it + * for the old socket.unix() interface. */ + lua_pushcfunction(L, compat_socket_unix_call); + lua_setfield(L, socket_unix_table, "__call"); + lua_pushvalue(L, socket_unix_table); + lua_setmetatable(L, socket_unix_table); -static int meth_connect(lua_State *L) -{ - p_unix un = (p_unix) auxiliar_checkclass(L, "unix{master}", 1); - const char *path = luaL_checkstring(L, 2); - const char *err = unix_tryconnect(un, path); - if (err) { - lua_pushnil(L); - lua_pushstring(L, err); - return 2; - } - /* turn master object into a client object */ - auxiliar_setclass(L, "unix{client}", 1); - lua_pushnumber(L, 1); return 1; } - -/*-------------------------------------------------------------------------*\ -* Closes socket used by object -\*-------------------------------------------------------------------------*/ -static int meth_close(lua_State *L) -{ - p_unix un = (p_unix) auxiliar_checkgroup(L, "unix{any}", 1); - socket_destroy(&un->sock); - lua_pushnumber(L, 1); - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Puts the sockt in listen mode -\*-------------------------------------------------------------------------*/ -static int meth_listen(lua_State *L) -{ - p_unix un = (p_unix) auxiliar_checkclass(L, "unix{master}", 1); - int backlog = (int) luaL_optnumber(L, 2, 32); - int err = socket_listen(&un->sock, backlog); - if (err != IO_DONE) { - lua_pushnil(L); - lua_pushstring(L, socket_strerror(err)); - return 2; - } - /* turn master object into a server object */ - auxiliar_setclass(L, "unix{server}", 1); - lua_pushnumber(L, 1); - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Shuts the connection down partially -\*-------------------------------------------------------------------------*/ -static int meth_shutdown(lua_State *L) -{ - /* SHUT_RD, SHUT_WR, SHUT_RDWR have the value 0, 1, 2, so we can use method index directly */ - static const char* methods[] = { "receive", "send", "both", NULL }; - p_unix tcp = (p_unix) auxiliar_checkclass(L, "unix{client}", 1); - int how = luaL_checkoption(L, 2, "both", methods); - socket_shutdown(&tcp->sock, how); - lua_pushnumber(L, 1); - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Just call tm methods -\*-------------------------------------------------------------------------*/ -static int meth_settimeout(lua_State *L) { - p_unix un = (p_unix) auxiliar_checkgroup(L, "unix{any}", 1); - return timeout_meth_settimeout(L, &un->tm); -} - -/*=========================================================================*\ -* Library functions -\*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Creates a master unix object -\*-------------------------------------------------------------------------*/ -static int global_create(lua_State *L) { - t_socket sock; - int err = socket_create(&sock, AF_UNIX, SOCK_STREAM, 0); - /* try to allocate a system socket */ - if (err == IO_DONE) { - /* allocate unix object */ - p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); - /* set its type as master object */ - auxiliar_setclass(L, "unix{master}", -1); - /* initialize remaining structure fields */ - socket_setnonblocking(&sock); - un->sock = sock; - io_init(&un->io, (p_send) socket_send, (p_recv) socket_recv, - (p_error) socket_ioerror, &un->sock); - timeout_init(&un->tm, -1, -1); - buffer_init(&un->buf, &un->io, &un->tm); - return 1; - } else { - lua_pushnil(L); - lua_pushstring(L, socket_strerror(err)); - return 2; - } -} diff --git a/src/unix.h b/src/unix.h index 8cc7a79..c203561 100644 --- a/src/unix.h +++ b/src/unix.h @@ -7,16 +7,12 @@ * This module is just an example of how to extend LuaSocket with a new * domain. \*=========================================================================*/ -#include "lua.h" +#include "luasocket.h" #include "buffer.h" #include "timeout.h" #include "socket.h" -#ifndef UNIX_API -#define UNIX_API extern -#endif - typedef struct t_unix_ { t_socket sock; t_io io; @@ -25,6 +21,6 @@ typedef struct t_unix_ { } t_unix; typedef t_unix *p_unix; -UNIX_API int luaopen_socket_unix(lua_State *L); +LUASOCKET_API int luaopen_socket_unix(lua_State *L); #endif /* UNIX_H */ diff --git a/src/unixdgram.c b/src/unixdgram.c new file mode 100644 index 0000000..69093d7 --- /dev/null +++ b/src/unixdgram.c @@ -0,0 +1,405 @@ +/*=========================================================================*\ +* Unix domain socket dgram submodule +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "auxiliar.h" +#include "socket.h" +#include "options.h" +#include "unix.h" + +#include +#include + +#include + +#define UNIXDGRAM_DATAGRAMSIZE 8192 + +/* provide a SUN_LEN macro if sys/un.h doesn't (e.g. Android) */ +#ifndef SUN_LEN +#define SUN_LEN(ptr) \ + ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + + strlen ((ptr)->sun_path)) +#endif + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int global_create(lua_State *L); +static int meth_connect(lua_State *L); +static int meth_bind(lua_State *L); +static int meth_send(lua_State *L); +static int meth_receive(lua_State *L); +static int meth_close(lua_State *L); +static int meth_setoption(lua_State *L); +static int meth_settimeout(lua_State *L); +static int meth_gettimeout(lua_State *L); +static int meth_getfd(lua_State *L); +static int meth_setfd(lua_State *L); +static int meth_dirty(lua_State *L); +static int meth_receivefrom(lua_State *L); +static int meth_sendto(lua_State *L); +static int meth_getsockname(lua_State *L); + +static const char *unixdgram_tryconnect(p_unix un, const char *path); +static const char *unixdgram_trybind(p_unix un, const char *path); + +/* unixdgram object methods */ +static luaL_Reg unixdgram_methods[] = { + {"__gc", meth_close}, + {"__tostring", auxiliar_tostring}, + {"bind", meth_bind}, + {"close", meth_close}, + {"connect", meth_connect}, + {"dirty", meth_dirty}, + {"getfd", meth_getfd}, + {"send", meth_send}, + {"sendto", meth_sendto}, + {"receive", meth_receive}, + {"receivefrom", meth_receivefrom}, + {"setfd", meth_setfd}, + {"setoption", meth_setoption}, + {"setpeername", meth_connect}, + {"setsockname", meth_bind}, + {"getsockname", meth_getsockname}, + {"settimeout", meth_settimeout}, + {"gettimeout", meth_gettimeout}, + {NULL, NULL} +}; + +/* socket option handlers */ +static t_opt optset[] = { + {"reuseaddr", opt_set_reuseaddr}, + {NULL, NULL} +}; + +/* functions in library namespace */ +static luaL_Reg func[] = { + {"dgram", global_create}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int unixdgram_open(lua_State *L) +{ + /* create classes */ + auxiliar_newclass(L, "unixdgram{connected}", unixdgram_methods); + auxiliar_newclass(L, "unixdgram{unconnected}", unixdgram_methods); + /* create class groups */ + auxiliar_add2group(L, "unixdgram{connected}", "unixdgram{any}"); + auxiliar_add2group(L, "unixdgram{unconnected}", "unixdgram{any}"); + auxiliar_add2group(L, "unixdgram{connected}", "select{able}"); + auxiliar_add2group(L, "unixdgram{unconnected}", "select{able}"); + + luaL_setfuncs(L, func, 0); + return 0; +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +static const char *unixdgram_strerror(int err) +{ + /* a 'closed' error on an unconnected means the target address was not + * accepted by the transport layer */ + if (err == IO_CLOSED) return "refused"; + else return socket_strerror(err); +} + +static int meth_send(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{connected}", 1); + p_timeout tm = &un->tm; + size_t count, sent = 0; + int err; + const char *data = luaL_checklstring(L, 2, &count); + timeout_markstart(tm); + err = socket_send(&un->sock, data, count, &sent, tm); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, unixdgram_strerror(err)); + return 2; + } + lua_pushnumber(L, (lua_Number) sent); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Send data through unconnected unixdgram socket +\*-------------------------------------------------------------------------*/ +static int meth_sendto(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); + size_t count, sent = 0; + const char *data = luaL_checklstring(L, 2, &count); + const char *path = luaL_checkstring(L, 3); + p_timeout tm = &un->tm; + int err; + struct sockaddr_un remote; + size_t len = strlen(path); + + if (len >= sizeof(remote.sun_path)) { + lua_pushnil(L); + lua_pushstring(L, "path too long"); + return 2; + } + + memset(&remote, 0, sizeof(remote)); + strcpy(remote.sun_path, path); + remote.sun_family = AF_UNIX; + timeout_markstart(tm); +#ifdef UNIX_HAS_SUN_LEN + remote.sun_len = sizeof(remote.sun_family) + sizeof(remote.sun_len) + + len + 1; + err = socket_sendto(&un->sock, data, count, &sent, (SA *) &remote, remote.sun_len, tm); +#else + err = socket_sendto(&un->sock, data, count, &sent, (SA *) &remote, + sizeof(remote.sun_family) + len, tm); +#endif + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, unixdgram_strerror(err)); + return 2; + } + lua_pushnumber(L, (lua_Number) sent); + return 1; +} + +static int meth_receive(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + char buf[UNIXDGRAM_DATAGRAMSIZE]; + size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); + char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; + int err; + p_timeout tm = &un->tm; + timeout_markstart(tm); + if (!dgram) { + lua_pushnil(L); + lua_pushliteral(L, "out of memory"); + return 2; + } + err = socket_recv(&un->sock, dgram, wanted, &got, tm); + /* Unlike STREAM, recv() of zero is not closed, but a zero-length packet. */ + if (err != IO_DONE && err != IO_CLOSED) { + lua_pushnil(L); + lua_pushstring(L, unixdgram_strerror(err)); + if (wanted > sizeof(buf)) free(dgram); + return 2; + } + lua_pushlstring(L, dgram, got); + if (wanted > sizeof(buf)) free(dgram); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Receives data and sender from a DGRAM socket +\*-------------------------------------------------------------------------*/ +static int meth_receivefrom(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); + char buf[UNIXDGRAM_DATAGRAMSIZE]; + size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); + char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; + struct sockaddr_un addr; + socklen_t addr_len = sizeof(addr); + int err; + p_timeout tm = &un->tm; + timeout_markstart(tm); + if (!dgram) { + lua_pushnil(L); + lua_pushliteral(L, "out of memory"); + return 2; + } + addr.sun_path[0] = '\0'; + err = socket_recvfrom(&un->sock, dgram, wanted, &got, (SA *) &addr, + &addr_len, tm); + /* Unlike STREAM, recv() of zero is not closed, but a zero-length packet. */ + if (err != IO_DONE && err != IO_CLOSED) { + lua_pushnil(L); + lua_pushstring(L, unixdgram_strerror(err)); + if (wanted > sizeof(buf)) free(dgram); + return 2; + } + + lua_pushlstring(L, dgram, got); + /* the path may be empty, when client send without bind */ + lua_pushstring(L, addr.sun_path); + if (wanted > sizeof(buf)) free(dgram); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Just call option handler +\*-------------------------------------------------------------------------*/ +static int meth_setoption(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + return opt_meth_setoption(L, optset, &un->sock); +} + +/*-------------------------------------------------------------------------*\ +* Select support methods +\*-------------------------------------------------------------------------*/ +static int meth_getfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + lua_pushnumber(L, (int) un->sock); + return 1; +} + +/* this is very dangerous, but can be handy for those that are brave enough */ +static int meth_setfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + un->sock = (t_socket) luaL_checknumber(L, 2); + return 0; +} + +static int meth_dirty(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + (void) un; + lua_pushboolean(L, 0); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Binds an object to an address +\*-------------------------------------------------------------------------*/ +static const char *unixdgram_trybind(p_unix un, const char *path) { + struct sockaddr_un local; + size_t len = strlen(path); + if (len >= sizeof(local.sun_path)) return "path too long"; + memset(&local, 0, sizeof(local)); + strcpy(local.sun_path, path); + local.sun_family = AF_UNIX; + size_t addrlen = SUN_LEN(&local); +#ifdef UNIX_HAS_SUN_LEN + local.sun_len = addrlen + 1; +#endif + int err = socket_bind(&un->sock, (SA *) &local, addrlen); + if (err != IO_DONE) socket_destroy(&un->sock); + return socket_strerror(err); +} + +static int meth_bind(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); + const char *path = luaL_checkstring(L, 2); + const char *err = unixdgram_trybind(un, path); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +static int meth_getsockname(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + struct sockaddr_un peer = {0}; + socklen_t peer_len = sizeof(peer); + + if (getsockname(un->sock, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } + + lua_pushstring(L, peer.sun_path); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Turns a master unixdgram object into a client object. +\*-------------------------------------------------------------------------*/ +static const char *unixdgram_tryconnect(p_unix un, const char *path) +{ + struct sockaddr_un remote; + size_t len = strlen(path); + if (len >= sizeof(remote.sun_path)) return "path too long"; + memset(&remote, 0, sizeof(remote)); + strcpy(remote.sun_path, path); + remote.sun_family = AF_UNIX; + timeout_markstart(&un->tm); + size_t addrlen = SUN_LEN(&remote); +#ifdef UNIX_HAS_SUN_LEN + remote.sun_len = addrlen + 1; +#endif + int err = socket_connect(&un->sock, (SA *) &remote, addrlen, &un->tm); + if (err != IO_DONE) socket_destroy(&un->sock); + return socket_strerror(err); +} + +static int meth_connect(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + const char *path = luaL_checkstring(L, 2); + const char *err = unixdgram_tryconnect(un, path); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + /* turn unconnected object into a connected object */ + auxiliar_setclass(L, "unixdgram{connected}", 1); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Closes socket used by object +\*-------------------------------------------------------------------------*/ +static int meth_close(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + socket_destroy(&un->sock); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Just call tm methods +\*-------------------------------------------------------------------------*/ +static int meth_settimeout(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + return timeout_meth_settimeout(L, &un->tm); +} + +static int meth_gettimeout(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + return timeout_meth_gettimeout(L, &un->tm); +} + +/*=========================================================================*\ +* Library functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a master unixdgram object +\*-------------------------------------------------------------------------*/ +static int global_create(lua_State *L) +{ + t_socket sock; + int err = socket_create(&sock, AF_UNIX, SOCK_DGRAM, 0); + /* try to allocate a system socket */ + if (err == IO_DONE) { + /* allocate unixdgram object */ + p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); + /* set its type as master object */ + auxiliar_setclass(L, "unixdgram{unconnected}", -1); + /* initialize remaining structure fields */ + socket_setnonblocking(&sock); + un->sock = sock; + io_init(&un->io, (p_send) socket_send, (p_recv) socket_recv, + (p_error) socket_ioerror, &un->sock); + timeout_init(&un->tm, -1, -1); + buffer_init(&un->buf, &un->io, &un->tm); + return 1; + } else { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(err)); + return 2; + } +} diff --git a/src/unixdgram.h b/src/unixdgram.h new file mode 100644 index 0000000..a1a0166 --- /dev/null +++ b/src/unixdgram.h @@ -0,0 +1,28 @@ +#ifndef UNIXDGRAM_H +#define UNIXDGRAM_H +/*=========================================================================*\ +* DGRAM object +* LuaSocket toolkit +* +* The dgram.h module provides LuaSocket with support for DGRAM protocol +* (AF_INET, SOCK_DGRAM). +* +* Two classes are defined: connected and unconnected. DGRAM objects are +* originally unconnected. They can be "connected" to a given address +* with a call to the setpeername function. The same function can be used to +* break the connection. +\*=========================================================================*/ + +#include "unix.h" + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int unixdgram_open(lua_State *L); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* UNIXDGRAM_H */ diff --git a/src/unixstream.c b/src/unixstream.c new file mode 100644 index 0000000..02aced9 --- /dev/null +++ b/src/unixstream.c @@ -0,0 +1,355 @@ +/*=========================================================================*\ +* Unix domain socket stream sub module +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "auxiliar.h" +#include "socket.h" +#include "options.h" +#include "unixstream.h" + +#include +#include + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int global_create(lua_State *L); +static int meth_connect(lua_State *L); +static int meth_listen(lua_State *L); +static int meth_bind(lua_State *L); +static int meth_send(lua_State *L); +static int meth_shutdown(lua_State *L); +static int meth_receive(lua_State *L); +static int meth_accept(lua_State *L); +static int meth_close(lua_State *L); +static int meth_setoption(lua_State *L); +static int meth_settimeout(lua_State *L); +static int meth_getfd(lua_State *L); +static int meth_setfd(lua_State *L); +static int meth_dirty(lua_State *L); +static int meth_getstats(lua_State *L); +static int meth_setstats(lua_State *L); +static int meth_getsockname(lua_State *L); + +static const char *unixstream_tryconnect(p_unix un, const char *path); +static const char *unixstream_trybind(p_unix un, const char *path); + +/* unixstream object methods */ +static luaL_Reg unixstream_methods[] = { + {"__gc", meth_close}, + {"__tostring", auxiliar_tostring}, + {"accept", meth_accept}, + {"bind", meth_bind}, + {"close", meth_close}, + {"connect", meth_connect}, + {"dirty", meth_dirty}, + {"getfd", meth_getfd}, + {"getstats", meth_getstats}, + {"setstats", meth_setstats}, + {"listen", meth_listen}, + {"receive", meth_receive}, + {"send", meth_send}, + {"setfd", meth_setfd}, + {"setoption", meth_setoption}, + {"setpeername", meth_connect}, + {"setsockname", meth_bind}, + {"getsockname", meth_getsockname}, + {"settimeout", meth_settimeout}, + {"shutdown", meth_shutdown}, + {NULL, NULL} +}; + +/* socket option handlers */ +static t_opt optset[] = { + {"keepalive", opt_set_keepalive}, + {"reuseaddr", opt_set_reuseaddr}, + {"linger", opt_set_linger}, + {NULL, NULL} +}; + +/* functions in library namespace */ +static luaL_Reg func[] = { + {"stream", global_create}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int unixstream_open(lua_State *L) +{ + /* create classes */ + auxiliar_newclass(L, "unixstream{master}", unixstream_methods); + auxiliar_newclass(L, "unixstream{client}", unixstream_methods); + auxiliar_newclass(L, "unixstream{server}", unixstream_methods); + + /* create class groups */ + auxiliar_add2group(L, "unixstream{master}", "unixstream{any}"); + auxiliar_add2group(L, "unixstream{client}", "unixstream{any}"); + auxiliar_add2group(L, "unixstream{server}", "unixstream{any}"); + + luaL_setfuncs(L, func, 0); + return 0; +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Just call buffered IO methods +\*-------------------------------------------------------------------------*/ +static int meth_send(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); + return buffer_meth_send(L, &un->buf); +} + +static int meth_receive(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); + return buffer_meth_receive(L, &un->buf); +} + +static int meth_getstats(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); + return buffer_meth_getstats(L, &un->buf); +} + +static int meth_setstats(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); + return buffer_meth_setstats(L, &un->buf); +} + +/*-------------------------------------------------------------------------*\ +* Just call option handler +\*-------------------------------------------------------------------------*/ +static int meth_setoption(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + return opt_meth_setoption(L, optset, &un->sock); +} + +/*-------------------------------------------------------------------------*\ +* Select support methods +\*-------------------------------------------------------------------------*/ +static int meth_getfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + lua_pushnumber(L, (int) un->sock); + return 1; +} + +/* this is very dangerous, but can be handy for those that are brave enough */ +static int meth_setfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + un->sock = (t_socket) luaL_checknumber(L, 2); + return 0; +} + +static int meth_dirty(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + lua_pushboolean(L, !buffer_isempty(&un->buf)); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Waits for and returns a client object attempting connection to the +* server object +\*-------------------------------------------------------------------------*/ +static int meth_accept(lua_State *L) { + p_unix server = (p_unix) auxiliar_checkclass(L, "unixstream{server}", 1); + p_timeout tm = timeout_markstart(&server->tm); + t_socket sock; + int err = socket_accept(&server->sock, &sock, NULL, NULL, tm); + /* if successful, push client socket */ + if (err == IO_DONE) { + p_unix clnt = (p_unix) lua_newuserdata(L, sizeof(t_unix)); + auxiliar_setclass(L, "unixstream{client}", -1); + /* initialize structure fields */ + socket_setnonblocking(&sock); + clnt->sock = sock; + io_init(&clnt->io, (p_send)socket_send, (p_recv)socket_recv, + (p_error) socket_ioerror, &clnt->sock); + timeout_init(&clnt->tm, -1, -1); + buffer_init(&clnt->buf, &clnt->io, &clnt->tm); + return 1; + } else { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(err)); + return 2; + } +} + +/*-------------------------------------------------------------------------*\ +* Binds an object to an address +\*-------------------------------------------------------------------------*/ +static const char *unixstream_trybind(p_unix un, const char *path) { + struct sockaddr_un local; + size_t len = strlen(path); + int err; + if (len >= sizeof(local.sun_path)) return "path too long"; + memset(&local, 0, sizeof(local)); + strcpy(local.sun_path, path); + local.sun_family = AF_UNIX; +#ifdef UNIX_HAS_SUN_LEN + local.sun_len = sizeof(local.sun_family) + sizeof(local.sun_len) + + len + 1; + err = socket_bind(&un->sock, (SA *) &local, local.sun_len); + +#else + err = socket_bind(&un->sock, (SA *) &local, + sizeof(local.sun_family) + len); +#endif + if (err != IO_DONE) socket_destroy(&un->sock); + return socket_strerror(err); +} + +static int meth_bind(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); + const char *path = luaL_checkstring(L, 2); + const char *err = unixstream_trybind(un, path); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +static int meth_getsockname(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + struct sockaddr_un peer = {0}; + socklen_t peer_len = sizeof(peer); + + if (getsockname(un->sock, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } + + lua_pushstring(L, peer.sun_path); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Turns a master unixstream object into a client object. +\*-------------------------------------------------------------------------*/ +static const char *unixstream_tryconnect(p_unix un, const char *path) +{ + struct sockaddr_un remote; + int err; + size_t len = strlen(path); + if (len >= sizeof(remote.sun_path)) return "path too long"; + memset(&remote, 0, sizeof(remote)); + strcpy(remote.sun_path, path); + remote.sun_family = AF_UNIX; + timeout_markstart(&un->tm); +#ifdef UNIX_HAS_SUN_LEN + remote.sun_len = sizeof(remote.sun_family) + sizeof(remote.sun_len) + + len + 1; + err = socket_connect(&un->sock, (SA *) &remote, remote.sun_len, &un->tm); +#else + err = socket_connect(&un->sock, (SA *) &remote, + sizeof(remote.sun_family) + len, &un->tm); +#endif + if (err != IO_DONE) socket_destroy(&un->sock); + return socket_strerror(err); +} + +static int meth_connect(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); + const char *path = luaL_checkstring(L, 2); + const char *err = unixstream_tryconnect(un, path); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + /* turn master object into a client object */ + auxiliar_setclass(L, "unixstream{client}", 1); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Closes socket used by object +\*-------------------------------------------------------------------------*/ +static int meth_close(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + socket_destroy(&un->sock); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Puts the sockt in listen mode +\*-------------------------------------------------------------------------*/ +static int meth_listen(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); + int backlog = (int) luaL_optnumber(L, 2, 32); + int err = socket_listen(&un->sock, backlog); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(err)); + return 2; + } + /* turn master object into a server object */ + auxiliar_setclass(L, "unixstream{server}", 1); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Shuts the connection down partially +\*-------------------------------------------------------------------------*/ +static int meth_shutdown(lua_State *L) +{ + /* SHUT_RD, SHUT_WR, SHUT_RDWR have the value 0, 1, 2, so we can use method index directly */ + static const char* methods[] = { "receive", "send", "both", NULL }; + p_unix stream = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); + int how = luaL_checkoption(L, 2, "both", methods); + socket_shutdown(&stream->sock, how); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Just call tm methods +\*-------------------------------------------------------------------------*/ +static int meth_settimeout(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + return timeout_meth_settimeout(L, &un->tm); +} + +/*=========================================================================*\ +* Library functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a master unixstream object +\*-------------------------------------------------------------------------*/ +static int global_create(lua_State *L) { + t_socket sock; + int err = socket_create(&sock, AF_UNIX, SOCK_STREAM, 0); + /* try to allocate a system socket */ + if (err == IO_DONE) { + /* allocate unixstream object */ + p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); + /* set its type as master object */ + auxiliar_setclass(L, "unixstream{master}", -1); + /* initialize remaining structure fields */ + socket_setnonblocking(&sock); + un->sock = sock; + io_init(&un->io, (p_send) socket_send, (p_recv) socket_recv, + (p_error) socket_ioerror, &un->sock); + timeout_init(&un->tm, -1, -1); + buffer_init(&un->buf, &un->io, &un->tm); + return 1; + } else { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(err)); + return 2; + } +} diff --git a/src/unixstream.h b/src/unixstream.h new file mode 100644 index 0000000..7916aff --- /dev/null +++ b/src/unixstream.h @@ -0,0 +1,29 @@ +#ifndef UNIXSTREAM_H +#define UNIXSTREAM_H +/*=========================================================================*\ +* UNIX STREAM object +* LuaSocket toolkit +* +* The unixstream.h module is basicly a glue that puts together modules buffer.h, +* timeout.h socket.h and inet.h to provide the LuaSocket UNIX STREAM (AF_UNIX, +* SOCK_STREAM) support. +* +* Three classes are defined: master, client and server. The master class is +* a newly created unixstream object, that has not been bound or connected. Server +* objects are unixstream objects bound to some local address. Client objects are +* unixstream objects either connected to some address or returned by the accept +* method of a server object. +\*=========================================================================*/ +#include "unix.h" + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int unixstream_open(lua_State *L); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* UNIXSTREAM_H */ diff --git a/src/url.lua b/src/url.lua index 7809535..8e0dc5c 100644 --- a/src/url.lua +++ b/src/url.lua @@ -49,7 +49,7 @@ local function make_set(t) return s end --- these are allowed withing a path segment, along with alphanum +-- these are allowed within a path segment, along with alphanum -- other characters must be escaped local segment_set = make_set { "-", "_", ".", "!", "~", "*", "'", "(", @@ -59,16 +59,16 @@ local segment_set = make_set { local function protect_segment(s) return string.gsub(s, "([^A-Za-z0-9_])", function (c) if segment_set[c] then return c - else return string.format("%%%02x", string.byte(c)) end + else return string.format("%%%02X", string.byte(c)) end end) end ----------------------------------------------------------------------------- --- Encodes a string into its escaped hexadecimal representation +-- Unencodes a escaped hexadecimal string into its binary representation -- Input --- s: binary string to be encoded +-- s: escaped hexadecimal string to be unencoded -- Returns --- escaped representation of string binary +-- unescaped binary representation of escaped hexadecimal binary ----------------------------------------------------------------------------- function _M.unescape(s) return (string.gsub(s, "%%(%x%x)", function(hex) @@ -76,6 +76,34 @@ function _M.unescape(s) end)) end +----------------------------------------------------------------------------- +-- Removes '..' and '.' components appropriately from a path. +-- Input +-- path +-- Returns +-- dot-normalized path +local function remove_dot_components(path) + local marker = string.char(1) + repeat + local was = path + path = path:gsub('//', '/'..marker..'/', 1) + until path == was + repeat + local was = path + path = path:gsub('/%./', '/', 1) + until path == was + repeat + local was = path + path = path:gsub('[^/]+/%.%./([^/]+)', '%1', 1) + until path == was + path = path:gsub('[^/]+/%.%./*$', '') + path = path:gsub('/%.%.$', '/') + path = path:gsub('/%.$', '/') + path = path:gsub('^/%.%./', '/') + path = path:gsub(marker, '') + return path +end + ----------------------------------------------------------------------------- -- Builds a path from a base path and a relative path -- Input @@ -85,23 +113,12 @@ end -- corresponding absolute path ----------------------------------------------------------------------------- local function absolute_path(base_path, relative_path) - if string.sub(relative_path, 1, 1) == "/" then return relative_path end - local path = string.gsub(base_path, "[^/]*$", "") - path = path .. relative_path - path = string.gsub(path, "([^/]*%./)", function (s) - if s ~= "./" then return s else return "" end - end) - path = string.gsub(path, "/%.$", "/") - local reduced - while reduced ~= path do - reduced = path - path = string.gsub(reduced, "([^/]*/%.%./)", function (s) - if s ~= "../../" then return "" else return s end - end) - end - path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) - if s ~= "../.." then return "" else return s end - end) + if string.sub(relative_path, 1, 1) == "/" then + return remove_dot_components(relative_path) end + base_path = base_path:gsub("[^/]*$", "") + if not base_path:find'/$' then base_path = base_path .. '/' end + local path = base_path .. relative_path + path = remove_dot_components(path) return path end @@ -131,11 +148,6 @@ function _M.parse(url, default) if not url or url == "" then return nil, "invalid url" end -- remove whitespace -- url = string.gsub(url, "%s", "") - -- get fragment - url = string.gsub(url, "#(.*)$", function(f) - parsed.fragment = f - return "" - end) -- get scheme url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", function(s) parsed.scheme = s; return "" end) @@ -144,6 +156,11 @@ function _M.parse(url, default) parsed.authority = n return "" end) + -- get fragment + url = string.gsub(url, "#(.*)$", function(f) + parsed.fragment = f + return "" + end) -- get query string url = string.gsub(url, "%?(.*)", function(q) parsed.query = q @@ -162,9 +179,9 @@ function _M.parse(url, default) function(u) parsed.userinfo = u; return "" end) authority = string.gsub(authority, ":([^:%]]*)$", function(p) parsed.port = p; return "" end) - if authority ~= "" then + if authority ~= "" then -- IPv6? - parsed.host = string.match(authority, "^%[(.+)%]$") or authority + parsed.host = string.match(authority, "^%[(.+)%]$") or authority end local userinfo = parsed.userinfo if not userinfo then return parsed end @@ -183,8 +200,9 @@ end -- a stringing with the corresponding URL ----------------------------------------------------------------------------- function _M.build(parsed) - local ppath = _M.parse_path(parsed.path or "") - local url = _M.build_path(ppath) + --local ppath = _M.parse_path(parsed.path or "") + --local url = _M.build_path(ppath) + local url = parsed.path or "" if parsed.params then url = url .. ";" .. parsed.params end if parsed.query then url = url .. "?" .. parsed.query end local authority = parsed.authority @@ -193,7 +211,7 @@ function _M.build(parsed) if string.find(authority, ":") then -- IPv6? authority = "[" .. authority .. "]" end - if parsed.port then authority = authority .. ":" .. parsed.port end + if parsed.port then authority = authority .. ":" .. base.tostring(parsed.port) end local userinfo = parsed.userinfo if parsed.user then userinfo = parsed.user @@ -219,16 +237,21 @@ end -- corresponding absolute url ----------------------------------------------------------------------------- function _M.absolute(base_url, relative_url) + local base_parsed if base.type(base_url) == "table" then base_parsed = base_url base_url = _M.build(base_parsed) else base_parsed = _M.parse(base_url) end + local result local relative_parsed = _M.parse(relative_url) - if not base_parsed then return relative_url - elseif not relative_parsed then return base_url - elseif relative_parsed.scheme then return relative_url + if not base_parsed then + result = relative_url + elseif not relative_parsed then + result = base_url + elseif relative_parsed.scheme then + result = relative_url else relative_parsed.scheme = base_parsed.scheme if not relative_parsed.authority then @@ -241,13 +264,14 @@ function _M.absolute(base_url, relative_url) relative_parsed.query = base_parsed.query end end - else + else relative_parsed.path = absolute_path(base_parsed.path or "", relative_parsed.path) end end - return _M.build(relative_parsed) + result = _M.build(relative_parsed) end + return remove_dot_components(result) end ----------------------------------------------------------------------------- diff --git a/src/usocket.c b/src/usocket.c index 8adc573..7965db6 100644 --- a/src/usocket.c +++ b/src/usocket.c @@ -6,12 +6,14 @@ * The penalty of calling select to avoid busy-wait is only paid when * the I/O call fail in the first place. \*=========================================================================*/ -#include -#include +#include "luasocket.h" #include "socket.h" #include "pierror.h" +#include +#include + /*-------------------------------------------------------------------------*\ * Wait for readable/writable/connected socket with timeout \*-------------------------------------------------------------------------*/ @@ -76,7 +78,7 @@ int socket_waitfd(p_socket ps, int sw, p_timeout tm) { * Initializes module \*-------------------------------------------------------------------------*/ int socket_open(void) { - /* instals a handler to ignore sigpipe or it will crash us */ + /* installs a handler to ignore sigpipe or it will crash us */ signal(SIGPIPE, SIG_IGN); return 1; } @@ -234,7 +236,7 @@ int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, *sent = 0; if (*ps == SOCKET_INVALID) return IO_CLOSED; for ( ;; ) { - long put = (long) sendto(*ps, data, count, 0, addr, len); + long put = (long) sendto(*ps, data, count, 0, addr, len); if (put >= 0) { *sent = put; return IO_DONE; @@ -438,14 +440,15 @@ const char *socket_gaistrerror(int err) { case EAI_FAMILY: return PIE_FAMILY; case EAI_MEMORY: return PIE_MEMORY; case EAI_NONAME: return PIE_NONAME; +#ifdef EAI_OVERFLOW case EAI_OVERFLOW: return PIE_OVERFLOW; +#endif #ifdef EAI_PROTOCOL case EAI_PROTOCOL: return PIE_PROTOCOL; #endif case EAI_SERVICE: return PIE_SERVICE; case EAI_SOCKTYPE: return PIE_SOCKTYPE; case EAI_SYSTEM: return strerror(errno); - default: return gai_strerror(err); + default: return LUA_GAI_STRERROR(err); } } - diff --git a/src/wsocket.c b/src/wsocket.c old mode 100644 new mode 100755 index 8ecb0fc..6cb1e41 --- a/src/wsocket.c +++ b/src/wsocket.c @@ -5,6 +5,8 @@ * The penalty of calling select to avoid busy-wait is only paid when * the I/O call fail in the first place. \*=========================================================================*/ +#include "luasocket.h" + #include #include "socket.h" @@ -131,11 +133,11 @@ int socket_connect(p_socket ps, SA *addr, socklen_t len, p_timeout tm) { /* we wait until something happens */ err = socket_waitfd(ps, WAITFD_C, tm); if (err == IO_CLOSED) { - int len = sizeof(err); + int elen = sizeof(err); /* give windows time to set the error (yes, disgusting) */ Sleep(10); /* find out why we failed */ - getsockopt(*ps, SOL_SOCKET, SO_ERROR, (char *)&err, &len); + getsockopt(*ps, SOL_SOCKET, SO_ERROR, (char *)&err, &elen); /* we KNOW there was an error. if 'why' is 0, we will return * "unknown error", but it's not really our fault */ return err > 0? err: IO_UNKNOWN; @@ -358,7 +360,7 @@ const char *socket_ioerror(p_socket ps, int err) { static const char *wstrerror(int err) { switch (err) { case WSAEINTR: return "Interrupted function call"; - case WSAEACCES: return PIE_ACCESS; // "Permission denied"; + case WSAEACCES: return PIE_ACCESS; /* "Permission denied"; */ case WSAEFAULT: return "Bad address"; case WSAEINVAL: return "Invalid argument"; case WSAEMFILE: return "Too many open files"; @@ -371,23 +373,23 @@ static const char *wstrerror(int err) { case WSAEPROTOTYPE: return "Protocol wrong type for socket"; case WSAENOPROTOOPT: return "Bad protocol option"; case WSAEPROTONOSUPPORT: return "Protocol not supported"; - case WSAESOCKTNOSUPPORT: return PIE_SOCKTYPE; // "Socket type not supported"; + case WSAESOCKTNOSUPPORT: return PIE_SOCKTYPE; /* "Socket type not supported"; */ case WSAEOPNOTSUPP: return "Operation not supported"; case WSAEPFNOSUPPORT: return "Protocol family not supported"; - case WSAEAFNOSUPPORT: return PIE_FAMILY; // "Address family not supported by protocol family"; - case WSAEADDRINUSE: return PIE_ADDRINUSE; // "Address already in use"; + case WSAEAFNOSUPPORT: return PIE_FAMILY; /* "Address family not supported by protocol family"; */ + case WSAEADDRINUSE: return PIE_ADDRINUSE; /* "Address already in use"; */ case WSAEADDRNOTAVAIL: return "Cannot assign requested address"; case WSAENETDOWN: return "Network is down"; case WSAENETUNREACH: return "Network is unreachable"; case WSAENETRESET: return "Network dropped connection on reset"; case WSAECONNABORTED: return "Software caused connection abort"; - case WSAECONNRESET: return PIE_CONNRESET; // "Connection reset by peer"; + case WSAECONNRESET: return PIE_CONNRESET; /* "Connection reset by peer"; */ case WSAENOBUFS: return "No buffer space available"; - case WSAEISCONN: return PIE_ISCONN; // "Socket is already connected"; + case WSAEISCONN: return PIE_ISCONN; /* "Socket is already connected"; */ case WSAENOTCONN: return "Socket is not connected"; case WSAESHUTDOWN: return "Cannot send after socket shutdown"; - case WSAETIMEDOUT: return PIE_TIMEDOUT; // "Connection timed out"; - case WSAECONNREFUSED: return PIE_CONNREFUSED; // "Connection refused"; + case WSAETIMEDOUT: return PIE_TIMEDOUT; /* "Connection timed out"; */ + case WSAECONNREFUSED: return PIE_CONNREFUSED; /* "Connection refused"; */ case WSAEHOSTDOWN: return "Host is down"; case WSAEHOSTUNREACH: return "No route to host"; case WSAEPROCLIM: return "Too many processes"; @@ -396,9 +398,9 @@ static const char *wstrerror(int err) { case WSANOTINITIALISED: return "Successful WSAStartup not yet performed"; case WSAEDISCON: return "Graceful shutdown in progress"; - case WSAHOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; // "Host not found"; + case WSAHOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; /* "Host not found"; */ case WSATRY_AGAIN: return "Nonauthoritative host not found"; - case WSANO_RECOVERY: return PIE_FAIL; // "Nonrecoverable name lookup error"; + case WSANO_RECOVERY: return PIE_FAIL; /* "Nonrecoverable name lookup error"; */ case WSANO_DATA: return "Valid name, no data record of requested type"; default: return "Unknown error"; } @@ -427,7 +429,6 @@ const char *socket_gaistrerror(int err) { #ifdef EAI_SYSTEM case EAI_SYSTEM: return strerror(errno); #endif - default: return gai_strerror(err); + default: return LUA_GAI_STRERROR(err); } } - diff --git a/test/auth/.htaccess b/test/auth/.htaccess index bb2794a..2509ae3 100644 --- a/test/auth/.htaccess +++ b/test/auth/.htaccess @@ -1,4 +1,4 @@ AuthName "test-auth" AuthType Basic - AuthUserFile /Users/diego/impa/luasocket/test/auth/.htpasswd + AuthUserFile /home/diego/impa/luasocket/test/auth/.htpasswd Require valid-user diff --git a/test/excepttest.lua b/test/excepttest.lua index ce9f197..80c9cb8 100644 --- a/test/excepttest.lua +++ b/test/excepttest.lua @@ -1,6 +1,30 @@ local socket = require("socket") -try = socket.newtry(function() - print("finalized!!!") + +local finalizer_called + +local func = socket.protect(function(err, ...) + local try = socket.newtry(function() + finalizer_called = true + end) + + if err then + return error(err, 0) + else + return try(...) + end end) -try = socket.protect(try) -print(try(nil, "it works")) + +local ret1, ret2, ret3 = func(false, 1, 2, 3) +assert(not finalizer_called, "unexpected finalizer call") +assert(ret1 == 1 and ret2 == 2 and ret3 == 3, "incorrect return values") + +ret1, ret2, ret3 = func(false, false, "error message") +assert(finalizer_called, "finalizer not called") +assert(ret1 == nil and ret2 == "error message" and ret3 == nil, "incorrect return values") + +local err = {key = "value"} +ret1, ret2 = pcall(func, err) +assert(not ret1, "error not rethrown") +assert(ret2 == err, "incorrect error rethrown") + +print("OK") diff --git a/test/find-connect-limit b/test/find-connect-limit index ad0c3f5..199e515 100755 --- a/test/find-connect-limit +++ b/test/find-connect-limit @@ -10,7 +10,7 @@ ulimit -n You'll probably need to be root to do this. ]] -require "socket" +socket = require "socket" host = arg[1] or "google.com" port = arg[2] or 80 diff --git a/test/ftptest.lua b/test/ftptest.lua index fb13326..3ea0d39 100644 --- a/test/ftptest.lua +++ b/test/ftptest.lua @@ -3,19 +3,31 @@ local ftp = require("socket.ftp") local url = require("socket.url") local ltn12 = require("ltn12") +-- use dscl to create user "luasocket" with password "password" +-- with home in /Users/diego/luasocket/test/ftp +-- with group com.apple.access_ftp +-- with shell set to /sbin/nologin +-- set /etc/ftpchroot to chroot luasocket +-- must set group com.apple.access_ftp on user _ftp (for anonymous access) +-- copy index.html to /var/empty/pub (home of user ftp) +-- start ftp server with +-- sudo -s launchctl load -w /System/Library/LaunchDaemons/ftp.plist +-- copy index.html to /Users/diego/luasocket/test/ftp +-- stop with +-- sudo -s launchctl unload -w /System/Library/LaunchDaemons/ftp.plist + -- override protection to make sure we see all errors --socket.protect = function(s) return s end dofile("testsupport.lua") -local host, port, index_file, index, back, err, ret +local host = host or "localhost" +local port, index_file, index, back, err, ret local t = socket.gettime() -host = host or "localhost" index_file = "index.html" - -- a function that returns a directory listing local function nlst(u) local t = {} @@ -55,27 +67,27 @@ assert(not err and back == index, err) print("ok") io.write("erasing before upload: ") -ret, err = dele("ftp://luasocket:pedrovian@" .. host .. "/index.up.html") -if not ret then print(err) +ret, err = dele("ftp://luasocket:password@" .. host .. "/index.up.html") +if not ret then print(err) else print("ok") end io.write("testing upload: ") -ret, err = ftp.put("ftp://luasocket:pedrovian@" .. host .. "/index.up.html;type=i", index) +ret, err = ftp.put("ftp://luasocket:password@" .. host .. "/index.up.html;type=i", index) assert(ret and not err, err) print("ok") io.write("downloading uploaded file: ") -back, err = ftp.get("ftp://luasocket:pedrovian@" .. host .. "/index.up.html;type=i") +back, err = ftp.get("ftp://luasocket:password@" .. host .. "/index.up.html;type=i") assert(ret and not err and index == back, err) print("ok") io.write("erasing after upload/download: ") -ret, err = dele("ftp://luasocket:pedrovian@" .. host .. "/index.up.html") -assert(ret and not err, err) +ret, err = dele("ftp://luasocket:password@" .. host .. "/index.up.html") +assert(ret and not err, err) print("ok") io.write("testing weird-character translation: ") -back, err = ftp.get("ftp://luasocket:pedrovian@" .. host .. "/%23%3f;type=i") +back, err = ftp.get("ftp://luasocket:password@" .. host .. "/%23%3f;type=i") assert(not err and back == index, err) print("ok") @@ -84,7 +96,7 @@ local back = {} ret, err = ftp.get{ url = "//stupid:mistake@" .. host .. "/index.html", user = "luasocket", - password = "pedrovian", + password = "password", type = "i", sink = ltn12.sink.table(back) } diff --git a/test/httptest.lua b/test/httptest.lua index 63ff921..3457b07 100644 --- a/test/httptest.lua +++ b/test/httptest.lua @@ -265,6 +265,37 @@ ignore = { } check_request(request, expect, ignore) +-- Use https://httpbin.org/#/Dynamic_data/get_base64__value_ for testing +----------------------------------------------------- +io.write("testing absolute https redirection: ") +request = { + url = "https://httpbin.org/redirect-to?url=https://httpbin.org/base64/THVhIFNvY2tldA==" +} +expect = { + code = 200, + body = "Lua Socket" +} +ignore = { + status = 1, + headers = 1 +} +check_request(request, expect, ignore) + +----------------------------------------------------- +io.write("testing relative https redirection: ") +request = { + url = "https://httpbin.org/redirect-to?url=/base64/THVhIFNvY2tldA==" +} +expect = { + code = 200, + body = "Lua Socket" +} +ignore = { + status = 1, + headers = 1 +} +check_request(request, expect, ignore) + ------------------------------------------------------------------------ --[[ io.write("testing proxy with redirection: ") diff --git a/test/ltn12test.lua b/test/ltn12test.lua index e3f85fb..0cafbc9 100644 --- a/test/ltn12test.lua +++ b/test/ltn12test.lua @@ -38,7 +38,7 @@ local function named(f, name) end -------------------------------- -local function split(size) +local function split(size) local buffer = "" local last_out = "" local last_in = "" @@ -50,12 +50,12 @@ local function split(size) return last_out end return function(chunk, done) - if done then - return not last_in and not last_out + if done then + return not last_in and not last_out end -- check if argument is consistent with state if not chunk then - if last_in and last_in ~= "" and last_out ~= "" then + if last_in and last_in ~= "" and last_out ~= "" then error("nil chunk following data chunk", 2) end if not last_out then error("extra nil chunk", 2) end @@ -67,8 +67,8 @@ local function split(size) return output(chunk) else if not last_in then error("data chunk following nil chunk", 2) end - if last_in ~= "" and last_out ~= "" then - error("data chunk following data chunk", 2) + if last_in ~= "" and last_out ~= "" then + error("data chunk following data chunk", 2) end buffer = chunk return output(chunk) @@ -85,7 +85,7 @@ local function format(chunk) end -------------------------------- -local function merge(size) +local function merge(size) local buffer = "" local last_out = "" local last_in = "" @@ -102,12 +102,12 @@ local function merge(size) return last_out end return function(chunk, done) - if done then - return not last_in and not last_out + if done then + return not last_in and not last_out end -- check if argument is consistent with state if not chunk then - if last_in and last_in ~= "" and last_out ~= "" then + if last_in and last_in ~= "" and last_out ~= "" then error("nil chunk following data chunk", 2) end if not last_out then error("extra nil chunk", 2) end @@ -119,8 +119,8 @@ local function merge(size) return output(chunk) else if not last_in then error("data chunk following nil chunk", 2) end - if last_in ~= "" and last_out ~= "" then - error("data chunk following data chunk", 2) + if last_in ~= "" and last_out ~= "" then + error("data chunk following data chunk", 2) end buffer = buffer .. chunk return output(chunk) @@ -180,6 +180,15 @@ assert(ltn12.pump.all(source, sink), "returned error") assert(table.concat(t) == s, "mismatch") print("ok") +-------------------------------- +io.write("testing source.table: ") +local inp = {'a','b','c','d','e'} +local source = ltn12.source.table(inp) +sink, t = ltn12.sink.table() +assert(ltn12.pump.all(source, sink), "returned error") +for i = 1, #inp do assert(t[i] == inp[i], "mismatch") end +print("ok") + -------------------------------- io.write("testing source.chain (with split): ") source = ltn12.source.string(s) diff --git a/test/mimetest.lua b/test/mimetest.lua index f5b3747..a3c89ac 100644 --- a/test/mimetest.lua +++ b/test/mimetest.lua @@ -15,27 +15,27 @@ local eb64test = "b64test.bin2" local db64test = "b64test.bin3" --- from Machado de Assis, "A Mão e a Rosa" +-- from Machado de Assis, "A M�o e a Rosa" local mao = [[ - Cursavam estes dois moços a academia de S. Paulo, estando - Luís Alves no quarto ano e Estêvão no terceiro. - Conheceram-se na academia, e ficaram amigos íntimos, tanto - quanto podiam sê-lo dois espíritos diferentes, ou talvez por - isso mesmo que o eram. Estêvão, dotado de extrema - sensibilidade, e não menor fraqueza de ânimo, afetuoso e - bom, não daquela bondade varonil, que é apanágio de uma alma - forte, mas dessa outra bondade mole e de cera, que vai à - mercê de todas as circunstâncias, tinha, além de tudo isso, - o infortúnio de trazer ainda sobre o nariz os óculos - cor-de-rosa de suas virginais ilusões. Luís Alves via bem - com os olhos da cara. Não era mau rapaz, mas tinha o seu - grão de egoísmo, e se não era incapaz de afeições, sabia - regê-las, moderá-las, e sobretudo guiá-las ao seu próprio + Cursavam estes dois mo�os a academia de S. Paulo, estando + Lu�s Alves no quarto ano e Est�v�o no terceiro. + Conheceram-se na academia, e ficaram amigos �ntimos, tanto + quanto podiam s�-lo dois esp�ritos diferentes, ou talvez por + isso mesmo que o eram. Est�v�o, dotado de extrema + sensibilidade, e n�o menor fraqueza de �nimo, afetuoso e + bom, n�o daquela bondade varonil, que � apan�gio de uma alma + forte, mas dessa outra bondade mole e de cera, que vai � + merc� de todas as circunst�ncias, tinha, al�m de tudo isso, + o infort�nio de trazer ainda sobre o nariz os �culos + cor-de-rosa de suas virginais ilus�es. Lu�s Alves via bem + com os olhos da cara. N�o era mau rapaz, mas tinha o seu + gr�o de ego�smo, e se n�o era incapaz de afei��es, sabia + reg�-las, moder�-las, e sobretudo gui�-las ao seu pr�prio interesse. Entre estes dois homens travara-se amizade - íntima, nascida para um na simpatia, para outro no costume. + �ntima, nascida para um na simpatia, para outro no costume. Eram eles os naturais confidentes um do outro, com a - diferença que Luís Alves dava menos do que recebia, e, ainda - assim, nem tudo o que dava exprimia grande confiança. + diferen�a que Lu�s Alves dava menos do que recebia, e, ainda + assim, nem tudo o que dava exprimia grande confian�a. ]] local function random(handle, io_err) @@ -44,8 +44,8 @@ local function random(handle, io_err) if not handle then error("source is empty!", 2) end local len = math.random(0, 1024) local chunk = handle:read(len) - if not chunk then - handle:close() + if not chunk then + handle:close() handle = nil end return chunk @@ -62,7 +62,7 @@ local what = nil local function transform(input, output, filter) local source = random(io.open(input, "rb")) local sink = ltn12.sink.file(io.open(output, "wb")) - if what then + if what then sink = ltn12.sink.chain(filter, sink) else source = ltn12.source.chain(source, filter) @@ -147,7 +147,7 @@ local function create_qptest() f:write(' ',string.char(32)) end f:write("\r\n") - + f:close() end @@ -157,7 +157,7 @@ local function cleanup_qptest() os.remove(dqptest) end --- create test file +-- create test file local function create_b64test() local f = assert(io.open(b64test, "wb")) local t = {} diff --git a/test/smtptest.lua b/test/smtptest.lua index b5380ff..9d06054 100644 --- a/test/smtptest.lua +++ b/test/smtptest.lua @@ -27,8 +27,8 @@ local total = function() end local similar = function(s1, s2) - return - string.lower(string.gsub(s1, "%s", "")) == + return + string.lower(string.gsub(s1, "%s", "")) == string.lower(string.gsub(s2, "%s", "")) end @@ -40,9 +40,9 @@ end local readfile = function(name) local f = io.open(name, "r") - if not f then + if not f then fail("unable to open file!") - return nil + return nil end local s = f:read("*a") f:close() @@ -52,7 +52,7 @@ end local empty = function() for i,v in ipairs(files) do local f = io.open(v, "w") - if not f then + if not f then fail("unable to open file!") end f:close() @@ -116,8 +116,8 @@ local wait = function(sentinel, n) while 1 do local mbox = parse(get()) if n == #mbox then break end - if socket.time() - sentinel.time > 50 then - to = 1 + if socket.time() - sentinel.time > 50 then + to = 1 break end socket.sleep(1) @@ -132,7 +132,7 @@ local stuffed_body = [[ This message body needs to be stuffed because it has a dot . -by itself on a line. +by itself on a line. Otherwise the mailer would think that the dot . @@ -219,7 +219,7 @@ else print("ok") end io.write("testing invalid from: ") local ret, err = socket.smtp.mail{ - from = ' " " (( _ * ', + from = ' " " (( _ * ', rcpt = rcpt, } if ret or not err then fail("wrong error message") @@ -227,7 +227,7 @@ else print(err) end io.write("testing no rcpt: ") local ret, err = socket.smtp.mail{ - from = from, + from = from, } if ret or not err then fail("wrong error message") else print(err) end diff --git a/test/tcp-getoptions b/test/tcp-getoptions index f9b3d1b..fbcc884 100755 --- a/test/tcp-getoptions +++ b/test/tcp-getoptions @@ -1,19 +1,35 @@ #!/usr/bin/env lua -require"socket" +local socket = require"socket" port = 8765 +function pcalltest(msg, o, opt) + local a = { pcall(o.getoption, o, opt) } + if a[1] then + print(msg, opt, unpack(a)) + else + print(msg, opt, 'fail: ' .. a[2]) + end +end + function options(o) print("options for", o) - for _, opt in ipairs{"keepalive", "reuseaddr", "tcp-nodelay"} do - print("getoption", opt, o:getoption(opt)) + for _, opt in ipairs{ + "keepalive", "reuseaddr", + "tcp-nodelay", "tcp-keepidle", "tcp-keepcnt", "tcp-keepintvl"} do + pcalltest("getoption", o, opt) end - print("getoption", "linger", - "on", o:getoption("linger").on, - "timeout", o:getoption("linger").timeout) + r = o:getoption'linger' + if r then + print("getoption", "linger", + "on", r.on, + "timeout", r.timeout) + else + print("getoption", "linger", "no result") + end end local m = socket.tcp() diff --git a/test/test_socket_error.lua b/test/test_socket_error.lua index bda6408..1b4b601 100644 --- a/test/test_socket_error.lua +++ b/test/test_socket_error.lua @@ -19,7 +19,7 @@ for i = 1, 10 do assert(ss == sock) else assert('timeout' == err, 'unexpected error :' .. tostring(err)) - end + end err = sock:getoption("error") -- i get 'connection refused' on WinXP if err then print("Passed! Error is '" .. err .. "'.") diff --git a/test/testclnt.lua b/test/testclnt.lua index ee1201f..170e187 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -669,7 +669,6 @@ local udp_methods = { "settimeout" } - ------------------------------------------------------------------------ test_methods(socket.udp(), udp_methods) do local sock = socket.tcp6() diff --git a/test/testmesg.lua b/test/testmesg.lua index 135a008..8c086d5 100644 --- a/test/testmesg.lua +++ b/test/testmesg.lua @@ -34,11 +34,11 @@ r, e = smtp.send{ print(r, e) --- creates a source to send a message with two parts. The first part is +-- creates a source to send a message with two parts. The first part is -- plain text, the second part is a PNG image, encoded as base64. source = smtp.message{ headers = { - -- Remember that headers are *ignored* by smtp.send. + -- Remember that headers are *ignored* by smtp.send. from = "Sicrano ", to = "Fulano ", subject = "Here is a message with attachments" @@ -49,18 +49,18 @@ source = smtp.message{ "Preamble might show up even in a MIME enabled client.", -- first part: No headers means plain text, us-ascii. -- The mime.eol low-level filter normalizes end-of-line markers. - [1] = { + [1] = { body = mime.eol(0, [[ - Lines in a message body should always end with CRLF. + Lines in a message body should always end with CRLF. The smtp module will *NOT* perform translation. It will perform necessary stuffing, though. ]]) }, - -- second part: Headers describe content the to be an image, + -- second part: Headers describe content the to be an image, -- sent under the base64 transfer content encoding. - -- Notice that nothing happens until the message is sent. Small + -- Notice that nothing happens until the message is sent. Small -- chunks are loaded into memory and translation happens on the fly. - [2] = { + [2] = { headers = { ["ConTenT-tYpE"] = 'image/png; name="luasocket.png"', ["content-disposition"] = 'attachment; filename="luasocket.png"', diff --git a/test/testsupport.lua b/test/testsupport.lua index b986088..4360b6b 100644 --- a/test/testsupport.lua +++ b/test/testsupport.lua @@ -7,7 +7,7 @@ function readfile(name) end function similar(s1, s2) - return string.lower(string.gsub(s1 or "", "%s", "")) == + return string.lower(string.gsub(s1 or "", "%s", "")) == string.lower(string.gsub(s2 or "", "%s", "")) end diff --git a/test/udp-zero-length-send b/test/udp-zero-length-send index a594944..9038c99 100755 --- a/test/udp-zero-length-send +++ b/test/udp-zero-length-send @@ -1,4 +1,4 @@ -#!/usr/bin/lua +#!/usr/bin/env lua --[[ Show that luasocket returns an error message on zero-length UDP sends, @@ -12,7 +12,7 @@ listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes ]] -require"socket" +socket = require"socket" s = assert(socket.udp()) r = assert(socket.udp()) diff --git a/test/udp-zero-length-send-recv b/test/udp-zero-length-send-recv index 541efd4..064ca52 100755 --- a/test/udp-zero-length-send-recv +++ b/test/udp-zero-length-send-recv @@ -1,4 +1,4 @@ -#!/usr/bin/lua +#!/usr/bin/env lua --[[ Show that luasocket returns an error message on zero-length UDP sends, @@ -12,7 +12,7 @@ listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes ]] -require"socket" +socket = require"socket" s = assert(socket.udp()) r = assert(socket.udp()) diff --git a/test/unixdgramclnt.lua b/test/unixdgramclnt.lua new file mode 100644 index 0000000..9bd60f7 --- /dev/null +++ b/test/unixdgramclnt.lua @@ -0,0 +1,9 @@ +socket = require"socket" +socket.unix = require"socket.unix" +c = assert(socket.unix.dgram()) +print(c:bind("/tmp/bar")) +while 1 do + local l = io.read("*l") + assert(c:sendto(l, "/tmp/foo")) + print(assert(c:receivefrom())) +end diff --git a/test/unixdgramsrvr.lua b/test/unixdgramsrvr.lua new file mode 100644 index 0000000..4c11f55 --- /dev/null +++ b/test/unixdgramsrvr.lua @@ -0,0 +1,9 @@ + socket = require"socket" + socket.unix = require"socket.unix" + u = assert(socket.unix.dgram()) + assert(u:bind("/tmp/foo")) + while 1 do + x, r = assert(u:receivefrom()) + print(x, r) + assert(u:sendto(">" .. x, r)) + end diff --git a/test/unixclnt.lua b/test/unixstreamclnt.lua similarity index 82% rename from test/unixclnt.lua rename to test/unixstreamclnt.lua index 5171535..4f2e1e3 100644 --- a/test/unixclnt.lua +++ b/test/unixstreamclnt.lua @@ -1,6 +1,6 @@ socket = require"socket" socket.unix = require"socket.unix" -c = assert(socket.unix()) +c = assert(socket.unix.stream()) assert(c:connect("/tmp/foo")) while 1 do local l = io.read() diff --git a/test/unixsrvr.lua b/test/unixstreamsrvr.lua similarity index 84% rename from test/unixsrvr.lua rename to test/unixstreamsrvr.lua index 81b9c99..0a5c644 100644 --- a/test/unixsrvr.lua +++ b/test/unixstreamsrvr.lua @@ -1,6 +1,6 @@ socket = require"socket" socket.unix = require"socket.unix" - u = assert(socket.unix()) + u = assert(socket.unix.stream()) assert(u:bind("/tmp/foo")) assert(u:listen()) c = assert(u:accept()) diff --git a/test/urltest.lua b/test/urltest.lua index 32cb348..9a3c470 100644 --- a/test/urltest.lua +++ b/test/urltest.lua @@ -60,8 +60,8 @@ end local check_absolute_url = function(base, relative, absolute) local res = socket.url.absolute(base, relative) - if res ~= absolute then - io.write("absolute: In test for '", relative, "' expected '", + if res ~= absolute then + io.write("absolute: In test for base='", base, "', rel='", relative, "' expected '", absolute, "' but got '", res, "'\n") os.exit() end @@ -73,7 +73,7 @@ local check_parse_url = function(gaba) local parsed = socket.url.parse(url) for i, v in pairs(gaba) do if v ~= parsed[i] then - io.write("parse: In test for '", url, "' expected ", i, " = '", + io.write("parse: In test for '", url, "' expected ", i, " = '", v, "' but got '", tostring(parsed[i]), "'\n") for i,v in pairs(parsed) do print(i,v) end os.exit() @@ -81,7 +81,7 @@ local check_parse_url = function(gaba) end for i, v in pairs(parsed) do if v ~= gaba[i] then - io.write("parse: In test for '", url, "' expected ", i, " = '", + io.write("parse: In test for '", url, "' expected ", i, " = '", tostring(gaba[i]), "' but got '", v, "'\n") for i,v in pairs(parsed) do print(i,v) end os.exit() @@ -90,10 +90,79 @@ local check_parse_url = function(gaba) end print("testing URL parsing") +check_parse_url{ + url = "scheme://user:pass$%?#wd@host:port/path;params?query#fragment", + scheme = "scheme", + authority = "user:pass$%?#wd@host:port", + host = "host", + port = "port", + userinfo = "user:pass$%?#wd", + password = "pass$%?#wd", + user = "user", + path = "/path", + params = "params", + query = "query", + fragment = "fragment" +} +check_parse_url{ + url = "scheme://user:pass?#wd@host:port/path;params?query#fragment", + scheme = "scheme", + authority = "user:pass?#wd@host:port", + host = "host", + port = "port", + userinfo = "user:pass?#wd", + password = "pass?#wd", + user = "user", + path = "/path", + params = "params", + query = "query", + fragment = "fragment" +} +check_parse_url{ + url = "scheme://user:pass-wd@host:port/path;params?query#fragment", + scheme = "scheme", + authority = "user:pass-wd@host:port", + host = "host", + port = "port", + userinfo = "user:pass-wd", + password = "pass-wd", + user = "user", + path = "/path", + params = "params", + query = "query", + fragment = "fragment" +} +check_parse_url{ + url = "scheme://user:pass#wd@host:port/path;params?query#fragment", + scheme = "scheme", + authority = "user:pass#wd@host:port", + host = "host", + port = "port", + userinfo = "user:pass#wd", + password = "pass#wd", + user = "user", + path = "/path", + params = "params", + query = "query", + fragment = "fragment" +} +check_parse_url{ + url = "scheme://user:pass#wd@host:port/path;params?query", + scheme = "scheme", + authority = "user:pass#wd@host:port", + host = "host", + port = "port", + userinfo = "user:pass#wd", + password = "pass#wd", + user = "user", + path = "/path", + params = "params", + query = "query", +} check_parse_url{ url = "scheme://userinfo@host:port/path;params?query#fragment", - scheme = "scheme", - authority = "userinfo@host:port", + scheme = "scheme", + authority = "userinfo@host:port", host = "host", port = "port", userinfo = "userinfo", @@ -106,8 +175,8 @@ check_parse_url{ check_parse_url{ url = "scheme://user:password@host:port/path;params?query#fragment", - scheme = "scheme", - authority = "user:password@host:port", + scheme = "scheme", + authority = "user:password@host:port", host = "host", port = "port", userinfo = "user:password", @@ -121,8 +190,8 @@ check_parse_url{ check_parse_url{ url = "scheme://userinfo@host:port/path;params?query#", - scheme = "scheme", - authority = "userinfo@host:port", + scheme = "scheme", + authority = "userinfo@host:port", host = "host", port = "port", userinfo = "userinfo", @@ -135,8 +204,8 @@ check_parse_url{ check_parse_url{ url = "scheme://userinfo@host:port/path;params?#fragment", - scheme = "scheme", - authority = "userinfo@host:port", + scheme = "scheme", + authority = "userinfo@host:port", host = "host", port = "port", userinfo = "userinfo", @@ -149,8 +218,8 @@ check_parse_url{ check_parse_url{ url = "scheme://userinfo@host:port/path;params#fragment", - scheme = "scheme", - authority = "userinfo@host:port", + scheme = "scheme", + authority = "userinfo@host:port", host = "host", port = "port", userinfo = "userinfo", @@ -162,8 +231,8 @@ check_parse_url{ check_parse_url{ url = "scheme://userinfo@host:port/path;?query#fragment", - scheme = "scheme", - authority = "userinfo@host:port", + scheme = "scheme", + authority = "userinfo@host:port", host = "host", port = "port", userinfo = "userinfo", @@ -176,8 +245,8 @@ check_parse_url{ check_parse_url{ url = "scheme://userinfo@host:port/path?query#fragment", - scheme = "scheme", - authority = "userinfo@host:port", + scheme = "scheme", + authority = "userinfo@host:port", host = "host", port = "port", userinfo = "userinfo", @@ -189,8 +258,8 @@ check_parse_url{ check_parse_url{ url = "scheme://userinfo@host:port/;params?query#fragment", - scheme = "scheme", - authority = "userinfo@host:port", + scheme = "scheme", + authority = "userinfo@host:port", host = "host", port = "port", userinfo = "userinfo", @@ -203,8 +272,8 @@ check_parse_url{ check_parse_url{ url = "scheme://userinfo@host:port", - scheme = "scheme", - authority = "userinfo@host:port", + scheme = "scheme", + authority = "userinfo@host:port", host = "host", port = "port", userinfo = "userinfo", @@ -213,7 +282,7 @@ check_parse_url{ check_parse_url{ url = "//userinfo@host:port/path;params?query#fragment", - authority = "userinfo@host:port", + authority = "userinfo@host:port", host = "host", port = "port", userinfo = "userinfo", @@ -226,7 +295,7 @@ check_parse_url{ check_parse_url{ url = "//userinfo@host:port/path", - authority = "userinfo@host:port", + authority = "userinfo@host:port", host = "host", port = "port", userinfo = "userinfo", @@ -236,7 +305,7 @@ check_parse_url{ check_parse_url{ url = "//userinfo@host/path", - authority = "userinfo@host", + authority = "userinfo@host", host = "host", userinfo = "userinfo", user = "userinfo", @@ -245,7 +314,7 @@ check_parse_url{ check_parse_url{ url = "//user:password@host/path", - authority = "user:password@host", + authority = "user:password@host", host = "host", userinfo = "user:password", password = "password", @@ -255,7 +324,7 @@ check_parse_url{ check_parse_url{ url = "//user:@host/path", - authority = "user:@host", + authority = "user:@host", host = "host", userinfo = "user:", password = "", @@ -265,7 +334,7 @@ check_parse_url{ check_parse_url{ url = "//user@host:port/path", - authority = "user@host:port", + authority = "user@host:port", host = "host", userinfo = "user", user = "user", @@ -275,7 +344,7 @@ check_parse_url{ check_parse_url{ url = "//host:port/path", - authority = "host:port", + authority = "host:port", port = "port", host = "host", path = "/path", @@ -283,14 +352,14 @@ check_parse_url{ check_parse_url{ url = "//host/path", - authority = "host", + authority = "host", host = "host", path = "/path", } check_parse_url{ url = "//host", - authority = "host", + authority = "host", host = "host", } @@ -364,7 +433,7 @@ check_parse_url{ check_parse_url{ url = "//userinfo@[::FFFF:129.144.52.38]:port/path;params?query#fragment", - authority = "userinfo@[::FFFF:129.144.52.38]:port", + authority = "userinfo@[::FFFF:129.144.52.38]:port", host = "::FFFF:129.144.52.38", port = "port", userinfo = "userinfo", @@ -378,7 +447,7 @@ check_parse_url{ check_parse_url{ url = "scheme://user:password@[::192.9.5.5]:port/path;params?query#fragment", scheme = "scheme", - authority = "user:password@[::192.9.5.5]:port", + authority = "user:password@[::192.9.5.5]:port", host = "::192.9.5.5", port = "port", userinfo = "user:password", @@ -393,7 +462,7 @@ check_parse_url{ print("testing URL building") check_build_url { url = "scheme://user:password@host:port/path;params?query#fragment", - scheme = "scheme", + scheme = "scheme", host = "host", port = "port", user = "user", @@ -430,7 +499,7 @@ check_build_url{ check_build_url { url = "scheme://user:password@host/path;params?query#fragment", - scheme = "scheme", + scheme = "scheme", host = "host", user = "user", password = "password", @@ -442,7 +511,7 @@ check_build_url { check_build_url { url = "scheme://user@host/path;params?query#fragment", - scheme = "scheme", + scheme = "scheme", host = "host", user = "user", path = "/path", @@ -453,7 +522,7 @@ check_build_url { check_build_url { url = "scheme://host/path;params?query#fragment", - scheme = "scheme", + scheme = "scheme", host = "host", path = "/path", params = "params", @@ -463,7 +532,7 @@ check_build_url { check_build_url { url = "scheme://host/path;params#fragment", - scheme = "scheme", + scheme = "scheme", host = "host", path = "/path", params = "params", @@ -472,7 +541,7 @@ check_build_url { check_build_url { url = "scheme://host/path#fragment", - scheme = "scheme", + scheme = "scheme", host = "host", path = "/path", fragment = "fragment" @@ -480,7 +549,7 @@ check_build_url { check_build_url { url = "scheme://host/path", - scheme = "scheme", + scheme = "scheme", host = "host", path = "/path", } @@ -498,7 +567,7 @@ check_build_url { check_build_url { url = "scheme://user:password@host:port/path;params?query#fragment", - scheme = "scheme", + scheme = "scheme", host = "host", port = "port", user = "user", @@ -512,7 +581,7 @@ check_build_url { check_build_url { url = "scheme://user:password@host:port/path;params?query#fragment", - scheme = "scheme", + scheme = "scheme", host = "host", port = "port", user = "user", @@ -527,7 +596,7 @@ check_build_url { check_build_url { url = "scheme://user:password@host:port/path;params?query#fragment", - scheme = "scheme", + scheme = "scheme", host = "host", port = "port", userinfo = "user:password", @@ -540,7 +609,7 @@ check_build_url { check_build_url { url = "scheme://user:password@host:port/path;params?query#fragment", - scheme = "scheme", + scheme = "scheme", authority = "user:password@host:port", path = "/path", params = "params", @@ -558,25 +627,37 @@ check_absolute_url("http://a/b/c/d;p?q#f", "/g", "http://a/g") check_absolute_url("http://a/b/c/d;p?q#f", "//g", "http://g") check_absolute_url("http://a/b/c/d;p?q#f", "?y", "http://a/b/c/d;p?y") check_absolute_url("http://a/b/c/d;p?q#f", "g?y", "http://a/b/c/g?y") -check_absolute_url("http://a/b/c/d;p?q#f", "g?y/./x", "http://a/b/c/g?y/./x") +check_absolute_url("http://a/b/c/d;p?q#f", "g?y/./x", "http://a/b/c/g?y/x") check_absolute_url("http://a/b/c/d;p?q#f", "#s", "http://a/b/c/d;p?q#s") check_absolute_url("http://a/b/c/d;p?q#f", "g#s", "http://a/b/c/g#s") -check_absolute_url("http://a/b/c/d;p?q#f", "g#s/./x", "http://a/b/c/g#s/./x") +check_absolute_url("http://a/b/c/d;p?q#f", "g#s/./x", "http://a/b/c/g#s/x") check_absolute_url("http://a/b/c/d;p?q#f", "g?y#s", "http://a/b/c/g?y#s") check_absolute_url("http://a/b/c/d;p?q#f", ";x", "http://a/b/c/d;x") check_absolute_url("http://a/b/c/d;p?q#f", "g;x", "http://a/b/c/g;x") check_absolute_url("http://a/b/c/d;p?q#f", "g;x?y#s", "http://a/b/c/g;x?y#s") check_absolute_url("http://a/b/c/d;p?q#f", ".", "http://a/b/c/") check_absolute_url("http://a/b/c/d;p?q#f", "./", "http://a/b/c/") +check_absolute_url("http://a/b/c/d;p?q#f", "./g", "http://a/b/c/g") +check_absolute_url("http://a/b/c/d;p?q#f", "./g/", "http://a/b/c/g/") +check_absolute_url("http://a/b/c/d;p?q#f", "././g", "http://a/b/c/g") +check_absolute_url("http://a/b/c/d;p?q#f", "././g/", "http://a/b/c/g/") +check_absolute_url("http://a/b/c/d;p?q#f", "g/.", "http://a/b/c/g/") +check_absolute_url("http://a/b/c/d;p?q#f", "g/./", "http://a/b/c/g/") +check_absolute_url("http://a/b/c/d;p?q#f", "g/./.", "http://a/b/c/g/") +check_absolute_url("http://a/b/c/d;p?q#f", "g/././", "http://a/b/c/g/") +check_absolute_url("http://a/b/c/d;p?q#f", "./.", "http://a/b/c/") +check_absolute_url("http://a/b/c/d;p?q#f", "././.", "http://a/b/c/") +check_absolute_url("http://a/b/c/d;p?q#f", "././g/./.", "http://a/b/c/g/") check_absolute_url("http://a/b/c/d;p?q#f", "..", "http://a/b/") check_absolute_url("http://a/b/c/d;p?q#f", "../", "http://a/b/") check_absolute_url("http://a/b/c/d;p?q#f", "../g", "http://a/b/g") check_absolute_url("http://a/b/c/d;p?q#f", "../..", "http://a/") check_absolute_url("http://a/b/c/d;p?q#f", "../../", "http://a/") check_absolute_url("http://a/b/c/d;p?q#f", "../../g", "http://a/g") +check_absolute_url("http://a/b/c/d;p?q#f", "../../../g", "http://a/g") check_absolute_url("http://a/b/c/d;p?q#f", "", "http://a/b/c/d;p?q#f") -check_absolute_url("http://a/b/c/d;p?q#f", "/./g", "http://a/./g") -check_absolute_url("http://a/b/c/d;p?q#f", "/../g", "http://a/../g") +check_absolute_url("http://a/b/c/d;p?q#f", "/./g", "http://a/g") +check_absolute_url("http://a/b/c/d;p?q#f", "/../g", "http://a/g") check_absolute_url("http://a/b/c/d;p?q#f", "g.", "http://a/b/c/g.") check_absolute_url("http://a/b/c/d;p?q#f", ".g", "http://a/b/c/.g") check_absolute_url("http://a/b/c/d;p?q#f", "g..", "http://a/b/c/g..") @@ -586,31 +667,53 @@ check_absolute_url("http://a/b/c/d;p?q#f", "./g/.", "http://a/b/c/g/") check_absolute_url("http://a/b/c/d;p?q#f", "g/./h", "http://a/b/c/g/h") check_absolute_url("http://a/b/c/d;p?q#f", "g/../h", "http://a/b/c/h") +check_absolute_url("http://a/b/c/d:p?q#f/", "../g/", "http://a/b/g/") +check_absolute_url("http://a/b/c/d:p?q#f/", "../g", "http://a/b/g") +check_absolute_url("http://a/b/c/d:p?q#f/", "../.g/", "http://a/b/.g/") +check_absolute_url("http://a/b/c/d:p?q#f/", "../.g", "http://a/b/.g") +check_absolute_url("http://a/b/c/d:p?q#f/", "../.g.h/", "http://a/b/.g.h/") +check_absolute_url("http://a/b/c/d:p?q#f/", "../.g.h", "http://a/b/.g.h") + +check_absolute_url("http://a/b/c/d:p?q#f/", "g.h/", "http://a/b/c/g.h/") +check_absolute_url("http://a/b/c/d:p?q#f/", "../g.h/", "http://a/b/g.h/") +check_absolute_url("http://a/", "../g.h/", "http://a/g.h/") + -- extra tests check_absolute_url("//a/b/c/d;p?q#f", "d/e/f", "//a/b/c/d/e/f") check_absolute_url("/a/b/c/d;p?q#f", "d/e/f", "/a/b/c/d/e/f") check_absolute_url("a/b/c/d", "d/e/f", "a/b/c/d/e/f") check_absolute_url("a/b/c/d/../", "d/e/f", "a/b/c/d/e/f") -check_absolute_url("http://velox.telemar.com.br", "/dashboard/index.html", +check_absolute_url("http://velox.telemar.com.br", "/dashboard/index.html", "http://velox.telemar.com.br/dashboard/index.html") +check_absolute_url("http://example.com/", "../.badhost.com/", "http://example.com/.badhost.com/") +check_absolute_url("http://example.com/", "...badhost.com/", "http://example.com/...badhost.com/") +check_absolute_url("http://example.com/a/b/c/d/", "../q", "http://example.com/a/b/c/q") +check_absolute_url("http://example.com/a/b/c/d/", "../../q", "http://example.com/a/b/q") +check_absolute_url("http://example.com/a/b/c/d/", "../../../q", "http://example.com/a/q") +check_absolute_url("http://example.com", ".badhost.com", "http://example.com/.badhost.com") +check_absolute_url("http://example.com/a/b/c/d/", "..//../../../q", "http://example.com/a/q") +check_absolute_url("http://example.com/a/b/c/d/", "..//a/../../../../q", "http://example.com/a/q") +check_absolute_url("http://example.com/a/b/c/d/", "..//a/..//../../../q", "http://example.com/a/b/q") +check_absolute_url("http://example.com/a/b/c/d/", "..//a/..///../../../../q", "http://example.com/a/b/q") +check_absolute_url("http://example.com/a/b/c/d/", "../x/a/../y/z/../../../../q", "http://example.com/a/b/q") print("testing path parsing and composition") check_parse_path("/eu/tu/ele", { "eu", "tu", "ele"; is_absolute = 1 }) check_parse_path("/eu/", { "eu"; is_absolute = 1, is_directory = 1 }) -check_parse_path("eu/tu/ele/nos/vos/eles/", +check_parse_path("eu/tu/ele/nos/vos/eles/", { "eu", "tu", "ele", "nos", "vos", "eles"; is_directory = 1}) check_parse_path("/", { is_absolute = 1, is_directory = 1}) check_parse_path("", { }) -check_parse_path("eu%01/%02tu/e%03l%04e/nos/vos%05/e%12les/", +check_parse_path("eu%01/%02tu/e%03l%04e/nos/vos%05/e%12les/", { "eu\1", "\2tu", "e\3l\4e", "nos", "vos\5", "e\18les"; is_directory = 1}) check_parse_path("eu/tu", { "eu", "tu" }) print("testing path protection") check_protect({ "eu", "-_.!~*'():@&=+$,", "tu" }, "eu/-_.!~*'():@&=+$,/tu") check_protect({ "eu ", "~diego" }, "eu%20/~diego") -check_protect({ "/eu>", " ", " ", " / tm then + if elapsed > tm then if err ~= "timeout" then fail("should have timed out") else pass("proper timeout") end elseif elapsed < tm then - if err then fail(err) + if err then fail(err) else pass("ok") end - else - if alldone then - if err then fail("unexpected error '%s'", err) + else + if alldone then + if err then fail("unexpected error '%s'", err) else pass("ok") end else - if err ~= "timeout" then fail(err) + if err ~= "timeout" then fail(err) else pass("proper timeoutk") end end end - else - if err then fail(err) - else pass("ok") end + else + if err then fail(err) + else pass("ok") end end end end @@ -104,7 +104,7 @@ function reconnect() print("done " .. i) ]] data, err = uconnect(host, port) - if not data then fail(err) + if not data then fail(err) else pass("connected!") end end @@ -116,8 +116,8 @@ else pass("connected!") end ------------------------------------------------------------------------ function test_methods(sock, methods) for _, v in pairs(methods) do - if type(sock[v]) ~= "function" then - fail(sock.class .. " method '" .. v .. "' not registered") + if type(sock[v]) ~= "function" then + fail(sock.class .. " method '" .. v .. "' not registered") end end pass(sock.class .. " methods are ok") @@ -132,7 +132,7 @@ function test_mixed(len) local p3 = "raw " .. string.rep("z", inter) .. "bytes" local p4 = "end" .. string.rep("w", inter) .. "bytes" local bp1, bp2, bp3, bp4 -remote (string.format("str = data:receive(%d)", +remote (string.format("str = data:receive(%d)", string.len(p1)+string.len(p2)+string.len(p3)+string.len(p4))) sent, err = data:send(p1..p2..p3..p4) if err then fail(err) end @@ -172,7 +172,7 @@ function test_rawline(len) reconnect() local str, str10, back, err str = string.rep(string.char(47), math.mod(len, 10)) - str10 = string.rep(string.char(120,21,77,4,5,0,7,36,44,100), + str10 = string.rep(string.char(120,21,77,4,5,0,7,36,44,100), math.floor(len/10)) str = str .. str10 remote "str = data:receive()" @@ -221,7 +221,7 @@ function test_totaltimeoutreceive(len, tm, sl) data:settimeout(tm, "total") local t = socket.gettime() str, err, partial, elapsed = data:receive(2*len) - check_timeout(tm, sl, elapsed, err, "receive", "total", + check_timeout(tm, sl, elapsed, err, "receive", "total", string.len(str or partial) == 2*len) end @@ -241,7 +241,7 @@ function test_totaltimeoutsend(len, tm, sl) data:settimeout(tm, "total") str = string.rep("a", 2*len) total, err, partial, elapsed = data:send(str) - check_timeout(tm, sl, elapsed, err, "send", "total", + check_timeout(tm, sl, elapsed, err, "send", "total", total == 2*len) end @@ -261,7 +261,7 @@ function test_blockingtimeoutreceive(len, tm, sl) ]], 2*tm, len, sl, sl)) data:settimeout(tm) str, err, partial, elapsed = data:receive(2*len) - check_timeout(tm, sl, elapsed, err, "receive", "blocking", + check_timeout(tm, sl, elapsed, err, "receive", "blocking", string.len(str or partial) == 2*len) end @@ -294,10 +294,10 @@ function empty_connect() data = server:accept() ]] data, err = socket.connect("", port) - if not data then + if not data then pass("ok") data = socket.connect(host, port) - else + else pass("gethostbyname returns localhost on empty string...") end end @@ -331,7 +331,7 @@ function test_closed() data:close() data = nil ]], str)) - -- try to get a line + -- try to get a line back, err, partial = data:receive() if not err then fail("should have gotten 'closed'.") elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") @@ -344,25 +344,25 @@ function test_closed() data = nil ]] total, err, partial = data:send(string.rep("ugauga", 100000)) - if not err then + if not err then pass("failed: output buffer is at least %d bytes long!", total) - elseif err ~= "closed" then + elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") - else - pass("graceful 'closed' received after %d bytes were sent", partial) + else + pass("graceful 'closed' received after %d bytes were sent", partial) end end ------------------------------------------------------------------------ function test_selectbugs() local r, s, e = socket.select(nil, nil, 0.1) - assert(type(r) == "table" and type(s) == "table" and + assert(type(r) == "table" and type(s) == "table" and (e == "timeout" or e == "error")) pass("both nil: ok") local udp = socket.udp() udp:close() r, s, e = socket.select({ udp }, { udp }, 0.1) - assert(type(r) == "table" and type(s) == "table" and + assert(type(r) == "table" and type(s) == "table" and (e == "timeout" or e == "error")) pass("closed sockets: ok") e = pcall(socket.select, "wrong", 1, 0.1) @@ -380,7 +380,7 @@ function accept_timeout() local t = socket.gettime() s:settimeout(1) local c, e = s:accept() - assert(not c, "should not accept") + assert(not c, "should not accept") assert(e == "timeout", string.format("wrong error message (%s)", e)) t = socket.gettime() - t assert(t < 2, string.format("took to long to give up (%gs)", t)) @@ -398,9 +398,9 @@ function connect_timeout() local t = socket.gettime() local r, e = c:connect("127.0.0.2", 80) assert(not r, "should not connect") - assert(socket.gettime() - t < 2, "took too long to give up.") + assert(socket.gettime() - t < 2, "took too long to give up.") c:close() - print("ok") + print("ok") end ------------------------------------------------------------------------ @@ -463,9 +463,9 @@ function getstats_test() data:receive(c) t = t + c local r, s, a = data:getstats() - assert(r == t, "received count failed" .. tostring(r) + assert(r == t, "received count failed" .. tostring(r) .. "/" .. tostring(t)) - assert(s == t, "sent count failed" .. tostring(s) + assert(s == t, "sent count failed" .. tostring(s) .. "/" .. tostring(t)) end print("ok") @@ -473,7 +473,7 @@ end ------------------------------------------------------------------------ -function test_nonblocking(size) +function test_nonblocking(size) reconnect() print("Testing " .. 2*size .. " bytes") remote(string.format([[ diff --git a/test/utestsrvr.lua b/test/utestsrvr.lua index a96b570..b6e4246 100644 --- a/test/utestsrvr.lua +++ b/test/utestsrvr.lua @@ -9,7 +9,7 @@ ack = "\n"; while 1 do print("server: waiting for client connection..."); control = assert(server:accept()); - while 1 do + while 1 do command = assert(control:receive()); assert(control:send(ack)); ((loadstring or load)(command))(); diff --git a/vc32.bat b/vc32.bat new file mode 100755 index 0000000..7ff8c0e --- /dev/null +++ b/vc32.bat @@ -0,0 +1,3 @@ +call "c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\vcvars32.bat" +cls +"c:\Program Files\Git\git-bash.exe" --cd-to-home diff --git a/vc64.bat b/vc64.bat new file mode 100755 index 0000000..ed5cb3a --- /dev/null +++ b/vc64.bat @@ -0,0 +1,3 @@ +call "c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.bat" +cls +"c:\Program Files\Git\git-bash.exe" --cd-to-home diff --git a/win32.cmd b/win32.cmd old mode 100644 new mode 100755 index 48522f0..5eda3b1 --- a/win32.cmd +++ b/win32.cmd @@ -1,12 +1 @@ -make PLAT=win32 LUAV=5.2 LUAINC_win32='c:\cygwin\home\diego\build\include' LUALIB_win32='c:\cygwin\home\diego\build\bin\release' - -#!/bin/sh -for p in Release Debug x64/Release x64/Debug; do - for el in mime socket; do - for e in dll lib; do - cp $p/$el/core.$e ../bin/$p/$el/ - done; - done; - cp src/ltn12.lua src/socket.lua src/mime.lua ../bin/$p/ - cp src/http.lua src/url.lua src/tp.lua src/ftp.lua src/headers.lua src/smtp.lua ../bin/$p/socket/ -done; +LUAV=5.3 PLAT=win32 LUAPREFIX_win32=/z/data/build/vc14 make diff --git a/win64.cmd b/win64.cmd new file mode 100755 index 0000000..b1f9ac0 --- /dev/null +++ b/win64.cmd @@ -0,0 +1 @@ +LUAV=5.3 PLAT=win64 LUAPREFIX_win64=/z/data/build/vc14 make