Skip to content
Snippets Groups Projects
cvslib.js 11.37 KiB
/* 	
	Javascript CVS client for Synchronet (2011)
	-	code by mcmlxxix
*/

CVS = new (function () {
	this.VERSION = "$Revision$".split(' ')[1];
	this.socket = undefined;
	
////////////////////////////////// SETTINGS

	/* connection defaults */
	this.CVSHOME = argv[0];
	this.CVSSERV = "cvs.synchro.net";
	this.CVSROOT = "/cvsroot/sbbs";
	this.CVSUSER = "anonymous";
	this.CVSPASS = "";
	this.CVSAUTH = "pserver";
	
	/* default global options */
	this.globalOptions = {
		'z':					-1,	// compression level (0 - 9)
		'x':					-1,	// encryption
		'r':					-1,	// read-only on new files
		'w':					-1	// read/write on new files
	}
	
	/* default common command options */
	this.commonOptions = {
		'P':					true, 	// prune empty directories 
		'R':					true	// recursive directory searching
	}
	
	/* default file filters */
	this.filter = {
		'.':					true,
		'..':					true,
		'core':					true,
		'RCSLOG':				true,
		'tags':					true,
		'TAGS':					true,
		'RCS':					true,
		'.make.state':			true,
		'.nse_depinfo':			true,
		'#*':					true,
		'.#*':					true,
		'cvslog.*':				true,
		',*':					true,
		'CVS':					true,
		'CVS.adm':				true,
		'.del-*':				true,
		'*.a':					true,
		'*.olb':				true,
		'*.o':					true,
		'*.obj':				true,
		'*.so':					true,
		'*.Z':					true,
		'*~':					true,
		'*.old':				true,
		'*.elc':				true,
		'*.ln':					true,
		'*.bak':				true,
		'*.BAK':				true,
		'*.orig':				true,
		'*.rej':				true,
		'*.exe':				true,
		'*.dll':				true,
		'*.pdb':				true,
		'*.lib':				true,
		'*.exp':				true,
		'_$*':					true,
		'*$':					true
	}
	
	/* default accepted responses  */
	this.validResponses = {
		'Checked-in':			true,	// Required
		'Checksum':				false,
		'Clear-rename':			false,
		'Clear-static-directory':false,
		'Clear-sticky':			false,
		'Copy-file':			false,
		'Created':				false,
		'E':					true,	// Handled
		'EntriesExtra':			false,
		'F':					true,	// Handled
		'M':					true,	// Handled
		'MT':					true,	// Handled
		'Mbinary':				false,
		'Merged':				true,	// Required
		'Mod-time':				false,
		'Mode':					false,
		'Module-expansion':		false,
		'New-entry':			false,
		'Notified':				false,
		'Patched':				false,
		'Rcs-diff':				false,
		'Remove-entry':			false,
		'Removed':				true,	// Required
		'Renamed':				false,
		'Set-checkin-prog':		false,
		'Set-static-directory':	false,
		'Set-sticky':			false,
		'Set-update-prog':		false,
		'Template':				false,
		'Update-existing':		false,
		'Updated':				true,	// Required
		'Valid-requests':		true,	// Handled
		'Wrapper-rcsOption':	false,
		'error':				true,	// Handled
		'ok':					true,	// Handled
	};
	
	/* populated by response to valid-requests  */
	this.validRequests = {
	};

////////////////////////////////// METHODS
	
	/* connect to server and authenticate */
	this.connect = function(srv,root,usr,pw) {
	
		if(srv)
			this.CVSSERV = srv;
		if(root)
			this.CVSROOT = root;
		if(usr)
			this.CVSUSER = usr;
		if(pw)
			this.CVSPASS = pw;
	
		this.socket=new Socket();
		this.socket.connect(this.CVSSERV,2401);
		this.authenticate();
		this.init();
	}
	
	/* initialize protocol */
	this.init = function() {
		this.protocol["Valid-responses"]();
		this.protocol["valid-requests"]();
		this.protocol["Root"](this.CVSROOT);
	}
	
	/* authenticate */
	this.authenticate = function() {
		/* authenticate client */
		switch(this.CVSAUTH.toUpperCase()) {
		case "PSERVER":
			/* begin auth */
			this.request("BEGIN AUTH REQUEST");
			/* send cvs root */
			this.request(this.CVSROOT);
			/* send cvs username */
			this.request(this.CVSUSER);
			/* send cvs password */
			this.socket.send("A");
			for(var c=0;c<this.CVSPASS.length;c++) 
				this.socket.sendBin(encode_char(this.CVSPASS[c]),1);
			this.socket.send("\n");
			this.request("END AUTH REQUEST");
			/* end auth  */
			break;
		case "GSSAPI":
			// todo??
			break;
		}
		
		var response=this.response;
		if(response == "I LOVE YOU") {
			log(LOG_DEBUG,"authenticated");
			return true;
		}
		if(response == "I HATE YOU") {
			log(LOG_ERROR,"authentication failed");
			return false;
		}
		else {
			log(LOG_ERROR,response);
			return false;
		}
	}

	/* close server socket connection */
	this.disconnect = function() {
		this.socket.close();
	}
	
	/* send a request or command to the cvs server */
	this.request = function(cmdstr) {
		this.verifyConnection();
		this.socket.send(cmdstr + "\n");
	}
	
	/*
	 * check for a response from the cvs server 
	 * This will handle whatever it can, and return
	 * only unhandled responses.
	 * 
	 * While it "handles" error, it still returns it.
	 */
	
	this.response getter = function() {
		function split_cmd(str, count) {
			if(count==undefined)
				count=2;

			var ret=[];

			while(--count) {
				var space=str.indexOf(' ');
				if(space==-1)
					break;
				ret.push(str.substr(0, space));
				str=str.substr(space+1);
			}
			ret.push(str);
			return ret;
		}
		
		for(;;) {
			this.verifyConnection();
			if(!this.socket.poll(1))
				return undefined;
			var response=this.socket.recvline(65535,10);
			var cmd=split_cmd(response,2);
			if(cmd.length < 2) {
				if(cmd[0] != 'ok')
					log(LOG_ERR, "Response with no arg: "+response);
				return response;
			}
			switch(cmd[0]) {
				case 'M':
					log(LOG_INFO, cmd[1]);
					if(js.global.writeln)
						writeln(cmd[1]);
					break;
				case 'MT':
					var m=cmd[1].split_cmd(' ',2);
					switch(m[0]) {
						case 'newline':
							if(js.global.writeln)
								writeln('');
							break;
						case 'text':
							log(LOG_INFO, m[1]);
							if(js.global.write)
								write(m[1]);
							break;
						default:
							if(m.length > 1) {
								log(LOG_INFO, m[1]);
								if(js.global.write)
									write(m[1]);
							}
							break;
					}
					break;
				case 'E':
					log(LOG_ERR, cmd[1]);
					if(js.global.writeln)
						writeln(cmd[1]);
					break;
				case 'F':
					break;
				case 'Valid-requests':
					var m=cmd[1].split(' ');
					for each(var r in m) {
						this.validRequests[r] = true;
					}
					break;
				case 'error':
					var m=cmd[1].split(' ',2);
					if(m[0].length > 0) {
						log(LOG_ERR, "ERROR "+m[0]+" - "+m[1]);
					}
					else {
						if(m[1].length > 0)
							log(LOG_ERR, "ERROR - "+m[1]);
					}
					return response;
				default:
					return response;
			}
		}
	}

	/* verify that socket is connected to the server */
	this.verifyConnection = function() {
		if(!this.socket || !this.socket.is_connected) {
			this.connect();
			this.init();
			if(!this.socket.is_connected) {
				throw("error connecting to server");
			}
		}
	}

////////////////////////////////// COMMAND LINE API

	/* parse a standard cvs command line string */
	this.command = function(str) {
		//  cvs [ cvs_options ] cvs_command [ command_options ] [ command_args ]
		var cmd=str.split(" ");
		
		/* if cvs has been passed as part of the command, strip it */
		if(cmd[0].toUpperCase() == "CVS")
			cmd.shift();
	}

////////////////////////////////// PROTOCOL API

	/* cvs protocol commands */
	this.protocol = {

		//TODO ---> http://www.cvsnt.org/cvsclient/Requests.html
	
		/* request a list of valid server requests */
		'valid-requests':function() {
			this.parent.request('valid-requests');
			var request=this.parent.response;
			if(request != 'ok') {
				log(LOG_ERR, "Unhandled response: '"+request+"'");
			}
		},

		/* tell server what responses we can handle */
		'Valid-responses':function() {
			var str="Valid-responses";
			for(var r in this.parent.validResponses) {
				if(this.parent.validResponses[r] == true)
					str+=" "+r;
			}
			this.parent.request(str);
		},
		
		/* specify CVSROOT on server */
		'Root':function(pathname) {
			this.parent.request("Root " + pathname);
		},
		
		/* specify directory on server */
		'Directory':function(dir,repository) {
			this.parent.request("Directory " + local-directory);
			/*
				repository \n
			*/
		},
		
		/* tell server local file revision */
		'Entry':function(str) {
			
		},
		
		/* tell server that a file has not been modified since last update or checkout */
		'Unchanged':function(filename) {
			this.parent.request("Unchanged " + filename);
		},
		
		/* send a pre-command-processing argument */
		'Argument':function(str) {
			this.parent.request("Argument " + str);
		},

		/* append a pre-command-processing argument */
		'Argumentx':function(str) {
			this.parent.request("Argumentx " + str);
		},
		
		/* notify server that an edit/unedit command took place */
		'Notify':function(filename) {
			this.parent.request("Notify " + filename);
			/*
				notification-type \t time \t clienthost \t
				working-dir \t watches \n
			*/
		},
		
		'Questionable':function(filename) {
			this.parent.request("Questionable " + filename);
		},
		
		'Case':function() {
			this.parent.request("Case");
		},
		
		'Utf8':function() {
			this.parent.request("Utf8");
		},
		
		'Global_option':function(option) {
			this.parent.request("Global_option " + option);
		},
	};
	this.protocol.parent=this;
	
////////////////////////////////// CLIENT API

	/* cvs client commands */
	this.commands = {
	
		//TODO ---> http://ximbiot.com/cvs/manual/cvs-1.11.23/cvs_16.html
		
		/* retrieve file updates */
		'update':function(dir,files,options) {
		
		},
		/* checkout files */
		'checkout':function(repository,options) {
		
		},
		/* add files */
		'add':function(dir,files,message,options) {
		
		},
		/* administration */
		'admin':function(dir,files,options) {
		
		},
		/* display which revision modified each line of a file */
		'annotate':function(dir,filename,options) {
		
		},
		/* commit changes to repository */
		'commit':function(dir,files,message,options) {
		
		},
		/* compare one file revision to another */
		'diff':function(dir,filename,options) {
		
		},
		/* export source */
		'export':function(repository,options) {
		
		},
		/* import source into cvs */
		'import':function(repository,dir,options) {
		
		},
		/* remove files from repository */
		'remove':function(dir,files,options) {
		
		}
	}

////////////////////////////////// INTERNAL FUNCTIONS

	/* cvs password encryption */
	function encode_char(ch) {
		var values=[];
		values["!"]=120;
		values['"']=53;
		values["%"]=109;
		values["&"]=72;
		values["'"]=108;
		values["("]=70;
		values[")"]=64;
		values["*"]=76;
		values["+"]=67;
		values[","]=116;
		values["-"]=74;
		values["."]=68;
		values["/"]=87;
		values["0"]=111;
		values["1"]=52;
		values["2"]=75;
		values["3"]=119;
		values["4"]=49;
		values["5"]=34;
		values["6"]=82;
		values["7"]=81;
		values["8"]=95;
		values["9"]=65;
		values[":"]=112;
		values[";"]=86;
		values["<"]=118;
		values["="]=110;
		values[">"]=122;
		values["?"]=105;
		values["A"]=57;
		values["B"]=83;
		values["C"]=43;
		values["D"]=46;
		values["E"]=102;
		values["F"]=40;
		values["G"]=89;
		values["H"]=38;
		values["I"]=103;
		values["J"]=45;
		values["K"]=50;
		values["L"]=42;
		values["M"]=123;
		values["N"]=91;
		values["O"]=35;
		values["P"]=125;
		values["Q"]=55;
		values["R"]=54;
		values["S"]=66;
		values["T"]=124;
		values["U"]=126;
		values["V"]=59;
		values["W"]=47;
		values["X"]=92;
		values["Y"]=71;
		values["Z"]=115;
		values["_"]=56;
		values["a"]=121;
		values["b"]=117;
		values["c"]=104;
		values["d"]=101;
		values["e"]=100;
		values["f"]=69;
		values["g"]=73;
		values["h"]=99;
		values["i"]=63;
		values["j"]=94;
		values["k"]=93;
		values["l"]=39;
		values["m"]=37;
		values["n"]=61;
		values["o"]=48;
		values["p"]=58;
		values["q"]=113;
		values["r"]=32;
		values["s"]=90;
		values["t"]=44;
		values["u"]=98;
		values["v"]=60;
		values["w"]=51;
		values["x"]=33;
		values["y"]=97;
		values["z"]=62;
		return values[ch];
	}
})();