diff --git a/libs/privilege.py b/libs/privilege.py new file mode 100644 index 0000000..58a037f --- /dev/null +++ b/libs/privilege.py @@ -0,0 +1,433 @@ +#! /usr/bin/python +""" +privilege.py + +Copyright (c) 2009-2010, Travis H. + +License terms: Same as those of Python itself. + +This module is designed to implement the privilege-dropping API +described here: +http://www.cs.berkeley.edu/~daw/papers/setuid-usenix02.pdf +http://www.cs.berkeley.edu/~daw/papers/setuid-login08b.pdf + +This topic is complex so you should read at least the second paper +before trying to understand why I'm doing this. + +NOTE: This code does not (yet) attempt to work on Solaris and AIX. +That is, you must have getres[ug]id and setres[ug]id to use it. + +TODO: +I have added setres[ug]id to python; unsure of when it will +appear; make a future version of this will check for its presence. + +Make interface more OO, less imperative. + +Implement drop_priv_temp, restore_priv. + +Implement on systems without setres[ug]id + +Contributors: +Kevin Gilette +""" + +from ctypes import * +from ctypes.util import find_library +import os +import pwd +import grp + +# Get the name of the Operating System +os_kernel = os.uname()[0] + +class PrivilegeFail(Exception): + pass + +clib = CDLL(find_library("c")) + +# Tested on x86-64 -- all tests pass when run as root +__uid_t = c_uint +__gid_t = c_uint + +_getresuid = clib.getresuid + +def getresuid(): + """Get the real, effective, and saved user IDs""" + # Create some memory locations for getresuid to write to. + r = __uid_t() + e = __uid_t() + s = __uid_t() + # Call getresuid syscall, passing by reference. + res = _getresuid(byref(r), byref(e), byref(s)) + if res < 0: raise pythonapi.PyErr_SetFromErrno(py_object(OSError)) + # Convert to python integers. + return r.value, e.value, s.value + +_getresgid = clib.getresgid + +def getresgid(): + """Get the real, effective, and saved group IDs""" + r = __gid_t() + e = __gid_t() + s = __gid_t() + # Call getresgid syscall, passing by reference. + res = _getresgid(byref(r), byref(e), byref(s)) + if res < 0: raise pythonapi.PyErr_SetFromErrno(py_object(OSError)) + # Convert to python integers. + return r.value, e.value, s.value + +# Import the setresuid system call using ctypes +_setresuid = clib.setresuid +_setresuid.argttypes = [__uid_t, __uid_t, __uid_t] +_setresuid.resttype = c_int + +# Import the setresgid system call using ctypes +_setresgid = clib.setresgid +_setresgid.argttypes = [__gid_t, __gid_t, __gid_t] +_setresgid.resttype = c_int + +def setresuid(ruid, euid, suid): + """Set the real, effective, and saved user IDs""" + res = _setresuid(__uid_t(ruid), __uid_t(euid), __uid_t(suid)) + if res < 0: raise pythonapi.PyErr_SetFromErrno(py_object(OSError)) + +def setresgid(rgid, egid, sgid): + """Set the real, effecive, and saved group IDs""" + res = _setresgid(__gid_t(rgid), __gid_t(egid), __gid_t(sgid)) + if res < 0: raise pythonapi.PyErr_SetFromErrno(py_object(OSError)) + +def sort_uniq(args): + """Sort a sequence, discarding duplicates.""" + return sorted(set(args)) + +class user_credentials: + """ + This represents the credentials associated with a user. + User ID + Group ID + Supplementary Groups + This is used as an argument to drop_permanently + """ + def __init__(self, uid, gid, sups): + # -1 has special meaning for several set*id calls (ignore) + if uid == -1: raise PrivilegeFail + if gid == -1: raise PrivilegeFail + nm = os.sysconf('SC_NGROUPS_MAX') + if nm < 0 or nm < len(sups): raise PrivilegeFail + self.uid = uid + self.gid = gid + self.sups = sort_uniq(sups) + +def eql_sups(current, target): + """ + Compare two supplementary group lists, and ignore if effective GID is in current but not target. + Prerequisite: The supplementary group lists are sorted and filtered for duplicates. + """ + egid = os.getegid() + my_current = sort_uniq(current) + # Instead of tediously ignoring this value, if it's in the current list, then go ahead + # and add it to the target list + if egid in current: + my_target = target + [ egid ] + else: + my_target = target + my_target = sort_uniq(my_target) + return my_current == my_target + +def get_sups(): + """This is here to give us a layer of abstraction relative to system calls""" + return os.getgroups() + +def set_sups(target_sups): + """ + This is designed to give us a layer of abstraction from the system calls. + It also accomodates FreeBSD's idiosyncracy (which is POSIX-compliant) of + keeping the egid in the supplementary groups list. + It also makes an effort to not call the setgroups routine if the target + group list is identical to the current one in force. + """ + global os_kernel + if os_kernel == 'FreeBSD': + target_sups = [ os.getegid() ] + target_sups + if os.geteuid() == 0: + # This will raise an OSError exception if it fails + os.setgroups(target_sups) + else: + cur_sups = get_sups() + # This will probably fail + if not eql_sups(cur_sups, target_sups): + # This will raise an OSError exception if it fails + os.setgroups(target_sups) + return True + +def set_gids(r, e, s): + """This is here to give us a layer of abstraction relative to system calls""" + setresgid(r, e, s) + +def set_uids(r, e, s): + """This is here to give us a layer of abstraction relative to system calls""" + setresuid(r, e, s) + +class res_ids: + """ + This represents the three IDs (group or user) associated with a process. + """ + def __init__(self, real, effective, saved): + self.r = real + self.e = effective + self.s = saved + +class proc_credentials: + """ + This obtains and represents the credentials associated with a process. + """ + def __init__(self): + self.uids = apply(res_ids, getresuid()) + self.gids = apply(res_ids, getresgid()) + self.sups = sort_uniq(os.getgroups()) + +def get_fs_ids(): + """Get filesystem IDs - applies only to Linux""" + uid = None + gid = None + file = open('/proc/self/status', 'r') + for line in file: + fields = line.split() + if fields[0] == 'Uid:': + uid = int(fields[4]) + elif fields[0] == 'Gid:': + gid = int(fields[4]) + return uid, gid + +def coerce_user(user): + if hasattr(user, '__int__'): + return int(user) + return pwd.getpwnam(user).pw_uid + +def coerce_group(group): + if hasattr(group, '__int__'): + return int(group) + return grp.getgrnam(group).gr_gid + +def drop_privileges_permanently(uid, gid, sups): + """ + This routine is designed to permanently drop all privileges to the + user, group, and supplementary groups specified. + """ + + uid = coerce_user(uid) + gid = coerce_group(gid) + sups = map(coerce_group, sups) + + # This does some syntax checking + ucred = user_credentials(uid, gid, sups) + + # This is for our convenience + u = uid + g = gid + + # Order is important in these three calls + set_sups(ucred.sups) + set_gids(g, g, g) # real, effective, saved + set_uids(u, u, u) # real, effective, saved + + # Check that we actually did what we expected or throw exception. + pc = proc_credentials() + # Portably compare the supplementary group list + if not eql_sups(pc.sups, ucred.sups): raise PrivilegeFail + # Check all the gids + if not (g == pc.gids.r and g == pc.gids.e and g == pc.gids.s): + raise PrivilegeFail + # Check all the uids + if not (u == pc.uids.r and u == pc.gids.e and u == pc.uids.s): + raise PrivilegeFail + global os_kernel + if os_kernel == 'Linux': + if get_fs_ids() != (u, g): raise PrivilegeFail + +# This is all test code +# It is run if this script is invoked directly +if __name__ == '__main__': + + import unittest + + class test_getresXid(unittest.TestCase): + """Test the calls to getresXid""" + def test__getresuid(self): + # TODO: is there any way to avoid redefining this here? I tried a global but it didn't + # work. + # Note: the double-leading underscore may be getting special-cased by python. + __uid_t = c_int + r = __uid_t() + e = __uid_t() + s = __uid_t() + ret = _getresuid(byref(r), byref(e), byref(s)) + self.assertEqual(ret, 0) + self.assertEqual(r.value, os.getuid()) + self.assertEqual(e.value, os.geteuid()) + # NOTE: no other portable way to get saved UID + def test_getresuid(self): + (r, e, s) = getresuid() + self.assertEqual(r, os.getuid()) + self.assertEqual(e, os.geteuid()) + # NOTE: no other portable way to get saved UID + def test__getresgid(self): + __gid_t = c_int + r = __gid_t() + e = __gid_t() + s = __gid_t() + ret = _getresgid(byref(r), byref(e), byref(s)) + self.assertEqual(ret, 0) + self.assertEqual(r.value, os.getgid()) + self.assertEqual(e.value, os.getegid()) + # NOTE: no other portable way to get saved GID + def test_getresgid(self): + (r, e, s) = getresgid() + self.assertEqual(r, os.getgid()) + self.assertEqual(e, os.getegid()) + # NOTE: no other portable way to get saved GID + + class test_setresuid(unittest.TestCase): + """Test the call to setresuid""" + def setUp(self): + self.uid = os.geteuid() + def test__setresuid(self): + __uid_t = c_int + r1 = __uid_t(1) + e1 = __uid_t(1) + # Must save root UID so that we can reset UIDs for other tests + s1 = __uid_t(0) + if self.uid == 0: + rv = _setresuid(r1, e1, s1) + self.assertEqual(rv, 0) + (r2, e2, s2) = getresuid() + self.assertEqual(r1.value, r2) + self.assertEqual(e1.value, e2) + self.assertEqual(s1.value, s2) + else: + rv = _setresuid(r1, e1, s1) + self.assertEqual(rv, -1) + def test_setresuid(self): + if self.uid == 0: + setresuid(1,1,0) + (r, e, s) = getresuid() + self.assertEqual(r, 1) + self.assertEqual(e, 1) + self.assertEqual(s, 0) + else: + self.assertRaises(OSError, setresuid, 1, 1, 1) + def tearDown(self): + if self.uid == 0: + # Restore UIDs for next test + _setresuid(0, 0, 0) + + class test_setresgid(unittest.TestCase): + """Test the call to setresgid""" + def setUp(self): + self.uid = os.geteuid() + def test__setresgid(self): + __gid_t = c_int + r1 = __gid_t(1) + e1 = __gid_t(1) + s1 = __gid_t(1) + if self.uid == 0: + rv = _setresgid(r1, e1, s1) + self.assertEqual(rv, 0) + (r2, e2, s2) = getresgid() + self.assertEqual(r1.value, r2) + self.assertEqual(e1.value, e2) + self.assertEqual(s1.value, s2) + else: + rv = _setresgid(r1, e1, s1) + self.assertEqual(rv, -1) + def test_setresgid(self): + if self.uid == 0: + setresgid(1,1,1) + (r, e, s) = getresgid() + self.assertEqual(r, 1) + self.assertEqual(e, 1) + self.assertEqual(s, 1) + else: + self.assertRaises(OSError, setresgid, 1, 1, 1) + + class test_sort_uniq(unittest.TestCase): + def test_sort_uniq(self): + l = [ 'c', 'a', 'b', 'a' ] + self.assertEqual(sort_uniq(l), [ 'a', 'b', 'c']) + + class test_user_credentials(unittest.TestCase): + def test_negatives(self): + self.assertRaises(PrivilegeFail, user_credentials, -1, 0, []) + self.assertRaises(PrivilegeFail, user_credentials, 0, -1, []) + uc = user_credentials(0, 0, [0, 1]) + + class test_eql_sups(unittest.TestCase): + def test_equal(self): + self.assert_(eql_sups([0, 1, 2], [0, 1, 2])) + def test_contains_egid(self): + self.assert_(eql_sups(sort_uniq([0, 1, 2, os.getegid()]), [0, 1, 2])) + self.assert_(eql_sups(sort_uniq([0, 1, os.getegid(), 9999]), [0, 1, 9999])) + + class test_get_sups(unittest.TestCase): + def test(self): + self.assertEqual(os.getgroups(), get_sups()) + + class test_set_sups(unittest.TestCase): + def test_equal(self): + sups = os.getgroups() + set_sups(sups) + self.assertEqual(sups, os.getgroups()) + def test_unequal(self): + old_sups = os.getgroups() + sups = [ 1, 2, 3 ] + if os.geteuid() == 0: + set_sups(sups) + self.assert_(eql_sups(os.getgroups(), sups)) + # Clean up by resetting supplementary groups + set_sups(old_sups) + else: + self.assertRaises(OSError, set_sups, sups) + + class test_res_ids(unittest.TestCase): + def test_res_ids(self): + ids = res_ids(1, 2, 3) + self.assertEqual(ids.r, 1) + self.assertEqual(ids.e, 2) + self.assertEqual(ids.s, 3) + + class test_proc_credentials(unittest.TestCase): + def test_pc(self): + pc = proc_credentials() + self.assertEqual(pc.uids.r, os.getuid()) + self.assertEqual(pc.uids.e, os.geteuid()) + self.assertEqual(pc.uids.s, (getresuid())[2]) + self.assertEqual(pc.gids.r, os.getgid()) + self.assertEqual(pc.gids.e, os.getegid()) + self.assertEqual(pc.gids.s, (getresgid())[2]) + self.assertEqual(pc.sups, sort_uniq(os.getgroups())) + + class test_get_fs_ids(unittest.TestCase): + def test_get_fs_ids(self): + uid, gid = get_fs_ids() + self.assertEqual(uid, os.getuid()) + self.assertEqual(gid, os.getgid()) + + class test_drop_privs(unittest.TestCase): + def test_drop_privs(self): + """This test must be run last""" + if os.geteuid() == 0: + drop_privileges_permanently(1, 1, [1]) + + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(test_getresXid)) + suite.addTest(unittest.makeSuite(test_setresuid)) + suite.addTest(unittest.makeSuite(test_setresgid)) + suite.addTest(unittest.makeSuite(test_sort_uniq)) + suite.addTest(unittest.makeSuite(test_user_credentials)) + suite.addTest(unittest.makeSuite(test_eql_sups)) + suite.addTest(unittest.makeSuite(test_set_sups)) + suite.addTest(unittest.makeSuite(test_res_ids)) + suite.addTest(unittest.makeSuite(test_proc_credentials)) + suite.addTest(unittest.makeSuite(test_get_fs_ids)) + suite.addTest(unittest.makeSuite(test_drop_privs)) + unittest.TextTestRunner().run(suite) diff --git a/settings.py b/settings.py index 00e1963..eef79a5 100644 --- a/settings.py +++ b/settings.py @@ -15,4 +15,14 @@ DEBUG = True # absolute path where the paste files should be store # default in projectdirectory/static/content/ # use "/" even under Windows -PASTE_FILES_ROOT = os.path.join(STATIC_FILES_ROOT, 'content') \ No newline at end of file +PASTE_FILES_ROOT = os.path.join(STATIC_FILES_ROOT, 'content') +# Port and host the embeded python server should be using in prod and in dev +PROD_HOST = "0.0.0.0" +PROD_PORT= "80" +DEV_HOST = "127.0.0.1" +DEV_PORT= "8000" + +# User and group the server should run as. Set to None if it should be the +# current user +USER = None +GROUP = None \ No newline at end of file diff --git a/src/paste.py b/src/paste.py index b7ecf63..f4d6f40 100644 --- a/src/paste.py +++ b/src/paste.py @@ -153,7 +153,7 @@ class Paste(object): if self.expiration == "burn_after_reading": self.expiration = self.expiration + '#%s' % datetime.now() - # writethe paste + # write the paste with open(self.path, 'w') as f: f.write(unicode(self.expiration) + '\n') f.write(self.content + '\n') diff --git a/start.py b/start.py index bc15d78..41f2e18 100644 --- a/start.py +++ b/start.py @@ -7,6 +7,10 @@ import os import hashlib +import thread +import time +import tempfile +import glob from datetime import datetime, timedelta @@ -14,6 +18,8 @@ from src import settings, setup_path, Paste setup_path() +from privilege import drop_privileges_permanently, coerce_user, coerce_group + from bottle import (Bottle, route, run, abort, static_file, debug, view, request) @@ -86,8 +92,32 @@ def server_static(filename): if __name__ == "__main__": + + def drop_privileges(): + time.sleep(5) + if settings.USER: + settings.GROUP = settings.GROUP or settings.USER + try: + user = coerce_user(settings.USER) + group = coerce_group(settings.GROUP) + + lock_files = glob.glob(os.path.join(tempfile.gettempdir(), + 'bottle.*.lock')) + for lock_file in lock_files: + os.chown(lock_file, user, group) + + drop_privileges_permanently(settings.USER, settings.GROUP, ()) + except Exception: + print "Failed to drop privileges. Running with current user." + + thread.start_new_thread(drop_privileges, ()) + if settings.DEBUG: debug(True) - run(app, host='localhost', port=8000, reloader=True, server="cherrypy") + run(app, host=settings.DEV_HOST, port=settings.DEV_PORT, + reloader=True, server="cherrypy") else: - run(app, host='localhost', port=8000, server="cherrypy") + run(app, host=settings.PROD_HOST, + port=settings.PROD_PORT, server="cherrypy") + + diff --git a/static/css/style.css b/static/css/style.css index 1c1c166..fc551ba 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -41,7 +41,7 @@ body { .paste-option { float:right; } - + select { width: 135px; @@ -152,17 +152,16 @@ ol.linenums span:first-child { li.L0, li.L1, li.L2, li.L3, li.L4, li.L5, li.L6, li.L7, li.L8, li.L9 { - list-style-type: decimal; - background: inherit; + list-style-type: decimal; + background: inherit; } -#clip-container div:hover, -#clip-button:hover, -#clip-button a:hover{ +a#clip-button.hover{ cursor:pointer; +text-decoration:underline; } -#copy-success { +#copy-success, #short-url-success { display:none; } diff --git a/static/js/ZeroClipboard.js b/static/js/ZeroClipboard.js index 5adde95..c4b548b 100755 --- a/static/js/ZeroClipboard.js +++ b/static/js/ZeroClipboard.js @@ -2,12 +2,12 @@ // Author: Joseph Huckaby var ZeroClipboard = { - + version: "1.0.7", clients: {}, // registered upload clients on page, indexed by id moviePath: 'ZeroClipboard.swf', // URL to movie nextId: 1, // ID of next movie - + $: function(thingy) { // simple DOM lookup utility function if (typeof(thingy) == 'string') thingy = document.getElementById(thingy); @@ -34,31 +34,31 @@ var ZeroClipboard = { } return thingy; }, - + setMoviePath: function(path) { // set path to ZeroClipboard.swf this.moviePath = path; }, - + dispatch: function(id, eventName, args) { - // receive event from flash movie, send to client + // receive event from flash movie, send to client var client = this.clients[id]; if (client) { client.receiveEvent(eventName, args); } }, - + register: function(id, client) { // register new client to receive events this.clients[id] = client; }, - + getDOMObjectPosition: function(obj, stopObj) { // get absolute coordinates for dom element var info = { - left: 0, - top: 0, - width: obj.width ? obj.width : obj.offsetWidth, + left: 0, + top: 0, + width: obj.width ? obj.width : obj.offsetWidth, height: obj.height ? obj.height : obj.offsetHeight }; @@ -70,25 +70,25 @@ var ZeroClipboard = { return info; }, - + Client: function(elem) { // constructor for new simple upload client this.handlers = {}; - + // unique ID this.id = ZeroClipboard.nextId++; this.movieId = 'ZeroClipboardMovie_' + this.id; - + // register client with singleton to receive flash events ZeroClipboard.register(this.id, this); - + // create movie if (elem) this.glue(elem); } }; ZeroClipboard.Client.prototype = { - + id: 0, // unique ID for us ready: false, // whether movie is ready to receive events or not movie: null, // reference to movie object @@ -96,28 +96,28 @@ ZeroClipboard.Client.prototype = { handCursorEnabled: true, // whether to show hand cursor, or default pointer cursor cssEffects: true, // enable CSS mouse effects on dom container handlers: null, // user event handlers - + glue: function(elem, appendElem, stylesToAdd) { // glue to DOM element // elem can be ID or actual DOM element object this.domElement = ZeroClipboard.$(elem); - + // float just above object, or zIndex 99 if dom element isn't set var zIndex = 99; if (this.domElement.style.zIndex) { zIndex = parseInt(this.domElement.style.zIndex, 10) + 1; } - + if (typeof(appendElem) == 'string') { appendElem = ZeroClipboard.$(appendElem); } else if (typeof(appendElem) == 'undefined') { appendElem = document.getElementsByTagName('body')[0]; } - + // find X/Y position of domElement var box = ZeroClipboard.getDOMObjectPosition(this.domElement, appendElem); - + // create floating DIV above element this.div = document.createElement('div'); var style = this.div.style; @@ -127,27 +127,27 @@ ZeroClipboard.Client.prototype = { style.width = '' + box.width + 'px'; style.height = '' + box.height + 'px'; style.zIndex = zIndex; - + if (typeof(stylesToAdd) == 'object') { for (addedStyle in stylesToAdd) { style[addedStyle] = stylesToAdd[addedStyle]; } } - - // style.backgroundColor = '#f00'; // debug - + + //style.backgroundColor = '#f00'; // debug + appendElem.appendChild(this.div); - + this.div.innerHTML = this.getHTML( box.width, box.height ); }, - + getHTML: function(width, height) { // return HTML for movie var html = ''; - var flashvars = 'id=' + this.id + - '&width=' + width + + var flashvars = 'id=' + this.id + + '&width=' + width + '&height=' + height; - + if (navigator.userAgent.match(/MSIE/)) { // IE gets an OBJECT tag var protocol = location.href.match(/^https/i) ? 'https://' : 'http://'; @@ -159,33 +159,33 @@ ZeroClipboard.Client.prototype = { } return html; }, - + hide: function() { // temporarily hide floater offscreen if (this.div) { this.div.style.left = '-2000px'; } }, - + show: function() { // show ourselves after a call to hide() this.reposition(); }, - + destroy: function() { // destroy control and floater if (this.domElement && this.div) { this.hide(); this.div.innerHTML = ''; - + var body = document.getElementsByTagName('body')[0]; try { body.removeChild( this.div ); } catch(e) {;} - + this.domElement = null; this.div = null; } }, - + reposition: function(elem) { // reposition our floating div, optionally to new container // warning: container CANNOT change size, only position @@ -193,7 +193,7 @@ ZeroClipboard.Client.prototype = { this.domElement = ZeroClipboard.$(elem); if (!this.domElement) this.hide(); } - + if (this.domElement && this.div) { var box = ZeroClipboard.getDOMObjectPosition(this.domElement); var style = this.div.style; @@ -201,13 +201,13 @@ ZeroClipboard.Client.prototype = { style.top = '' + box.top + 'px'; } }, - + setText: function(newText) { // set text to be copied to clipboard this.clipText = newText; if (this.ready) this.movie.setText(newText); }, - + addEventListener: function(eventName, func) { // add user event listener for event // event types: load, queueStart, fileStart, fileComplete, queueComplete, progress, error, cancel @@ -215,22 +215,22 @@ ZeroClipboard.Client.prototype = { if (!this.handlers[eventName]) this.handlers[eventName] = []; this.handlers[eventName].push(func); }, - + setHandCursor: function(enabled) { // enable hand cursor (true), or default arrow cursor (false) this.handCursorEnabled = enabled; if (this.ready) this.movie.setHandCursor(enabled); }, - + setCSSEffects: function(enabled) { // enable or disable CSS effects on DOM container this.cssEffects = !!enabled; }, - + receiveEvent: function(eventName, args) { // receive event from flash eventName = eventName.toString().toLowerCase().replace(/^on/, ''); - + // special behavior for certain events switch (eventName) { case 'load': @@ -242,7 +242,7 @@ ZeroClipboard.Client.prototype = { setTimeout( function() { self.receiveEvent('load', null); }, 1 ); return; } - + // firefox on pc needs a "kick" in order to set these in certain cases if (!this.ready && navigator.userAgent.match(/Firefox/) && navigator.userAgent.match(/Windows/)) { var self = this; @@ -250,19 +250,19 @@ ZeroClipboard.Client.prototype = { this.ready = true; return; } - + this.ready = true; this.movie.setText( this.clipText ); this.movie.setHandCursor( this.handCursorEnabled ); break; - + case 'mouseover': if (this.domElement && this.cssEffects) { this.domElement.addClass('hover'); if (this.recoverActive) this.domElement.addClass('active'); } break; - + case 'mouseout': if (this.domElement && this.cssEffects) { this.recoverActive = false; @@ -273,13 +273,13 @@ ZeroClipboard.Client.prototype = { this.domElement.removeClass('hover'); } break; - + case 'mousedown': if (this.domElement && this.cssEffects) { this.domElement.addClass('active'); } break; - + case 'mouseup': if (this.domElement && this.cssEffects) { this.domElement.removeClass('active'); @@ -287,11 +287,11 @@ ZeroClipboard.Client.prototype = { } break; } // switch eventName - + if (this.handlers[eventName]) { for (var idx = 0, len = this.handlers[eventName].length; idx < len; idx++) { var func = this.handlers[eventName][idx]; - + if (typeof(func) == 'function') { // actual function reference func(this, args); @@ -307,5 +307,5 @@ ZeroClipboard.Client.prototype = { } // foreach event handler defined } // user defined handler for event } - + }; diff --git a/static/js/behavior.js b/static/js/behavior.js index 917f645..c8d5c52 100644 --- a/static/js/behavior.js +++ b/static/js/behavior.js @@ -38,13 +38,24 @@ zerobin = { }, get_keys: function(){ var keys = new Array(); - for(i=0; i<=localStorage.length; i++){ if(localStorage.key(i) != null) keys[i] = parseInt(localStorage.key(i),10); } return keys.sort(zerobin.numOrdA); }, + /** Get a tinyurl using JSONP */ + getTinyURL: function(longURL, success) { + + callback = 'zerobin_tiny_url_callback'; + window[callback] = function(response){ + success(response.tinyurl); + delete window[callback]; + }; + + var api = 'http://json-tinyurl.appspot.com/?url='; + $.getJSON(api + encodeURIComponent(longURL) + '&callback=' + callback); + }, support_localstorage: function(){ if (localStorage){ return true; @@ -91,6 +102,13 @@ zerobin = { }else{ return 'Sorry your browser does not support LocalStorage, We cannot display your previous pastes.'; } + }, + get_paste_content: function(){ + var content_clone = '' ; + $("#paste-content li").each(function(index) { + content_clone = content_clone + $(this).text() + '\n'; + }); + return content_clone; } }; @@ -145,21 +163,32 @@ if (content && key) { if (!error) { + $('#short-url').click(function(e) { + e.preventDefault(); + $('#short-url').text('Loading short url...'); + zerobin.getTinyURL(window.location.toString(), function(tinyurl){ + clip.setText(tinyurl); + $('#copy-success').hide(); + $('#short-url-success') + .html('Short url: ' + tinyurk + '') + .show('fadeUp'); + $('#short-url').text('Get short url'); + }); + }); + prettyPrint(); /* Setup flash clipboard button */ ZeroClipboard.setMoviePath('/static/js/ZeroClipboard.swf' ); - var clip = new ZeroClipboard.Client(); - clip.addEventListener('onMouseUp', function(){ - clip.setText($('#paste-content').text()); + var clip = new ZeroClipboard.Client(); + clip.addEventListener('mouseup', function(){ + clip.setText(zerobin.get_paste_content()); }); clip.addEventListener('complete', function(){ - $('#copy-success').show(); + $('#copy-success').show('fadeUp', function(){clip.reposition()}); }); - clip.addEventListener('onLoad', function(){ - }); - clip.glue('clip-button', 'clip-container' ); + clip.glue('clip-button'); window.onresize = clip.reposition; } @@ -192,13 +221,12 @@ $('#content').live('keyup change', function(){ /* Display previous pastes */ $('.previous-pastes .items').html(zerobin.get_pastes()); + + /* clone a paste */ $('.btn-clone').click(function(e){ e.preventDefault(); - var content_clone = '' ; - $("#paste-content li").each(function(index) { - content_clone = content_clone + $(this).text() + '\n'; - }); + var content_clone = zerobin.get_paste_content(); $('.submit-form').show(); $('.paste-form').remove(); $('#content').val(content_clone); diff --git a/static/js/swfobject.js b/static/js/swfobject.js deleted file mode 100644 index 8eafe9d..0000000 --- a/static/js/swfobject.js +++ /dev/null @@ -1,4 +0,0 @@ -/* SWFObject v2.2 - is released under the MIT License -*/ -var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab + + - - Copy To Clipboard - + Copy To Clipboard + | + Get short url + Clone New Paste
- - Copy To Clipboard - + Copy To Clipboard + | + Get short url + Clone New Paste