From 514721c24c7212c084ad2530e8239ff121097818 Mon Sep 17 00:00:00 2001
From: Mike Buland <eichlan@xagasoft.com>
Date: Mon, 6 Apr 2009 19:13:51 +0000
Subject: Ok, I rearranged some things, we have a tools dir now, those build by
 default. Also I added a bunch of classes that I've been tinkering with that
 are almost ready for use, so I figured I may as well throw them in here.

---
 src/fastcgi.cpp        | 277 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/fastcgi.h          | 120 +++++++++++++++++++++
 src/tests/fastcgi.cpp  |  81 +++++++++++++++
 src/tests/nidstool.cpp | 241 ------------------------------------------
 src/tests/url.cpp      |  45 ++++----
 src/tools/nidstool.cpp | 241 ++++++++++++++++++++++++++++++++++++++++++
 src/unit/array.unit    |   4 +-
 src/urn.cpp            |   0
 src/urn.h              |   0
 9 files changed, 746 insertions(+), 263 deletions(-)
 create mode 100644 src/fastcgi.cpp
 create mode 100644 src/fastcgi.h
 create mode 100644 src/tests/fastcgi.cpp
 delete mode 100644 src/tests/nidstool.cpp
 create mode 100644 src/tools/nidstool.cpp
 create mode 100644 src/urn.cpp
 create mode 100644 src/urn.h

(limited to 'src')

