From: Mike Frysinger Date: Sun, 23 Mar 2014 06:52:50 +0000 (-0400) Subject: init X-Git-Url: https://git.wh0rd.org/?a=commitdiff_plain;ds=inline;p=chrome-ext%2Fcrftp.git init --- 769a03c3016421d96d6fb7e6662687cfa3ab54b4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1477675 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.depend +*.[ao] +*.bc +*.nmf +*.[np]exe diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5623dde --- /dev/null +++ b/.gitmodules @@ -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 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 index 0000000..419698e --- /dev/null +++ b/chrome-bootstrap @@ -0,0 +1 @@ +Subproject commit 419698ec6fe922487d2fe3f11c92fd1c8ffbd8a6 diff --git a/dist/makedist.sh b/dist/makedist.sh new file mode 100755 index 0000000..49c277b --- /dev/null +++ b/dist/makedist.sh @@ -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 index 0000000..f302a2e --- /dev/null +++ b/dist/pngcrush.sh @@ -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 index 0000000..18dee69 --- /dev/null +++ b/html/main.html @@ -0,0 +1,37 @@ + + + + + + CrFTP + + + + + + + Status: Initializing JS +
+ ftp:// + + : + + @ + + : + +
+ + command: +
+ file transfer: + + + +
+ +
+
+ + + diff --git a/images/icon-128x128.png b/images/icon-128x128.png new file mode 100644 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 index 0000000..0925103 --- /dev/null +++ b/images/icon.svg @@ -0,0 +1,250 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + Mixe arrow + 2008-06-08T09:56:02 + a mixed arrow + http://openclipart.org/detail/17306/mixe-arrow-by-czara1 + + + czara1 + + + + + arrow + blue + clip art + clipart + + + + + + + + + + + diff --git a/js/launcher.js b/js/launcher.js new file mode 100644 index 0000000..47be824 --- /dev/null +++ b/js/launcher.js @@ -0,0 +1,8 @@ +// Written by Mike Frysinger . 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 index 0000000..71586c6 --- /dev/null +++ b/js/main.js @@ -0,0 +1,10 @@ +// Written by Mike Frysinger . 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 index 0000000..d30bc48 --- /dev/null +++ b/manifest.files @@ -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 index 0000000..1eb10a5 --- /dev/null +++ b/manifest.json @@ -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 index 0000000..866a7d8 --- /dev/null +++ b/pnacl/Makefile @@ -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 index 0000000..bc50f0c --- /dev/null +++ b/pnacl/crftp.css @@ -0,0 +1,31 @@ +/* Written by Mike Frysinger . 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 index 0000000..4535f25 --- /dev/null +++ b/pnacl/crftp.js @@ -0,0 +1,198 @@ +// Written by Mike Frysinger . 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 index 0000000..ea7f169 --- /dev/null +++ b/pnacl/ftplib/README @@ -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 index 0000000..dd14486 --- /dev/null +++ b/pnacl/ftplib/ftplib.c @@ -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 +#endif +#if defined(_WIN32) +#include +#endif +#include +#include +#include +#include +#include +#if defined(__unix__) +#include +#include +#include +#include +#include +#include +#include +#elif defined(VMS) +#include +#include +#include +#include +#include +#elif defined(_WIN32) +#include +#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 + +#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 + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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 +} + +/* + * 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; +} + +/* + * 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; +} + +GLOBALDEF int FtpSetResponseCallback(FtpResponseCallback respcb, netbuf *nControl) +{ + nControl->respcb = respcb; + return 0; +} +GLOBALDEF int FtpSetSendCmdCallback(FtpResponseCallback sendcb, netbuf *nControl) +{ + nControl->sendcb = sendcb; + return 0; +} + +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; +} + +/* + * 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); +} + +/* + * 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); +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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); +} + +/* + * 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); +} + +/* + * 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; +} + +#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 + +/* + * 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; +} + +/* + * 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); +} + +/* + * 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); +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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 index 0000000..36bcacc --- /dev/null +++ b/pnacl/ftplib/ftplib.h @@ -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 +#include + +/* 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 index 0000000..7bc3e83 --- /dev/null +++ b/pnacl/index.html @@ -0,0 +1,39 @@ + + + + + + CrFTP Demo + + + + + + +

CrFTP Demo

+

Status: Initializing JS

+
+
+ + ftp:// + + : + + @ + + : + +
+ command: +
+ file transfer: + + + +
+
+

Log:

+
+
+ + diff --git a/pnacl/inet_addr.c b/pnacl/inet_addr.c new file mode 100644 index 0000000..87e2a6b --- /dev/null +++ b/pnacl/inet_addr.c @@ -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 +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +/* + * 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 . + */ +#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 index 0000000..211682a --- /dev/null +++ b/pnacl/main.js @@ -0,0 +1,78 @@ +// Written by Mike Frysinger . 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 index 0000000..8d4e85b --- /dev/null +++ b/pnacl/ppapi.c @@ -0,0 +1,515 @@ +// Written by Mike Frysinger . Released into the public domain. Suck it. + +#include +#include +#include +#include +#include +#include +#include +#include + +#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", "", A(1, FtpChdir), + {"cd"}, }, + { "delete", "", A(1, FtpDelete), + {"del", + "dele", + "rm", + "remove", + "unlink"}, }, + { "dir", "", A(1, _FtpDir), }, + { "quit", NULL, A(0, _FtpQuit), + {"close", + "disconnect", + "exit"}, }, + { "get", " ", A(2, _FtpGet), }, + { "list", "", A(1, _FtpNlst), + {"ls"}, }, + { "login", " ", A(2, FtpLogin), }, + { "mkdir", "", A(1, FtpMkdir), + {"mkd"}, }, + { "pwd", NULL, A(0, _FtpPwd), + {"cwd"}, }, + { "put", " ", A(2, _FtpPut), + {"stor", + "store"}, }, + { "rename", " ", A(2, FtpRename), + {"mv"} }, + { "rmdir", "", A(1, FtpRmdir), + {"rmd"}, }, + { "site", "", A(1, FtpSite), }, + { "size", "", 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] ? : ""); + + /* Handle the connect command specially to deal with state. */ + if (!strcmp(argv[0], "connect")) { + if (argc != 2) { + SendStatus(STATUS_ERROR, "usage: connect "); + 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 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 )"); + 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 changes. */ +static void +Instance_DidChangeView(PP_Instance instance, PP_Resource view_resource) +{} + +/* Callback when the 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 index 0000000..32c4012 --- /dev/null +++ b/pnacl/util/queue.c @@ -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 +#include + +#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 index 0000000..059de1d --- /dev/null +++ b/pnacl/util/queue.h @@ -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 +#include + +/* 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 index 0000000..07e746c --- /dev/null +++ b/pnacl/util/util.h @@ -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); +}