--- /dev/null
+.depend
+*.[ao]
+*.bc
+*.nmf
+*.[np]exe
--- /dev/null
+[submodule "chrome-bootstrap"]
+ path = chrome-bootstrap
+ url = https://github.com/roykolak/chrome-bootstrap.git
--- /dev/null
+CLOSURE = closure-compiler --language_in ECMASCRIPT5
+YUICOMPRESSOR = yuicompressor
+
+all:
+
+%.js.min: %.js
+ $(CLOSURE) $< > $@
+
+JS_FILES = $(shell grep '[.]js$$' manifest.files)
+js-min: $(JS_FILES:=.min)
+
+%.css.min: %.css
+ $(YUICOMPRESSOR) $< > $@
+
+CSS_FILES = $(shell grep '[.]css$$' manifest.files)
+css-min: $(CSS_FILES:=.min)
+
+check: css-min js-min
+
+dist:
+ ./dist/makedist.sh
+
+.PHONY: all clean check css-min dist js-min
--- /dev/null
+Subproject commit 419698ec6fe922487d2fe3f11c92fd1c8ffbd8a6
--- /dev/null
+#!/bin/bash -e
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+case $1 in
+-h|--help)
+ echo "Usage: $0 [rev]"
+ exit 0
+ ;;
+esac
+
+json_value() {
+ local key=$1
+ sed -n -r \
+ -e '/^[[:space:]]*"'"${key}"'"/s|.*:[[:space:]]*"([^"]*)",?$|\1|p' \
+ manifest.json
+}
+
+PN=$(json_value name | sed 's:[[:space:]/]:_:g' | tr '[:upper:]' '[:lower:]')
+if [[ ${PN} == "__msg_name__" ]] ; then
+ PN=$(basename "$(pwd)")
+fi
+PV=$(json_value version)
+rev=${1:-0}
+PVR="${PV}.${rev}"
+P="${PN}-${PVR}"
+
+rm -rf "${P}"
+mkdir "${P}"
+
+while read line ; do
+ dir=$(dirname "${line}")
+ mkdir -p "${P}/${dir}"
+ if [[ ${line} == *.[np]exe ]]; then
+ make -C "${dir}" -j
+ fi
+ ln "${line}" "${P}/${line}"
+done < <(sed 's:#.*::' manifest.files)
+cp Makefile manifest.files manifest.json "${P}/"
+
+make -C "${P}" -j {css,js}-min
+while read line ; do
+ mv "${line}.min" "${line}"
+done < <(find "${P}" -name '*.js' -o -name '*.css')
+rm "${P}"/{manifest.files,Makefile}
+
+sed -i \
+ -e '/"version"/s:"[^"]*",:"'${PVR}'",:' \
+ "${P}/manifest.json"
+
+zip="${P}.zip"
+rm -f "${zip}"
+zip -r "${zip}" "${P}"
+rm -rf "${P}"
+du -b "${zip}"
--- /dev/null
+#!/bin/bash -xe
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+size()
+{
+ stat -c %s "$@"
+}
+
+do_apngopt()
+{
+ local png new
+
+ for png in "$@"; do
+ new="${png}.new"
+ apngopt "${png}" "${new}"
+ if [[ $(size "${png}") -gt $(size "${new}") ]]; then
+ mv "${new}" "${png}"
+ else
+ rm "${new}"
+ fi
+ done
+}
+
+do_pngcrush()
+{
+ local png new
+
+ pngcrush -e .png.new "$@"
+ for png in "$@"; do
+ new="${png}.new"
+ mv "${new}" "${png}"
+ done
+}
+
+main()
+{
+ if [ $# -eq 0 ]; then
+ set -- $(find -name '*.png')
+ fi
+
+ if type -P apngopt >/dev/null; then
+ # apngopt likes to corrupt images.
+ : do_apngopt "$@"
+ elif type -P pngcrush >/dev/null; then
+ do_pngcrush "$@"
+ else
+ echo "error: could not find apngopt or pngcrush"
+ exit 1
+ fi
+}
+main "$@"
--- /dev/null
+<!-- Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it. -->
+<!doctype html>
+<html>
+
+<head>
+ <title>CrFTP</title>
+ <script type="text/javascript" src="../pnacl/crftp.js"></script>
+ <link rel="stylesheet" type="text/css" href="../pnacl/crftp.css">
+</head>
+
+<body>
+
+ Status: <code id='status'>Initializing JS</code>
+ <div id='UI'>
+ ftp://
+ <input id='user' type='text' value='anonymous'>
+ :
+ <input id='pass' type='text' value='luser@crftp'>
+ @
+ <input id='host' type='text' value='192.168.0.2'>
+ :
+ <input id='port' type='number' value='21' min='1' max='65535'>
+ <br>
+ <button id='connect'>Connect</button>
+ command: <input id='cmd' type='text' value='dir null'>
+ <br>
+ file transfer:
+ <button id='upload'>Up</button>
+ <button id='download'>Down</button>
+ <input id='files' type='file'>
+ </div>
+
+ <div id='log'></div>
+ <div id='listener'></div>
+
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ id="svg3825"
+ viewBox="0 0 60 60"
+ version="1.0"
+ inkscape:version="0.48.3.1 r9886"
+ width="100%"
+ height="100%"
+ sodipodi:docname="arrow.svg"
+ inkscape:export-filename="/usr/local/src/chrome-ext/crftp/images/arrow.png"
+ inkscape:export-xdpi="192"
+ inkscape:export-ydpi="192">
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1600"
+ inkscape:window-height="879"
+ id="namedview22"
+ showgrid="false"
+ fit-margin-left="0"
+ inkscape:zoom="10.429825"
+ inkscape:cx="10.446059"
+ inkscape:cy="30.760674"
+ inkscape:window-x="-4"
+ inkscape:window-y="-4"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg3825" />
+ <defs
+ id="defs3827">
+ <linearGradient
+ id="linearGradient16937"
+ y2="438.39999"
+ xlink:href="#linearGradient8026"
+ gradientUnits="userSpaceOnUse"
+ x2="956.66998"
+ gradientTransform="matrix(0.38084,0,0,0.40052,364.09,111.48)"
+ y1="373.76999"
+ x1="910.45001" />
+ <linearGradient
+ id="linearGradient8026">
+ <stop
+ id="stop8028"
+ stop-color="#fff"
+ offset="0" />
+ <stop
+ id="stop8030"
+ stop-color="#fff"
+ stop-opacity="0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient16935"
+ y2="411.56"
+ xlink:href="#linearGradient8026"
+ gradientUnits="userSpaceOnUse"
+ x2="929.51001"
+ gradientTransform="matrix(-0.42035,0,0,-0.39706,1098.9,448.81)"
+ y1="448.69"
+ x1="962.53003" />
+ <linearGradient
+ id="linearGradient16933"
+ y2="451.01999"
+ xlink:href="#linearGradient15728"
+ gradientUnits="userSpaceOnUse"
+ x2="957.15002"
+ gradientTransform="matrix(-0.40982,0.0041891,-0.0041891,-0.40982,1089.8,449.01)"
+ y1="368.14999"
+ x1="913.59998" />
+ <linearGradient
+ id="linearGradient15728">
+ <stop
+ id="stop15730"
+ stop-color="#000097"
+ offset="0"
+ style="stop-color:#3b79bc;stop-opacity:1;" />
+ <stop
+ id="stop15732"
+ stop-color="#6efff9"
+ offset="1"
+ style="stop-color:#94b8e0;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient16931"
+ y2="451.01999"
+ xlink:href="#linearGradient15728"
+ gradientUnits="userSpaceOnUse"
+ x2="957.15002"
+ gradientTransform="matrix(0.40982,-0.0041891,0.0041891,0.40982,333.65,110.79)"
+ y1="369.47"
+ x1="917.92999" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15728"
+ id="linearGradient3004"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.40982,-0.0041891,0.0041891,0.40982,333.65,110.79)"
+ x1="917.92999"
+ y1="369.47"
+ x2="957.15002"
+ y2="451.01999" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15728"
+ id="linearGradient3006"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.40982,0.0041891,-0.0041891,-0.40982,1089.8,449.01)"
+ x1="913.59998"
+ y1="368.14999"
+ x2="957.15002"
+ y2="451.01999" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8026"
+ id="linearGradient3008"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.42035,0,0,-0.39706,1098.9,448.81)"
+ x1="962.53003"
+ y1="448.69"
+ x2="929.51001"
+ y2="411.56" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8026"
+ id="linearGradient3010"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.38084,0,0,0.40052,364.09,111.48)"
+ x1="910.45001"
+ y1="373.76999"
+ x2="956.66998"
+ y2="438.39999" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8026"
+ id="linearGradient3013"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.58321838,0,0,0.61335633,-503.73257,-229.79953)"
+ x1="910.45001"
+ y1="373.76999"
+ x2="956.66998"
+ y2="438.39999" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8026"
+ id="linearGradient3016"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.64372399,0,0,-0.60805768,623.55546,286.78763)"
+ x1="962.53003"
+ y1="448.69"
+ x2="929.51001"
+ y2="411.56" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15728"
+ id="linearGradient3019"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.62759835,0.00641519,-0.00641519,-0.62759835,609.61972,287.09391)"
+ x1="913.59998"
+ y1="368.14999"
+ x2="957.15002"
+ y2="451.01999" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15728"
+ id="linearGradient3022"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.62759835,-0.00641519,0.00641519,0.62759835,-550.34839,-230.85619)"
+ x1="917.92999"
+ y1="369.47"
+ x2="957.15002"
+ y2="451.01999" />
+ </defs>
+ <path
+ id="path15339"
+ d="m 29.393708,23.509346 13.828542,17.53453 13.93574,-17.91738 -8.177676,0.01531 c 0,0 1.041352,-7.457918 -3.093428,-11.86835 C 41.752106,6.84771 32.150228,6.878338 32.150228,6.878338 c 0,0 4.272606,2.434926 5.834634,6.921928 1.577342,4.487002 0.750386,9.586564 0.750386,9.586564 l -9.34154,0.122512 z"
+ style="fill:url(#linearGradient3022);fill-rule:evenodd;stroke:#2e5c91;stroke-width:1.53139997;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path15341"
+ d="M 29.90825,32.728374 16.095022,15.193844 2.143968,33.111224 10.321644,33.09591 c 0,0 -1.041352,7.457918 3.093428,11.86835 4.13478,4.425746 13.751972,4.395118 13.751972,4.395118 0,0 -4.272606,-2.434926 -5.849948,-6.921928 -1.562028,-4.487002 -0.735072,-9.586564 -0.735072,-9.586564 l 9.326226,-0.122512 z"
+ style="fill:url(#linearGradient3019);fill-opacity:1;fill-rule:evenodd;stroke:#2e5c91;stroke-width:1.53139997;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path15742"
+ d="M 23.568254,25.775818 16.66164,17.39906 5.32928,31.748278 11.562078,31.71765 c 8.606468,-0.61256 6.263426,-5.712122 12.006176,-5.941832 z"
+ style="fill:url(#linearGradient3016);fill-rule:evenodd"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path15740"
+ d="m 32.379938,25.040746 5.558982,6.8913 c 5.911204,-1.332318 4.410432,-6.50845 9.663134,-7.549802 0,0 1.485458,-6.385938 -1.485458,-11.40893 C 43.252878,8.118776 35.25897,8.348486 35.25897,8.348486 c 0,0 3.093428,1.745796 4.51763,6.1256 1.424202,4.395118 0.168454,10.444148 0.168454,10.444148 l -7.565116,0.122512 z"
+ style="fill:url(#linearGradient3013);fill-rule:evenodd"
+ inkscape:connector-curvature="0" />
+ <metadata
+ id="metadata20">
+ <rdf:RDF>
+ <cc:Work>
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/publicdomain/" />
+ <dc:publisher>
+ <cc:Agent
+ rdf:about="http://openclipart.org/">
+ <dc:title>Openclipart</dc:title>
+ </cc:Agent>
+ </dc:publisher>
+ <dc:title>Mixe arrow</dc:title>
+ <dc:date>2008-06-08T09:56:02</dc:date>
+ <dc:description>a mixed arrow</dc:description>
+ <dc:source>http://openclipart.org/detail/17306/mixe-arrow-by-czara1</dc:source>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>czara1</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>arrow</rdf:li>
+ <rdf:li>blue</rdf:li>
+ <rdf:li>clip art</rdf:li>
+ <rdf:li>clipart</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/publicdomain/">
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Reproduction" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Distribution" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+</svg>
--- /dev/null
+// Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it.
+
+chrome.app.runtime.onLaunched.addListener(function() {
+ chrome.app.window.create('html/main.html', {
+ minWidth: 640,
+ minHeight: 480,
+ });
+});
--- /dev/null
+// Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it.
+
+document.addEventListener('DOMContentLoaded', function() {
+ $('#cmd').onkeypress = send_command;
+ $('#connect').onclick = connect;
+ $('#upload').onclick = upload;
+ $('#download').onclick = download;
+
+ crftp.initialize('../pnacl/crftp.nmf');
+});
--- /dev/null
+chrome-bootstrap/chrome-bootstrap.css
+html/main.html
+html/options.html
+js/launcher.js
+js/common.js
+js/options.js
+js/popup.js
+pnacl/pnacl.js
+pnacl/pnacl.nmf
+pnacl/pnacl.pexe
--- /dev/null
+{
+ "manifest_version": 2,
+ "minimum_chrome_version": "33",
+ "name": "CrFTP",
+ "version": "1.0",
+ "description": "The Chrome FTP client",
+ "icons": {
+ "128": "images/icon-128x128.png"
+ },
+ "app": {
+ "background": {
+ "scripts": ["js/launcher.js"]
+ }
+ },
+ "offline_enabled": true,
+ "sockets": {
+ "tcp": {
+ "connect": "*"
+ }
+ },
+ "permissions": [
+ "storage",
+ {"socket": [
+ "resolve-host",
+ "tcp-connect:*.*",
+ "tcp-listen"
+ ]},
+ {"fileSystem": ["write", "retainEntries", "directory"]}
+ ]
+}
--- /dev/null
+CROSS_COMPILE = $(NACL_SDK_ROOT)/toolchain/linux_pnacl/bin/pnacl-
+CC = $(CROSS_COMPILE)clang
+CXX = $(CROSS_COMPILE)clang++
+FINALIZE = $(CROSS_COMPILE)finalize
+STRIP = $(CROSS_COMPILE)strip
+ABICHECK = $(CROSS_COMPILE)abicheck
+CREATE_NMF = $(NACL_SDK_ROOT)/tools/create_nmf.py
+
+DEFAULT_FLAGS = -O2 -g -pipe
+CFLAGS ?= $(DEFAULT_FLAGS)
+CFLAGS += -Wall -pthread -ffunction-sections -fdata-sections
+CPPFLAGS += \
+ -D__UINT64_MAX \
+ -D__unix__ \
+ -I. \
+ -I$(PWD)/util
+
+# Now pull in the system code.
+CPPFLAGS += -I$(NACL_SDK_ROOT)/include -I$(NACL_SDK_ROOT)/include/newlib
+LDFLAGS += -L$(NACL_SDK_ROOT)/lib/pnacl/Release
+# We only use ppapi, but nacl_io needs ppapi_cpp.
+LDLIBS += -lppapi -lnacl_io -lppapi_cpp
+
+SRCS = ftplib.c ppapi.c queue.c inet_addr.c
+OBJS = $(SRCS:.c=.o)
+
+all: crftp.nmf
+
+%.nmf: %.pexe
+ $(CREATE_NMF) $< > $@
+
+%.pexe: %.bc
+ $(FINALIZE) -o $@ $<
+# The ABI checker segfaults currently.
+# $(ABICHECK) $@
+
+crftp.bc: $(OBJS)
+ $(CXX) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
+
+clean:
+ rm -f *.pexe *.nmf *.bc *.o
+
+httpd:
+ $(NACL_SDK_ROOT)/tools/httpd.py -C $(PWD) -p 7000 --no-dir-check >/dev/null 2>&1 &
+
+VPATH = ftplib util
+
+-include .depend
+.depend: $(SRCS)
+ $(CC) $(CPPFLAGS) -MM $^ > $@
+
+.PHONY: all clean
+.PRECIOUS: %.pexe
--- /dev/null
+/* Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it. */
+
+body {
+ font-family: sans-serif;
+}
+
+#log {
+ font-family: monospace;
+ border: 1px solid black;
+ overflow-x: auto;
+ overflow-y: auto;
+ height: 30em;
+ white-space: pre;
+}
+
+#log .response {
+ color: green;
+}
+#log .status {
+ color: black;
+}
+#log .cmd {
+ color: blue;
+}
+#log .error {
+ color: red;
+}
+
+button#connect {
+ width: 10em;
+}
--- /dev/null
+// Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it.
+
+function $(s) { return document.querySelector(s); }
+
+var crftp = (function() {
+
+ var nacl_module = null;
+
+ var mimeType = 'application/x-pnacl';
+
+ // Generate the NaCl module. We do this on the fly so we can attach to events
+ // in the parent element in case loading the NaCl module crashed.
+ function create(nmf)
+ {
+ // Attach listeners early.
+ var listener = document.getElementById('listener');
+ listener.addEventListener('load', moduleDidLoad, true);
+ listener.addEventListener('message', handleMessage, true);
+ listener.addEventListener('error', handleError, true);
+ listener.addEventListener('crash', handleCrash, true);
+
+ // Create the NaCl module.
+ var embed = nacl_module = document.createElement('embed');
+ embed.name = embed.id = 'nacl_module';
+ // Don't hide it in case there is a plugin/load error.
+ embed.width = embed.height = 200;
+ embed.src = nmf;
+ embed.type = mimeType;
+
+ // Attach it to the DOM so it'll start running.
+ listener.appendChild(embed);
+
+ crftp.module = nacl_module;
+ }
+
+ function handleError(event)
+ {
+ set_status('ERROR [' + nacl_module.lastError + ']');
+ }
+
+ function handleCrash(event)
+ {
+ if (nacl_module.exitStatus == -1)
+ set_status('CRASHED');
+ else
+ set_status('EXITED [' + nacl_module.exitStatus + ']');
+ set_ui(false);
+ }
+
+ function moduleDidLoad()
+ {
+ set_status('RUNNING');
+ nacl_module.style.height = '0';
+
+ set_ui(true);
+ $('#connect').focus();
+ }
+
+ // Callback from the NaCl module (PPB_Messaging.PostMessage).
+ var levels = Array('status', 'response', 'cmd', 'error', 'release');
+ function handleMessage(message_event)
+ {
+ if (typeof message_event.data == "string") {
+ log(message_event.data, levels[0]);
+ } else {
+ var event = message_event.data[0];
+ var data = message_event.data[1];
+ if (event == 4)
+ URL.revokeObjectURL(data);
+ else
+ log(data, levels[event]);
+ }
+ }
+
+ // Unload the NaCl module.
+ function destroy()
+ {
+ nacl_module.parentNode.removeChild(nacl_module);
+ crftp.module = nacl_module = null;
+ }
+
+ var log_ele = undefined;
+ function log(message, opt_class)
+ {
+ var span = document.createElement('span');
+ span.className = opt_class || 'status';
+ if (message.substr(-1) != '\n')
+ message += '\n';
+ span.textContent = message;
+ log_ele.appendChild(span);
+ log_ele.scrollTop = log_ele.scrollHeight;
+ }
+
+ var status_ele = undefined;
+ function set_status(status)
+ {
+ status_ele.innerHTML = status;
+ }
+
+ function initialize(nmf)
+ {
+ set_ui(false);
+
+ log_ele = document.getElementById('log');
+ status_ele = document.getElementById('status');
+
+ set_status('Page loaded');
+
+ // See if NaCl is even supported. Makes debugging easier.
+ if (navigator.mimeTypes[mimeType] === undefined) {
+ set_status('Browser does not support ' + mimeType + ' or is disabled');
+
+ // We're good, so try loading the module if needed.
+ } else if (nacl_module == null) {
+ set_status('Loading NaCl module');
+ create(nmf);
+ } else {
+ set_status('Waiting');
+ }
+ }
+
+ function postMessage()
+ {
+ var args = Array.prototype.slice.call(arguments);
+ nacl_module.postMessage(args);
+ }
+
+ function connect(host, opt_port, opt_user, opt_pass)
+ {
+ if (opt_port === undefined) {
+ if (host.indexOf(':') == -1)
+ host += ':21';
+ } else
+ host += ':' + port
+
+ postMessage('connect', host);
+
+ if (opt_user !== undefined)
+ login(opt_user, opt_pass);
+ }
+
+ function login(user, opt_pass)
+ {
+ var pass = opt_pass || 'luser@crftp';
+ postMessage('login', user, pass);
+ }
+
+ function disconnect()
+ {
+ postMessage('disconnect');
+ }
+
+ function chdir(path)
+ {
+ postMessage('chdir', path);
+ }
+ function dir(path)
+ {
+ postMessage('dir', path);
+ }
+ function list(path)
+ {
+ postMessage('list', path);
+ }
+ function put(files)
+ {
+ files.forEach(function(file) {
+ var url = file[0], name = file[1];
+ postMessage('put', url, name);
+ });
+ }
+ function get(files)
+ {
+ files.forEach(function(file) {
+ var url = file[0], name = file[1];
+ postMessage('get', url, name);
+ });
+ }
+
+ return {
+ module: null,
+
+ create: create,
+ destroy: destroy,
+ initialize: initialize,
+
+ connect: connect,
+ disconnect: disconnect,
+ login: login,
+ chdir: chdir,
+ dir: dir,
+ list: list,
+ put: put,
+ get: get,
+ raw: postMessage,
+ };
+
+}());
--- /dev/null
+package: ftplib
+version: 4.0
+license: http://www.perlfoundation.org/artistic_license_2_0
+homepage: http://nbpfaus.net/~pfau/ftplib/
--- /dev/null
+/***************************************************************************/
+/* */
+/* ftplib.c - callable ftp access routines */
+/* Copyright (C) 1996-2001, 2013 Thomas Pfau, tfpfau@gmail.com */
+/* 1407 Thomas Ave, North Brunswick, NJ, 08902 */
+/* */
+/* This library is free software. You can redistribute it and/or */
+/* modify it under the terms of the Artistic License 2.0. */
+/* */
+/* This library is distributed in the hope that it will be useful, */
+/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
+/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
+/* Artistic License 2.0 for more details. */
+/* */
+/* See the file LICENSE or */
+/* http://www.perlfoundation.org/artistic_license_2_0 */
+/* */
+/***************************************************************************/
+
+#if defined(__unix__) || defined(__VMS)
+#include <unistd.h>
+#endif
+#if defined(_WIN32)
+#include <windows.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#if defined(__unix__)
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#elif defined(VMS)
+#include <types.h>
+#include <socket.h>
+#include <in.h>
+#include <netdb.h>
+#include <inet.h>
+#elif defined(_WIN32)
+#include <winsock.h>
+#endif
+#undef _REENTRANT
+/* We don't support "192.168.0.2:ftp" syntax -- use a port # only. */
+#define getservbyname(...) NULL
+#define getservbyname_r(...) NULL
+#define hstrerror(e) e
+
+#define BUILDING_LIBRARY
+#include "ftplib.h"
+
+#if defined(__UINT64_MAX) && !defined(PRIu64)
+#if ULONG_MAX == __UINT32_MAX
+#define PRIu64 "llu"
+#else
+#define PRIu64 "lu"
+#endif
+#endif
+
+#if defined(_WIN32)
+#define SETSOCKOPT_OPTVAL_TYPE (const char *)
+#else
+#define SETSOCKOPT_OPTVAL_TYPE (void *)
+#endif
+
+#define FTPLIB_BUFSIZ 8192
+#define RESPONSE_BUFSIZ 1024
+#define TMP_BUFSIZ 1024
+#define ACCEPT_TIMEOUT 30
+
+#define FTPLIB_CONTROL 0
+#define FTPLIB_READ 1
+#define FTPLIB_WRITE 2
+
+#if !defined FTPLIB_DEFMODE
+#define FTPLIB_DEFMODE FTPLIB_PASSIVE
+#endif
+
+struct NetBuf {
+ char *cput,*cget;
+ int handle;
+ int cavail,cleft;
+ char *buf;
+ int dir;
+ netbuf *ctrl;
+ netbuf *data;
+ int cmode;
+ struct timeval idletime;
+ FtpCallback idlecb;
+ void *idlearg;
+ unsigned long int xfered;
+ unsigned long int cbbytes;
+ unsigned long int xfered1;
+ FtpResponseCallback respcb, sendcb;
+ char response[RESPONSE_BUFSIZ];
+};
+
+char *version =
+ "ftplib Release 4.0 07-Jun-2013, copyright 1996-2003, 2013 Thomas Pfau";
+
+GLOBALDEF int ftplib_debug = 2;
+
+#if defined(__unix__) || defined(VMS)
+int net_read(int fd, char *buf, size_t len)
+{
+ while ( 1 )
+ {
+ int c = read(fd, buf, len);
+ if ( c == -1 )
+ {
+ if ( errno != EINTR && errno != EAGAIN )
+ return -1;
+ }
+ else
+ {
+ return c;
+ }
+ }
+}
+
+int net_write(int fd, const char *buf, size_t len)
+{
+ int done = 0;
+ while ( len > 0 )
+ {
+ int c = write( fd, buf, len );
+ if ( c == -1 )
+ {
+ if ( errno != EINTR && errno != EAGAIN )
+ return -1;
+ }
+ else if ( c == 0 )
+ {
+ return done;
+ }
+ else
+ {
+ buf += c;
+ done += c;
+ len -= c;
+ }
+ }
+ return done;
+}
+#define net_close close
+#elif defined(_WIN32)
+#define net_read(x,y,z) recv(x,y,z,0)
+#define net_write(x,y,z) send(x,y,z,0)
+#define net_close closesocket
+#endif
+\f
+#if defined(NEED_MEMCCPY)
+/*
+ * VAX C does not supply a memccpy routine so I provide my own
+ */
+void *memccpy(void *dest, const void *src, int c, size_t n)
+{
+ int i=0;
+ const unsigned char *ip=src;
+ unsigned char *op=dest;
+
+ while (i < n)
+ {
+ if ((*op++ = *ip++) == c)
+ break;
+ i++;
+ }
+ if (i == n)
+ return NULL;
+ return op;
+}
+#endif
+#if defined(NEED_STRDUP)
+/*
+ * strdup - return a malloc'ed copy of a string
+ */
+char *strdup(const char *src)
+{
+ int l = strlen(src) + 1;
+ char *dst = malloc(l);
+ if (dst)
+ strcpy(dst,src);
+ return dst;
+}
+#endif
+\f
+/*
+ * socket_wait - wait for socket to receive or flush data
+ *
+ * return 1 if no user callback, otherwise, return value returned by
+ * user callback
+ */
+static int socket_wait(netbuf *ctl)
+{
+ fd_set fd,*rfd = NULL,*wfd = NULL;
+ struct timeval tv;
+ int rv = 0;
+ if ((ctl->dir == FTPLIB_CONTROL) || (ctl->idlecb == NULL))
+ return 1;
+ if (ctl->dir == FTPLIB_WRITE)
+ wfd = &fd;
+ else
+ rfd = &fd;
+ FD_ZERO(&fd);
+ do
+ {
+ FD_SET(ctl->handle,&fd);
+ tv = ctl->idletime;
+ rv = select(ctl->handle+1, rfd, wfd, NULL, &tv);
+ if (rv == -1)
+ {
+ rv = 0;
+ strncpy(ctl->ctrl->response, strerror(errno),
+ sizeof(ctl->ctrl->response));
+ break;
+ }
+ else if (rv > 0)
+ {
+ rv = 1;
+ break;
+ }
+ }
+ while ((rv = ctl->idlecb(ctl, ctl->xfered, ctl->idlearg)));
+ return rv;
+}
+\f
+/*
+ * read a line of text
+ *
+ * return -1 on error or bytecount
+ */
+static int readline(char *buf,int max,netbuf *ctl)
+{
+ int x,retval = 0;
+ char *end,*bp=buf;
+ int eof = 0;
+
+ if ((ctl->dir != FTPLIB_CONTROL) && (ctl->dir != FTPLIB_READ))
+ return -1;
+ if (max == 0)
+ return 0;
+ do
+ {
+ if (ctl->cavail > 0)
+ {
+ x = (max >= ctl->cavail) ? ctl->cavail : max-1;
+ end = memccpy(bp,ctl->cget,'\n',x);
+ if (end != NULL)
+ x = end - bp;
+ retval += x;
+ bp += x;
+ *bp = '\0';
+ max -= x;
+ ctl->cget += x;
+ ctl->cavail -= x;
+ if (end != NULL)
+ {
+ bp -= 2;
+ if (strcmp(bp,"\r\n") == 0)
+ {
+ *bp++ = '\n';
+ *bp++ = '\0';
+ --retval;
+ }
+ break;
+ }
+ }
+ if (max == 1)
+ {
+ *buf = '\0';
+ break;
+ }
+ if (ctl->cput == ctl->cget)
+ {
+ ctl->cput = ctl->cget = ctl->buf;
+ ctl->cavail = 0;
+ ctl->cleft = FTPLIB_BUFSIZ;
+ }
+ if (eof)
+ {
+ if (retval == 0)
+ retval = -1;
+ break;
+ }
+ if (!socket_wait(ctl))
+ return retval;
+ if ((x = net_read(ctl->handle,ctl->cput,ctl->cleft)) == -1)
+ {
+ if (ftplib_debug)
+ perror("read");
+ retval = -1;
+ break;
+ }
+ if (x == 0)
+ eof = 1;
+ ctl->cleft -= x;
+ ctl->cavail += x;
+ ctl->cput += x;
+ }
+ while (1);
+ return retval;
+}
+\f
+/*
+ * write lines of text
+ *
+ * return -1 on error or bytecount
+ */
+static int writeline(const char *buf, int len, netbuf *nData)
+{
+ int x, nb=0, w;
+ const char *ubp = buf;
+ char *nbp;
+ char lc=0;
+
+ if (nData->dir != FTPLIB_WRITE)
+ return -1;
+ nbp = nData->buf;
+ for (x=0; x < len; x++)
+ {
+ if ((*ubp == '\n') && (lc != '\r'))
+ {
+ if (nb == FTPLIB_BUFSIZ)
+ {
+ if (!socket_wait(nData))
+ return x;
+ w = net_write(nData->handle, nbp, FTPLIB_BUFSIZ);
+ if (w != FTPLIB_BUFSIZ)
+ {
+ if (ftplib_debug)
+ printf("net_write(1) returned %d, errno = %d\n", w, errno);
+ return(-1);
+ }
+ nb = 0;
+ }
+ nbp[nb++] = '\r';
+ }
+ if (nb == FTPLIB_BUFSIZ)
+ {
+ if (!socket_wait(nData))
+ return x;
+ w = net_write(nData->handle, nbp, FTPLIB_BUFSIZ);
+ if (w != FTPLIB_BUFSIZ)
+ {
+ if (ftplib_debug)
+ printf("net_write(2) returned %d, errno = %d\n", w, errno);
+ return(-1);
+ }
+ nb = 0;
+ }
+ nbp[nb++] = lc = *ubp++;
+ }
+ if (nb)
+ {
+ if (!socket_wait(nData))
+ return x;
+ w = net_write(nData->handle, nbp, nb);
+ if (w != nb)
+ {
+ if (ftplib_debug)
+ printf("net_write(3) returned %d, errno = %d\n", w, errno);
+ return(-1);
+ }
+ }
+ return len;
+}
+\f
+/*
+ * read a response from the server
+ *
+ * return 0 if first char doesn't match
+ * return 1 if first char matches
+ */
+static int readresp(char c, netbuf *nControl)
+{
+ char match[5];
+ if (readline(nControl->response,RESPONSE_BUFSIZ,nControl) == -1)
+ {
+ if (ftplib_debug)
+ perror("Control socket read failed");
+ return 0;
+ }
+ if (ftplib_debug > 1)
+ fprintf(stderr,"%s",nControl->response);
+ if (nControl->respcb)
+ nControl->respcb(nControl, nControl->response);
+ if (nControl->response[3] == '-')
+ {
+ strncpy(match,nControl->response,3);
+ match[3] = ' ';
+ match[4] = '\0';
+ do
+ {
+ if (readline(nControl->response,RESPONSE_BUFSIZ,nControl) == -1)
+ {
+ if (ftplib_debug)
+ perror("Control socket read failed");
+ return 0;
+ }
+ if (ftplib_debug > 1)
+ fprintf(stderr,"%s",nControl->response);
+ }
+ while (strncmp(nControl->response,match,4));
+ }
+ if (nControl->response[0] == c)
+ return 1;
+ return 0;
+}
+\f
+/*
+ * FtpInit for stupid operating systems that require it (Windows NT)
+ */
+GLOBALDEF void FtpInit(void)
+{
+#if defined(_WIN32)
+ WORD wVersionRequested;
+ WSADATA wsadata;
+ int err;
+ wVersionRequested = MAKEWORD(1,1);
+ if ((err = WSAStartup(wVersionRequested,&wsadata)) != 0)
+ fprintf(stderr,"Network failed to start: %d\n",err);
+#endif
+}
+\f
+/*
+ * FtpLastResponse - return a pointer to the last response received
+ */
+GLOBALDEF char *FtpLastResponse(netbuf *nControl)
+{
+ if ((nControl) && (nControl->dir == FTPLIB_CONTROL))
+ return nControl->response;
+ return NULL;
+}
+\f
+/*
+ * FtpConnect - connect to remote server
+ *
+ * return 1 if connected, 0 if not
+ */
+GLOBALDEF int FtpConnect(const char *host, FtpResponseCallback respcb, netbuf **nControl)
+{
+ int sControl;
+ struct sockaddr_in sin;
+ int on=1;
+ netbuf *ctrl;
+ char *lhost;
+ char *pnum;
+
+ memset(&sin,0,sizeof(sin));
+ sin.sin_family = AF_INET;
+ lhost = strdup(host);
+ pnum = strchr(lhost,':');
+ if (pnum == NULL)
+ pnum = "ftp";
+ else
+ *pnum++ = '\0';
+ if (isdigit((int)*pnum))
+ sin.sin_port = htons(atoi(pnum));
+ else
+ {
+ struct servent *pse;
+#if _REENTRANT
+ struct servent se;
+ char tmpbuf[TMP_BUFSIZ];
+ int i;
+ if ( ( i = getservbyname_r(pnum,"tcp",&se,tmpbuf,TMP_BUFSIZ,&pse) ) != 0 )
+ {
+ errno = i;
+ if ( ftplib_debug )
+ perror("getservbyname_r");
+ free(lhost);
+ return 0;
+ }
+#else
+ if ((pse = getservbyname(pnum,"tcp") ) == NULL )
+ {
+ if ( ftplib_debug )
+ perror("getservbyname");
+ free(lhost);
+ return 0;
+ }
+#endif
+ sin.sin_port = pse->s_port;
+ }
+ if ((sin.sin_addr.s_addr = inet_addr(lhost)) == INADDR_NONE)
+ {
+ struct hostent *phe;
+#ifdef _REENTRANT
+ struct hostent he;
+ char tmpbuf[TMP_BUFSIZ];
+ int i, herr;
+ if ( ( i = gethostbyname_r( lhost, &he, tmpbuf, TMP_BUFSIZ, &phe, &herr ) ) != 0 )
+ {
+ if ( ftplib_debug )
+ fprintf(stderr, "gethostbyname: %s\n", hstrerror(herr));
+ free(lhost);
+ return 0;
+ }
+#else
+ if ((phe = gethostbyname(lhost)) == NULL)
+ {
+ if (ftplib_debug)
+ fprintf(stderr, "gethostbyname: %i\n", hstrerror(h_errno));
+ free(lhost);
+ return 0;
+ }
+#endif
+ memcpy((char *)&sin.sin_addr, phe->h_addr, phe->h_length);
+ }
+ free(lhost);
+ sControl = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (sControl == -1)
+ {
+ if (ftplib_debug)
+ perror("socket");
+ return 0;
+ }
+ if (setsockopt(sControl,SOL_SOCKET,SO_REUSEADDR,
+ SETSOCKOPT_OPTVAL_TYPE &on, sizeof(on)) == -1)
+ {
+ if (ftplib_debug)
+ perror("setsockopt");
+ net_close(sControl);
+ return 0;
+ }
+ if (connect(sControl, (struct sockaddr *)&sin, sizeof(sin)) == -1)
+ {
+ if (ftplib_debug)
+ perror("connect");
+ net_close(sControl);
+ return 0;
+ }
+ ctrl = calloc(1,sizeof(netbuf));
+ if (ctrl == NULL)
+ {
+ if (ftplib_debug)
+ perror("calloc");
+ net_close(sControl);
+ return 0;
+ }
+ ctrl->buf = malloc(FTPLIB_BUFSIZ);
+ if (ctrl->buf == NULL)
+ {
+ if (ftplib_debug)
+ perror("calloc");
+ net_close(sControl);
+ free(ctrl);
+ return 0;
+ }
+ ctrl->respcb = respcb;
+ ctrl->sendcb = NULL;
+ ctrl->handle = sControl;
+ ctrl->dir = FTPLIB_CONTROL;
+ ctrl->ctrl = NULL;
+ ctrl->cmode = FTPLIB_DEFMODE;
+ ctrl->idlecb = NULL;
+ ctrl->idletime.tv_sec = ctrl->idletime.tv_usec = 0;
+ ctrl->idlearg = NULL;
+ ctrl->xfered = 0;
+ ctrl->xfered1 = 0;
+ ctrl->cbbytes = 0;
+ if (readresp('2', ctrl) == 0)
+ {
+ net_close(sControl);
+ free(ctrl->buf);
+ free(ctrl);
+ return 0;
+ }
+ *nControl = ctrl;
+ return 1;
+}
+\f
+GLOBALDEF int FtpSetResponseCallback(FtpResponseCallback respcb, netbuf *nControl)
+{
+ nControl->respcb = respcb;
+ return 0;
+}
+GLOBALDEF int FtpSetSendCmdCallback(FtpResponseCallback sendcb, netbuf *nControl)
+{
+ nControl->sendcb = sendcb;
+ return 0;
+}
+\f
+GLOBALDEF int FtpSetCallback(const FtpCallbackOptions *opt, netbuf *nControl)
+{
+ nControl->idlecb = opt->cbFunc;
+ nControl->idlearg = opt->cbArg;
+ nControl->idletime.tv_sec = opt->idleTime / 1000;
+ nControl->idletime.tv_usec = (opt->idleTime % 1000) * 1000;
+ nControl->cbbytes = opt->bytesXferred;
+ return 1;
+}
+GLOBALDEF int FtpClearCallback(netbuf *nControl)
+{
+ nControl->idlecb = NULL;
+ nControl->idlearg = NULL;
+ nControl->idletime.tv_sec = 0;
+ nControl->idletime.tv_usec = 0;
+ nControl->cbbytes = 0;
+ return 1;
+}
+/*
+ * FtpOptions - change connection options
+ *
+ * returns 1 if successful, 0 on error
+ */
+GLOBALDEF int FtpOptions(int opt, long val, netbuf *nControl)
+{
+ int v,rv=0;
+ switch (opt)
+ {
+ case FTPLIB_CONNMODE:
+ v = (int) val;
+ if ((v == FTPLIB_PASSIVE) || (v == FTPLIB_PORT))
+ {
+ nControl->cmode = v;
+ rv = 1;
+ }
+ break;
+ case FTPLIB_CALLBACK:
+ nControl->idlecb = (FtpCallback) val;
+ rv = 1;
+ break;
+ case FTPLIB_IDLETIME:
+ v = (int) val;
+ rv = 1;
+ nControl->idletime.tv_sec = v / 1000;
+ nControl->idletime.tv_usec = (v % 1000) * 1000;
+ break;
+ case FTPLIB_CALLBACKARG:
+ rv = 1;
+ nControl->idlearg = (void *) val;
+ break;
+ case FTPLIB_CALLBACKBYTES:
+ rv = 1;
+ nControl->cbbytes = (int) val;
+ break;
+ }
+ return rv;
+}
+\f
+/*
+ * FtpSendCmd - send a command and wait for expected response
+ *
+ * return 1 if proper response received, 0 otherwise
+ */
+static int FtpSendCmd(const char *cmd, char expresp, netbuf *nControl)
+{
+ char buf[TMP_BUFSIZ];
+ if (nControl->dir != FTPLIB_CONTROL)
+ return 0;
+ if (ftplib_debug > 2)
+ fprintf(stderr,"%s\n",cmd);
+ if ((strlen(cmd) + 3) > sizeof(buf))
+ return 0;
+ sprintf(buf,"%s\r\n",cmd);
+ if (nControl->sendcb)
+ nControl->sendcb(nControl, buf);
+ if (net_write(nControl->handle,buf,strlen(buf)) <= 0)
+ {
+ if (ftplib_debug)
+ perror("write");
+ return 0;
+ }
+ return readresp(expresp, nControl);
+}
+\f
+/*
+ * FtpLogin - log in to remote server
+ *
+ * return 1 if logged in, 0 otherwise
+ */
+GLOBALDEF int FtpLogin(const char *user, const char *pass, netbuf *nControl)
+{
+ char tempbuf[64];
+
+ if (((strlen(user) + 7) > sizeof(tempbuf)) ||
+ ((strlen(pass) + 7) > sizeof(tempbuf)))
+ return 0;
+ sprintf(tempbuf,"USER %s",user);
+ if (!FtpSendCmd(tempbuf,'3',nControl))
+ {
+ if (nControl->response[0] == '2')
+ return 1;
+ return 0;
+ }
+ sprintf(tempbuf,"PASS %s",pass);
+ return FtpSendCmd(tempbuf,'2',nControl);
+}
+\f
+/*
+ * FtpOpenPort - set up data connection
+ *
+ * return 1 if successful, 0 otherwise
+ */
+static int FtpOpenPort(netbuf *nControl, netbuf **nData, int mode, int dir)
+{
+ int sData;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in in;
+ } sin;
+ struct linger lng = { 0, 0 };
+ unsigned int l;
+ int on=1;
+ netbuf *ctrl;
+ char *cp;
+ unsigned int v[6];
+ char buf[TMP_BUFSIZ];
+
+ if (nControl->dir != FTPLIB_CONTROL)
+ return -1;
+ if ((dir != FTPLIB_READ) && (dir != FTPLIB_WRITE))
+ {
+ sprintf(nControl->response, "Invalid direction %d\n", dir);
+ return -1;
+ }
+ if ((mode != FTPLIB_ASCII) && (mode != FTPLIB_IMAGE))
+ {
+ sprintf(nControl->response, "Invalid mode %c\n", mode);
+ return -1;
+ }
+ l = sizeof(sin);
+ if (nControl->cmode == FTPLIB_PASSIVE)
+ {
+ memset(&sin, 0, l);
+ sin.in.sin_family = AF_INET;
+ if (!FtpSendCmd("PASV",'2',nControl))
+ return -1;
+ cp = strchr(nControl->response,'(');
+ if (cp == NULL)
+ return -1;
+ cp++;
+ sscanf(cp,"%u,%u,%u,%u,%u,%u",&v[2],&v[3],&v[4],&v[5],&v[0],&v[1]);
+ sin.sa.sa_data[2] = v[2];
+ sin.sa.sa_data[3] = v[3];
+ sin.sa.sa_data[4] = v[4];
+ sin.sa.sa_data[5] = v[5];
+ if (sin.sa.sa_data[2] == 0 && sin.sa.sa_data[3] == 0 &&
+ sin.sa.sa_data[4] == 0 && sin.sa.sa_data[5] == 0)
+ ;
+ sin.sa.sa_data[0] = v[0];
+ sin.sa.sa_data[1] = v[1];
+ }
+ else
+ {
+ if (getsockname(nControl->handle, &sin.sa, &l) < 0)
+ {
+ if (ftplib_debug)
+ perror("getsockname");
+ return -1;
+ }
+ }
+ sData = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
+ if (sData == -1)
+ {
+ if (ftplib_debug)
+ perror("socket");
+ return -1;
+ }
+ if (setsockopt(sData,SOL_SOCKET,SO_REUSEADDR,
+ SETSOCKOPT_OPTVAL_TYPE &on,sizeof(on)) == -1)
+ {
+ if (ftplib_debug)
+ perror("setsockopt");
+ net_close(sData);
+ return -1;
+ }
+ if (setsockopt(sData,SOL_SOCKET,SO_LINGER,
+ SETSOCKOPT_OPTVAL_TYPE &lng,sizeof(lng)) == -1)
+ {
+ if (ftplib_debug)
+ perror("setsockopt");
+ net_close(sData);
+ return -1;
+ }
+ if (nControl->cmode == FTPLIB_PASSIVE)
+ {
+ if (connect(sData, &sin.sa, sizeof(sin.sa)) == -1)
+ {
+ if (ftplib_debug)
+ perror("connect");
+ net_close(sData);
+ return -1;
+ }
+ }
+ else
+ {
+ sin.in.sin_port = 0;
+ if (bind(sData, &sin.sa, sizeof(sin)) == -1)
+ {
+ if (ftplib_debug)
+ perror("bind");
+ net_close(sData);
+ return -1;
+ }
+ if (listen(sData, 1) < 0)
+ {
+ if (ftplib_debug)
+ perror("listen");
+ net_close(sData);
+ return -1;
+ }
+ if (getsockname(sData, &sin.sa, &l) < 0)
+ return -1;
+ sprintf(buf, "PORT %d,%d,%d,%d,%d,%d",
+ (unsigned char) sin.sa.sa_data[2],
+ (unsigned char) sin.sa.sa_data[3],
+ (unsigned char) sin.sa.sa_data[4],
+ (unsigned char) sin.sa.sa_data[5],
+ (unsigned char) sin.sa.sa_data[0],
+ (unsigned char) sin.sa.sa_data[1]);
+ if (!FtpSendCmd(buf,'2',nControl))
+ {
+ net_close(sData);
+ return -1;
+ }
+ }
+ ctrl = calloc(1,sizeof(netbuf));
+ if (ctrl == NULL)
+ {
+ if (ftplib_debug)
+ perror("calloc");
+ net_close(sData);
+ return -1;
+ }
+ if ((mode == 'A') && ((ctrl->buf = malloc(FTPLIB_BUFSIZ)) == NULL))
+ {
+ if (ftplib_debug)
+ perror("calloc");
+ net_close(sData);
+ free(ctrl);
+ return -1;
+ }
+ ctrl->handle = sData;
+ ctrl->dir = dir;
+ ctrl->idletime = nControl->idletime;
+ ctrl->idlearg = nControl->idlearg;
+ ctrl->xfered = 0;
+ ctrl->xfered1 = 0;
+ ctrl->cbbytes = nControl->cbbytes;
+ if (ctrl->idletime.tv_sec || ctrl->idletime.tv_usec || ctrl->cbbytes)
+ ctrl->idlecb = nControl->idlecb;
+ else
+ ctrl->idlecb = NULL;
+ *nData = ctrl;
+ return 1;
+}
+\f
+/*
+ * FtpAcceptConnection - accept connection from server
+ *
+ * return 1 if successful, 0 otherwise
+ */
+static int FtpAcceptConnection(netbuf *nData, netbuf *nControl)
+{
+ int sData;
+ struct sockaddr addr;
+ unsigned int l;
+ int i;
+ struct timeval tv;
+ fd_set mask;
+ int rv = 0;
+
+ FD_ZERO(&mask);
+ FD_SET(nControl->handle, &mask);
+ FD_SET(nData->handle, &mask);
+ tv.tv_usec = 0;
+ tv.tv_sec = ACCEPT_TIMEOUT;
+ i = nControl->handle;
+ if (i < nData->handle)
+ i = nData->handle;
+ i = select(i+1, &mask, NULL, NULL, &tv);
+ if (i == -1)
+ {
+ strncpy(nControl->response, strerror(errno),
+ sizeof(nControl->response));
+ net_close(nData->handle);
+ nData->handle = 0;
+ }
+ else if (i == 0)
+ {
+ strcpy(nControl->response, "timed out waiting for connection");
+ net_close(nData->handle);
+ nData->handle = 0;
+ }
+ else
+ {
+ if (FD_ISSET(nData->handle, &mask))
+ {
+ l = sizeof(addr);
+ sData = accept(nData->handle, &addr, &l);
+ i = errno;
+ net_close(nData->handle);
+ if (sData > 0)
+ {
+ rv = 1;
+ nData->handle = sData;
+ }
+ else
+ {
+ strncpy(nControl->response, strerror(i),
+ sizeof(nControl->response));
+ nData->handle = 0;
+ }
+ }
+ else if (FD_ISSET(nControl->handle, &mask))
+ {
+ net_close(nData->handle);
+ nData->handle = 0;
+ readresp('2', nControl);
+ }
+ }
+ return rv;
+}
+\f
+/*
+ * FtpAccess - return a handle for a data stream
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpAccess(const char *path, int typ, int mode, netbuf *nControl,
+ netbuf **nData)
+{
+ char buf[TMP_BUFSIZ];
+ int dir;
+ if ((path == NULL) &&
+ ((typ == FTPLIB_FILE_WRITE) || (typ == FTPLIB_FILE_READ)))
+ {
+ sprintf(nControl->response,
+ "Missing path argument for file transfer\n");
+ return 0;
+ }
+ sprintf(buf, "TYPE %c", mode);
+ if (!FtpSendCmd(buf, '2', nControl))
+ return 0;
+ switch (typ)
+ {
+ case FTPLIB_DIR:
+ strcpy(buf,"NLST");
+ dir = FTPLIB_READ;
+ break;
+ case FTPLIB_DIR_VERBOSE:
+ strcpy(buf,"LIST");
+ dir = FTPLIB_READ;
+ break;
+ case FTPLIB_FILE_READ:
+ strcpy(buf,"RETR");
+ dir = FTPLIB_READ;
+ break;
+ case FTPLIB_FILE_WRITE:
+ strcpy(buf,"STOR");
+ dir = FTPLIB_WRITE;
+ break;
+ default:
+ sprintf(nControl->response, "Invalid open type %d\n", typ);
+ return 0;
+ }
+ if (path != NULL)
+ {
+ int i = strlen(buf);
+ buf[i++] = ' ';
+ if ((strlen(path) + i + 1) >= sizeof(buf))
+ return 0;
+ strcpy(&buf[i],path);
+ }
+ if (FtpOpenPort(nControl, nData, mode, dir) == -1)
+ return 0;
+ if (!FtpSendCmd(buf, '1', nControl))
+ {
+ FtpClose(*nData);
+ *nData = NULL;
+ return 0;
+ }
+ (*nData)->ctrl = nControl;
+ nControl->data = *nData;
+ if (nControl->cmode == FTPLIB_PORT)
+ {
+ if (!FtpAcceptConnection(*nData,nControl))
+ {
+ FtpClose(*nData);
+ *nData = NULL;
+ nControl->data = NULL;
+ return 0;
+ }
+ }
+ return 1;
+}
+\f
+/*
+ * FtpRead - read from a data connection
+ */
+GLOBALDEF int FtpRead(void *buf, int max, netbuf *nData)
+{
+ int i;
+ if (nData->dir != FTPLIB_READ)
+ return 0;
+ if (nData->buf)
+ i = readline(buf, max, nData);
+ else
+ {
+ i = socket_wait(nData);
+ if (i != 1)
+ return 0;
+ i = net_read(nData->handle, buf, max);
+ }
+ if (i == -1)
+ return 0;
+ nData->xfered += i;
+ if (nData->idlecb && nData->cbbytes)
+ {
+ nData->xfered1 += i;
+ if (nData->xfered1 > nData->cbbytes)
+ {
+ if (nData->idlecb(nData, nData->xfered, nData->idlearg) == 0)
+ return 0;
+ nData->xfered1 = 0;
+ }
+ }
+ return i;
+}
+\f
+/*
+ * FtpWrite - write to a data connection
+ */
+GLOBALDEF int FtpWrite(const void *buf, int len, netbuf *nData)
+{
+ int i;
+ if (nData->dir != FTPLIB_WRITE)
+ return 0;
+ if (nData->buf)
+ i = writeline(buf, len, nData);
+ else
+ {
+ socket_wait(nData);
+ i = net_write(nData->handle, buf, len);
+ }
+ if (i == -1)
+ return 0;
+ nData->xfered += i;
+ if (nData->idlecb && nData->cbbytes)
+ {
+ nData->xfered1 += i;
+ if (nData->xfered1 > nData->cbbytes)
+ {
+ nData->idlecb(nData, nData->xfered, nData->idlearg);
+ nData->xfered1 = 0;
+ }
+ }
+ return i;
+}
+\f
+/*
+ * FtpClose - close a data connection
+ */
+GLOBALDEF int FtpClose(netbuf *nData)
+{
+ netbuf *ctrl;
+ switch (nData->dir)
+ {
+ case FTPLIB_WRITE:
+ /* potential problem - if buffer flush fails, how to notify user? */
+ if (nData->buf != NULL)
+ writeline(NULL, 0, nData);
+ case FTPLIB_READ:
+ if (nData->buf)
+ free(nData->buf);
+ shutdown(nData->handle,2);
+ net_close(nData->handle);
+ ctrl = nData->ctrl;
+ free(nData);
+ if (ctrl)
+ {
+ ctrl->data = NULL;
+ if (ctrl->response[0] != '4' && ctrl->response[0] != 5)
+ return readresp('2', ctrl);
+ }
+ return 1;
+ case FTPLIB_CONTROL:
+ if (nData->data)
+ {
+ nData->ctrl = NULL;
+ FtpClose(nData->data);
+ }
+ net_close(nData->handle);
+ free(nData);
+ return 0;
+ }
+ return 1;
+}
+\f
+/*
+ * FtpSite - send a SITE command
+ *
+ * return 1 if command successful, 0 otherwise
+ */
+GLOBALDEF int FtpSite(const char *cmd, netbuf *nControl)
+{
+ char buf[TMP_BUFSIZ];
+
+ if ((strlen(cmd) + 7) > sizeof(buf))
+ return 0;
+ sprintf(buf,"SITE %s",cmd);
+ if (!FtpSendCmd(buf,'2',nControl))
+ return 0;
+ return 1;
+}
+\f
+/*
+ * FtpSysType - send a SYST command
+ *
+ * Fills in the user buffer with the remote system type. If more
+ * information from the response is required, the user can parse
+ * it out of the response buffer returned by FtpLastResponse().
+ *
+ * return 1 if command successful, 0 otherwise
+ */
+GLOBALDEF int FtpSysType(char *buf, int max, netbuf *nControl)
+{
+ int l = max;
+ char *b = buf;
+ char *s;
+ if (!FtpSendCmd("SYST",'2',nControl))
+ return 0;
+ s = &nControl->response[4];
+ while ((--l) && (*s != ' '))
+ *b++ = *s++;
+ *b++ = '\0';
+ return 1;
+}
+\f
+/*
+ * FtpMkdir - create a directory at server
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpMkdir(const char *path, netbuf *nControl)
+{
+ char buf[TMP_BUFSIZ];
+
+ if ((strlen(path) + 6) > sizeof(buf))
+ return 0;
+ sprintf(buf,"MKD %s",path);
+ if (!FtpSendCmd(buf,'2', nControl))
+ return 0;
+ return 1;
+}
+\f
+/*
+ * FtpChdir - change path at remote
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpChdir(const char *path, netbuf *nControl)
+{
+ char buf[TMP_BUFSIZ];
+
+ if ((strlen(path) + 6) > sizeof(buf))
+ return 0;
+ sprintf(buf,"CWD %s",path);
+ if (!FtpSendCmd(buf,'2',nControl))
+ return 0;
+ return 1;
+}
+\f
+/*
+ * FtpCDUp - move to parent directory at remote
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpCDUp(netbuf *nControl)
+{
+ if (!FtpSendCmd("CDUP",'2',nControl))
+ return 0;
+ return 1;
+}
+\f
+/*
+ * FtpRmdir - remove directory at remote
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpRmdir(const char *path, netbuf *nControl)
+{
+ char buf[TMP_BUFSIZ];
+
+ if ((strlen(path) + 6) > sizeof(buf))
+ return 0;
+ sprintf(buf,"RMD %s",path);
+ if (!FtpSendCmd(buf,'2',nControl))
+ return 0;
+ return 1;
+}
+\f
+/*
+ * FtpPwd - get working directory at remote
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpPwd(char *path, int max, netbuf *nControl)
+{
+ int l = max;
+ char *b = path;
+ char *s;
+ if (!FtpSendCmd("PWD",'2',nControl))
+ return 0;
+ s = strchr(nControl->response, '"');
+ if (s == NULL)
+ return 0;
+ s++;
+ while ((--l) && (*s) && (*s != '"'))
+ *b++ = *s++;
+ *b++ = '\0';
+ return 1;
+}
+\f
+/*
+ * FtpXfer - issue a command and transfer data
+ *
+ * return 1 if successful, 0 otherwise
+ */
+static int FtpXfer(FtpDataCallback cb, const char *path,
+ netbuf *nControl, int typ, int mode)
+{
+ int l,c;
+ char *dbuf;
+ netbuf *nData;
+ int rv=1;
+
+ if (!FtpAccess(path, typ, mode, nControl, &nData))
+ return 0;
+ dbuf = malloc(FTPLIB_BUFSIZ);
+ if (typ == FTPLIB_FILE_WRITE)
+ {
+ while ((l = cb(dbuf, FTPLIB_BUFSIZ)) > 0)
+ {
+ if ((c = FtpWrite(dbuf, l, nData)) < l)
+ {
+ printf("short write: passed %d, wrote %d\n", l, c);
+ rv = 0;
+ break;
+ }
+ }
+ }
+ else
+ {
+ while ((l = FtpRead(dbuf, FTPLIB_BUFSIZ, nData)) > 0)
+ {
+ if (cb(dbuf, l) == 0)
+ {
+ if (ftplib_debug)
+ perror("localfile write");
+ rv = 0;
+ break;
+ }
+ }
+ }
+ free(dbuf);
+ FtpClose(nData);
+ return rv;
+}
+\f
+/*
+ * FtpNlst - issue an NLST command and write response to output
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpNlst(FtpDataCallback cb, const char *path,
+ netbuf *nControl)
+{
+ return FtpXfer(cb, path, nControl, FTPLIB_DIR, FTPLIB_ASCII);
+}
+\f
+/*
+ * FtpDir - issue a LIST command and write response to output
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpDir(FtpDataCallback cb, const char *path, netbuf *nControl)
+{
+ return FtpXfer(cb, path, nControl, FTPLIB_DIR_VERBOSE, FTPLIB_ASCII);
+}
+\f
+/*
+ * FtpSize - determine the size of a remote file
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpSize(const char *path, unsigned int *size, char mode, netbuf *nControl)
+{
+ char cmd[TMP_BUFSIZ];
+ int resp,rv=1;
+ unsigned int sz;
+
+ if ((strlen(path) + 7) > sizeof(cmd))
+ return 0;
+ sprintf(cmd, "TYPE %c", mode);
+ if (!FtpSendCmd(cmd, '2', nControl))
+ return 0;
+ sprintf(cmd,"SIZE %s",path);
+ if (!FtpSendCmd(cmd,'2',nControl))
+ rv = 0;
+ else
+ {
+ if (sscanf(nControl->response, "%d %u", &resp, &sz) == 2)
+ *size = sz;
+ else
+ rv = 0;
+ }
+ return rv;
+}
+\f
+#if defined(__UINT64_MAX)
+/*
+ * FtpSizeLong - determine the size of a remote file
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpSizeLong(const char *path, fsz_t *size, char mode, netbuf *nControl)
+{
+ char cmd[TMP_BUFSIZ];
+ int resp,rv=1;
+ fsz_t sz;
+
+ if ((strlen(path) + 7) > sizeof(cmd))
+ return 0;
+ sprintf(cmd, "TYPE %c", mode);
+ if (!FtpSendCmd(cmd, '2', nControl))
+ return 0;
+ sprintf(cmd,"SIZE %s",path);
+ if (!FtpSendCmd(cmd,'2',nControl))
+ rv = 0;
+ else
+ {
+ if (sscanf(nControl->response, "%d %" PRIu64 "", &resp, &sz) == 2)
+ *size = sz;
+ else
+ rv = 0;
+ }
+ return rv;
+}
+#endif
+\f
+/*
+ * FtpModDate - determine the modification date of a remote file
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpModDate(const char *path, char *dt, int max, netbuf *nControl)
+{
+ char buf[TMP_BUFSIZ];
+ int rv = 1;
+
+ if ((strlen(path) + 7) > sizeof(buf))
+ return 0;
+ sprintf(buf,"MDTM %s",path);
+ if (!FtpSendCmd(buf,'2',nControl))
+ rv = 0;
+ else
+ strncpy(dt, &nControl->response[4], max);
+ return rv;
+}
+\f
+/*
+ * FtpGet - issue a GET command and write received data to output
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpGet(FtpDataCallback cb, const char *path,
+ char mode, netbuf *nControl)
+{
+ return FtpXfer(cb, path, nControl, FTPLIB_FILE_READ, mode);
+}
+\f
+/*
+ * FtpPut - issue a PUT command and send data from input
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpPut(FtpDataCallback cb, const char *path, char mode,
+ netbuf *nControl)
+{
+ return FtpXfer(cb, path, nControl, FTPLIB_FILE_WRITE, mode);
+}
+\f
+/*
+ * FtpRename - rename a file at remote
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpRename(const char *src, const char *dst, netbuf *nControl)
+{
+ char cmd[TMP_BUFSIZ];
+
+ if (((strlen(src) + 7) > sizeof(cmd)) ||
+ ((strlen(dst) + 7) > sizeof(cmd)))
+ return 0;
+ sprintf(cmd,"RNFR %s",src);
+ if (!FtpSendCmd(cmd,'3',nControl))
+ return 0;
+ sprintf(cmd,"RNTO %s",dst);
+ if (!FtpSendCmd(cmd,'2',nControl))
+ return 0;
+ return 1;
+}
+\f
+/*
+ * FtpDelete - delete a file at remote
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpDelete(const char *fnm, netbuf *nControl)
+{
+ char cmd[TMP_BUFSIZ];
+
+ if ((strlen(fnm) + 7) > sizeof(cmd))
+ return 0;
+ sprintf(cmd,"DELE %s",fnm);
+ if (!FtpSendCmd(cmd,'2', nControl))
+ return 0;
+ return 1;
+}
+\f
+/*
+ * FtpQuit - disconnect from remote
+ *
+ * return 1 if successful, 0 otherwise
+ */
+GLOBALDEF int FtpQuit(netbuf *nControl)
+{
+ if (nControl->dir != FTPLIB_CONTROL)
+ return 0;
+ FtpSendCmd("QUIT",'2',nControl);
+ net_close(nControl->handle);
+ free(nControl->buf);
+ free(nControl);
+ return 1;
+}
--- /dev/null
+/***************************************************************************/
+/* */
+/* ftplib.h - header file for callable ftp access routines */
+/* Copyright (C) 1996-2001, 2013 Thomas Pfau, tfpfau@gmail.com */
+/* 1407 Thomas Ave, North Brunswick, NJ, 08902 */
+/* */
+/* This library is free software. You can redistribute it and/or */
+/* modify it under the terms of the Artistic License 2.0. */
+/* */
+/* This library is distributed in the hope that it will be useful, */
+/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
+/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
+/* Artistic License 2.0 for more details. */
+/* */
+/* See the file LICENSE or */
+/* http://www.perlfoundation.org/artistic_license_2_0 */
+/* */
+/***************************************************************************/
+
+#if !defined(__FTPLIB_H)
+#define __FTPLIB_H
+
+#if defined(__unix__) || defined(VMS)
+#define GLOBALDEF
+#define GLOBALREF extern
+#elif defined(_WIN32)
+#if defined BUILDING_LIBRARY
+#define GLOBALDEF __declspec(dllexport)
+#define GLOBALREF __declspec(dllexport)
+#else
+#define GLOBALREF __declspec(dllimport)
+#endif
+#endif
+
+#include <limits.h>
+#include <inttypes.h>
+
+/* FtpAccess() type codes */
+#define FTPLIB_DIR 1
+#define FTPLIB_DIR_VERBOSE 2
+#define FTPLIB_FILE_READ 3
+#define FTPLIB_FILE_WRITE 4
+
+/* FtpAccess() mode codes */
+#define FTPLIB_ASCII 'A'
+#define FTPLIB_IMAGE 'I'
+#define FTPLIB_TEXT FTPLIB_ASCII
+#define FTPLIB_BINARY FTPLIB_IMAGE
+
+/* connection modes */
+#define FTPLIB_PASSIVE 1
+#define FTPLIB_PORT 2
+
+/* connection option names */
+#define FTPLIB_CONNMODE 1
+#define FTPLIB_CALLBACK 2
+#define FTPLIB_IDLETIME 3
+#define FTPLIB_CALLBACKARG 4
+#define FTPLIB_CALLBACKBYTES 5
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(__UINT64_MAX)
+typedef uint64_t fsz_t;
+#else
+typedef uint32_t fsz_t;
+#endif
+
+typedef struct NetBuf netbuf;
+typedef int (*FtpCallback)(netbuf *nControl, fsz_t xfered, void *arg);
+
+typedef struct FtpCallbackOptions {
+ FtpCallback cbFunc; /* function to call */
+ void *cbArg; /* argument to pass to function */
+ unsigned int bytesXferred; /* callback if this number of bytes transferred */
+ unsigned int idleTime; /* callback if this many milliseconds have elapsed */
+} FtpCallbackOptions;
+
+typedef int (*FtpDataCallback)(char *, int);
+typedef int (*FtpResponseCallback)(netbuf *nControl, const char *resp);
+GLOBALREF int FtpSetResponseCallback(FtpResponseCallback, netbuf *);
+GLOBALDEF int FtpSetSendCmdCallback(FtpResponseCallback, netbuf *);
+
+GLOBALREF int ftplib_debug;
+GLOBALREF void FtpInit(void);
+GLOBALREF char *FtpLastResponse(netbuf *nControl);
+GLOBALREF int FtpConnect(const char *host, FtpResponseCallback respcb, netbuf **nControl);
+GLOBALREF int FtpOptions(int opt, long val, netbuf *nControl);
+GLOBALREF int FtpSetCallback(const FtpCallbackOptions *opt, netbuf *nControl);
+GLOBALREF int FtpClearCallback(netbuf *nControl);
+GLOBALREF int FtpLogin(const char *user, const char *pass, netbuf *nControl);
+GLOBALREF int FtpAccess(const char *path, int typ, int mode, netbuf *nControl,
+ netbuf **nData);
+GLOBALREF int FtpRead(void *buf, int max, netbuf *nData);
+GLOBALREF int FtpWrite(const void *buf, int len, netbuf *nData);
+GLOBALREF int FtpClose(netbuf *nData);
+GLOBALREF int FtpSite(const char *cmd, netbuf *nControl);
+GLOBALREF int FtpSysType(char *buf, int max, netbuf *nControl);
+GLOBALREF int FtpMkdir(const char *path, netbuf *nControl);
+GLOBALREF int FtpChdir(const char *path, netbuf *nControl);
+GLOBALREF int FtpCDUp(netbuf *nControl);
+GLOBALREF int FtpRmdir(const char *path, netbuf *nControl);
+GLOBALREF int FtpPwd(char *path, int max, netbuf *nControl);
+GLOBALREF int FtpNlst(FtpDataCallback cb, const char *path, netbuf *nControl);
+GLOBALREF int FtpDir(FtpDataCallback cb, const char *path, netbuf *nControl);
+GLOBALREF int FtpSize(const char *path, unsigned int *size, char mode, netbuf *nControl);
+#if defined(__UINT64_MAX)
+GLOBALREF int FtpSizeLong(const char *path, fsz_t *size, char mode, netbuf *nControl);
+#endif
+GLOBALREF int FtpModDate(const char *path, char *dt, int max, netbuf *nControl);
+GLOBALREF int FtpGet(FtpDataCallback cb, const char *path, char mode,
+ netbuf *nControl);
+GLOBALREF int FtpPut(FtpDataCallback cb, const char *path, char mode,
+ netbuf *nControl);
+GLOBALREF int FtpRename(const char *src, const char *dst, netbuf *nControl);
+GLOBALREF int FtpDelete(const char *fnm, netbuf *nControl);
+GLOBALREF int FtpQuit(netbuf *nControl);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif /* __FTPLIB_H */
--- /dev/null
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="Pragma" content="no-cache">
+ <meta http-equiv="Expires" content="-1">
+ <title>CrFTP Demo</title>
+ <script type="text/javascript" src="crftp.js"></script>
+ <script type="text/javascript" src="main.js"></script>
+ <link rel="stylesheet" type="text/css" href="crftp.css">
+</head>
+
+<body>
+ <h1>CrFTP Demo</h1>
+ <h2>Status: <code id='status'>Initializing JS</code></h2>
+ <hr>
+ <div id='UI'>
+ <button id='connect'>Connect</button>
+ ftp://
+ <input id='user' type='text' value='anonymous'>
+ :
+ <input id='pass' type='text' value='luser@crftp'>
+ @
+ <input id='host' type='text' value='192.168.0.2'>
+ :
+ <input id='port' type='number' value='21' min='1' max='65535'>
+ <br>
+ command: <input id='cmd' type='text' value='dir null'>
+ <br>
+ file transfer:
+ <button id='upload'>Up</button>
+ <button id='download'>Down</button>
+ <input id='files' type='file'>
+ </div>
+ <hr>
+ <p><b>Log:</b></p>
+ <div id='log'></div>
+ <div id='listener'></div>
+</body>
+</html>
--- /dev/null
+/* $KAME: inet_addr.c,v 1.5 2001/08/20 02:32:40 itojun Exp $ */
+
+/*
+ * ++Copyright++ 1983, 1990, 1993
+ * -
+ * Copyright (c) 1983, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * -
+ * Portions Copyright (c) 1993 by Digital Equipment Corporation.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies, and that
+ * the name of Digital Equipment Corporation not be used in advertising or
+ * publicity pertaining to distribution of the document or software without
+ * specific, written prior permission.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT
+ * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ * -
+ * --Copyright--
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)inet_addr.c 8.1 (Berkeley) 6/17/93";
+#endif /* LIBC_SCCS and not lint */
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <machine/endian.h>
+
+#include <sys/param.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+/*
+ * ASCII internet address interpretation routine.
+ * The value returned is in network order.
+ */
+in_addr_t /* XXX should be struct in_addr :( */
+inet_addr(cp)
+ const char *cp;
+{
+ struct in_addr val;
+
+ if (inet_aton(cp, &val))
+ return (val.s_addr);
+ return (INADDR_NONE);
+}
+
+/*
+ * Check whether "cp" is a valid ASCII representation
+ * of an Internet address and convert to a binary address.
+ * Returns 1 if the address is valid, 0 if not.
+ * This replaces inet_addr, the return value from which
+ * cannot distinguish between failure and a local broadcast address.
+ */
+int
+inet_aton(cp, addr)
+ const char *cp;
+ struct in_addr *addr;
+{
+ u_long parts[4];
+ in_addr_t val;
+ char *c;
+ char *endptr;
+ int gotend, n;
+
+ c = (char *)cp;
+ n = 0;
+ /*
+ * Run through the string, grabbing numbers until
+ * the end of the string, or some error
+ */
+ gotend = 0;
+ while (!gotend) {
+ errno = 0;
+ val = strtoul(c, &endptr, 0);
+
+ if (errno == ERANGE) /* Fail completely if it overflowed. */
+ return (0);
+
+ /*
+ * If the whole string is invalid, endptr will equal
+ * c.. this way we can make sure someone hasn't
+ * gone '.12' or something which would get past
+ * the next check.
+ */
+ if (endptr == c)
+ return (0);
+ parts[n] = val;
+ c = endptr;
+
+ /* Check the next character past the previous number's end */
+ switch (*c) {
+ case '.' :
+ /* Make sure we only do 3 dots .. */
+ if (n == 3) /* Whoops. Quit. */
+ return (0);
+ n++;
+ c++;
+ break;
+
+ case '\0':
+ gotend = 1;
+ break;
+
+ default:
+ if (isspace((unsigned char)*c)) {
+ gotend = 1;
+ break;
+ } else
+ return (0); /* Invalid character, so fail */
+ }
+
+ }
+
+ /*
+ * Concoct the address according to
+ * the number of parts specified.
+ */
+
+ switch (n) {
+ case 0: /* a -- 32 bits */
+ /*
+ * Nothing is necessary here. Overflow checking was
+ * already done in strtoul().
+ */
+ break;
+ case 1: /* a.b -- 8.24 bits */
+ if (val > 0xffffff || parts[0] > 0xff)
+ return (0);
+ val |= parts[0] << 24;
+ break;
+
+ case 2: /* a.b.c -- 8.8.16 bits */
+ if (val > 0xffff || parts[0] > 0xff || parts[1] > 0xff)
+ return (0);
+ val |= (parts[0] << 24) | (parts[1] << 16);
+ break;
+
+ case 3: /* a.b.c.d -- 8.8.8.8 bits */
+ if (val > 0xff || parts[0] > 0xff || parts[1] > 0xff ||
+ parts[2] > 0xff)
+ return (0);
+ val |= (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8);
+ break;
+ }
+
+ if (addr != NULL)
+ addr->s_addr = htonl(val);
+ return (1);
+}
+
+#if 0
+/*
+ * Weak aliases for applications that use certain private entry points,
+ * and fail to include <arpa/inet.h>.
+ */
+#undef inet_addr
+__weak_reference(__inet_addr, inet_addr);
+#undef inet_aton
+__weak_reference(__inet_aton, inet_aton);
+#endif
--- /dev/null
+// Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it.
+
+function set_ui(enabled)
+{
+ var eles = document.querySelectorAll('#UI *');
+ for (var i = 0; i < eles.length; ++i)
+ eles[i].disabled = !enabled;
+}
+
+function connect()
+{
+ var c = $('#connect');
+ var cmd = $('#cmd');
+ if (c.innerText == 'Connect') {
+ crftp.connect($('#host').value + ':' + $('#port').value);
+ crftp.login($('#user').value, $('#pass').value);
+ c.innerText = 'Disconnect';
+ cmd.disabled = false;
+ cmd.focus();
+ } else {
+ crftp.disconnect();
+ c.innerText = 'Connect';
+ cmd.disabled = true;
+ }
+}
+
+function send_command(e)
+{
+ if (e.keyCode == 13) {
+ var args = this.value.split(' ');
+ for (var i = 0; i < args.length; ++i)
+ if (args[i] === "null")
+ args[i] = null;
+ crftp.raw.apply(null, args);
+ }
+}
+
+function files_get()
+{
+ var ret = [];
+ var files = $('#files').files;
+
+ if (files.length == 0) {
+ alert('select some files first');
+ return ret;
+ }
+
+ for (var i = 0; i < files.length; ++i) {
+ ret.push([
+ URL.createObjectURL(files[i]),
+ files[i].name,
+ ]);
+ }
+
+ return ret;
+}
+
+function upload()
+{
+ crftp.put(files_get());
+}
+
+function download()
+{
+// b = new Blob();
+// u = URL.createObjectURL(b);
+// crftp.get([[u, 'Makefile']]);
+// crftp.get(files_get());
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+ $('#cmd').onkeypress = send_command;
+ $('#connect').onclick = connect;
+ $('#upload').onclick = upload;
+ $('#download').onclick = download;
+
+ crftp.initialize('crftp.nmf');
+});
--- /dev/null
+// Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it.
+
+#include <assert.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ftplib/ftplib.h"
+
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/pp_module.h"
+#include "ppapi/c/pp_var.h"
+#include "ppapi/c/ppb.h"
+#include "ppapi/c/ppb_console.h"
+#include "ppapi/c/ppb_instance.h"
+#include "ppapi/c/ppb_messaging.h"
+#include "ppapi/c/ppb_url_loader.h"
+#include "ppapi/c/ppb_url_request_info.h"
+#include "ppapi/c/ppb_url_response_info.h"
+#include "ppapi/c/ppb_var.h"
+#include "ppapi/c/ppb_var_array.h"
+#include "ppapi/c/ppp.h"
+#include "ppapi/c/ppp_instance.h"
+#include "ppapi/c/ppp_messaging.h"
+#include "nacl_io/nacl_io.h"
+
+typedef struct PP_Var PP_Var;
+typedef struct PP_CompletionCallback PP_CompletionCallback;
+
+static PP_Instance pp_instance = 0;
+static PPB_GetInterface ppb_get_browser_interface = NULL;
+static const PPB_Console *ppb_console_interface = NULL;
+static const PPB_Messaging *ppb_messaging_interface = NULL;
+static const PPB_URLLoader *ppb_urlloader_interface = NULL;
+static const PPB_URLRequestInfo *ppb_urlrequestinfo_interface = NULL;
+static const PPB_URLResponseInfo *ppb_urlresponseinfo_interface = NULL;
+static const PPB_Var *ppb_var_interface = NULL;
+static const PPB_VarArray *ppb_var_array_interface = NULL;
+
+#include "util.h"
+#include "queue.h"
+
+/** A handle to the thread the handles messages. */
+static pthread_t handle_message_thread;
+
+static netbuf *pp_conn = NULL;
+
+enum {
+ STATUS_DEBUG = 0,
+ STATUS_RESPONSE,
+ STATUS_CMD,
+ STATUS_ERROR,
+ STATUS_RELEASE,
+};
+__attribute__((format(printf, 2, 3)))
+static void SendStatus(int32_t status, const char *format, ...)
+{
+ va_list args;
+ PP_Var var_array, var_status, var_msg;
+ char *string;
+
+ va_start(args, format);
+ string = VprintfToNewString(format, args);
+ va_end(args);
+
+ var_status = PP_MakeInt32(status);
+ var_msg = ppb_var_interface->VarFromUtf8(string, strlen(string));
+ free(string);
+
+ var_array = ppb_var_array_interface->Create();
+ ppb_var_array_interface->Set(var_array, 0, var_status);
+ ppb_var_array_interface->Set(var_array, 1, var_msg);
+
+ ppb_messaging_interface->PostMessage(pp_instance, var_array);
+ ppb_var_interface->Release(var_msg);
+ ppb_var_interface->Release(var_array);
+}
+
+static int
+ftplib_data_debug_callback(char *buf, int len)
+{
+ SendStatus(STATUS_DEBUG, "%*s", len, buf);
+ return len;
+}
+
+static int
+ftplib_data_response_callback(char *buf, int len)
+{
+ SendStatus(STATUS_RESPONSE, "%*s", len, buf);
+ return len;
+}
+
+static int _FtpQuit(netbuf *conn)
+{
+ /* Make sure to invalidate our handle since the core freed it. */
+ pp_conn = NULL;
+
+ /* First try a simple quit. If that fails, close it harder. */
+ return FtpQuit(conn) ? : FtpClose(conn);
+}
+static int _FtpDir(const char *path, netbuf *conn)
+{
+ return FtpDir(ftplib_data_response_callback, path, conn);
+}
+static int _FtpNlst(const char *path, netbuf *conn)
+{
+ return FtpNlst(ftplib_data_response_callback, path, conn);
+}
+static int _FtpPwd(netbuf *conn)
+{
+ char b[1];
+ return FtpPwd(b, sizeof(b), conn);
+}
+static int _FtpSize(const char *path, netbuf *conn)
+{
+ fsz_t size;
+ return FtpSizeLong(path, &size, FTPLIB_BINARY, conn);
+}
+static int _FtpSysType(netbuf *conn)
+{
+ char b[1];
+ return FtpSysType(b, sizeof(b), conn);
+}
+static int _FtpCat(const char *path, netbuf *conn)
+{
+ return FtpGet(ftplib_data_debug_callback, path, FTPLIB_ASCII, conn);
+}
+
+static int _FtpPut(const char *url, const char *name, netbuf *conn)
+{
+ int32_t result;
+ int ret;
+ netbuf *data;
+ char buf[8192];
+
+ /* Start the FTP data connection. */
+ ret = FtpAccess(name, FTPLIB_FILE_WRITE, FTPLIB_BINARY, conn, &data);
+ if (!ret)
+ return ret;
+ SendStatus(STATUS_DEBUG, "Data connection established");
+
+ /* Set up the URL reader. */
+ PP_Resource url_rc = ppb_urlloader_interface->Create(pp_instance);
+
+ PP_Resource url_request_rc = ppb_urlrequestinfo_interface->Create(pp_instance);
+ PP_Var var_url = CStrToVar(url);
+ PP_Var var_method = CStrToVar("GET");
+ ppb_urlrequestinfo_interface->SetProperty(url_request_rc, PP_URLREQUESTPROPERTY_URL, var_url);
+ ppb_urlrequestinfo_interface->SetProperty(url_request_rc, PP_URLREQUESTPROPERTY_METHOD, var_method);
+
+ result = ppb_urlloader_interface->Open(url_rc, url_request_rc, PP_BlockUntilComplete());
+ if (result != PP_OK) {
+ SendStatus(STATUS_ERROR, "internal error: open url failed %s", url);
+ goto done;
+ }
+
+ /* Start streaming the data to the server. */
+ do {
+ result = ppb_urlloader_interface->ReadResponseBody(url_rc, buf, sizeof(buf), PP_BlockUntilComplete());
+ if (result > 0)
+ FtpWrite(buf, result, data);
+ } while (result > 0);
+ if (result != PP_OK)
+ SendStatus(STATUS_ERROR, "internal error: reading url failed %i", result);
+
+ done:
+ /* Break down the URL streamer and FTP connection. */
+ SendStatus(STATUS_RELEASE, "%s", url);
+ SendStatus(STATUS_DEBUG, "Data connection finished");
+ ppb_urlloader_interface->Close(url_rc);
+ ppb_var_interface->Release(var_method);
+ ppb_var_interface->Release(var_url);
+ ppb_urlloader_interface->Close(url_rc);
+
+ FtpClose(data);
+
+ return ret;
+}
+
+static int _FtpGet(const char *url, const char *name, netbuf *conn)
+{
+ int32_t result;
+ int ret;
+ netbuf *data;
+ char buf[8192];
+
+ /* Start the FTP data connection. */
+ ret = FtpAccess(name, FTPLIB_FILE_READ, FTPLIB_BINARY, conn, &data);
+ if (!ret)
+ return ret;
+ SendStatus(STATUS_DEBUG, "Data connection established");
+
+ /* Set up the URL writer. */
+ PP_Resource url_rc = ppb_urlloader_interface->Create(pp_instance);
+
+ PP_Resource url_request_rc = ppb_urlrequestinfo_interface->Create(pp_instance);
+ PP_Var var_url = CStrToVar(url);
+ PP_Var var_method = CStrToVar("PUT");
+ ppb_urlrequestinfo_interface->SetProperty(url_request_rc, PP_URLREQUESTPROPERTY_URL, var_url);
+ ppb_urlrequestinfo_interface->SetProperty(url_request_rc, PP_URLREQUESTPROPERTY_METHOD, var_method);
+
+ /* Start streaming the data from the server. */
+ do {
+ ret = FtpRead(buf, sizeof(buf), data);
+ if (ret > 0) {
+ result = ppb_urlrequestinfo_interface->AppendDataToBody(url_request_rc, buf, ret);
+ if (result != PP_TRUE) {
+ SendStatus(STATUS_ERROR, "internal error: appending body failed %i", result);
+ break;
+ }
+ }
+//SendStatus(STATUS_DEBUG, "ret = %i result = %i", ret, result);
+ } while (ret > 0);
+
+ /* Save the data to the URL. */
+ result = ppb_urlloader_interface->Open(url_rc, url_request_rc, PP_BlockUntilComplete());
+ if (result != PP_OK)
+ SendStatus(STATUS_ERROR, "internal error: open url failed %s", url);
+
+ /* Break down the URL streamer and FTP connection. */
+ SendStatus(STATUS_RELEASE, "%s", url);
+ SendStatus(STATUS_DEBUG, "Data connection finished");
+ ppb_urlloader_interface->Close(url_rc);
+ ppb_var_interface->Release(var_method);
+ ppb_var_interface->Release(var_url);
+ ppb_urlloader_interface->Close(url_rc);
+
+ FtpClose(data);
+
+ return ret;
+}
+
+struct cmd {
+ const char *cmd;
+ const char *usage;
+ uint32_t argc;
+ union {
+ int (*a0)(netbuf *);
+ int (*a1)(const char *, netbuf *);
+ int (*a2)(const char *, const char *, netbuf *);
+ } cb;
+ const char * const aliases[10];
+};
+#define A(n, f) n, .cb.a##n = f
+static const struct cmd cmds[] = {
+ { "cat", "<path", A(1, _FtpCat), },
+ { "cdup", NULL, A(0, FtpCDUp), },
+ { "chdir", "<path>", A(1, FtpChdir),
+ {"cd"}, },
+ { "delete", "<path>", A(1, FtpDelete),
+ {"del",
+ "dele",
+ "rm",
+ "remove",
+ "unlink"}, },
+ { "dir", "<path>", A(1, _FtpDir), },
+ { "quit", NULL, A(0, _FtpQuit),
+ {"close",
+ "disconnect",
+ "exit"}, },
+ { "get", "<local> <remote>", A(2, _FtpGet), },
+ { "list", "<path>", A(1, _FtpNlst),
+ {"ls"}, },
+ { "login", "<user> <pass>", A(2, FtpLogin), },
+ { "mkdir", "<path>", A(1, FtpMkdir),
+ {"mkd"}, },
+ { "pwd", NULL, A(0, _FtpPwd),
+ {"cwd"}, },
+ { "put", "<local> <remote>", A(2, _FtpPut),
+ {"stor",
+ "store"}, },
+ { "rename", "<src> <dst>", A(2, FtpRename),
+ {"mv"} },
+ { "rmdir", "<path>", A(1, FtpRmdir),
+ {"rmd"}, },
+ { "site", "<cmd>", A(1, FtpSite), },
+ { "size", "<path>", A(1, _FtpSize), },
+ { "syst", NULL, A(0, _FtpSysType),
+ {"systyp",
+ "systype"}, },
+};
+
+static const struct cmd *lookup_cmd(const char *scmd)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(cmds); ++i) {
+ const struct cmd *cmd = &cmds[i];
+ if (!strcmp(cmd->cmd, scmd))
+ return cmd;
+
+ const char * const *aliases = cmd->aliases;
+ size_t a;
+ for (a = 0; aliases[a]; ++a)
+ if (!strcmp(aliases[a], scmd))
+ return cmd;
+ }
+
+ return NULL;
+}
+
+static const struct cmd *check_cmd(const char *scmd, uint32_t argc)
+{
+ const struct cmd *cmd = lookup_cmd(scmd);
+ if (!cmd)
+ return cmd;
+
+ if (cmd->argc != argc) {
+ SendStatus(STATUS_ERROR, "usage: %s %s", scmd, cmd->usage);
+ return NULL;
+ } else
+ return cmd;
+}
+
+static int
+ftplib_response_callback(netbuf *conn, const char *resp)
+{
+ SendStatus(STATUS_RESPONSE, "%s", resp);
+ return 0;
+}
+
+static int
+ftplib_sendcmd_callback(netbuf *conn, const char *resp)
+{
+ SendStatus(STATUS_CMD, "%s", resp);
+ return 0;
+}
+
+/* Process each message the main thread gave us. */
+static void
+HandleMessage(char **argv, uint32_t argc)
+{
+ int ret;
+ uint32_t i;
+ const struct cmd *cmd;
+
+ console.log("DEBUG");
+ for (i = 0; i < argc; ++i)
+ console.log(argv[i] ? : "<NULL>");
+
+ /* Handle the connect command specially to deal with state. */
+ if (!strcmp(argv[0], "connect")) {
+ if (argc != 2) {
+ SendStatus(STATUS_ERROR, "usage: connect <host[:port]>");
+ return;
+ }
+
+ if (pp_conn)
+ FtpClose(pp_conn);
+
+ SendStatus(STATUS_DEBUG, "Connecting to %s ...", argv[1]);
+ if (!FtpConnect(argv[1], ftplib_response_callback, &pp_conn)) {
+ SendStatus(STATUS_ERROR, "Connection failed");
+ } else {
+ SendStatus(STATUS_DEBUG, "Connection established");
+ FtpSetSendCmdCallback(ftplib_sendcmd_callback, pp_conn);
+ }
+
+ return;
+ } else if (pp_conn == NULL) {
+ SendStatus(STATUS_ERROR, "Not connected!");
+ return;
+ }
+
+ cmd = check_cmd(argv[0], argc - 1);
+ if (cmd == NULL) {
+ SendStatus(STATUS_ERROR, "unknown command: %s", argv[0]);
+ return;
+ }
+
+ switch (cmd->argc) {
+ case 0: ret = cmd->cb.a0(pp_conn); break;
+ case 1: ret = cmd->cb.a1(argv[1], pp_conn); break;
+ case 2: ret = cmd->cb.a2(argv[1], argv[2], pp_conn); break;
+ default:
+ ret = 0;
+ console.log("unhandled arg count");
+ }
+ if (ret == 0)
+ SendStatus(STATUS_ERROR, "error %s", FtpLastResponse(pp_conn));
+}
+
+/* Background worker thread. */
+static void *
+HandleMessageThread(void *arg)
+{
+ while (1) {
+ struct q_ele *msg = DequeueMessage();
+ HandleMessage(msg->argv, msg->argc);
+ CArrFree(msg->argv, msg->argc);
+ }
+}
+
+/* Callback when the <embed> is created. */
+static PP_Bool
+Instance_DidCreate(PP_Instance instance, uint32_t argc,
+ const char *argn[], const char *argv[])
+{
+ pp_instance = instance;
+ nacl_io_init_ppapi(instance, ppb_get_browser_interface);
+
+ pthread_create(&handle_message_thread, NULL, &HandleMessageThread, NULL);
+ InitializeMessageQueue();
+
+ console.tip("CrFTP module created (written by Mike Frysinger <vapier@gmail.com>)");
+ extern char *version;
+ console.tip(version);
+
+ return PP_TRUE;
+}
+
+/* Callback when the instance is destroyed. */
+static void
+Instance_DidDestroy(PP_Instance instance)
+{
+ if (pp_conn)
+ FtpClose(pp_conn);
+ pp_conn = NULL;
+}
+
+/* Callback when the <embed> changes. */
+static void
+Instance_DidChangeView(PP_Instance instance, PP_Resource view_resource)
+{}
+
+/* Callback when the <embed> focus changes. */
+static void
+Instance_DidChangeFocus(PP_Instance instance, PP_Bool has_focus)
+{}
+
+/* Callback when the document finishes loading. */
+static PP_Bool
+Instance_HandleDocumentLoad(PP_Instance instance, PP_Resource url_loader)
+{
+ /* NaCl modules do not need to handle the document load function. */
+ return PP_FALSE;
+}
+
+/* Callback when the JS sends us a message.
+ *
+ * We expect messages to be an array of commands for the ftplib core.
+ */
+static void
+Messaging_HandleMessage(PP_Instance instance, PP_Var message)
+{
+ if (message.type != PP_VARTYPE_ARRAY) {
+ console.error("Invalid message received");
+ return;
+ }
+
+ char **argv;
+ uint32_t argc;
+ if (!VarToCArr(message, &argv, &argc)) {
+ console.error("Could not read message");
+ return;
+ }
+
+ if (argc == 0) {
+ console.log("Missing command");
+ CArrFree(argv, argc);
+ } else if (!EnqueueMessage(argv, argc)) {
+ SendStatus(STATUS_ERROR, "internal error: dropped message due to full queue");
+ CArrFree(argv, argc);
+ }
+}
+
+/* Callback to get pointers to the other module funcs. */
+PP_EXPORT const void *
+PPP_GetInterface(const char *interface_name)
+{
+ if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0) {
+ static PPP_Instance instance_interface = {
+ &Instance_DidCreate,
+ &Instance_DidDestroy,
+ &Instance_DidChangeView,
+ &Instance_DidChangeFocus,
+ &Instance_HandleDocumentLoad,
+ };
+ return &instance_interface;
+ } else if (strcmp(interface_name, PPP_MESSAGING_INTERFACE) == 0) {
+ static PPP_Messaging messaging_interface = {
+ &Messaging_HandleMessage,
+ };
+ return &messaging_interface;
+ }
+ return NULL;
+}
+
+/* Callback to initialize the module. */
+PP_EXPORT int32_t
+PPP_InitializeModule(PP_Module a_module_id, PPB_GetInterface get_browser)
+{
+ FtpInit();
+
+ ppb_get_browser_interface = get_browser;
+ ppb_console_interface = get_browser(PPB_CONSOLE_INTERFACE);
+ ppb_messaging_interface = get_browser(PPB_MESSAGING_INTERFACE);
+ ppb_var_interface = get_browser(PPB_VAR_INTERFACE);
+ ppb_var_array_interface = get_browser(PPB_VAR_ARRAY_INTERFACE);
+ ppb_urlloader_interface = get_browser(PPB_URLLOADER_INTERFACE);
+ ppb_urlrequestinfo_interface = get_browser(PPB_URLREQUESTINFO_INTERFACE);
+ ppb_urlresponseinfo_interface = get_browser(PPB_URLRESPONSEINFO_INTERFACE);
+
+ return PP_OK;
+}
+
+/* Callback before we shutdown the module. */
+PP_EXPORT void
+PPP_ShutdownModule(void)
+{
+}
--- /dev/null
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "queue.h"
+
+#include <pthread.h>
+#include <stdlib.h>
+
+#define MAX_QUEUE_SIZE 16
+
+/** A mutex that guards |g_queue|. */
+static pthread_mutex_t g_queue_mutex;
+
+/** A condition variable that is signalled when |g_queue| is not empty. */
+static pthread_cond_t g_queue_not_empty_cond;
+
+/** A circular queue of messages from JavaScript to be handled.
+ *
+ * If g_queue_start < g_queue_end:
+ * all elements in the range [g_queue_start, g_queue_end) are valid.
+ * If g_queue_start > g_queue_end:
+ * all elements in the ranges [0, g_queue_end) and
+ * [g_queue_start, MAX_QUEUE_SIZE) are valid.
+ * If g_queue_start == g_queue_end, and g_queue_size > 0:
+ * all elements in the g_queue are valid.
+ * If g_queue_start == g_queue_end, and g_queue_size == 0:
+ * No elements are valid. */
+static struct q_ele g_queue[MAX_QUEUE_SIZE];
+
+/** The index of the head of the queue. */
+static int g_queue_start = 0;
+
+/** The index of the tail of the queue, non-inclusive. */
+static int g_queue_end = 0;
+
+/** The size of the queue. */
+static int g_queue_size = 0;
+
+/** Return whether the queue is empty.
+ *
+ * NOTE: this function assumes g_queue_mutex lock is held.
+ * @return non-zero if the queue is empty. */
+static int IsQueueEmpty() { return g_queue_size == 0; }
+
+/** Return whether the queue is full.
+ *
+ * NOTE: this function assumes g_queue_mutex lock is held.
+ * @return non-zero if the queue is full. */
+static int IsQueueFull() { return g_queue_size == MAX_QUEUE_SIZE; }
+
+/** Initialize the message queue. */
+void InitializeMessageQueue() {
+ pthread_mutex_init(&g_queue_mutex, NULL);
+ pthread_cond_init(&g_queue_not_empty_cond, NULL);
+}
+
+/** Enqueue a message (i.e. add to the end)
+ *
+ * If the queue is full, the message will be dropped.
+ *
+ * NOTE: this function assumes g_queue_mutex is _NOT_ held.
+ * @param[in] message The message to enqueue.
+ * @return non-zero if the message was added to the queue. */
+bool EnqueueMessage(char **argv, uint32_t argc) {
+ pthread_mutex_lock(&g_queue_mutex);
+
+ /* We shouldn't block the main thread waiting for the queue to not be full,
+ * so just drop the message. */
+ if (IsQueueFull()) {
+ pthread_mutex_unlock(&g_queue_mutex);
+ return false;
+ }
+
+ struct q_ele *msg = &g_queue[g_queue_end];
+ msg->argv = argv;
+ msg->argc = argc;
+ g_queue_end = (g_queue_end + 1) % MAX_QUEUE_SIZE;
+ g_queue_size++;
+
+ pthread_cond_signal(&g_queue_not_empty_cond);
+
+ pthread_mutex_unlock(&g_queue_mutex);
+
+ return true;
+}
+
+/** Dequeue a message and return it.
+ *
+ * This function blocks until a message is available. It should not be called
+ * on the main thread.
+ *
+ * NOTE: this function assumes g_queue_mutex is _NOT_ held.
+ * @return The message at the head of the queue. */
+struct q_ele *DequeueMessage(void) {
+ struct q_ele *message = NULL;
+
+ pthread_mutex_lock(&g_queue_mutex);
+
+ while (IsQueueEmpty()) {
+ pthread_cond_wait(&g_queue_not_empty_cond, &g_queue_mutex);
+ }
+
+ message = &g_queue[g_queue_start];
+ g_queue_start = (g_queue_start + 1) % MAX_QUEUE_SIZE;
+ g_queue_size--;
+
+ pthread_mutex_unlock(&g_queue_mutex);
+
+ return message;
+}
--- /dev/null
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef QUEUE_H_
+#define QUEUE_H_
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+/* This file implements a single-producer/single-consumer queue, using a mutex
+ * and a condition variable.
+ *
+ * There are techniques to implement a queue like this without using memory
+ * barriers or locks on x86, but ARM's memory system is different from x86, so
+ * we cannot make the same assumptions about visibility order of writes. Using a
+ * mutex is slower, but also simpler.
+ *
+ * We make the assumption that messages are only enqueued on the main thread
+ * and consumed on the worker thread. Because we don't want to block the main
+ * thread, EnqueueMessage will return zero if the message could not be enqueued.
+ *
+ * DequeueMessage will block until a message is available using a condition
+ * variable. Again, this may not be as fast as spin-waiting, but will consume
+ * much less CPU (and battery), which is important to consider for ChromeOS
+ * devices. */
+
+struct q_ele {
+ char **argv;
+ uint32_t argc;
+};
+
+void InitializeMessageQueue(void);
+bool EnqueueMessage(char **argv, uint32_t argc);
+struct q_ele *DequeueMessage();
+
+#endif /* QUEUE_H_ */
--- /dev/null
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#define MAX(a, b) (((a) > (b)) ? (a) : (b))
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
+
+/**
+ * Creates new string PP_Var from C string. The resulting object will be a
+ * refcounted string object. It will be AddRef()ed for the caller. When the
+ * caller is done with it, it should be Release()d.
+ * @param[in] str C string to be converted to PP_Var
+ * @return PP_Var containing string.
+ */
+static struct PP_Var CStrToVar(const char *str)
+{
+ if (ppb_var_interface)
+ return ppb_var_interface->VarFromUtf8(str, strlen(str));
+ else
+ return PP_MakeUndefined();
+}
+
+/**
+ * Convert a PP_Var to a C string, given a buffer.
+ * @param[in] var The PP_Var to convert.
+ * @param[out] buffer The buffer to write to.
+ * @param[in] length The length of |buffer|.
+ * @return The number of characters written.
+ */
+static uint32_t VarToCStr(struct PP_Var var, char *buffer, uint32_t length)
+{
+ if (ppb_var_interface == NULL)
+ return 0;
+
+ uint32_t var_length;
+ const char* str = ppb_var_interface->VarToUtf8(var, &var_length);
+ /* str is NOT NULL-terminated. Copy using memcpy. */
+ uint32_t min_length = MIN(var_length, length - 1);
+ memcpy(buffer, str, min_length);
+ buffer[min_length] = 0;
+
+ return min_length;
+}
+
+/**
+ * Printf to a newly allocated C string.
+ * @param[in] format A printf format string.
+ * @param[in] args The printf arguments.
+ * @return The newly constructed string. Caller takes ownership. */
+static char *VprintfToNewString(const char *format, va_list args)
+{
+ va_list args_copy;
+ int length;
+ char *buffer;
+ int result;
+
+ va_copy(args_copy, args);
+ length = vsnprintf(NULL, 0, format, args);
+ buffer = malloc(length + 1); /* +1 for NULL-terminator. */
+ result = vsnprintf(&buffer[0], length + 1, format, args_copy);
+ if (result != length) {
+ assert(0);
+ return NULL;
+ }
+ return buffer;
+}
+
+/**
+ * Send a message to the JavaScript Console
+ */
+static void console_log(const char *str, PP_LogLevel level)
+{
+ if (ppb_console_interface) {
+ struct PP_Var var = CStrToVar(str);
+ ppb_console_interface->Log(pp_instance, level, var);
+ ppb_var_interface->Release(var);
+ }
+}
+#define _Console(lvl) \
+static void _console_##lvl(const char *s) { console_log(s, PP_LOGLEVEL_##lvl); }
+_Console(TIP)
+_Console(LOG)
+_Console(WARNING)
+_Console(ERROR)
+#define Console(lvl) _console_##lvl
+
+/*
+ * Create a javaascript like console object:
+ * console.log('foo');
+ */
+static struct {
+ void (*tip)(const char *);
+ void (*log)(const char *);
+ void (*warn)(const char *);
+ void (*error)(const char *);
+} console = {
+ .tip = Console(TIP),
+ .log = Console(LOG),
+ .warn = Console(WARNING),
+ .error = Console(ERROR),
+};
+
+static bool VarToCArr(struct PP_Var var_array, char ***array, uint32_t *length)
+{
+ char buf[1024];
+ uint32_t i;
+
+ if (ppb_var_array_interface == NULL) {
+ fail:
+ *length = 0;
+ return false;
+ }
+
+ *length = ppb_var_array_interface->GetLength(var_array);
+ if (*length > (1 << 16))
+ goto fail;
+ *array = malloc(*length * sizeof(*array));
+ if (!*array)
+ goto fail;
+
+ for (i = 0; i < *length; ++i) {
+ struct PP_Var var_ele = ppb_var_array_interface->Get(var_array, i);;
+ uint32_t len = VarToCStr(var_ele, buf, sizeof(buf));
+ (*array)[i] = len ? strdup(buf) : NULL;
+ }
+
+ return true;
+}
+
+static void CArrFree(char **array, uint32_t length)
+{
+ uint32_t i;
+ for (i = 0; i < length; ++i)
+ free(array[i]);
+ free(array);
+}