diff --git a/src/fastcgi.cpp b/src/fastcgi.cpp
new file mode 100644
index 0000000..aba3b71
--- /dev/null
+++ b/src/fastcgi.cpp
@@ -0,0 +1,277 @@
+#include "bu/fastcgi.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+
+#include "bu/membuf.h"
+
+#include "bu/sio.h"
+using Bu::sio;
+using Bu::Fmt;
+
+Bu::FastCgi::FastCgi() :
+	pSrv( NULL ),
+	bRunning( true )
+{
+	pSrv = new Bu::ServerSocket( STDIN_FILENO, false );
+}
+
+Bu::FastCgi::FastCgi( int iPort ) :
+	pSrv( NULL ),
+	bRunning( true )
+{
+	pSrv = new Bu::ServerSocket( iPort );
+}
+
+Bu::FastCgi::~FastCgi()
+{
+}
+
+bool Bu::FastCgi::isEmbedded()
+{
+	struct sockaddr name;
+	socklen_t namelen = sizeof(name);
+	if( getpeername( STDIN_FILENO, &name, &namelen ) != 0 &&
+		errno == ENOTCONN )
+	{
+		sio << "errno = " << errno << " (" << strerror( errno ) << ")" <<
+			sio.nl;
+		sio << "Socket found" << sio.nl;
+		return true;
+	}
+	else
+	{
+		sio << "errno = " << errno << " (" << strerror( errno ) << ")" <<
+			sio.nl;
+		sio << "No socket detected, running in standalone mode" << sio.nl;
+		return false;
+	}
+}
+
+void Bu::FastCgi::read( Bu::Socket &s, Bu::FastCgi::Record &r )
+{
+	s.read( &r, sizeof(Record) );
+	r.uRequestId = ntohs( r.uRequestId );
+	r.uContentLength = ntohs( r.uContentLength );
+}
+
+void Bu::FastCgi::write( Bu::Socket &s, Bu::FastCgi::Record r )
+{
+	r.uRequestId = htons( r.uRequestId );
+	r.uContentLength = htons( r.uContentLength );
+	s.write( &r, sizeof(Record) );
+}
+
+void Bu::FastCgi::read( Bu::Socket &s, Bu::FastCgi::BeginRequestBody &b )
+{
+	s.read( &b, sizeof(BeginRequestBody) );
+	b.uRole = ntohs( b.uRole );
+}
+
+void Bu::FastCgi::write( Bu::Socket &s, Bu::FastCgi::EndRequestBody b )
+{
+	b.uStatus = htonl( b.uStatus );
+	s.write( &b, sizeof(b) );
+}
+
+uint32_t Bu::FastCgi::readLen( Bu::Socket &s, uint16_t &uRead )
+{
+	uint8_t uByte[4];
+	s.read( uByte, 1 );
+	uRead++;
+	if( uByte[0] >> 7 == 0 )
+		return uByte[0];
+	
+	s.read( uByte+1, 3 );
+	uRead += 3;
+	return ((uByte[0]&0x7f)<<24)|(uByte[1]<<16)|(uByte[2]<<8)|(uByte[3]);
+}
+
+void Bu::FastCgi::readPair( Bu::Socket &s, StrHash &hParams, uint16_t &uRead )
+{
+	uint32_t uName = readLen( s, uRead );
+	uint32_t uValue = readLen( s, uRead );
+	uRead += uName + uValue;
+	unsigned char *sName = new unsigned char[uName];
+	s.read( sName, uName );
+	Bu::FString fsName( (char *)sName, uName );
+	delete[] sName;
+
+	if( uValue > 0 )
+	{
+		unsigned char *sValue = new unsigned char[uValue];
+		s.read( sValue, uValue );
+		Bu::FString fsValue( (char *)sValue, uValue );
+		hParams.insert( fsName, fsValue );
+		delete[] sValue;
+	}
+	else
+	{
+		hParams.insert( fsName, "" );
+	}
+}
+
+void Bu::FastCgi::run()
+{
+	bRunning = true;
+	while( bRunning )
+	{
+		int iSock = pSrv->accept( 5 );
+		if( iSock < 0 )
+			continue;
+
+		Bu::Socket s( iSock );
+		s.setBlocking( true );
+		try
+		{
+			for(;;)
+			{
+				Record r;
+				read( s, r );
+				while( aChannel.getSize() < r.uRequestId )
+					aChannel.append( NULL );
+				Channel *pChan = NULL;
+				if( r.uRequestId > 0 )
+					pChan = aChannel[r.uRequestId-1];
+
+				sio << "Record (id=" << r.uRequestId << ", len=" << 
+					r.uContentLength << ", pad=" << 
+					(int)r.uPaddingLength << "):  ";
+				fflush( stdout );
+				
+				switch( (RequestType)r.uType )
+				{
+					case typeBeginRequest:
+						sio << "Begin Request.";
+						{
+							BeginRequestBody b;
+							read( s, b );
+							if( pChan != NULL )
+							{
+								sio << "Error!!!" << sio.nl;
+								return;
+							}
+							pChan = aChannel[r.uRequestId-1] = new Channel();
+						}
+						break;
+
+					case typeParams:
+						sio << "Params.";
+						if( r.uContentLength == 0 )
+						{
+							pChan->uFlags |= chflgParamsDone;
+						}
+						else
+						{
+							uint16_t uUsed = 0;
+							while( uUsed < r.uContentLength )
+							{
+								readPair( s, pChan->hParams, uUsed );
+							}
+						}
+						break;
+
+					case typeStdIn:
+						sio << "StdIn.";
+						if( r.uContentLength == 0 )
+						{
+							pChan->uFlags |= chflgStdInDone;
+						}
+						else
+						{
+							char *buf = new char[r.uContentLength];
+							sio << " (read " << s.read( buf, r.uContentLength )
+								<< ")";
+							pChan->sStdIn.append( buf, r.uContentLength );
+							delete[] buf;
+						}
+						break;
+
+					case typeData:
+						sio << "Data.";
+						if( r.uContentLength == 0 )
+						{
+							pChan->uFlags |= chflgDataDone;
+						}
+						else
+						{
+							char *buf = new char[r.uContentLength];
+							s.read( buf, r.uContentLength );
+							pChan->sData.append( buf, r.uContentLength );
+							delete[] buf;
+						}
+						break;
+
+					case typeStdOut:
+					case typeStdErr:
+					case typeEndRequest:
+					case typeAbortRequest:
+					case typeGetValuesResult:
+						sio << "Scary.";
+						// ??? we shouldn't get these.
+						break;
+					
+				}
+
+				sio << sio.nl;
+
+				if( pChan )
+				{
+					if( pChan->uFlags == chflgAllDone )
+					{
+						Bu::MemBuf mStdOut, mStdErr;
+						int iRet = request(
+							pChan->hParams, pChan->sStdIn,
+							mStdOut, mStdErr
+							);
+
+						Bu::FString &sStdOut = mStdOut.getString();
+						Bu::FString &sStdErr = mStdErr.getString();
+
+						Record rOut;
+						rOut.uVersion = 1;
+						rOut.uRequestId = r.uRequestId;
+						rOut.uPaddingLength = 0;
+						if( sStdOut.getSize() > 0 )
+						{
+							rOut.uType = typeStdOut;
+							rOut.uContentLength = sStdOut.getSize();
+							write( s, rOut );
+							s.write( sStdOut );
+						}
+						rOut.uType = typeStdOut;
+						rOut.uContentLength = 0;
+						write( s, rOut );
+						if( sStdErr.getSize() > 0 )
+						{
+							rOut.uType = typeStdErr;
+							rOut.uContentLength = sStdErr.getSize();
+							write( s, rOut );
+							s.write( sStdOut );
+						}
+						rOut.uType = typeStdErr;
+						rOut.uContentLength = 0;
+						write( s, rOut );
+
+						rOut.uType = typeEndRequest;
+						rOut.uContentLength = 8;
+						write( s, rOut );
+						
+						EndRequestBody b;
+						memset( &b, 0, sizeof(b) );
+						b.uStatus =  iRet;
+						write( s, b );
+
+						delete pChan;
+						aChannel[r.uRequestId-1] = NULL;
+					}
+				}
+			}
+		}
+		catch( Bu::SocketException &e )
+		{
+			//sio << "Bu::SocketException: " << e.what() << sio.nl;
+		}
+	}
+}
+
diff --git a/src/fastcgi.h b/src/fastcgi.h
new file mode 100644
index 0000000..83e9b83
--- /dev/null
+++ b/src/fastcgi.h
@@ -0,0 +1,120 @@
+#ifndef BU_FAST_CGI_H
+#define BU_FAST_CGI_H
+
+#include "bu/fstring.h"
+#include "bu/hash.h"
+#include "bu/array.h"
+#include "bu/socket.h"
+#include "bu/serversocket.h"
+
+namespace Bu
+{
+	class Stream;
+
+	class FastCgi
+	{
+	public:
+		FastCgi( int iPort );
+		FastCgi();
+		virtual ~FastCgi();
+
+		static bool isEmbedded();
+
+		typedef Bu::Hash<Bu::FString, Bu::FString> StrHash;
+		enum RequestType
+		{
+			typeBeginRequest	= 1,
+			typeAbortRequest	= 2,
+			typeEndRequest		= 3,
+			typeParams			= 4,
+			typeStdIn			= 5,
+			typeStdOut			= 6,
+			typeStdErr			= 7,
+			typeData			= 8,
+			typeGetValues		= 9,
+			typeGetValuesResult	= 10
+		};
+
+		enum Role
+		{
+			roleResponder		= 1,
+			roleAuthorizer		= 2,
+			roleFilter			= 3
+		};
+
+		enum Flags
+		{
+			flagsKeepConn		= 1
+		};
+
+		enum Status
+		{
+			statusRequestComplete	= 0,
+			statusCantMpxConn		= 1,
+			statusOverloaded		= 2,
+			statusUnknownRole		= 3
+		};
+
+		typedef struct {
+			uint8_t uVersion;
+			uint8_t uType;
+			uint16_t uRequestId;
+			uint16_t uContentLength;
+			uint8_t uPaddingLength;
+			uint8_t uReserved;
+		} Record;
+
+		typedef struct {
+			uint16_t uRole;
+			uint8_t uFlags;
+			uint8_t reserved[5];
+		} BeginRequestBody;
+
+		typedef struct {
+			uint32_t uStatus;
+			uint8_t uProtocolStatus;
+			uint8_t reserved[3];
+		} EndRequestBody;
+
+		typedef struct Channel {
+			Channel() : uFlags( 0 ) { }
+			StrHash hParams;
+			Bu::FString sStdIn;
+			Bu::FString sData;
+			uint8_t uFlags;
+		} Channel;
+
+		enum ChannelFlags
+		{
+			chflgParamsDone		= 0x01,
+			chflgStdInDone		= 0x02,
+			chflgDataDone		= 0x04,
+
+			chflgAllDone		= 0x03
+		};
+
+		virtual void run();
+
+		virtual void init() { };
+		virtual int request( const StrHash &hParams,
+			const Bu::FString &sStdIn, Bu::Stream &sStdOut,
+			Bu::Stream &sStdErr )=0;
+		virtual void deinit() { };
+
+	private:
+		void read( Bu::Socket &s, Record &r );
+		void read( Bu::Socket &s, BeginRequestBody &b );
+		uint32_t readLen( Bu::Socket &s, uint16_t &uUsed );
+		void readPair( Bu::Socket &s, StrHash &hParams, uint16_t &uUsed );
+
+		void write( Bu::Socket &s, Record r );
+		void write( Bu::Socket &s, EndRequestBody b );
+
+	private:
+		Bu::ServerSocket *pSrv;
+		bool bRunning;
+		Bu::Array<Channel *> aChannel;
+	};
+};
+
+#endif
diff --git a/src/tests/fastcgi.cpp b/src/tests/fastcgi.cpp
new file mode 100644
index 0000000..53dd68a
--- /dev/null
+++ b/src/tests/fastcgi.cpp
@@ -0,0 +1,81 @@
+#include "bu/fastcgi.h"
+
+class Cgi : public Bu::FastCgi
+{
+public:
+	Cgi() :
+		Bu::FastCgi::FastCgi()
+	{
+	}
+
+	Cgi( int iPort ) :
+		Bu::FastCgi::FastCgi( iPort )
+	{
+	}
+
+	virtual ~Cgi()
+	{
+	}
+
+	virtual int request( const StrHash &hParams,
+		const Bu::FString &sStdIn, Bu::Stream &sStdOut,
+		Bu::Stream &sStdErr )
+	{
+		Bu::FString sOut("Content-Type: text/html\r\n\r\n");
+		sOut += "<html><body><h1>Environment:</h1><ul>";
+		for( StrHash::const_iterator i = hParams.begin(); i; i++ )
+		{
+			sOut += "<li>" + i.getKey() + " = " +
+				i.getValue() + "</li>";
+		}
+		sOut += "</ul>";
+		char buf[2048];
+		sOut += "<h1>Cwd:</h1><ul><li>";
+		sOut += getcwd( buf, 2048 );
+		sOut += "</li></ul>";
+		sOut += "<h1>Stdin:</h1>";
+		sOut.formatAppend("%d bytes<pre>", sStdIn.getSize() );
+		Bu::FString sL, sR;
+		for( Bu::FString::const_iterator i = sStdIn.begin();
+			i; i++ )
+		{
+			sL.formatAppend("%02X ",
+				(unsigned int)((unsigned char)*i) );
+			if( *i < 27 )
+				sR += ". ";
+			else
+				sR.formatAppend("&#%d; ",
+					(unsigned int)((unsigned char)*i) );
+			if( sL.getSize()/3 == 8 )
+			{
+				sOut += sL + " | " + sR + "\n";
+				sL = sR = "";
+			}
+		}
+		if( sL != "" )
+		{
+			while( sL.getSize()/3 < 8 )
+				sL += "   ";
+			sOut += sL + " | " + sR + "\n";
+		}
+		sOut += "</pre><hr/><pre>";
+		sOut += sStdIn;
+		sOut += "</pre>";
+		sOut += "<form method=\"post\" enctype=\"multipart/form-data\"><textarea name=\"bob\"></textarea><br /><input type=\"file\" name=\"somefile\" /><br /><input type=\"submit\" name=\"yeah\" value=\"try it\" /></form>";
+		sOut += "</body></html>";
+
+		sStdOut.write( sOut );
+
+		return 0;
+	}
+};
+
+int main()
+{
+	Cgi c( 8789 );
+
+	c.run();
+
+	return 0;
+}
+
diff --git a/src/tests/nidstool.cpp b/src/tests/nidstool.cpp
deleted file mode 100644
index d1465ce..0000000
--- a/src/tests/nidstool.cpp
+++ /dev/null
@@ -1,241 +0,0 @@
-#include "bu/file.h"
-#include "bu/nids.h"
-#include "bu/nidsstream.h"
-#include "bu/paramproc.h"
-
-#include <stdlib.h>
-
-typedef struct Block
-{
-	uint32_t uFirstBlock;
-	uint32_t uNextBlock;
-	uint32_t uBytesUsed;
-} Block;
-
-class Param : public Bu::ParamProc
-{
-public:
-	Param( int argc, char *argv[] )
-	{
-		addHelpBanner("nidstool - Do stuff with nids files.\n\n");
-		addParam("info", 'i', mkproc(Param::procInfo),
-			"Print some info about the file.");
-		addParam("dump", 'd', mkproc(Param::procDump),
-			"Dump a stream to a file.");
-		addParam("analyze", 'a', mkproc(Param::procAnalyze),
-			"Analyze a nids file.");
-		addParam("copy", 'c', mkproc(Param::procCopy),
-			"Copy a nids file, changing settings.");
-		addParam("help", 'h', mkproc(Bu::ParamProc::help), "This help.");
-		process( argc, argv );
-	}
-
-	virtual ~Param()
-	{
-	}
-
-	void printInfo( Bu::Nids &n )
-	{
-		printf("File info:\n");
-		printf("  Header overhead: %db\n", n.getBlockStart() );
-		printf("  Block size:      %db\n", n.getBlockSize() );
-		printf("  Block count:     %d\n", n.getNumBlocks() );
-		printf("  Blocks used:     %d (%d%%)\n", n.getNumUsedBlocks(),
-			n.getNumUsedBlocks()*100/n.getNumBlocks() );
-		printf("  Block overhead:  %db\n", n.getBlockOverhead() );
-		printf("  Block storage:   %db (%d%%)\n",
-			n.getBlockSize()-n.getBlockOverhead(),
-			(n.getBlockSize()-n.getBlockOverhead())*100/n.getBlockSize() );
-	}
-
-	int procInfo( int argc, char *argv[] )
-	{
-		if( argc < 1 )
-		{
-			printf("You must provide a file name.\n");
-			exit( 1 );
-		}
-
-		Bu::File fIn( argv[0], Bu::File::Read );
-		Bu::Nids n( fIn );
-		n.initialize();
-
-		printInfo( n );
-
-		if( argc >= 2 )
-		{
-			uint32_t uStream = strtoul( argv[1], NULL, 0 );
-			uint32_t uBlock = uStream;
-
-			Block b;
-
-			for(;;)
-			{
-				fIn.setPos( n.getBlockStart()+n.getBlockSize()*uBlock );
-				fIn.read( &b, sizeof(Block) );
-				printf("Stream %u:  block %u, next %u, %ub used.\n",
-					uStream, uBlock, b.uNextBlock, b.uBytesUsed
-					);
-				if( b.uNextBlock == 0xFFFFFFFFUL )
-					break;
-				uBlock = b.uNextBlock;
-			}
-			printf("Stream End.\n");
-
-			return 2;
-		}
-
-		return 1;
-	}
-
-	int procDump( int argc, char *argv[] )
-	{
-		if( argc < 3 )
-		{
-			printf("You must provide a nids file, a stream id, and an output "
-				"file.\n");
-			exit( 1 );
-		}
-
-		Bu::File fIn( argv[0], Bu::File::Read );
-		Bu::Nids n( fIn );
-		n.initialize();
-
-		int iStream = strtol( argv[1], NULL, 0 );
-		Bu::NidsStream sIn = n.openStream( iStream );
-
-		Bu::File fOut( argv[2], Bu::File::Write|Bu::File::Create );
-		int iTotal = 0;
-		char buf[100];
-		for(;;)
-		{
-			int iRead = sIn.read( buf, 100 );
-			iTotal += fOut.write( buf, iRead );
-			if( iRead < 100 )
-				break;
-		}
-
-		printf("Wrote %db from stream %d in %s to %s.\n",
-			iTotal, iStream, argv[0], argv[2] );
-		return 3;
-	}
-
-	int procAnalyze( int argc, char *argv[] )
-	{
-		if( argc < 1 )
-		{
-			printf("You must provide a file name.\n");
-			exit( 1 );
-		}
-
-		Bu::File fIn( argv[0], Bu::File::Read );
-		Bu::Nids n( fIn );
-		n.initialize();
-
-		printInfo( n );
-
-		int iStreamCnt = 0;
-		int iStreamTotal = 0;
-		int iOneBlock = 0;
-		uint32_t iLargest = 0;
-		uint32_t iSmallest = 0;
-		int iWaste = 0;
-		int iUsable = n.getBlockSize()-n.getBlockOverhead();
-		Block b;
-		for( int j = 0; j < n.getNumBlocks(); j++ )
-		{
-			fIn.setPos( n.getBlockStart()+n.getBlockSize()*j );
-			fIn.read( &b, sizeof(Block) );
-			if( b.uFirstBlock != (uint32_t)j )
-				continue;
-			
-			iStreamCnt++;
-			iStreamTotal += b.uBytesUsed;
-
-			if( b.uNextBlock == 0xFFFFFFFFUL )
-			{
-				iOneBlock++;
-				iWaste += iUsable - b.uBytesUsed;
-			}
-			else
-			{
-				iWaste += iUsable - (b.uBytesUsed%iUsable);
-			}
-			
-			if( j == 0 )
-			{
-				iSmallest = iLargest = b.uBytesUsed;
-			}
-			else
-			{
-				if( iLargest < b.uBytesUsed )
-					iLargest = b.uBytesUsed;
-				if( iSmallest > b.uBytesUsed )
-					iSmallest = b.uBytesUsed;
-			}
-		}
-		printf("Steam analysis:\n");
-		printf("  Stream count:          %d\n", iStreamCnt );
-		printf("  Stream size:           %db/%db/%db (min/avr/max)\n",
-			iSmallest, iStreamTotal/iStreamCnt, iLargest );
-		printf("  One-block streams:     %d (%d%%)\n",
-			iOneBlock, iOneBlock*100/iStreamCnt );
-		printf("  Total wasted space:    %db (%d%%)\n",
-			iWaste, iWaste*100/iStreamTotal );
-		printf("  Avr blocks-per-stream: %f%%\n",
-			(float)n.getNumBlocks()/(float)iStreamCnt );
-
-		return 1;
-	}
-
-	int procCopy( int argc, char *argv[] )
-	{
-		if( argc < 3 )
-		{
-			printf("You must provide source stream, blocksize, destination.\n");
-			exit( 1 );
-		}
-
-		Bu::File fIn( argv[0], Bu::File::Read );
-		Bu::Nids nIn( fIn );
-		nIn.initialize();
-
-		Bu::File fOut( argv[2], Bu::File::Read|Bu::File::Write|Bu::File::Create|
-				Bu::File::Truncate );
-		Bu::Nids nOut( fOut );
-		nOut.initialize( strtol( argv[1], 0, NULL ) );
-
-		Block b;
-		for( int j = 0; j < nIn.getNumBlocks(); j++ )
-		{
-			fIn.setPos( nIn.getBlockStart()+nIn.getBlockSize()*j );
-			fIn.read( &b, sizeof(Block) );
-			if( b.uFirstBlock != (uint32_t)j )
-				continue;
-
-			Bu::NidsStream sIn = nIn.openStream( j );
-			int iNew = nOut.createStream();
-			Bu::NidsStream sOut = nOut.openStream( iNew );
-
-			char buf[1024];
-			for(;;)
-			{
-				int iRead = sIn.read( buf, 1024 );
-				sOut.write( buf, iRead );
-				if( iRead < 1024 )
-					break;
-			}
-		}
-
-		return 3;
-	}
-};
-
-
-int main( int argc, char *argv[] )
-{
-	Param p( argc, argv );
-
-	return 0;
-}
-
diff --git a/src/tests/url.cpp b/src/tests/url.cpp
index c9af676..4dc8c46 100644
--- a/src/tests/url.cpp
+++ b/src/tests/url.cpp
@@ -4,28 +4,31 @@
 
 int main( int argc, char *argv[] )
 {
-	printf("encodede: %s\n", Bu::Url::encode( argv[1] ).getStr() );
-	printf("decodede: %s\n", Bu::Url::decode( argv[1] ).getStr() );
-	Bu::Url u( argv[1] );
-
-	printf("Protocol: %s\n", u.getProtocol().getStr() );
-	printf("User:     %s\n", u.getUser().getStr() );
-	printf("Pass:     %s\n", u.getPass().getStr() );
-	printf("Host:     %s\n", u.getHost().getStr() );
-	printf("Path:     %s\n", u.getPath().getStr() );
-	try
-	{
-		printf("Port:     %d\n", u.getPort() );
-	} catch( Bu::ExceptionBase &e )
+	for( argc--, argv++; argc >= 0; argc--, argv++ )
 	{
-		printf("Port:     not set.\n");
-	}
-	printf("Parameters:\n");
-	for( Bu::Url::ParamList::const_iterator i = u.getParamBegin(); i; i++ )
-	{
-		printf("  \"%s\" = \"%s\"\n",
-			(*i).sName.getStr(), (*i).sValue.getStr()
-			);
+		printf("encodede: %s\n", Bu::Url::encode( *argv ).getStr() );
+		printf("decodede: %s\n", Bu::Url::decode( *argv ).getStr() );
+		Bu::Url u( *argv );
+
+		printf("Protocol: %s\n", u.getProtocol().getStr() );
+		printf("User:     %s\n", u.getUser().getStr() );
+		printf("Pass:     %s\n", u.getPass().getStr() );
+		printf("Host:     %s\n", u.getHost().getStr() );
+		printf("Path:     %s\n", u.getPath().getStr() );
+		try
+		{
+			printf("Port:     %d\n", u.getPort() );
+		} catch( Bu::ExceptionBase &e )
+		{
+			printf("Port:     not set.\n");
+		}
+		printf("Parameters:\n");
+		for( Bu::Url::ParamList::const_iterator i = u.getParamBegin(); i; i++ )
+		{
+			printf("  \"%s\" = \"%s\"\n",
+				(*i).sName.getStr(), (*i).sValue.getStr()
+				);
+		}
 	}
 
 	return 0;
diff --git a/src/tools/nidstool.cpp b/src/tools/nidstool.cpp
new file mode 100644
index 0000000..d1465ce
--- /dev/null
+++ b/src/tools/nidstool.cpp
@@ -0,0 +1,241 @@
+#include "bu/file.h"
+#include "bu/nids.h"
+#include "bu/nidsstream.h"
+#include "bu/paramproc.h"
+
+#include <stdlib.h>
+
+typedef struct Block
+{
+	uint32_t uFirstBlock;
+	uint32_t uNextBlock;
+	uint32_t uBytesUsed;
+} Block;
+
+class Param : public Bu::ParamProc
+{
+public:
+	Param( int argc, char *argv[] )
+	{
+		addHelpBanner("nidstool - Do stuff with nids files.\n\n");
+		addParam("info", 'i', mkproc(Param::procInfo),
+			"Print some info about the file.");
+		addParam("dump", 'd', mkproc(Param::procDump),
+			"Dump a stream to a file.");
+		addParam("analyze", 'a', mkproc(Param::procAnalyze),
+			"Analyze a nids file.");
+		addParam("copy", 'c', mkproc(Param::procCopy),
+			"Copy a nids file, changing settings.");
+		addParam("help", 'h', mkproc(Bu::ParamProc::help), "This help.");
+		process( argc, argv );
+	}
+
+	virtual ~Param()
+	{
+	}
+
+	void printInfo( Bu::Nids &n )
+	{
+		printf("File info:\n");
+		printf("  Header overhead: %db\n", n.getBlockStart() );
+		printf("  Block size:      %db\n", n.getBlockSize() );
+		printf("  Block count:     %d\n", n.getNumBlocks() );
+		printf("  Blocks used:     %d (%d%%)\n", n.getNumUsedBlocks(),
+			n.getNumUsedBlocks()*100/n.getNumBlocks() );
+		printf("  Block overhead:  %db\n", n.getBlockOverhead() );
+		printf("  Block storage:   %db (%d%%)\n",
+			n.getBlockSize()-n.getBlockOverhead(),
+			(n.getBlockSize()-n.getBlockOverhead())*100/n.getBlockSize() );
+	}
+
+	int procInfo( int argc, char *argv[] )
+	{
+		if( argc < 1 )
+		{
+			printf("You must provide a file name.\n");
+			exit( 1 );
+		}
+
+		Bu::File fIn( argv[0], Bu::File::Read );
+		Bu::Nids n( fIn );
+		n.initialize();
+
+		printInfo( n );
+
+		if( argc >= 2 )
+		{
+			uint32_t uStream = strtoul( argv[1], NULL, 0 );
+			uint32_t uBlock = uStream;
+
+			Block b;
+
+			for(;;)
+			{
+				fIn.setPos( n.getBlockStart()+n.getBlockSize()*uBlock );
+				fIn.read( &b, sizeof(Block) );
+				printf("Stream %u:  block %u, next %u, %ub used.\n",
+					uStream, uBlock, b.uNextBlock, b.uBytesUsed
+					);
+				if( b.uNextBlock == 0xFFFFFFFFUL )
+					break;
+				uBlock = b.uNextBlock;
+			}
+			printf("Stream End.\n");
+
+			return 2;
+		}
+
+		return 1;
+	}
+
+	int procDump( int argc, char *argv[] )
+	{
+		if( argc < 3 )
+		{
+			printf("You must provide a nids file, a stream id, and an output "
+				"file.\n");
+			exit( 1 );
+		}
+
+		Bu::File fIn( argv[0], Bu::File::Read );
+		Bu::Nids n( fIn );
+		n.initialize();
+
+		int iStream = strtol( argv[1], NULL, 0 );
+		Bu::NidsStream sIn = n.openStream( iStream );
+
+		Bu::File fOut( argv[2], Bu::File::Write|Bu::File::Create );
+		int iTotal = 0;
+		char buf[100];
+		for(;;)
+		{
+			int iRead = sIn.read( buf, 100 );
+			iTotal += fOut.write( buf, iRead );
+			if( iRead < 100 )
+				break;
+		}
+
+		printf("Wrote %db from stream %d in %s to %s.\n",
+			iTotal, iStream, argv[0], argv[2] );
+		return 3;
+	}
+
+	int procAnalyze( int argc, char *argv[] )
+	{
+		if( argc < 1 )
+		{
+			printf("You must provide a file name.\n");
+			exit( 1 );
+		}
+
+		Bu::File fIn( argv[0], Bu::File::Read );
+		Bu::Nids n( fIn );
+		n.initialize();
+
+		printInfo( n );
+
+		int iStreamCnt = 0;
+		int iStreamTotal = 0;
+		int iOneBlock = 0;
+		uint32_t iLargest = 0;
+		uint32_t iSmallest = 0;
+		int iWaste = 0;
+		int iUsable = n.getBlockSize()-n.getBlockOverhead();
+		Block b;
+		for( int j = 0; j < n.getNumBlocks(); j++ )
+		{
+			fIn.setPos( n.getBlockStart()+n.getBlockSize()*j );
+			fIn.read( &b, sizeof(Block) );
+			if( b.uFirstBlock != (uint32_t)j )
+				continue;
+			
+			iStreamCnt++;
+			iStreamTotal += b.uBytesUsed;
+
+			if( b.uNextBlock == 0xFFFFFFFFUL )
+			{
+				iOneBlock++;
+				iWaste += iUsable - b.uBytesUsed;
+			}
+			else
+			{
+				iWaste += iUsable - (b.uBytesUsed%iUsable);
+			}
+			
+			if( j == 0 )
+			{
+				iSmallest = iLargest = b.uBytesUsed;
+			}
+			else
+			{
+				if( iLargest < b.uBytesUsed )
+					iLargest = b.uBytesUsed;
+				if( iSmallest > b.uBytesUsed )
+					iSmallest = b.uBytesUsed;
+			}
+		}
+		printf("Steam analysis:\n");
+		printf("  Stream count:          %d\n", iStreamCnt );
+		printf("  Stream size:           %db/%db/%db (min/avr/max)\n",
+			iSmallest, iStreamTotal/iStreamCnt, iLargest );
+		printf("  One-block streams:     %d (%d%%)\n",
+			iOneBlock, iOneBlock*100/iStreamCnt );
+		printf("  Total wasted space:    %db (%d%%)\n",
+			iWaste, iWaste*100/iStreamTotal );
+		printf("  Avr blocks-per-stream: %f%%\n",
+			(float)n.getNumBlocks()/(float)iStreamCnt );
+
+		return 1;
+	}
+
+	int procCopy( int argc, char *argv[] )
+	{
+		if( argc < 3 )
+		{
+			printf("You must provide source stream, blocksize, destination.\n");
+			exit( 1 );
+		}
+
+		Bu::File fIn( argv[0], Bu::File::Read );
+		Bu::Nids nIn( fIn );
+		nIn.initialize();
+
+		Bu::File fOut( argv[2], Bu::File::Read|Bu::File::Write|Bu::File::Create|
+				Bu::File::Truncate );
+		Bu::Nids nOut( fOut );
+		nOut.initialize( strtol( argv[1], 0, NULL ) );
+
+		Block b;
+		for( int j = 0; j < nIn.getNumBlocks(); j++ )
+		{
+			fIn.setPos( nIn.getBlockStart()+nIn.getBlockSize()*j );
+			fIn.read( &b, sizeof(Block) );
+			if( b.uFirstBlock != (uint32_t)j )
+				continue;
+
+			Bu::NidsStream sIn = nIn.openStream( j );
+			int iNew = nOut.createStream();
+			Bu::NidsStream sOut = nOut.openStream( iNew );
+
+			char buf[1024];
+			for(;;)
+			{
+				int iRead = sIn.read( buf, 1024 );
+				sOut.write( buf, iRead );
+				if( iRead < 1024 )
+					break;
+			}
+		}
+
+		return 3;
+	}
+};
+
+
+int main( int argc, char *argv[] )
+{
+	Param p( argc, argv );
+
+	return 0;
+}
+
diff --git a/src/unit/array.unit b/src/unit/array.unit
index 3a777d3..b6528eb 100644
--- a/src/unit/array.unit
+++ b/src/unit/array.unit
@@ -36,7 +36,7 @@
 	
 	const Bu::Array<int> &ci = ai;
 	j = 0;
-	for( Bu::Array<int>::const_iterator i = ci.begin(); i != ci.end(); i++ )
+	for( Bu::Array<int>::const_iterator i = ci.begin(); i; i++ )
 		unitTest( (*i) == j++ );
 	unitTest( j == 10 );
 }
@@ -46,6 +46,8 @@
 	Bu::Array<int> ai;
 	for( Bu::Array<int>::iterator i = ai.begin(); i != ai.end(); i++ )
 		unitFailed("Empty lists shouldn't be iterated through.");
+	for( Bu::Array<int>::iterator i = ai.begin(); i; i++ )
+		unitFailed("Empty lists shouldn't be iterated through.");
 }
 
 {%copy}
diff --git a/src/urn.cpp b/src/urn.cpp
new file mode 100644
index 0000000..e69de29
diff --git a/src/urn.h b/src/urn.h
new file mode 100644
index 0000000..e69de29
-- 
cgit v1.2.3