aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/trunk/infrastructure/com.etherpad
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--trunk/trunk/infrastructure/com.etherpad.openofficeservice/importexport.scala189
-rw-r--r--trunk/trunk/infrastructure/com.etherpad/easysync2support.scala167
-rw-r--r--trunk/trunk/infrastructure/com.etherpad/licensing.scala169
-rw-r--r--trunk/trunk/infrastructure/com.etherpad/main.scala23
4 files changed, 548 insertions, 0 deletions
diff --git a/trunk/trunk/infrastructure/com.etherpad.openofficeservice/importexport.scala b/trunk/trunk/infrastructure/com.etherpad.openofficeservice/importexport.scala
new file mode 100644
index 0000000..f5150ad
--- /dev/null
+++ b/trunk/trunk/infrastructure/com.etherpad.openofficeservice/importexport.scala
@@ -0,0 +1,189 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.etherpad.openofficeservice;
+
+import net.appjet.common.sars.{SarsServer,SarsMessageHandler};
+
+import java.io.{DataInputStream,DataOutputStream};
+import java.io.{File,FileOutputStream,ByteArrayInputStream,ByteArrayOutputStream};
+
+class OOSException(m: String) extends RuntimeException(m);
+class UnsupportedFormatException(format: String) extends OOSException("Unsupported format: "+format);
+object TemporaryFailure extends OOSException("Temporary failure");
+
+// stub object here. Please replace if you'd like to use openoffice!
+object OpenOfficeServerUtility {
+ def checkServerAvailability(host: String, port: Int): Boolean = {
+ return false;
+ }
+ def runOpenOfficeServer(path: String, host: String, port: Int, timeout: Int, wait: Boolean) {
+ // nothing
+ }
+}
+
+class OpenOfficeFileConverter {
+ def setOpenOfficeServerDetails(host: String, port: Int) {
+ // nothing
+ }
+
+ def convertFile(src: File, dst: File, converter: String, extension: String): Boolean = {
+ return false;
+ }
+}
+
+object OpenOfficeService {
+ val formats = Map(
+ "pdf" -> "writer_pdf_Export",
+ "doc" -> "MS Word 97",
+ "html" -> "HTML (StarWriter)",
+ "odt" -> "writer8",
+ //"html" -> "XHTML Writer File",
+ "txt" -> "Text"
+ );
+
+ def createTempFile(bytes: Array[byte], suffix: String) = {
+ var f = File.createTempFile("ooconvert-", if (suffix == null) { null } else if (suffix == "") { "" } else { "."+suffix });
+ if (bytes != null) {
+ val fos = new FileOutputStream(f);
+ fos.write(bytes);
+ }
+ f;
+ }
+
+ var soffice = "soffice";
+ def setExecutable(exec: String) {
+ soffice = exec;
+ }
+
+ def convertFile(from: String, to: String, bytes: Array[byte]): Array[byte] = {
+ if (from == to) {
+ return bytes;
+ }
+
+ val tempFile = createTempFile(bytes, from);
+ val outFile = createTempFile(null, to);
+
+ val openOfficeServerHost = "localhost";
+ val openOfficeServerPort = 8100;
+ if (! OpenOfficeServerUtility.checkServerAvailability(openOfficeServerHost, openOfficeServerPort)) {
+ try {
+ OpenOfficeServerUtility.runOpenOfficeServer(soffice, openOfficeServerHost, openOfficeServerPort, 20000, true);
+ } catch {
+ case e: java.io.IOException => {
+ e.printStackTrace();
+ throw TemporaryFailure;
+ }
+ }
+ }
+ var converter = new OpenOfficeFileConverter();
+ converter.setOpenOfficeServerDetails(openOfficeServerHost, openOfficeServerPort);
+ var status = false;
+ try {
+ status = converter.convertFile(tempFile, outFile, formats(to), to);
+ } catch {
+ case e => {
+ e.printStackTrace();
+ throw new OOSException("Unknown exception occurred: "+e.getMessage());
+ }
+ }
+ if (status == false) {
+ throw new UnsupportedFormatException(from);
+ }
+ net.appjet.common.util.BetterFile.getFileBytes(outFile);
+ }
+
+ def main(args: Array[String]) {
+ if (args.length > 0) {
+ soffice = args(0);
+ if (soffice.length == 0) {
+ exit(1);
+ }
+ }
+
+ // Query format:
+ // from: String, to: String, count: Int, bytes: Array[byte]
+ // Response format:
+ // status: Int, <data>
+ // status 0 (success) - <data>: count: Int, bytes: Array[byte]
+ // status 1 (temporary failure) - <data>: <none>
+ // status 2 (permanent failure) - <data>: type: Int
+ // type - 0: unknown failure.
+ // - 1: unsupported format
+ val handler = new SarsMessageHandler {
+ override def handle(b: Array[byte]): Option[Array[byte]] = {
+ val is = new DataInputStream(new ByteArrayInputStream(b));
+ val from = is.readUTF;
+ val to = is.readUTF;
+ val len = is.readInt;
+ val bytes = new Array[byte](len);
+ is.readFully(bytes);
+ var status = 0;
+ var permfailuretype = 0;
+
+ println("Converting "+from+" -> "+to+" ("+len+" bytes)");
+
+ val output = try {
+ convertFile(from, to, bytes);
+ } catch {
+ case TemporaryFailure => {
+ status = 1;
+ null;
+ }
+ case e: UnsupportedFormatException => {
+ status = 2;
+ permfailuretype = 1;
+ null;
+ }
+ case e => {
+ status = 2;
+ permfailuretype = 0;
+ e.printStackTrace();
+ null;
+ }
+ }
+
+ val retBytes = new ByteArrayOutputStream();
+ val ret = new DataOutputStream(retBytes);
+ if (status != 0) {
+ ret.writeInt(status); // error
+ status match {
+ case 2 => {
+ ret.writeInt(permfailuretype);
+ }
+ case _ => { }
+ }
+ } else {
+ ret.writeInt(0); // success
+ ret.writeInt(output.length);
+ ret.write(output, 0, output.length);
+ }
+ Some(retBytes.toByteArray());
+ }
+ }
+
+ val server = new SarsServer("ooffice-password", handler, None, 8101);
+ server.start();
+ println("Server running...");
+ server.join();
+ println("Server quitting...");
+ }
+}
+
+
+
+
+
diff --git a/trunk/trunk/infrastructure/com.etherpad/easysync2support.scala b/trunk/trunk/infrastructure/com.etherpad/easysync2support.scala
new file mode 100644
index 0000000..9f1c527
--- /dev/null
+++ b/trunk/trunk/infrastructure/com.etherpad/easysync2support.scala
@@ -0,0 +1,167 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.etherpad;
+
+object Easysync2Support {
+
+ def numToString(d: Int): String = java.lang.Integer.toString(d.toInt, 36); // lowercase
+ def stringToNum(s: String): Int = java.lang.Integer.parseInt(s, 36);
+
+ def opAssembler() = new OpAssembler();
+
+ class OpAssembler() {
+ val buf = new StringBuilder(1000);
+ def append(op: Op) {
+ append(op.opcode, op.chars, op.lines, op.attribs);
+ }
+ def append(opcode: String, chars: Int, lines: Int, attribs: String) {
+ buf.append(attribs);
+ if (lines > 0) {
+ buf.append('|');
+ buf.append(numToString(lines));
+ }
+ buf.append(opcode);
+ buf.append(numToString(chars));
+ }
+ override def toString(): String = buf.toString;
+ def clear() { buf.clear; }
+ }
+
+ def isAlphanum(c: Char) = (c >= '0' && c <= '9' || c >= 'a' && c <= 'z');
+
+ case object OpParseError extends Error;
+
+ def nextOpInString(str: String, startIndex: Int): Object = {
+ var i = startIndex;
+
+ try {
+ def lookingAt(c: Char) = (i < str.length && str.charAt(i) == c);
+ def lookingAtAlphanum() = (i < str.length && isAlphanum(str.charAt(i)));
+ def atEnd() = (i >= str.length);
+ def readAlphanum(): Int = {
+ if (! lookingAtAlphanum()) {
+ throw OpParseError;
+ }
+ val start = i;
+ while (lookingAtAlphanum()) {
+ i += 1;
+ }
+ val end = i;
+ stringToNum(str.substring(start, end));
+ }
+
+ while (lookingAt('*')) {
+ i += 1;
+ if (! lookingAtAlphanum()) {
+ throw OpParseError;
+ }
+ while (lookingAtAlphanum()) {
+ i += 1;
+ }
+ }
+ val attribsEnd = i;
+
+ var lines_ = 0;
+ if (lookingAt('|')) {
+ i += 1;
+ lines_ = readAlphanum();
+ }
+
+ if (lookingAt('?')) {
+ return new { val opcode = "?"; }
+ }
+ if (! (lookingAt('+') || lookingAt('-') || lookingAt('='))) {
+ throw OpParseError;
+ }
+ val opcode_ = str.substring(i, i+1);
+ i += 1;
+ val chars_ = readAlphanum();
+
+ return new Op(opcode_, chars_, lines_, str.substring(startIndex, attribsEnd)) {
+ val lastIndex = i;
+ };
+ }
+ catch { case OpParseError => null }
+ }
+
+ case class Op(var opcode: String, var chars: Int, var lines: Int, var attribs: String);
+ def newOp() = Op("", 0, 0, "");
+ def clearOp(op: Op) { op.opcode = ""; op.chars = 0; op.lines = 0; op.attribs = ""; }
+
+ // ported from easysync2.js
+ class MergingOpAssembler {
+ val assem = opAssembler();
+ var bufOp = newOp();
+
+ var bufOpAdditionalCharsAfterNewline = 0;
+
+ def flush(isEndDocument: Boolean) {
+ if (bufOp.opcode.length > 0) {
+ if (isEndDocument && bufOp.opcode == "=" && bufOp.attribs.length == 0) {
+ // final merged keep, leave it implicit
+ }
+ else {
+ assem.append(bufOp);
+ if (bufOpAdditionalCharsAfterNewline > 0) {
+ bufOp.chars = bufOpAdditionalCharsAfterNewline;
+ bufOp.lines = 0;
+ assem.append(bufOp);
+ bufOpAdditionalCharsAfterNewline = 0;
+ }
+ }
+ bufOp.opcode = "";
+ }
+ }
+ def append(opcode: String, chars: Int, lines: Int, attribs: String) {
+ if (chars > 0) {
+ if (bufOp.opcode == opcode && bufOp.attribs == attribs) {
+ if (lines > 0) {
+ // bufOp and additional chars are all mergeable into a multi-line op
+ bufOp.chars += bufOpAdditionalCharsAfterNewline + chars;
+ bufOp.lines += lines;
+ bufOpAdditionalCharsAfterNewline = 0;
+ }
+ else if (bufOp.lines == 0) {
+ // both bufOp and op are in-line
+ bufOp.chars += chars;
+ }
+ else {
+ // append in-line text to multi-line bufOp
+ bufOpAdditionalCharsAfterNewline += chars;
+ }
+ }
+ else {
+ flush(false);
+ bufOp = Op(opcode, chars, lines, attribs);
+ }
+ }
+ }
+ def endDocument() {
+ flush(true);
+ }
+ override def toString() = {
+ flush(false);
+ assem.toString();
+ }
+ def clear() {
+ assem.clear();
+ clearOp(bufOp);
+ }
+ }
+
+ def mergingOpAssembler() = new MergingOpAssembler();
+}
diff --git a/trunk/trunk/infrastructure/com.etherpad/licensing.scala b/trunk/trunk/infrastructure/com.etherpad/licensing.scala
new file mode 100644
index 0000000..9318f78
--- /dev/null
+++ b/trunk/trunk/infrastructure/com.etherpad/licensing.scala
@@ -0,0 +1,169 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.etherpad;
+
+import net.appjet.oui.{Encryptomatic, config};
+import net.appjet.common.util.BetterFile;
+
+import java.io.{FileInputStream, FileOutputStream, ByteArrayInputStream, ByteArrayOutputStream, PrintWriter}
+
+import java.security._;
+import java.security.spec._;
+
+object Licensing {
+ val publicKey = "s0dD94jKFjlSHIumgDQ4ldcyIyna1vMHmG5tsgkP987eBTW88XeEIUTo5JtWOjPzb252GURUrr7MReTqMz6NnsOupeJMqtXgjuVxcXbK8AnckxkxhRqMiFfBW39T9NzPgq09yBdH4tKGlPZQmgaBvjFI8cXTYa7a64LrDnrzrpDhDdJsZPZI2kE7G4vBCGijuhsZpyowK8zT5y2cKqIgIdLxUnXNFtayDi0oyuX1ywfBds2OEil9fEUQOQvkcHAt6kYfPXkE2XgQZFasAv0DPeWMUEtaHTbMaQn1U6BsfmsKjHlLhM3oWEzp0wEwUWxCC39iHYjxa5QKtxm5BNvUTTqJgkoEvk7Uu08j8jhFeCFosph6igDWPmyfAPKTnETXJobO2VON83bVHlX8UfKonnalMy0Hnw2C0I7c0UE0MtMIRtJxtwU62a311Ohp1EVrY4LwKIFfqRMVWKDP0cjXDkJyjJS58rC1DRU7SfPspgfuOy5YZo9sLKztXfzAPzNbXerQ24m2AjmYLV4JQked7MnaKQ6VfyZbFBK5th9NFcJwY1bGbIHW2EsKmiKUoNjPKRJ6VMC7odUCIXQyE9J";
+
+ val pkhash = "f7a3dd5940a3f79904b81e4d32a08e2efaa0b2ab";
+ val keyVersion = 2.toByte;
+
+ def thanksForStealingFromPoorHackersTryingToEkeAMeagerLivingFromThisCruelWorld =
+ Encryptomatic.bytesToAscii(MessageDigest.getInstance("SHA1").digest(publicKey.getBytes())) == pkhash;
+ def sha1(b: Array[Byte]): String = Encryptomatic.bytesToAscii(MessageDigest.getInstance("SHA1").digest(b));
+ def sha1(s: String): String = sha1(s.getBytes("UTF-8"));
+
+ def toBytes(i: Int): Array[Byte] =
+ Array((i >> 24).toByte,
+ (i >> 16).toByte,
+ (i >> 8).toByte,
+ i.toByte);
+ def toByte(i: Int): Array[Byte] =
+ Array(i.toByte);
+ def toBytes(l: Long): Array[Byte] =
+ Array((l >> 56).toByte,
+ (l >> 48).toByte,
+ (l >> 40).toByte,
+ (l >> 32).toByte,
+ (l >> 24).toByte,
+ (l >> 16).toByte,
+ (l >> 8).toByte,
+ l.toByte);
+
+ def toInt(b0: Array[Byte]): Int = {
+ val b = b0.map(_.toInt & 0x00FF);
+ (b(0) << 24) | (b(1) << 16) | (b(2) << 8) | b(3);
+ }
+ def toInt(b: Byte): Int = b.toInt & 0x00FF;
+
+ def toLong(b0: Array[Byte]): Long = {
+ val b = b0.map(_.toLong & 0x000000FF);
+ (b(0) << 56) | (b(1) << 48) | (b(2) << 40) | (b(3) << 32) | (b(4) << 24) | (b(5) << 16) | (b(6) << 8) | b(7);
+ }
+
+ def generateKey(personName: String, organizationName: String, expiresDate: Long, editionId: Int, userQuota: Int, majorVersion: Int, minorVersion: Int, patchVersion: Int) = {
+ if (config.licenseGeneratorKey == null) {
+ throw new RuntimeException("No private key available to generate license key.");
+ }
+ def privateKey = Encryptomatic.readPrivateKey("DSA", new FileInputStream(config.licenseGeneratorKey));
+ def clean(s: String) = s.replaceAll(":", "-");
+ val keyPrefix =
+ List(personName, organizationName, expiresDate.toString, editionId.toString, userQuota.toString, majorVersion.toString, minorVersion.toString, patchVersion.toString).map(clean).mkString(":");
+ val sig = Encryptomatic.sign(new ByteArrayInputStream(keyPrefix.getBytes("UTF-8")), privateKey)
+
+ List(personName, organizationName).mkString(":") + ":" +
+ Encryptomatic.bytesToAscii(
+ Array.concat[Byte](Array(keyVersion), // don't want BigInt dropping bytes, that'd be sad. :(
+ toBytes(expiresDate),
+ toBytes(editionId),
+ toBytes(userQuota),
+ toByte(majorVersion),
+ toByte(minorVersion),
+ toByte(patchVersion),
+ sig));
+ }
+
+ def decodeKey(key: String) = try {
+ val Array(personName0, organizationName0, sigAndInfo) = key.split(":");
+ val sigAndInfoBytes = Encryptomatic.asciiToBytes(sigAndInfo);
+ val thisKeyVersion = toInt(sigAndInfoBytes(0));
+ val expiresDate0 = toLong(sigAndInfoBytes.slice(1, 9));
+ val editionId0 = toInt(sigAndInfoBytes.slice(9, 13));
+ val userQuota0 = toInt(sigAndInfoBytes.slice(13, 17));
+ val (majorVersion0, minorVersion0, patchVersion0) =
+ if (thisKeyVersion >= 2) {
+ (toInt(sigAndInfoBytes(17)), toInt(sigAndInfoBytes(18)), toInt(sigAndInfoBytes(19)));
+ } else {
+ (0, 0, 0);
+ }
+ val sig = sigAndInfoBytes.drop(if (thisKeyVersion >= 2) 20 else 17);
+ val keyPrefix = {
+ var a = Seq(personName0, organizationName0, expiresDate0.toString, editionId0.toString, userQuota0.toString);
+ if (thisKeyVersion >= 2) {
+ a = a ++ Seq(majorVersion0.toString, minorVersion0.toString, patchVersion0.toString);
+ }
+ a.mkString(":");
+ }
+ if (! Encryptomatic.verify(new ByteArrayInputStream(keyPrefix.getBytes("UTF-8")),
+ Encryptomatic.readPublicKey("DSA",
+ new ByteArrayInputStream(publicKey.getBytes())), sig)) {
+ null;
+ } else {
+ new {
+ def personName = personName0;
+ def organizationName = organizationName0;
+ def expiresDate = expiresDate0;
+ def editionId = editionId0;
+ def userQuota = userQuota0;
+ def majorVersion = majorVersion0;
+ def minorVersion = minorVersion0;
+ def patchVersion = patchVersion0;
+ }
+ }
+ } catch {
+ case e => null;
+ }
+
+ def main(args: Array[String]) {
+ args(0) match {
+ case "genkeypair" => {
+ println("Generating keypair...");
+ Encryptomatic.writeKeyPair(Encryptomatic.generateKeyPair("DSA"), args(1), args(2));
+ println("Done.");
+ }
+ case "genmainkey" => {
+ println("Generating key for etherpad.com...");
+ config.values("licenseGeneratorKey") = args(1);
+ val out = new PrintWriter(new FileOutputStream(args(2)));
+ out.print(generateKey("etherpad", "AppJet", -1, 0, -1, 0, 0, 0))
+ out.close();
+ println("Done.");
+ }
+ case "test" => {
+ println("Testing key generation.");
+ config.values("licenseGeneratorKey") = args(1);
+ val key = generateKey("Foo Bar", "Baz, Inc.", System.currentTimeMillis() + 86400*1000, 0, 100, 1, 2, 3);
+ println("Key is: "+key);
+ val obj = decodeKey(key);
+ println(List(obj.personName, obj.organizationName, obj.expiresDate, obj.editionId, obj.userQuota, obj.majorVersion, obj.minorVersion, obj.patchVersion).mkString(", "));
+ }
+ case "parsekey" => {
+ println("Testing key decode.");
+ val obj = decodeKey(args(1));
+ println("Key: "+List(obj.personName, obj.organizationName, obj.expiresDate, obj.editionId, obj.userQuota, obj.majorVersion, obj.minorVersion, obj.patchVersion).mkString(", "));
+ }
+ case "testascii" => {
+ val one = 17;
+ val two = -1L;
+ val three = (Math.random*Math.pow(10, (Math.random*10).toInt)).toInt;
+ println(List(one, two, three).mkString(", "));
+ println(List(toInt(toBytes(one)), toLong(toBytes(two)), toInt(toBytes(three))).mkString(", "));
+ val bytes = Encryptomatic.asciiToBytes(Encryptomatic.bytesToAscii(Array.concat[Byte](Array(1.toByte), toBytes(one), toBytes(two), toBytes(three))));
+ println("I can has bytes: "+bytes.length);
+ println(List(toInt(bytes.slice(1, 5)), toLong(bytes.slice(5, 13)), toInt(bytes.slice(13, 17))).mkString(", "));
+ }
+ }
+ }
+}
diff --git a/trunk/trunk/infrastructure/com.etherpad/main.scala b/trunk/trunk/infrastructure/com.etherpad/main.scala
new file mode 100644
index 0000000..5110aba
--- /dev/null
+++ b/trunk/trunk/infrastructure/com.etherpad/main.scala
@@ -0,0 +1,23 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.etherpad;
+
+object main {
+ def main(args: Array[String]) {
+ net.appjet.oui.main.main(args);
+ }
+} \ No newline at end of file