]> git.wh0rd.org - chrome-ext/crftp.git/commitdiff
init main
authorMike Frysinger <vapier@gentoo.org>
Sun, 23 Mar 2014 06:52:50 +0000 (02:52 -0400)
committerMike Frysinger <vapier@gentoo.org>
Mon, 31 Mar 2014 05:40:28 +0000 (01:40 -0400)
26 files changed:
.gitignore [new file with mode: 0644]
.gitmodules [new file with mode: 0644]
Makefile [new file with mode: 0644]
chrome-bootstrap [new submodule]
dist/makedist.sh [new file with mode: 0755]
dist/pngcrush.sh [new file with mode: 0755]
html/main.html [new file with mode: 0644]
images/icon-128x128.png [new file with mode: 0644]
images/icon.svg [new file with mode: 0644]
js/launcher.js [new file with mode: 0644]
js/main.js [new file with mode: 0644]
manifest.files [new file with mode: 0644]
manifest.json [new file with mode: 0644]
pnacl/Makefile [new file with mode: 0644]
pnacl/crftp.css [new file with mode: 0644]
pnacl/crftp.js [new file with mode: 0644]
pnacl/ftplib/README [new file with mode: 0644]
pnacl/ftplib/ftplib.c [new file with mode: 0644]
pnacl/ftplib/ftplib.h [new file with mode: 0644]
pnacl/index.html [new file with mode: 0644]
pnacl/inet_addr.c [new file with mode: 0644]
pnacl/main.js [new file with mode: 0644]
pnacl/ppapi.c [new file with mode: 0644]
pnacl/util/queue.c [new file with mode: 0644]
pnacl/util/queue.h [new file with mode: 0644]
pnacl/util/util.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..1477675
--- /dev/null
@@ -0,0 +1,5 @@
+.depend
+*.[ao]
+*.bc
+*.nmf
+*.[np]exe
diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..5623dde
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "chrome-bootstrap"]
+       path = chrome-bootstrap
+       url = https://github.com/roykolak/chrome-bootstrap.git
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..47d1a38
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,23 @@
+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
diff --git a/chrome-bootstrap b/chrome-bootstrap
new file mode 160000 (submodule)
index 0000000..419698e
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit 419698ec6fe922487d2fe3f11c92fd1c8ffbd8a6
diff --git a/dist/makedist.sh b/dist/makedist.sh
new file mode 100755 (executable)
index 0000000..49c277b
--- /dev/null
@@ -0,0 +1,56 @@
+#!/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}"
diff --git a/dist/pngcrush.sh b/dist/pngcrush.sh
new file mode 100755 (executable)
index 0000000..f302a2e
--- /dev/null
@@ -0,0 +1,53 @@
+#!/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 "$@"
diff --git a/html/main.html b/html/main.html
new file mode 100644 (file)
index 0000000..18dee69
--- /dev/null
@@ -0,0 +1,37 @@
+<!-- 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>
diff --git a/images/icon-128x128.png b/images/icon-128x128.png
new file mode 100644 (file)
index 0000000..4eb3b42
Binary files /dev/null and b/images/icon-128x128.png differ
diff --git a/images/icon.svg b/images/icon.svg
new file mode 100644 (file)
index 0000000..0925103
--- /dev/null
@@ -0,0 +1,250 @@
+<?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>
diff --git a/js/launcher.js b/js/launcher.js
new file mode 100644 (file)
index 0000000..47be824
--- /dev/null
@@ -0,0 +1,8 @@
+// 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,
+       });
+});
diff --git a/js/main.js b/js/main.js
new file mode 100644 (file)
index 0000000..71586c6
--- /dev/null
@@ -0,0 +1,10 @@
+// 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');
+});
diff --git a/manifest.files b/manifest.files
new file mode 100644 (file)
index 0000000..d30bc48
--- /dev/null
@@ -0,0 +1,10 @@
+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
diff --git a/manifest.json b/manifest.json
new file mode 100644 (file)
index 0000000..1eb10a5
--- /dev/null
@@ -0,0 +1,30 @@
+{
+  "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"]}
+  ]
+}
diff --git a/pnacl/Makefile b/pnacl/Makefile
new file mode 100644 (file)
index 0000000..866a7d8
--- /dev/null
@@ -0,0 +1,53 @@
+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
diff --git a/pnacl/crftp.css b/pnacl/crftp.css
new file mode 100644 (file)
index 0000000..bc50f0c
--- /dev/null
@@ -0,0 +1,31 @@
+/* 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;
+}
diff --git a/pnacl/crftp.js b/pnacl/crftp.js
new file mode 100644 (file)
index 0000000..4535f25
--- /dev/null
@@ -0,0 +1,198 @@
+// 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,
+       };
+
+}());
diff --git a/pnacl/ftplib/README b/pnacl/ftplib/README
new file mode 100644 (file)
index 0000000..ea7f169
--- /dev/null
@@ -0,0 +1,4 @@
+package: ftplib
+version: 4.0
+license: http://www.perlfoundation.org/artistic_license_2_0
+homepage: http://nbpfaus.net/~pfau/ftplib/
diff --git a/pnacl/ftplib/ftplib.c b/pnacl/ftplib/ftplib.c
new file mode 100644 (file)
index 0000000..dd14486
--- /dev/null
@@ -0,0 +1,1443 @@
+/***************************************************************************/
+/*                                                                        */
+/* 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;
+}
diff --git a/pnacl/ftplib/ftplib.h b/pnacl/ftplib/ftplib.h
new file mode 100644 (file)
index 0000000..36bcacc
--- /dev/null
@@ -0,0 +1,125 @@
+/***************************************************************************/
+/*                                                                        */
+/* 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 */
diff --git a/pnacl/index.html b/pnacl/index.html
new file mode 100644 (file)
index 0000000..7bc3e83
--- /dev/null
@@ -0,0 +1,39 @@
+<!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>
diff --git a/pnacl/inet_addr.c b/pnacl/inet_addr.c
new file mode 100644 (file)
index 0000000..87e2a6b
--- /dev/null
@@ -0,0 +1,203 @@
+/*     $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
diff --git a/pnacl/main.js b/pnacl/main.js
new file mode 100644 (file)
index 0000000..211682a
--- /dev/null
@@ -0,0 +1,78 @@
+// 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');
+});
diff --git a/pnacl/ppapi.c b/pnacl/ppapi.c
new file mode 100644 (file)
index 0000000..8d4e85b
--- /dev/null
@@ -0,0 +1,515 @@
+// 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)
+{
+}
diff --git a/pnacl/util/queue.c b/pnacl/util/queue.c
new file mode 100644 (file)
index 0000000..32c4012
--- /dev/null
@@ -0,0 +1,112 @@
+/* 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;
+}
diff --git a/pnacl/util/queue.h b/pnacl/util/queue.h
new file mode 100644 (file)
index 0000000..059de1d
--- /dev/null
@@ -0,0 +1,38 @@
+/* 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_ */
diff --git a/pnacl/util/util.h b/pnacl/util/util.h
new file mode 100644 (file)
index 0000000..07e746c
--- /dev/null
@@ -0,0 +1,133 @@
+#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);
+}