├── linkdrop ├── lib │ ├── __init__.py │ ├── constants.py │ ├── base.py │ ├── app_globals.py │ ├── shortener.py │ └── metrics.py ├── tests │ ├── lib │ │ ├── __init__.py │ │ ├── test_metrics.py │ │ └── test_shortener.py │ ├── controllers │ │ ├── __init__.py │ │ └── test_contacts.py │ ├── functional │ │ └── __init__.py │ ├── invalid_requests │ │ ├── __init__.py │ │ ├── test_send.py │ │ └── test_contacts.py │ ├── services │ │ ├── __init__.py │ │ ├── corpus │ │ │ ├── http-twitter.com-auth-successful │ │ │ │ ├── expected-f1-response.json │ │ │ │ ├── meta.json │ │ │ │ ├── request-0 │ │ │ │ └── response-0 │ │ │ ├── http-api.linkedin.com-auth-successful │ │ │ │ ├── expected-f1-response.json │ │ │ │ ├── meta.json │ │ │ │ ├── response-0 │ │ │ │ └── request-0 │ │ │ ├── http-graph.facebook.com-auth-successful │ │ │ │ ├── expected-f1-response.json │ │ │ │ ├── meta.json │ │ │ │ ├── response-0 │ │ │ │ └── response-1 │ │ │ ├── smtp-smtp.gmail.com-send-successful │ │ │ │ ├── meta.json │ │ │ │ └── expected-f1-data.json │ │ │ ├── smtp-smtp.gmail.com-send-server-disconnected │ │ │ │ ├── meta.json │ │ │ │ ├── expected-f1-data.json │ │ │ │ └── smtp-trace │ │ │ ├── http-api.twitter.com-contacts-successful │ │ │ │ ├── meta.json │ │ │ │ ├── request-0 │ │ │ │ ├── expected-f1-data.json │ │ │ │ └── response-0 │ │ │ ├── smtp-smtp.gmail.com-send-recipient-syntax-error │ │ │ │ ├── meta.json │ │ │ │ ├── expected-f1-data.json │ │ │ │ └── smtp-trace │ │ │ ├── http-social.yahooapis.com-contacts-revoked-token │ │ │ │ ├── expected-f1-data.json │ │ │ │ ├── meta.json │ │ │ │ └── response-0 │ │ │ ├── http-mail.yahooapis.com-send-revoked-token │ │ │ │ ├── meta.json │ │ │ │ ├── expected-f1-data.json │ │ │ │ └── response-0 │ │ │ ├── http-api.linkedin.com-contacts-successful │ │ │ │ ├── request-0 │ │ │ │ ├── meta.json │ │ │ │ ├── expected-f1-data.json │ │ │ │ └── response-0 │ │ │ ├── http-api.twitter.com-send-public-error-duplicate │ │ │ │ ├── meta.json │ │ │ │ ├── expected-f1-data.json │ │ │ │ ├── f1-request.json │ │ │ │ ├── request-0 │ │ │ │ └── response-0 │ │ │ ├── http-mail.yahooapis.com-send-success │ │ │ │ ├── meta.json │ │ │ │ ├── expected-f1-data.json │ │ │ │ └── response-0 │ │ │ ├── http-api.linkedin.com-send-anyone-successful │ │ │ │ ├── meta.json │ │ │ │ ├── response-0 │ │ │ │ ├── f1-request.json │ │ │ │ ├── expected-f1-data.json │ │ │ │ └── request-0 │ │ │ ├── http-api.linkedin.com-send-private-successful │ │ │ │ ├── meta.json │ │ │ │ ├── response-0 │ │ │ │ ├── expected-f1-data.json │ │ │ │ ├── f1-request.json │ │ │ │ └── request-0 │ │ │ ├── http-api.linkedin.com-send-connections-successful │ │ │ │ ├── meta.json │ │ │ │ ├── response-0 │ │ │ │ ├── f1-request.json │ │ │ │ ├── expected-f1-data.json │ │ │ │ └── request-0 │ │ │ ├── http-api.twitter.com-send-direct-successful │ │ │ │ ├── meta.json │ │ │ │ ├── request-0 │ │ │ │ ├── f1-request.json │ │ │ │ ├── expected-f1-data.json │ │ │ │ └── response-0 │ │ │ ├── http-api.twitter.com-send-public-successful │ │ │ │ ├── meta.json │ │ │ │ ├── f1-request.json │ │ │ │ ├── request-0 │ │ │ │ ├── expected-f1-data.json │ │ │ │ └── response-0 │ │ │ ├── http-social.yahooapis.com-contacts-success │ │ │ │ ├── meta.json │ │ │ │ ├── expected-f1-data.json │ │ │ │ └── response-0 │ │ │ ├── http-social.yahooapis.com-send-404-not-on-accelerator │ │ │ │ ├── meta.json │ │ │ │ ├── README.txt │ │ │ │ ├── expected-f1-data.json │ │ │ │ └── response-0 │ │ │ ├── http-graph.facebook.com-contacts-list-no-groups │ │ │ │ ├── expected-f1-data.json │ │ │ │ ├── meta.json │ │ │ │ └── response-0 │ │ │ ├── http-graph.facebook.com-send-successful │ │ │ │ ├── expected-f1-data.json │ │ │ │ ├── meta.json │ │ │ │ ├── f1-request.json │ │ │ │ ├── request-0 │ │ │ │ └── response-0 │ │ │ ├── http-www.google.com-contacts-successful │ │ │ │ ├── meta.json │ │ │ │ └── expected-f1-data.json │ │ │ ├── http-social.yahooapis.com-contacts-500-internal-server-error │ │ │ │ ├── meta.json │ │ │ │ ├── README.txt │ │ │ │ ├── expected-f1-data.json │ │ │ │ └── response-0 │ │ │ ├── http-graph.facebook.com-send-group-wall │ │ │ │ ├── meta.json │ │ │ │ ├── expected-f1-data.json │ │ │ │ ├── f1-request.json │ │ │ │ ├── request-0 │ │ │ │ └── response-0 │ │ │ ├── http-graph.facebook.com-send-400-validating-token │ │ │ │ ├── meta.json │ │ │ │ ├── expected-f1-data.json │ │ │ │ ├── README.txt │ │ │ │ └── response-0 │ │ │ ├── http-graph.facebook.com-send-with-revoked-token │ │ │ │ ├── meta.json │ │ │ │ ├── expected-f1-data.json │ │ │ │ └── response-0 │ │ │ └── README.txt │ │ └── test_headers.py │ └── __init__.py ├── __init__.py ├── templates │ ├── text_email.mako │ └── html_email.mako ├── controllers │ ├── __init__.py │ ├── account.py │ └── contacts.py ├── wsgiapp.py └── static.py ├── tools ├── webbuild │ └── requirejs │ │ ├── build │ │ ├── lib │ │ │ ├── rhino │ │ │ │ ├── js.jar │ │ │ │ └── LICENSE │ │ │ └── closure │ │ │ │ └── compiler.jar │ │ ├── build.sh │ │ ├── buildebug.sh │ │ ├── jslib │ │ │ ├── commandLine.js │ │ │ ├── logger.js │ │ │ ├── lang.js │ │ │ └── pragma.js │ │ └── build.js │ │ └── require │ │ ├── rhino.js │ │ └── text.js └── makespec ├── extensions └── firefox-share │ ├── src │ ├── chrome │ │ ├── skin │ │ │ ├── status-shared.png │ │ │ ├── extension-icon.png │ │ │ ├── status-sharing.png │ │ │ ├── toolbar-button.png │ │ │ ├── toolbar-button-active.png │ │ │ ├── toolbar-button-glow.png │ │ │ ├── windows │ │ │ │ ├── status-shared.png │ │ │ │ ├── extension-icon.png │ │ │ │ ├── status-sharing.png │ │ │ │ ├── toolbar-button.png │ │ │ │ ├── toolbar-button-glow.png │ │ │ │ ├── toolbar-button-active.png │ │ │ │ └── overlay.css │ │ │ ├── web │ │ │ │ └── installed.css │ │ │ └── overlay.css │ │ ├── locale │ │ │ └── en-US.properties │ │ └── content │ │ │ └── down.html │ ├── install.rdf │ └── modules │ │ └── addonutils.js │ └── testapi.html ├── .gitignore ├── MANIFEST.in ├── bin ├── linkdrop-sync └── update ├── wsgi └── linkdrop.wsgi ├── setup.cfg ├── f1-nginx.conf ├── grinder ├── grinder.properties ├── README.txt └── sendutil.py ├── docs └── index.txt ├── apache └── f1.conf ├── README.production ├── f1.spec.in ├── setup.py ├── misc └── lsprofcalltree.py ├── README.md ├── production.ini ├── staging.ini ├── Makefile └── grinder.ini /linkdrop/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linkdrop/tests/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linkdrop/tests/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linkdrop/tests/functional/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linkdrop/tests/invalid_requests/__init__.py: -------------------------------------------------------------------------------- 1 | # nothing to see here... 2 | -------------------------------------------------------------------------------- /linkdrop/tests/services/__init__.py: -------------------------------------------------------------------------------- 1 | # nothing to see here, move along... 2 | -------------------------------------------------------------------------------- /linkdrop/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger('share') 4 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-twitter.com-auth-successful/expected-f1-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": 302 3 | } 4 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-auth-successful/expected-f1-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": 302 3 | } 4 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-auth-successful/expected-f1-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": 302 3 | } 4 | -------------------------------------------------------------------------------- /tools/webbuild/requirejs/build/lib/rhino/js.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/f1/HEAD/tools/webbuild/requirejs/build/lib/rhino/js.jar -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/smtp-smtp.gmail.com-send-successful/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "reason": "successful send", 3 | "protocol": "smtp" 4 | } 5 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-auth-successful/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "reason": "successful auth", 3 | "protocol": "http" 4 | } 5 | -------------------------------------------------------------------------------- /tools/webbuild/requirejs/build/lib/closure/compiler.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/f1/HEAD/tools/webbuild/requirejs/build/lib/closure/compiler.jar -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/skin/status-shared.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/f1/HEAD/extensions/firefox-share/src/chrome/skin/status-shared.png -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/smtp-smtp.gmail.com-send-server-disconnected/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "smtp", 3 | "reason": "smtp exception" 4 | } 5 | -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/skin/extension-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/f1/HEAD/extensions/firefox-share/src/chrome/skin/extension-icon.png -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/skin/status-sharing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/f1/HEAD/extensions/firefox-share/src/chrome/skin/status-sharing.png -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/skin/toolbar-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/f1/HEAD/extensions/firefox-share/src/chrome/skin/toolbar-button.png -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-contacts-successful/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "automatic success save" 4 | } 5 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/smtp-smtp.gmail.com-send-recipient-syntax-error/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "reason": "rejected recipients", 3 | "protocol": "smtp" 4 | } 5 | -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/skin/toolbar-button-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/f1/HEAD/extensions/firefox-share/src/chrome/skin/toolbar-button-active.png -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/skin/toolbar-button-glow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/f1/HEAD/extensions/firefox-share/src/chrome/skin/toolbar-button-glow.png -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/skin/windows/status-shared.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/f1/HEAD/extensions/firefox-share/src/chrome/skin/windows/status-shared.png -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/skin/windows/extension-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/f1/HEAD/extensions/firefox-share/src/chrome/skin/windows/extension-icon.png -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/skin/windows/status-sharing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/f1/HEAD/extensions/firefox-share/src/chrome/skin/windows/status-sharing.png -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/skin/windows/toolbar-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/f1/HEAD/extensions/firefox-share/src/chrome/skin/windows/toolbar-button.png -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/skin/windows/toolbar-button-glow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/f1/HEAD/extensions/firefox-share/src/chrome/skin/windows/toolbar-button-glow.png -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/skin/windows/toolbar-button-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/f1/HEAD/extensions/firefox-share/src/chrome/skin/windows/toolbar-button-active.png -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-social.yahooapis.com-contacts-revoked-token/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "error": "*", 4 | "status": 401 5 | }, 6 | "result": null 7 | } 8 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-twitter.com-auth-successful/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "automatic success save", 4 | "uri": "https://twitter.com/oauth/request_token" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/smtp-smtp.gmail.com-send-successful/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | {"result": 2 | {"status": "message sent", "shorturl": null, "from": null, "to": "you@example.com"}, "error": null 3 | } 4 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-mail.yahooapis.com-send-revoked-token/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "yahoo error", 4 | "uri": "http://mail.yahooapis.com/ws/mail/v1.1/jsonrpc" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-contacts-successful/request-0: -------------------------------------------------------------------------------- 1 | GET /v1/people/~/connections?count=25 2 | x-li-format: json 3 | accept-encoding: gzip, deflate 4 | user-agent: Python-httplib2/$Rev$ 5 | 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-contacts-successful/request-0: -------------------------------------------------------------------------------- 1 | GET /1/statuses/followers.json?screen_name=mytwitterid&cursor=-1 2 | accept-encoding: gzip, deflate 3 | user-agent: Python-httplib2/$Rev$ 4 | 5 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-send-public-error-duplicate/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "no reason", 4 | "uri": "https://api.twitter.com/1/statuses/update.json" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-mail.yahooapis.com-send-success/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "automatic success save", 4 | "uri": "http://mail.yahooapis.com/ws/mail/v1.1/jsonrpc" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-auth-successful/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "automatic success save", 4 | "uri": "https://api.linkedin.com/uas/oauth/requestToken" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-send-anyone-successful/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "automatic success save", 4 | "uri": "http://api.linkedin.com/v1/people/~/shares" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-send-private-successful/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "automatic success save", 4 | "uri": "http://api.linkedin.com/v1/people/~/mailbox" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-send-connections-successful/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "automatic success save", 4 | "uri": "http://api.linkedin.com/v1/people/~/shares" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-send-direct-successful/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "automatic success save", 4 | "uri": "https://api.twitter.com/1/direct_messages/new.json" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-send-public-successful/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "automatic success save", 4 | "uri": "https://api.twitter.com/1/statuses/update.json" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-social.yahooapis.com-contacts-success/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "automatic success save", 4 | "uri": "http://social.yahooapis.com/v1/user/xxx/contacts" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-contacts-successful/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "automatic success save", 4 | "uri": "http://api.linkedin.com/v1/people/~/connections?count=25" 5 | } 6 | -------------------------------------------------------------------------------- /tools/webbuild/requirejs/build/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | MYDIR=`cd \`dirname "$0"\`; pwd` 4 | java -classpath $MYDIR/lib/rhino/js.jar:$MYDIR/lib/closure/compiler.jar org.mozilla.javascript.tools.shell.Main $MYDIR/build.js $MYDIR "$@" 5 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-social.yahooapis.com-send-404-not-on-accelerator/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "non-json yahoo response", 4 | "uri": "http://mail.yahooapis.com/ws/mail/v1.1/jsonrpc" 5 | } 6 | -------------------------------------------------------------------------------- /tools/webbuild/requirejs/build/buildebug.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | MYDIR=`cd \`dirname "$0"\`; pwd` 4 | java -classpath $MYDIR/lib/rhino/js.jar:$MYDIR/lib/closure/compiler.jar org.mozilla.javascript.tools.debugger.Main $MYDIR/build.js $MYDIR "$@" 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | *~ 4 | .coverage 5 | .ropeproject 6 | /lib 7 | coverage.xml 8 | nosetests.xml 9 | bin 10 | build 11 | deps 12 | dist 13 | htmlcov 14 | include 15 | web/current 16 | data 17 | env 18 | ShareCore.egg-info 19 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-send-private-successful/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 201 Created 2 | date: Tue, 19 Apr 2011 07:14:32 GMT 3 | status: 201 4 | content-length: 0 5 | vary: Accept-Encoding 6 | server: Apache-Coyote/1.1 7 | 8 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-contacts-list-no-groups/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": null, 3 | "result": { 4 | "entry": [], 5 | "itemsPerPage": 0, 6 | "startIndex": 0 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/smtp-smtp.gmail.com-send-recipient-syntax-error/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | {"result": null, 2 | "error": {"status": 555, "message": " 5.5.2 Syntax error. wt14sm2727478icb.16", 3 | "provider": "smtp.gmail.com"}} 4 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-successful/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | {"result": 2 | {"facebook.com": "12345678", 3 | "shorturl": null, 4 | "from": null, 5 | "id": "12345678", 6 | "to": null}, 7 | "error": null} 8 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-www.google.com-contacts-successful/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "successful contacts fetch", 4 | "uri": "http://www.google.com/m8/feeds/contacts/default/full?v=1&max-results=500" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/smtp-smtp.gmail.com-send-server-disconnected/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "message": "Connection unexpectedly closed", 4 | "provider": "smtp.gmail.com" 5 | }, 6 | "result": null 7 | } 8 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-social.yahooapis.com-contacts-revoked-token/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "failed restcall response", 4 | "uri": "http://social.yahooapis.com/v1/user/ABKBGPOIGN7Q3ABMM6QJMA3H2Q/contacts" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-social.yahooapis.com-contacts-500-internal-server-error/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "failed restcall response", 4 | "uri": "http://social.yahooapis.com/v1/user/ABKBGPOIGN7Q3ABMM6QJMA3H2Q/contacts" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-mail.yahooapis.com-send-revoked-token/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "description": "*", 4 | "lang": "*", 5 | "provider": "yahoo.com", 6 | "status": 401 7 | }, 8 | "result": null 9 | } 10 | -------------------------------------------------------------------------------- /linkdrop/lib/constants.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # json-rpc error codes, we'll use these since the error response object 4 | # is basically json-rpc style. 5 | PARSE_ERROR = -32700 6 | INVALID_REQUEST = -32600 7 | METHOD_NOT_FOUND = -32601 8 | INVALID_PARAMS = -32602 9 | INTERNAL_ERROR = -32603 10 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-send-anyone-successful/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 201 Created 2 | status: 201 3 | vary: Accept-Encoding 4 | server: Apache-Coyote/1.1 5 | location: http://api.linkedin.com/v1/people/ppppppp/shares/sssssss 6 | date: Tue, 19 Apr 2011 05:24:44 GMT 7 | 8 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-social.yahooapis.com-contacts-500-internal-server-error/README.txt: -------------------------------------------------------------------------------- 1 | Note that this response was received directly after revoking the tokens for 2 | F1 via the Yahoo web interface. Not clear yet if this is reproducible or if 3 | the 500 is just a coincidence... 4 | -------------------------------------------------------------------------------- /tools/webbuild/requirejs/build/lib/rhino/LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Rhino (js.jar from http://www.mozilla.org/rhino/) was 2 | initially developed by Netscape Communications Corporation and is 3 | provided by the Dojo Foundation "as is" under the MPL 1.1 license, 4 | available at http://www.mozilla.org/MPL 5 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md Makefile LICENSE f1.spec 2 | include *.ini 3 | recursive-include apache * 4 | recursive-include extensions * 5 | recursive-include linkdrop * 6 | recursive-include grinder * 7 | recursive-include docs * 8 | recursive-include tools * 9 | recursive-include wsgi * 10 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-send-public-error-duplicate/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "code": 0, 4 | "message": "Status is a duplicate.", 5 | "provider": "twitter.com", 6 | "status": 403 7 | }, 8 | "result": null 9 | } 10 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-successful/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "reason": "successful send", 3 | "uri": "https://graph.facebook.com/me/feed?access_token=163981616966631%7Ce2ec309fd7215d9ef45a5317-100001556529144%7C7XhOQsvAEPKh0rNa0jikqcT-G08", 4 | "protocol": "http" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-social.yahooapis.com-send-404-not-on-accelerator/README.txt: -------------------------------------------------------------------------------- 1 | Note that this response was received directly after revoking the tokens for 2 | F1 via the Yahoo web interface, but it isn't clear it is related directly to 3 | that. The error persisted for at least 30 minutes. 4 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-contacts-list-no-groups/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "reason": "successful send", 3 | "uri": "https://graph.facebook.com/me/groups?access_token=163981616966631%7Ce2ec309fd7215d9ef45a5317-100001556529144%7C7XhOQsvAEPKh0rNa0jikqcT-G08", 4 | "protocol": "http" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-send-connections-successful/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 201 Created 2 | status: 201 3 | content-length: 0 4 | vary: Accept-Encoding 5 | server: Apache-Coyote/1.1 6 | location: http://api.linkedin.com/v1/people/pppppppp/shares/ssssssss 7 | date: Tue, 19 Apr 2011 07:11:40 GMT 8 | 9 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-group-wall/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "http", 3 | "reason": "automatic success save", 4 | "uri": "https://graph.facebook.com/197108586994419/feed?access_token=163981616966631%7Ce2ec309fd7215d9ef45a5317-100001556529144%7C7XhOQsvAEPKh0rNa0jikqcT-G08" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-mail.yahooapis.com-send-success/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": null, 3 | "result": { 4 | "from": null, 5 | "mid": "1_697_AK9aimIAAGVaTYKx8QZyXwiYxQc", 6 | "shorturl": null, 7 | "to": "you@example.com", 8 | "uid": 5 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-400-validating-token/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "reason": "status update with revoked access token", 3 | "uri": "https://graph.facebook.com/me/feed?access_token=163981616966631%7C3bc557f2260a2f9857edaa61-100001556529144%7CGmTVeDKXz5roDH9wdu2IBjfuGFk", 4 | "protocol": "http" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-successful/f1-request.json: -------------------------------------------------------------------------------- 1 | {"domain": "facebook.com", 2 | "shareType": "wall", 3 | "message": "to my wall", 4 | "link": "http://slashdot.org/", 5 | "title": "Slashdot: News for nerds, stuff that matters", 6 | "account": {"oauth_token": "foo", "oauth_token_secret": "bar"} 7 | } 8 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-with-revoked-token/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "reason": "status update with revoked access token", 3 | "uri": "https://graph.facebook.com/me/feed?access_token=163981616966631%7C3bc557f2260a2f9857edaa61-100001556529144%7CGmTVeDKXz5roDH9wdu2IBjfuGFk", 4 | "protocol": "http" 5 | } 6 | -------------------------------------------------------------------------------- /linkdrop/templates/text_email.mako: -------------------------------------------------------------------------------- 1 | ${c.message} 2 | 3 | *${c.title}* 4 | 5 | % if c.shorturl: 6 | ${c.shorturl} 7 | % elif c.longurl: 8 | ${c.longurl} 9 | % endif 10 | 11 | ${c.description} 12 | 13 | -- 14 | shared via Mozilla F1 for Firefox -- http://f1.mozillamessaging.com/ 15 | :: share links with the people that matter to you :: 16 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-400-validating-token/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "code": 0, 4 | "message": "Error validating access token.", 5 | "provider": "facebook.com", 6 | "status": 401, 7 | "type": "OAuthException" 8 | }, 9 | "result": null 10 | } 11 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-with-revoked-token/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "code": 0, 4 | "message": "Error validating access token.", 5 | "provider": "facebook.com", 6 | "status": 401, 7 | "type": "OAuthException" 8 | }, 9 | "result": null 10 | } 11 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-social.yahooapis.com-send-404-not-on-accelerator/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "debug_message": "*", 4 | "message": "The service is temporarily unavailable - please try again later.", 5 | "provider": "yahoo.com", 6 | "status": 503 7 | }, 8 | "result": {} 9 | } 10 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-group-wall/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": null, 3 | "result": { 4 | "facebook.com": "197108586994419_197126613659283", 5 | "from": null, 6 | "id": "197108586994419_197126613659283", 7 | "shorturl": null, 8 | "to": "197108586994419" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-social.yahooapis.com-contacts-500-internal-server-error/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "debug_message": "*", 4 | "message": "The service is temporarily unavailable - please try again later.", 5 | "provider": "yahoo.com", 6 | "status": 503 7 | }, 8 | "result": null 9 | } 10 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-group-wall/f1-request.json: -------------------------------------------------------------------------------- 1 | {"domain": "facebook.com", 2 | "shareType": "groupWall", 3 | "to": "197108586994419", 4 | "message": "group test", 5 | "link": "http://slashdot.org/", 6 | "title": "Slashdot: News for nerds, stuff that matters", 7 | "account": {"oauth_token": "foo", "oauth_token_secret": "bar"} 8 | } 9 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-send-anyone-successful/f1-request.json: -------------------------------------------------------------------------------- 1 | {"domain": "linkedin.com", 2 | "shareType": "public", 3 | "to": "anyone", 4 | "message": "nerdy for anyone", 5 | "link": "http://slashdot.org/", 6 | "title": "Slashdot: News for nerds, stuff that matters", 7 | "account": {"oauth_token": "foo", "oauth_token_secret": "bar", "username": "mylinkedinid"} 8 | } 9 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-400-validating-token/README.txt: -------------------------------------------------------------------------------- 1 | The error captured here appears to be a transient error - it doesn't 2 | seem to be the "normal" error when the token is invalid. 3 | 4 | Note however that it was generated immediately after revoking the token 5 | in the yahoo web interface, so it may turn out to be related to caching 6 | etc and common immediately after being revoked. 7 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-send-connections-successful/f1-request.json: -------------------------------------------------------------------------------- 1 | {"domain": "linkedin.com", 2 | "shareType": "myConnections", 3 | "to": "connections-only", 4 | "message": "nerdy for my connections!", 5 | "link": "http://slashdot.org/", 6 | "title": "Slashdot: News for nerds, stuff that matters", 7 | "account": {"oauth_token": "foo", "oauth_token_secret": "bar", "username": "mylinkedinid"} 8 | } 9 | -------------------------------------------------------------------------------- /bin/linkdrop-sync: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ME=`hostname -s` 4 | HOSTS="rd-linkdrop-01 rd-linkdrop-02 rd-linkdrop-stage-01" 5 | ARGS=$* 6 | 7 | for h in $HOSTS; do 8 | if [ "$ME" != "$h" ]; then 9 | rsync --exclude='*.pyc' --exclude=.ssh/id_rsa --exclude=.bash_history -av --delete --checksum $HOME/ $h:$HOME/ $ARGS 10 | echo "[$h] Restarting httpd" 11 | ssh $h "sudo /etc/rc.d/init.d/httpd graceful" 12 | fi 13 | done 14 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-send-public-successful/f1-request.json: -------------------------------------------------------------------------------- 1 | {"domain": "twitter.com", 2 | "shareType": "wall", 3 | "message": "nerdy!", 4 | "link": "http://slashdot.org/", 5 | "shorturl": "http://bit.ly/eT3aUn", 6 | "title": "Slashdot: News for nerds, stuff that matters", 7 | "shareType": "public", 8 | "account": {"oauth_token": "foo", "oauth_token_secret": "bar", "username": "mytwitterid"} 9 | } 10 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-send-private-successful/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": null, 3 | "result": { 4 | "content-length": "0", 5 | "date": "Tue, 19 Apr 2011 07:14:32 GMT", 6 | "from": null, 7 | "server": "Apache-Coyote/1.1", 8 | "shorturl": null, 9 | "status": "201", 10 | "to": "ppppp", 11 | "vary": "Accept-Encoding" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-send-public-error-duplicate/f1-request.json: -------------------------------------------------------------------------------- 1 | {"domain": "twitter.com", 2 | "shareType": "wall", 3 | "message": "nerdy!", 4 | "link": "http://slashdot.org/", 5 | "shorturl": "http://bit.ly/eT3aUn", 6 | "title": "Slashdot: News for nerds, stuff that matters", 7 | "shareType": "public", 8 | "account": {"oauth_token": "foo", "oauth_token_secret": "bar", "username": "mytwitterid"} 9 | } 10 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-send-direct-successful/request-0: -------------------------------------------------------------------------------- 1 | POST /1/direct_messages/new.json 2 | content-type: application/x-www-form-urlencoded 3 | user-agent: Python-httplib2/$Rev$ 4 | 5 | oauth_nonce=80434363&oauth_timestamp=1302677241&text=for%20a%20nerd%20http%3A%2F%2Fbit.ly%2FeT3aUn&oauth_consumer_key=the_consumer_key&oauth_signature_method=HMAC-SHA1&oauth_version=1.0&oauth_token=the_token&user=123456&oauth_signature=the_signature%3D 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-send-direct-successful/f1-request.json: -------------------------------------------------------------------------------- 1 | {"domain": "twitter.com", 2 | "shareType": "wall", 3 | "message": "for a nerd", 4 | "link": "http://slashdot.org/", 5 | "shorturl": "http://bit.ly/eT3aUn", 6 | "title": "Slashdot: News for nerds, stuff that matters", 7 | "shareType": "direct", 8 | "to": "123456", 9 | "account": {"oauth_token": "foo", "oauth_token_secret": "bar", "username": "mytwitterid"} 10 | } 11 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-send-public-successful/request-0: -------------------------------------------------------------------------------- 1 | POST /1/statuses/update.json 2 | content-type: application/x-www-form-urlencoded 3 | user-agent: Python-httplib2/$Rev$ 4 | 5 | status=nerdy%21%20http%3A%2F%2Fbit.ly%2FeT3aUn&oauth_body_hash=2jmj7l5rSw0yVb%2FvlWAYkK%2FYBwk%3D&oauth_nonce=63594112&oauth_timestamp=1302661687&oauth_consumer_key=12345&oauth_signature_method=HMAC-SHA1&oauth_version=1.0&oauth_token=678&oauth_signature=xyzzy 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-send-public-error-duplicate/request-0: -------------------------------------------------------------------------------- 1 | POST /1/statuses/update.json 2 | content-type: application/x-www-form-urlencoded 3 | user-agent: Python-httplib2/$Rev$ 4 | 5 | status=nerdy%21%20http%3A%2F%2Fbit.ly%2FeT3aUn&oauth_body_hash=hash_value%3D&oauth_nonce=19583129&oauth_timestamp=1302663610&oauth_consumer_key=the_consumer_key&oauth_signature_method=HMAC-SHA1&oauth_version=1.0&oauth_token=the_token&oauth_signature=the_sig%3D 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-send-anyone-successful/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": null, 3 | "result": { 4 | "date": "Tue, 19 Apr 2011 05:24:44 GMT", 5 | "from": null, 6 | "location": "http://api.linkedin.com/v1/people/ppppppp/shares/sssssss", 7 | "server": "Apache-Coyote/1.1", 8 | "shorturl": null, 9 | "status": "201", 10 | "to": "anyone", 11 | "vary": "Accept-Encoding" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-send-private-successful/f1-request.json: -------------------------------------------------------------------------------- 1 | {"domain": "linkedin.com", 2 | "shareType": "contact", 3 | "to": "ppppp", 4 | "message": "nerdy just for you!", 5 | "link": "http://slashdot.org/", 6 | "title": "Slashdot: News for nerds, stuff that matters", 7 | "account": {"oauth_token": "foo", 8 | "oauth_token_secret": "bar", 9 | "username": "mylinkedinid", 10 | "verifiedEmail": "me@somewhere.com"} 11 | } 12 | -------------------------------------------------------------------------------- /wsgi/linkdrop.wsgi: -------------------------------------------------------------------------------- 1 | # Find ourselves 2 | import os, sys 3 | basedir = os.path.abspath(os.path.dirname(__file__)) 4 | __here__ = os.path.dirname(__file__) 5 | __parent__ = os.path.dirname(__here__) 6 | 7 | sys.path.append(__parent__) 8 | 9 | from paste.script.util.logging_config import fileConfig 10 | fileConfig('%s/development.ini' % __parent__) 11 | 12 | from paste.deploy import loadapp 13 | 14 | # And deploy our app from raindrop.ini 15 | application = loadapp('config:../production.ini', relative_to=basedir) 16 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-auth-successful/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | status: 200 3 | content-location: https://api.linkedin.com/uas/oauth/requestToken 4 | vary: Accept-Encoding 5 | server: Apache-Coyote/1.1 6 | -content-encoding: gzip 7 | date: Tue, 19 Apr 2011 04:19:01 GMT 8 | content-type: text/plain 9 | 10 | oauth_token=the_token&oauth_token_secret=the_secret&oauth_callback_confirmed=true&xoauth_request_auth_url=https%3A%2F%2Fapi.linkedin.com%2Fuas%2Foauth%2Fauthorize&oauth_expires_in=599 11 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-mail.yahooapis.com-send-revoked-token/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 401 Authorization Required 2 | status: 401 3 | age: 0 4 | vary: Accept-Encoding 5 | server: YTS/1.19.5 6 | connection: close 7 | date: Sun, 20 Mar 2011 22:39:02 GMT 8 | content-type: application/json 9 | www-authenticate: OAuth oauth_problem="token_expired", realm="yahooapis.com" 10 | 11 | {"error":{"lang":"en-US","description":"Please provide valid credentials. OAuth oauth_problem=\"token_expired\", realm=\"yahooapis.com\""}} -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-social.yahooapis.com-contacts-success/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": null, 3 | "result": { 4 | "entry": [ 5 | { 6 | "displayName": "Test Test", 7 | "emails": [ 8 | { 9 | "primary": false, 10 | "value": "test@test.com" 11 | } 12 | ], 13 | "nickname": "test" 14 | } 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/locale/en-US.properties: -------------------------------------------------------------------------------- 1 | sharesidebar.title=Mozilla F1 2 | openShareSidebar.commandkey=S 3 | openShareSidebar.modifierskey=shift accel 4 | ffshareContext.label=Share Page... 5 | ffshareContext.accesskey=S 6 | ffshareMenu.label=Share Page... 7 | ffshareMenu.accesskey=h 8 | ffshareToolbarButton.label=F1 9 | ffshareToolbarButton.tooltip=Use F1 to share the current page via Twitter, Facebook, and more. 10 | ffshareProblem.status=There was a problem sharing this page. 11 | ffshareProblem.code=F1 Share Failure -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-twitter.com-auth-successful/request-0: -------------------------------------------------------------------------------- 1 | GET /oauth/request_token 2 | accept-encoding: gzip, deflate 3 | authorization: OAuth realm="", oauth_body_hash="2jmj7l5rSw0yVb%2FvlWAYkK%2FYBwk%3D", oauth_nonce="63516674", oauth_timestamp="1302661664", oauth_consumer_key="secret_key", oauth_signature_method="HMAC-SHA1", oauth_version="1.0", oauth_signature="the_sig%3D", oauth_callback="http%3A%2F%2Flinkdrop.caraveo.com%3A5000%2Fapi%2Faccount%2Fverify%3Fprovider%3Dtwitter.com" 4 | user-agent: Python-httplib2/$Rev$ 5 | 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-auth-successful/request-0: -------------------------------------------------------------------------------- 1 | GET /uas/oauth/requestToken 2 | accept-encoding: gzip, deflate 3 | authorization: OAuth realm="", oauth_body_hash="2jmj7l5rSw0yVb%2FvlWAYkK%2FYBwk%3D", oauth_nonce="9999999", oauth_timestamp="1303186687", oauth_consumer_key="the_key", oauth_signature_method="HMAC-SHA1", oauth_version="1.0", oauth_signature="the_sig%3D", oauth_callback="http%3A%2F%2Flinkdrop.caraveo.com%3A5000%2Fapi%2Faccount%2Fverify%3Fprovider%3Dlinkedin.com" 4 | user-agent: Python-httplib2/$Rev$ 5 | 6 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-send-connections-successful/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": null, 3 | "result": { 4 | "content-length": "0", 5 | "date": "Tue, 19 Apr 2011 07:11:40 GMT", 6 | "from": null, 7 | "location": "http://api.linkedin.com/v1/people/pppppppp/shares/ssssssss", 8 | "server": "Apache-Coyote/1.1", 9 | "shorturl": null, 10 | "status": "201", 11 | "to": "connections-only", 12 | "vary": "Accept-Encoding" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-contacts-list-no-groups/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | status: 200 3 | x-cnection: close 4 | content-location: https://graph.facebook.com/me/groups?access_token=12345678 5 | x-powered-by: HPHP 6 | expires: Sat, 01 Jan 2000 00:00:00 GMT 7 | x-fb-server: 10.36.49.106 8 | etag: "1050253aec7b29caff644806927dabfa81406eee" 9 | pragma: no-cache 10 | cache-control: private, no-cache, no-store, must-revalidate 11 | date: Tue, 08 Mar 2011 05:39:48 GMT 12 | content-type: text/javascript; charset=UTF-8 13 | 14 | {"data":[]} 15 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-400-validating-token/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 400 Bad Request 2 | status: 400 3 | x-cnection: close 4 | x-powered-by: HPHP 5 | expires: Sat, 01 Jan 2000 00:00:00 GMT 6 | x-fb-server: 10.32.28.112 7 | pragma: no-cache 8 | cache-control: no-store 9 | date: Tue, 08 Mar 2011 05:39:29 GMT 10 | content-type: text/javascript; charset=UTF-8 11 | www-authenticate: OAuth "Facebook Platform" "invalid_token" "Error validating access token." 12 | 13 | {"error":{"type":"OAuthException","message":"Error validating access token."}} 14 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-with-revoked-token/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 400 Bad Request 2 | status: 400 3 | x-cnection: close 4 | x-powered-by: HPHP 5 | expires: Sat, 01 Jan 2000 00:00:00 GMT 6 | x-fb-server: 10.32.28.112 7 | pragma: no-cache 8 | cache-control: no-store 9 | date: Tue, 08 Mar 2011 05:39:29 GMT 10 | content-type: text/javascript; charset=UTF-8 11 | www-authenticate: OAuth "Facebook Platform" "invalid_token" "Error validating access token." 12 | 13 | {"error":{"type":"OAuthException","message":"Error validating access token."}} 14 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build = dev 3 | tag_svn_revision = true 4 | 5 | # Babel configuration 6 | [compile_catalog] 7 | domain = linkdrop 8 | directory = linkdrop/i18n 9 | statistics = true 10 | 11 | [extract_messages] 12 | add_comments = TRANSLATORS: 13 | output_file = linkdrop/i18n/linkdrop.pot 14 | width = 80 15 | 16 | [init_catalog] 17 | domain = linkdrop 18 | input_file = linkdrop/i18n/linkdrop.pot 19 | output_dir = linkdrop/i18n 20 | 21 | [update_catalog] 22 | domain = linkdrop 23 | input_file = linkdrop/i18n/linkdrop.pot 24 | output_dir = linkdrop/i18n 25 | previous = true 26 | -------------------------------------------------------------------------------- /f1-nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes 5; 3 | worker_rlimit_nofile 30000; 4 | error_log /var/log/nginx/error.log; 5 | pid /var/run/nginx.pid; 6 | 7 | events { 8 | worker_connections 10000; 9 | } 10 | 11 | 12 | http { 13 | server { 14 | listen 80 default; 15 | keepalive_timeout 120; 16 | tcp_nodelay on; 17 | location / { 18 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 19 | proxy_set_header Host $http_host; 20 | proxy_redirect off; 21 | proxy_pass http://localhost:5000; 22 | break; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-social.yahooapis.com-contacts-revoked-token/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 401 Authorization Required 2 | status: 401 3 | via: HTTP/1.1 r1.ycpi.aue.yahoo.net (YahooTrafficServer/1.19.5 [cMsSf ]) 4 | age: 0 5 | vary: Accept-Encoding 6 | server: YTS/1.19.5 7 | connection: keep-alive 8 | cache-control: private 9 | date: Sun, 20 Mar 2011 22:38:41 GMT 10 | content-type: application/json 11 | www-authenticate: OAuth oauth_problem="token_expired", realm="yahooapis.com" 12 | 13 | {"error":{"lang":"en-US","description":"Please provide valid credentials. OAuth oauth_problem=\"token_expired\", realm=\"yahooapis.com\""}} -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-auth-successful/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | status: 200 3 | x-cnection: close 4 | content-location: https://graph.facebook.com/oauth/access_token?client_secret=1234&code=5678&client_id=1234&redirect_uri=http%3A//linkdrop.caraveo.com%3A5000/api/account/verify%3Fprovider%3Dfacebook.com 5 | x-powered-by: HPHP 6 | expires: Sat, 01 Jan 2000 00:00:00 GMT 7 | x-fb-server: 10.28.28.119 8 | x-fb-rev: 353909 9 | pragma: no-cache 10 | cache-control: private, no-cache, no-store, must-revalidate 11 | date: Wed, 16 Mar 2011 04:28:07 GMT 12 | content-type: text/plain; charset=UTF-8 13 | 14 | access_token=12345678 15 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-send-anyone-successful/request-0: -------------------------------------------------------------------------------- 1 | POST /v1/people/~/shares 2 | x-li-format: json 3 | content-type: application/json 4 | authorization: OAuth realm="http://api.linkedin.com", oauth_body_hash="the_hash_value", oauth_nonce="111111", oauth_timestamp="1303190627", oauth_consumer_key="the_consumer_key", oauth_signature_method="HMAC-SHA1", oauth_version="1.0", oauth_token="the_token", oauth_signature="the_sig%3D" 5 | user-agent: Python-httplib2/$Rev$ 6 | 7 | {"comment": "nerdy for anyone", "content": {"submitted-image-url": "", "description": "", "submitted-url": "http://slashdot.org/", "title": ""}, "visibility": {"code": "anyone"}} 8 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-send-connections-successful/request-0: -------------------------------------------------------------------------------- 1 | POST /v1/people/~/shares 2 | x-li-format: json 3 | content-type: application/json 4 | authorization: OAuth realm="http://api.linkedin.com", oauth_body_hash="the_hash%3D", oauth_nonce="9999999", oauth_timestamp="1303197039", oauth_consumer_key="the_key", oauth_signature_method="HMAC-SHA1", oauth_version="1.0", oauth_token="the_token", oauth_signature="the_sig%3D" 5 | user-agent: Python-httplib2/$Rev$ 6 | 7 | {"comment": "nerdy for my connections!", "content": {"submitted-image-url": "", "description": "", "submitted-url": "http://slashdot.org/", "title": ""}, "visibility": {"code": "connections-only"}} 8 | -------------------------------------------------------------------------------- /grinder/grinder.properties: -------------------------------------------------------------------------------- 1 | # This is the default properties for grinder. 2 | grinder.script=send.py 3 | grinder.threads=20 4 | grinder.runs=50 5 | 6 | # Set to zero to oauth only at the start (to get the cookie) and never again. 7 | # Set to 1 to oauth every single send. Set to (eg) 10 to oauth every 10 sends. 8 | linkdrop.sends_per_oauth=0 9 | 10 | # Set to zero to disable 11 | # Set to a number > 0 to hit the /static/ pages that many times per send request 12 | # linkdrop.static_per_send=1 13 | # linkdrop.static_url=/blank.html 14 | 15 | # What is the url for the linkdrop being tested 16 | #linkdrop.host= 17 | 18 | # What service to test (facebook.com/twitter.com) 19 | # linkdrop.service=twitter.com 20 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-social.yahooapis.com-send-404-not-on-accelerator/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 404 Not Found on Accelerator 2 | status: 404 3 | content-length: 240 4 | via: HTTP/1.1 r02.ycpi.aue.yahoo.net (YahooTrafficServer/1.20.0 [c s f ]) 5 | content-language: en 6 | server: YTS/1.20.0 7 | connection: close 8 | cache-control: no-store 9 | date: Fri, 18 Mar 2011 06:19:34 GMT 10 | content-type: text/html 11 | 12 | Not Found on Accelerator 13 | 14 | 15 | Your requested URL was not found. 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/index.txt: -------------------------------------------------------------------------------- 1 | linkdrop 2 | ++++++++ 3 | 4 | This is the main index page of your documentation. It should be written in 5 | `reStructuredText format `_. 6 | 7 | You can generate your documentation in HTML format by running this command:: 8 | 9 | setup.py pudge 10 | 11 | For this to work you will need to download and install `buildutils`_, 12 | `pudge`_, and `pygments`_. The ``pudge`` command is disabled by 13 | default; to ativate it in your project, run:: 14 | 15 | setup.py addcommand -p buildutils.pudge_command 16 | 17 | .. _buildutils: http://pypi.python.org/pypi/buildutils 18 | .. _pudge: http://pudge.lesscode.org/ 19 | .. _pygments: http://pygments.org/ 20 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-mail.yahooapis.com-send-success/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | status: 200 3 | x-response-time: 230 4 | x-ywsc-debug: {"system.hostname":"web121413.mail.ne1.yahoo.com","request.time.total":230} 5 | age: 1 6 | vary: Accept-Encoding 7 | server: YTS/1.19.5 8 | x-server-timestamp: 1300410865 9 | connection: keep-alive 10 | cache-control: private 11 | date: Fri, 18 Mar 2011 01:14:25 GMT 12 | p3p: policyref="http://info.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV" 13 | content-type: application/json; charset=UTF-8 14 | 15 | {"result":{"mid":"1_697_AK9aimIAAGVaTYKx8QZyXwiYxQc","uid":5},"error":null,"id":"jsonrpc"} 16 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/smtp-smtp.gmail.com-send-recipient-syntax-error/smtp-trace: -------------------------------------------------------------------------------- 1 | < 220 mx.google.com ESMTP wt14sm2727478icb.16 2 | > ehlo [192.168.0.9] 3 | < 250 mx.google.com at your service, [58.160.65.179] 4 | + SIZE 35651584 5 | + 8BITMIME 6 | + STARTTLS 7 | + ENHANCEDSTATUSCODES 8 | > STARTTLS 9 | < 220 2.0.0 Ready to start TLS 10 | > ehlo [192.168.0.9] 11 | < 250 mx.google.com at your service, [58.160.65.179] 12 | + SIZE 35651584 13 | + 8BITMIME 14 | + AUTH LOGIN PLAIN XOAUTH 15 | + ENHANCEDSTATUSCODES 16 | > AUTH XOAUTH 12345678= 17 | < 235 2.7.0 Accepted 18 | > mail FROM: size=6044 19 | < 250 2.1.0 OK wt14sm2727478icb.16 20 | > rcpt TO:<@you> 21 | < 555 5.5.2 Syntax error. wt14sm2727478icb.16 22 | > rset 23 | < 250 2.1.5 Flushed wt14sm2727478icb.16 24 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-successful/request-0: -------------------------------------------------------------------------------- 1 | POST /me/feed?access_token=foo 2 | content-length: 486 3 | content-type: multipart/form-data; boundary=----------$_BOUNDARY_44622151192664055966921585685697383826_$ 4 | user-agent: Python-httplib2/$Rev$ 5 | 6 | ------------$_BOUNDARY_44622151192664055966921585685697383826_$ 7 | Content-Disposition: form-data; name="message" 8 | 9 | to my wall 10 | ------------$_BOUNDARY_44622151192664055966921585685697383826_$ 11 | Content-Disposition: form-data; name="link" 12 | 13 | http://slashdot.org/ 14 | ------------$_BOUNDARY_44622151192664055966921585685697383826_$ 15 | Content-Disposition: form-data; name="name" 16 | 17 | Slashdot: News for nerds, stuff that matters 18 | ------------$_BOUNDARY_44622151192664055966921585685697383826_$-- 19 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-group-wall/request-0: -------------------------------------------------------------------------------- 1 | POST /197108586994419/feed?access_token=foo 2 | content-length: 490 3 | content-type: multipart/form-data; boundary=----------$_BOUNDARY_175686444534009779909065571888283846774_$ 4 | user-agent: Python-httplib2/$Rev$ 5 | 6 | ------------$_BOUNDARY_175686444534009779909065571888283846774_$ 7 | Content-Disposition: form-data; name="message" 8 | 9 | group test 10 | ------------$_BOUNDARY_175686444534009779909065571888283846774_$ 11 | Content-Disposition: form-data; name="link" 12 | 13 | http://slashdot.org/ 14 | ------------$_BOUNDARY_175686444534009779909065571888283846774_$ 15 | Content-Disposition: form-data; name="name" 16 | 17 | Slashdot: News for nerds, stuff that matters 18 | ------------$_BOUNDARY_175686444534009779909065571888283846774_$-- 19 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-send-private-successful/request-0: -------------------------------------------------------------------------------- 1 | POST /v1/people/~/mailbox 2 | x-li-format: json 3 | content-type: application/json 4 | authorization: OAuth realm="http://api.linkedin.com", oauth_body_hash="the_hash%3D", oauth_nonce="999999", oauth_timestamp="1303197211", oauth_consumer_key="the_key", oauth_signature_method="HMAC-SHA1", oauth_version="1.0", oauth_token="the_token", oauth_signature="the_sig%3D" 5 | user-agent: Python-httplib2/$Rev$ 6 | 7 | {"body": "nerdy just for you!\n\n*Slashdot: News for nerds, stuff that matters*\n\nhttp://slashdot.org/\n\n\n\n--\nshared via Mozilla F1 for Firefox -- http://f1.mozillamessaging.com/\n:: share links with the people that matter to you ::\n", "recipients": {"values": [{"person": {"_path": "/people/ppppp"}}]}, "subject": "A web link has been shared with you"} 8 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-successful/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | status: 200 3 | x-cnection: close 4 | x-powered-by: HPHP 5 | set-cookie: cur_max_lag=20; path=/; domain=.facebook.com; httponly, datr=KMF1TcI3ixhP8DaBXhV9RS2I; expires=Thu, 07-Mar-2013 05:39:52 GMT; path=/; domain=.facebook.com; httponly, L=20; path=/; domain=.facebook.com; httponly, made_write_conn=1299562792; path=/; domain=.facebook.com, W=1299562792; path=/; domain=.facebook.com 6 | expires: Sat, 01 Jan 2000 00:00:00 GMT 7 | x-fb-server: 10.36.55.109 8 | pragma: no-cache 9 | cache-control: private, no-cache, no-store, must-revalidate 10 | date: Tue, 08 Mar 2011 05:39:52 GMT 11 | p3p: CP="Facebook does not have a P3P policy. Learn why here: http://fb.me/p3p" 12 | content-type: text/javascript; charset=UTF-8 13 | 14 | {"id":"12345678"} 15 | -------------------------------------------------------------------------------- /tools/webbuild/requirejs/build/jslib/commandLine.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license RequireJS Copyright (c) 2010, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/jrburke/requirejs for details 5 | */ 6 | 7 | /*jslint */ 8 | /*global Packages: false */ 9 | "use strict"; 10 | 11 | var commandLine = {}; 12 | (function () { 13 | var runtime = Packages.java.lang.Runtime.getRuntime(); 14 | 15 | /** 16 | * Executes a command on the command line. May not work right in 17 | * Windows environments, except maybe via something like cygwin. 18 | * @param {String} command the command to run on the command line. 19 | */ 20 | commandLine.exec = function (command) { 21 | var process = runtime.exec(["/bin/sh", "-c", command]); 22 | process.waitFor(); 23 | }; 24 | }()); 25 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-contacts-successful/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": null, 3 | "result": { 4 | "entry": [ 5 | { 6 | "accounts": [ 7 | { 8 | "domain": "linkedin.com", 9 | "userid": "11111111", 10 | "username": "" 11 | } 12 | ], 13 | "displayName": "Jean Reilly", 14 | "urls": [ 15 | { 16 | "primary": true, 17 | "type": "profile", 18 | "value": "http://www.linkedin.com/profile?viewProfile=&key=999999&authToken=xxxx&authType=name&trk=api*xxx*xxx*" 19 | } 20 | ] 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-send-group-wall/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | status: 200 3 | x-cnection: close 4 | set-cookie: cur_max_lag=20; path=/; domain=.facebook.com; httponly, datr=uK6jTSIlIQ8KJyesmnZBU_42; expires=Thu, 11-Apr-2013 01:45:28 GMT; path=/; domain=.facebook.com; httponly, L=20; path=/; domain=.facebook.com; httponly, made_write_conn=1302572728; path=/; domain=.facebook.com, W=1302572728; path=/; domain=.facebook.com 5 | expires: Sat, 01 Jan 2000 00:00:00 GMT 6 | x-fb-server: 10.32.75.104 7 | content-length: 40 8 | x-fb-rev: 364572 9 | pragma: no-cache 10 | cache-control: private, no-cache, no-store, must-revalidate 11 | date: Tue, 12 Apr 2011 01:45:29 GMT 12 | p3p: CP="Facebook does not have a P3P policy. Learn why here: http://fb.me/p3p" 13 | content-type: text/javascript; charset=UTF-8 14 | 15 | {"id":"197108586994419_197126613659283"} -------------------------------------------------------------------------------- /grinder/README.txt: -------------------------------------------------------------------------------- 1 | This directory holds scripts and config files for using "The Grinder". 2 | 3 | Quick start: 4 | 5 | * Download The Grinder via http://grinder.sourceforge.net and follow it's 6 | instructions - in summary: 7 | - Download the .zip distro and unpack the .jar files somewhere. 8 | - set CLASSPATH={PATH_YOU_INSTALLED_TO}\lib\grinder.jar 9 | 10 | * Run a stand-alone test: 11 | % java net.grinder.Grinder 12 | To see the statistic for this type of run, you must view the tail of the 13 | out_*.log file generated by the run. 14 | 15 | * Run a large test 16 | % java net.grinder.Console 17 | and on each machine you want to use for testing: 18 | % java net.grinder.Grinder 19 | Start the tests from the console. The cumulative stats will be reported 20 | in this console. 21 | 22 | * Configure the tests: Open the grinder.properties file and look for the 23 | 'linkdrop.*' options. 24 | 25 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | source ~/linkdrop-env/bin/activate 4 | 5 | #if run from a hook, GIT_DIR is set 6 | unset GIT_DIR 7 | 8 | #find ourselves 9 | SELF=`readlink -f $0` 10 | WORKDIR=`dirname $SELF` 11 | 12 | LINKDROP_HOME=`cd $WORKDIR/.. && pwd` 13 | 14 | export PATH=$PATH:$LINKDROP_HOME/bin 15 | 16 | cd $LINKDROP_HOME 17 | 18 | PRE=`git show --pretty=format:"%H|%ci" --quiet` 19 | 20 | ( 21 | git pull 22 | ) 2>&1 > git.log 23 | 24 | POST=`git show --pretty=format:"%H|%ci" --quiet` 25 | 26 | if [ "$PRE" != "$POST" ]; then 27 | echo "Update from $PRE to $POST" 28 | HOST=`hostname -s` 29 | echo "$POST $HOST" > web/version.txt 30 | make -B web 31 | cat git.log 32 | git diff | perl -pi -e's[(oauth.(google|facebook|twitter).com.(consumer_secret|app_secret)\s+=\s+)(.*)][$1XXXXXXXXXXXX]g' | perl -pi -e's[(sqlalchemy.url = mysql://linkdrop:)(.*)(@.*)][$1XXXXXXXX$3]g' 33 | python setup.py develop 34 | linkdrop-sync 35 | fi 36 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-send-public-error-duplicate/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 403 Forbidden 2 | status: 403 3 | content-length: 73 4 | x-transaction: 1302663623-42498-39072 5 | set-cookie: k=58.160.65.179.1302663623449661; path=/; expires=Wed, 20-Apr-11 03:00:23 GMT; domain=.twitter.com, guest_id=12345; path=/; expires=Fri, 13 May 2011 03:00:23 GMT, lang=en; path=/, _twitter_sess=twitter_session_id; domain=.twitter.com; path=/; HttpOnly 6 | expires: Tue, 31 Mar 1981 05:00:00 GMT 7 | vary: Accept-Encoding 8 | x-transaction-mask: a6183ffa5f8ca943ff1b53b5644ef114 9 | server: hi 10 | x-revision: DEV 11 | last-modified: Wed, 13 Apr 2011 03:00:23 GMT 12 | x-runtime: 0.03088 13 | pragma: no-cache 14 | cache-control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0 15 | date: Wed, 13 Apr 2011 03:00:23 GMT 16 | content-type: application/json; charset=utf-8 17 | 18 | {"request":"\/1\/statuses\/update.json","error":"Status is a duplicate."} 19 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-social.yahooapis.com-contacts-500-internal-server-error/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 500 Internal Server Error 2 | status: 500 3 | content-length: 202 4 | via: HTTP/1.1 r2.ycpi.aue.yahoo.net (YahooTrafficServer/1.19.5 [cMsSf ]) 5 | age: 0 6 | x-yahoo-social-host: ws137.socdir.sp2.yahoo.com 7 | vary: Authorization,Accept-Encoding 8 | server: YTS/1.19.5 9 | connection: keep-alive 10 | -content-encoding: gzip 11 | cache-control: private 12 | date: Fri, 18 Mar 2011 05:47:48 GMT 13 | p3p: policyref="http://info.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV" 14 | content-type: application/json; charset="utf-8" 15 | 16 | {"error":{"uri":"http://www.yahooapis.com/v1/errors/500","lang":"en-US","description":"Internal server error","detail":"ContactAPIException ab_client errorcode: -50001 errormsg: Server internal error"}} -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-twitter.com-auth-successful/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | status: 200 3 | x-xss-protection: 1; mode=block 4 | content-location: https://twitter.com/oauth/request_token 5 | x-transaction: 1302661675-60570-10104 6 | set-cookie: k=58.160.65.179.1302661675451287; path=/; expires=Wed, 20-Apr-11 02:27:55 GMT; domain=.twitter.com, guest_id=12345; path=/; expires=Fri, 13 May 2011 02:27:55 GMT, _twitter_sess=twitter_session_id; domain=.twitter.com; path=/; HttpOnly 7 | expires: Tue, 31 Mar 1981 05:00:00 GMT 8 | vary: Accept-Encoding 9 | server: hi 10 | x-revision: DEV 11 | last-modified: Wed, 13 Apr 2011 02:27:55 GMT 12 | x-runtime: 0.01213 13 | pragma: no-cache 14 | cache-control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0 15 | date: Wed, 13 Apr 2011 02:27:55 GMT 16 | x-frame-options: SAMEORIGIN 17 | content-type: text/html; charset=utf-8 18 | -content-encoding: gzip 19 | 20 | oauth_token=the_token&oauth_token_secret=the_secret&oauth_callback_confirmed=true 21 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-graph.facebook.com-auth-successful/response-1: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | status: 200 3 | x-cnection: close 4 | content-location: https://graph.facebook.com/me?access_token=1234&fields=id%2Cfirst_name%2Clast_name%2Cname%2Clink%2Cbirthday%2Cemail%2Cwebsite%2Cverified%2Cpicture%2Cgender%2Ctimezone 5 | x-powered-by: HPHP 6 | set-cookie: datr=1234; expires=Thu, 07-Mar-2013 05:39:46 GMT; path=/; domain=.facebook.com; httponly 7 | expires: Sat, 01 Jan 2000 00:00:00 GMT 8 | x-fb-server: 10.36.7.130 9 | etag: "1234" 10 | pragma: no-cache 11 | cache-control: private, no-cache, no-store, must-revalidate 12 | date: Tue, 08 Mar 2011 05:39:46 GMT 13 | p3p: CP="Facebook does not have a P3P policy. Learn why here: http://fb.me/p3p" 14 | content-type: text/javascript; charset=UTF-8 15 | 16 | {"id":"1234","first_name":"First","last_name":"Last","name":"First Last","link":"http:\/\/www.facebook.com\/profile.php?id=1234","gender":"male","timezone":11,"picture":"http:\/\/b.static.ak.fbcdn.net\/rsrc.php\/v1\/yo\/r\/1234.gif"} 17 | -------------------------------------------------------------------------------- /extensions/firefox-share/src/install.rdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ffshare@mozilla.org 5 | 2 6 | true 7 | true 8 | F1 by Mozilla Labs 9 | 0.8.3 10 | Mozilla 11 | 12 | http://f1.mozillamessaging.com/ 13 | resource://ffshare/chrome/skin/extension-icon.png 14 | 15 | 16 | {ec8030f7-c20a-464f-9b0e-13a3a9e97384} 17 | 4.0b10pre 18 | 6.0a1 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tools/webbuild/requirejs/build/jslib/logger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license RequireJS Copyright (c) 2010, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/jrburke/requirejs for details 5 | */ 6 | var logger = { 7 | TRACE: 0, 8 | INFO: 1, 9 | WARN: 2, 10 | ERROR: 3, 11 | level: 0, 12 | logPrefix: "", 13 | 14 | trace: function(message){ 15 | if(this.level <= this.TRACE){ 16 | this._print(message); 17 | } 18 | }, 19 | 20 | info: function(message){ 21 | if(this.level <= this.INFO){ 22 | this._print(message); 23 | } 24 | }, 25 | 26 | warn: function(message){ 27 | if(this.level <= this.WARN){ 28 | this._print(message); 29 | } 30 | }, 31 | 32 | error: function(message){ 33 | if(this.level <= this.ERROR){ 34 | this._print(message); 35 | } 36 | }, 37 | 38 | _print: function(message){ 39 | this._sysPrint((this.logPrefix ? (this.logPrefix + " ") : "") + message); 40 | }, 41 | 42 | _sysPrint: function(message){ 43 | print(message); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /linkdrop/lib/base.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is Raindrop. 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Messaging, Inc.. 18 | # Portions created by the Initial Developer are Copyright (C) 2009 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # 23 | 24 | """The base Controller API 25 | 26 | Provides the BaseController class for subclassing. 27 | """ 28 | 29 | 30 | class BaseController(object): 31 | def __init__(self, app): 32 | self.app = app 33 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-contacts-successful/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": null, 3 | "result": { 4 | "entry": [ 5 | { 6 | "accounts": [ 7 | { 8 | "domain": "twitter.com", 9 | "userid": 3333333, 10 | "username": "myfollower" 11 | } 12 | ], 13 | "displayName": "My Follower", 14 | "photos": [ 15 | { 16 | "type": "profile", 17 | "value": "http://a1.twimg.com/profile_images/123456/my_pic.png" 18 | } 19 | ], 20 | "published": "Sat Feb 21 05:55:31 +0000 2009", 21 | "urls": [ 22 | { 23 | "primary": false, 24 | "value": "http://me.com" 25 | } 26 | ] 27 | } 28 | ], 29 | "itemsPerPage": 1, 30 | "startIndex": 0, 31 | "totalResults": 1 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-www.google.com-contacts-successful/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": null, 3 | "result": { 4 | "entry": [ 5 | { 6 | "displayName": "Test User", 7 | "emails": [ 8 | { 9 | "primary": "true", 10 | "value": "someone@somewhere.com" 11 | } 12 | ] 13 | }, 14 | { 15 | "displayName": "Funny char in email addy", 16 | "emails": [ 17 | { 18 | "primary": "true", 19 | "value": "funny_\u00a9har@skippinet.com.au" 20 | } 21 | ] 22 | }, 23 | { 24 | "displayName": "Mark with funny \u00a9haracter", 25 | "emails": [ 26 | { 27 | "primary": "true", 28 | "value": "someone@somewhere.com" 29 | } 30 | ] 31 | } 32 | ], 33 | "itemsPerPage": "500", 34 | "startIndex": "1", 35 | "totalResults": "4" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/smtp-smtp.gmail.com-send-server-disconnected/smtp-trace: -------------------------------------------------------------------------------- 1 | < 220 mx.google.com ESMTP 25sm1848097wfb.22 2 | > ehlo [192.168.0.9] 3 | < 250 mx.google.com at your service, [58.160.65.179] 4 | + SIZE 35651584 5 | + 8BITMIME 6 | + STARTTLS 7 | + ENHANCEDSTATUSCODES 8 | > STARTTLS 9 | < 220 2.0.0 Ready to start TLS 10 | > ehlo [192.168.0.9] 11 | < 250 mx.google.com at your service, [58.160.65.179] 12 | + SIZE 35651584 13 | + 8BITMIME 14 | + AUTH LOGIN PLAIN XOAUTH 15 | + ENHANCEDSTATUSCODES 16 | > AUTH XOAUTH R0VUIGh0dHBzOi8vbWFpbC5nb29nbGUuY29tL21haWwvYi9za2lwcHkuaGFtbW9uZC50ZXN0QGdtYWlsLmNvbS9zbXRwLyBvYXV0aF9jb25zdW1lcl9rZXk9Imxpbmtkcm9wLmNhcmF2ZW8uY29tIixvYXV0aF9ub25jZT0iNzYxNzMyMjAiLG9hdXRoX3NpZ25hdHVyZT0iQ3EzZmFUNyUyQjJ4VDF0Q0JYNTBXMnUlMkY4b1BGVSUzRCIsb2F1dGhfc2lnbmF0dXJlX21ldGhvZD0iSE1BQy1TSEExIixvYXV0aF90aW1lc3RhbXA9IjEzMDAzMjU5NjMiLG9hdXRoX3Rva2VuPSIxJTJGeFh4ZEJJRGpxMVBoaFhzQnBYU1FUemNYTHZDN3RYNHJMX2ZWaHhQOTJKQSIsb2F1dGhfdmVyc2lvbj0iMS4wIg== 17 | < 235 2.7.0 Accepted 18 | > mail FROM: size=6054 19 | < 250 2.1.0 OK 25sm1848097wfb.22 20 | > rcpt TO: 21 | < 250 2.1.5 OK 25sm1848097wfb.22 22 | > data 23 | E {"args": ["Connection unexpectedly closed"], "name": "SMTPServerDisconnected", "module": "smtplib"} -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/skin/web/installed.css: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is Raindrop. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * Mozilla Messaging, Inc.. 18 | * Portions created by the Initial Developer are Copyright (C) 2009 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * */ 23 | 24 | #downloadFF4 { 25 | display: none; 26 | } 27 | 28 | #header.row #noButtonFF4 { 29 | display: block; 30 | text-align: right; 31 | color: #888; 32 | } 33 | 34 | #header.row #noButtonFF4 img { 35 | vertical-align:text-bottom; 36 | } 37 | /* XXX we could replace the download with something else */ 38 | #clickToolbar { 39 | display: block !important; 40 | } -------------------------------------------------------------------------------- /apache/f1.conf: -------------------------------------------------------------------------------- 1 | LogLevel warn 2 | ServerName f1.mozillamessaging.com 3 | 4 | LoadModule wsgi_module modules/mod_wsgi26.so 5 | WSGISocketPrefix /tmp/wsgi 6 | 7 | WSGIPythonHome /usr/local/python2.6 8 | 9 | WSGIDaemonProcess linkdrop \ 10 | user=linkdrop \ 11 | group=linkdrop \ 12 | python-path=/home/linkdrop/linkdrop-env/lib/python2.6/site-packages \ 13 | processes=10 \ 14 | threads=10 \ 15 | 16 | WSGIProcessGroup linkdrop 17 | 18 | DocumentRoot /home/linkdrop/linkdrop/web-static 19 | 20 | AddType application/x-xpinstall .xpi 21 | 22 | WSGIScriptAlias /api /home/linkdrop/linkdrop/wsgi/linkdrop.wsgi 23 | 24 | 25 | Order deny,allow 26 | allow from all 27 | AddOutputFilterByType DEFLATE text/html text/plain text/xml text/json text/css application/x-javascript 28 | FileETag MTime Size 29 | 30 | 31 | 32 | SSLRequireSSL On 33 | 34 | 35 | 36 | ProxyPass http://rd-db-01/mycheckpoint 37 | ProxyPassReverse http://rd-db-01/mycheckpoint 38 | 39 | 40 | ExtendedStatus On 41 | 42 | SetHandler server-status 43 | Order deny,allow 44 | Deny from all 45 | Allow from 10.200.0.0/16 127.0.0.1/8 46 | 47 | 48 | AddType image/x-icon .ico 49 | 50 | Redirect /frontpage https://f1.mozillamessaging.com 51 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.linkedin.com-contacts-successful/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | status: 200 3 | content-location: http://api.linkedin.com/v1/people/~/connections?count=500&oauth_body_hash=K%2BiMpCQsduglOsYkdIUQZQMtaDM%3D&oauth_nonce999999&oauth_timestamp=1303186697&oauth_consumer_key=the_key&oauth_signature_method=HMAC-SHA1&oauth_version=1.0&oauth_token=the_token&oauth_signature=the_sig%3D 4 | vary: x-li-format,Accept-Encoding 5 | server: Apache-Coyote/1.1 6 | -content-encoding: gzip 7 | date: Tue, 19 Apr 2011 04:19:09 GMT 8 | x-li-format: json 9 | content-type: application/json;charset=UTF-8 10 | 11 | { 12 | "values": [{ 13 | "headline": "Banking Professional", 14 | "id": "11111111", 15 | "lastName": "Reilly", 16 | "location": { 17 | "name": "Vancouver, Canada Area", 18 | "country": {"code": "ca"} 19 | }, 20 | "siteStandardProfileRequest": {"url": "http://www.linkedin.com/profile?viewProfile=&key=999999&authToken=xxxx&authType=name&trk=api*xxx*xxx*"}, 21 | "apiStandardProfileRequest": { 22 | "headers": { 23 | "values": [{ 24 | "name": "x-li-auth-token", 25 | "value": "name:TVgH" 26 | }], 27 | "_total": 1 28 | }, 29 | "url": "http://api.linkedin.com/v1/people/11111111" 30 | }, 31 | "industry": "Banking", 32 | "firstName": "Jean" 33 | }], 34 | "_total": 1 35 | } 36 | -------------------------------------------------------------------------------- /tools/webbuild/requirejs/build/build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright (c) 2010, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/jrburke/requirejs for details 5 | */ 6 | 7 | /* 8 | * This file will optimize files that can be loaded via require.js into one file. 9 | * This file needs Rhino to require, and if the Closure compiler is used to minify 10 | * files, Java 6 is required. 11 | * 12 | * Call this file like so: 13 | * java -jar path/to/js.jar build.js directory/containing/build.js/ build.js 14 | * 15 | * General use: 16 | * 17 | * Create a build.js file that has the build options you want and pass that 18 | * build file to this file to do the build. See example.build.js for more information. 19 | */ 20 | 21 | /*jslint regexp: false, nomen: false, plusplus: false */ 22 | /*global load: false, print: false, quit: false, logger: false, 23 | fileUtil: false, lang: false, pragma: false, optimize: false, build: false, 24 | java: false, Packages: false */ 25 | 26 | "use strict"; 27 | var require; 28 | 29 | (function (args) { 30 | var requireBuildPath = args[0]; 31 | if (requireBuildPath.charAt(requireBuildPath.length - 1) !== "/") { 32 | requireBuildPath += "/"; 33 | } 34 | load(requireBuildPath + "jslib/build.js"); 35 | build(args); 36 | 37 | }(Array.prototype.slice.call(arguments))); 38 | -------------------------------------------------------------------------------- /tools/webbuild/requirejs/build/jslib/lang.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright (c) 2010, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/jrburke/requirejs for details 5 | */ 6 | 7 | /*jslint plusplus: false */ 8 | /*global */ 9 | 10 | "use strict"; 11 | 12 | var lang = { 13 | backSlashRegExp: /\\/g, 14 | 15 | /** 16 | * Simple function to mix in properties from source into target, 17 | * but only if target does not already have a property of the same name. 18 | */ 19 | mixin: function (target, source, override) { 20 | //Use an empty object to avoid other bad JS code that modifies 21 | //Object.prototype. 22 | var empty = {}, prop; 23 | for (prop in source) { 24 | if (override || !(prop in target)) { 25 | target[prop] = source[prop]; 26 | } 27 | } 28 | }, 29 | 30 | delegate: (function () { 31 | // boodman/crockford delegation w/ cornford optimization 32 | function TMP() {} 33 | return function (obj, props) { 34 | TMP.prototype = obj; 35 | var tmp = new TMP(); 36 | TMP.prototype = null; 37 | if (props) { 38 | lang.mixin(tmp, props); 39 | } 40 | return tmp; // Object 41 | }; 42 | }()) 43 | }; 44 | 45 | -------------------------------------------------------------------------------- /extensions/firefox-share/testapi.html: -------------------------------------------------------------------------------- 1 | 23 | 25 | 26 | 27 | <!-- Insert your title here --> 28 | 29 | 30 | test me 33 | 34 | 35 | -------------------------------------------------------------------------------- /linkdrop/lib/app_globals.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is Raindrop. 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Messaging, Inc.. 18 | # Portions created by the Initial Developer are Copyright (C) 2009 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # 23 | """The application's Globals object""" 24 | 25 | from beaker.cache import CacheManager 26 | from beaker.util import parse_cache_config_options 27 | 28 | 29 | class Globals(object): 30 | """Globals acts as a container for objects available throughout the 31 | life of the application 32 | 33 | """ 34 | 35 | def __init__(self, config): 36 | """One instance of Globals is created during application 37 | initialization and is available during requests via the 38 | 'app_globals' variable 39 | 40 | """ 41 | self.cache = CacheManager(**parse_cache_config_options(config)) 42 | -------------------------------------------------------------------------------- /linkdrop/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is Raindrop. 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Messaging, Inc.. 18 | # Portions created by the Initial Developer are Copyright (C) 2009 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): Tarek Ziade 22 | # 23 | 24 | # XXX services instance to be moved in the future application 25 | # object once Pylons gets removed 26 | 27 | from linkoauth import Services 28 | 29 | services = None 30 | 31 | 32 | def get_services(config): 33 | global services 34 | if services is None: 35 | enabled = int(config.get('sstatus.ttl', '0')) 36 | servers = config['sstatus.servers'].split(',') 37 | domains = config['sstatus.domains'].split(',') 38 | ttl = int(config.get('sstatus.ttl', '60')) 39 | services = Services(domains, servers, ttl, enabled) 40 | return services 41 | -------------------------------------------------------------------------------- /tools/makespec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | 5 | # The package version 6 | my $version = shift; 7 | # The list of requirements 8 | my $requires = shift; 9 | # The git revision 10 | my $git = shift; 11 | my ($git_cnt, $git_rev) = (split('-', $git))[-2,-1]; 12 | $git = $git_cnt . '.git.' . $git_rev; 13 | 14 | my $prefix = "%{f1_name_prefix}python%{pyver}-"; 15 | 16 | my %requires; 17 | open (my $f, $requires); 18 | while (my $line = <$f>) { 19 | chomp $line; 20 | $line =~ s/^python-//g; 21 | $line = lc ($line); 22 | if ($line =~ />=|<=|==/) { 23 | $line =~ /(.*)(>=|<=|==)(.*)/; 24 | my ($name, $cmp, $version) = ($1, $2, $3); 25 | $requires{"$name"} = "$cmp $version"; 26 | } 27 | else 28 | { 29 | $requires{$line} = ""; 30 | } 31 | } 32 | 33 | my $requires = <<"EOF"; 34 | # Automatically added from setup.py by $0 35 | EOF 36 | 37 | # These are os-provided packages 38 | my %included = ( 39 | 'httplib2' => 1, 40 | 'nose' => 1, 41 | ); 42 | 43 | my %excluded = ( 44 | ); 45 | 46 | foreach my $req (sort keys %requires) { 47 | next if exists $excluded{$req}; 48 | print STDERR "Inclindng $req\n"; 49 | my $reqs; 50 | 51 | if (exists $included{$req}) { 52 | $reqs = "python%{pyver}-$req"; 53 | } 54 | else { 55 | $reqs = $prefix . $req; 56 | } 57 | $requires .= <<"EOF"; 58 | Requires: $reqs $requires{$req} 59 | BuildRequires: $reqs $requires{$req} 60 | 61 | EOF 62 | } 63 | 64 | while (<>) { 65 | s/%%version%%/$version/g; 66 | s/%%buildrequires%%//g; 67 | s/%%requires%%/$requires/g; 68 | s/%%git%%/.$git/g; 69 | print; 70 | } 71 | -------------------------------------------------------------------------------- /README.production: -------------------------------------------------------------------------------- 1 | s is without staging right now... 2 | 3 | If you can "ssh linkdrop@rd-admin-01.mozillamessaging.com" then these instructions will work for you. If not, contact gozer and don't bother with this until you can ssh into the box. 4 | 5 | 6 | -Initial setup on local machine: 7 | 8 | > git clone git@github.com:mozilla/f1.git 9 | > cd f1 10 | > git remote add production linkdrop@rd-admin-01.mozillamessaging.com:f1-prod 11 | > git branch prod 12 | 13 | -Switch to a branch 14 | 15 | > git checkout prod 16 | 17 | -Merge from master branch on production to the current branch: 18 | 19 | > git pull production master 20 | 21 | -Push from our prod branch to the master branch on production: 22 | 23 | > git push production prod:master 24 | 25 | -Merge from master branch on github (f1) into our current branch: 26 | 27 | > git pull origin master 28 | 29 | 30 | What I've been doing when I want to push to production: 31 | 32 | git checkout prod 33 | git pull production master 34 | git pull origin master 35 | git push production prod:master 36 | git checkout master 37 | 38 | That can all be simplified later via git config file (and there may be a better way to do it anyway). When you push to production, you will see a lot of output. I'd pipe that to a file and then examine the file for any errors. That output is basically a sync to the live servers as a git hook in the push. As I understand from gozer, there is also a cron job that checks every few minutes in case the update from push fails. 39 | 40 | I'm not certain about git branches yet, so be sure you do NOT push to origin from the prod branch. 41 | 42 | Shane 43 | 44 | -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/skin/overlay.css: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is Raindrop. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * Mozilla Messaging, Inc.. 18 | * Portions created by the Initial Developer are Copyright (C) 2009 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * */ 23 | @namespace xul url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul); 24 | 25 | #share-button { 26 | list-style-image: url("resource://ffshare/chrome/skin/toolbar-button.png"); 27 | width: auto; 28 | height: auto; 29 | } 30 | 31 | #share-button[checked] { 32 | list-style-image: url("resource://ffshare/chrome/skin/toolbar-button-active.png"); 33 | } 34 | 35 | #share-button[firstRun] { 36 | list-style-image: url("resource://ffshare/chrome/skin/toolbar-button-glow.png"); 37 | } 38 | 39 | #share-button[status="start"] { 40 | list-style-image: url("resource://ffshare/chrome/skin/status-sharing.png"); 41 | } 42 | 43 | #share-button[status="finished"] { 44 | list-style-image: url("resource://ffshare/chrome/skin/status-shared.png"); 45 | } 46 | -------------------------------------------------------------------------------- /linkdrop/tests/lib/test_metrics.py: -------------------------------------------------------------------------------- 1 | from linkdrop.lib import metrics 2 | from linkdrop.tests import TestController 3 | from mock import Mock 4 | from nose import tools 5 | 6 | 7 | class TestMetricsConsumer(TestController): 8 | @tools.raises(NotImplementedError) 9 | def test_consume_raises_notimplemented(self): 10 | mc = metrics.MetricsConsumer() 11 | mc.consume('somedata') 12 | 13 | 14 | class TestMetricsCollector(TestController): 15 | def setUp(self): 16 | self.consumer = Mock() 17 | self.collector = metrics.MetricsCollector(self.consumer) 18 | 19 | def test_get_distinct_attr(self): 20 | res = self.collector._get_distinct_attrs(None) 21 | tools.eq_(res, dict()) 22 | distinct_ob = dict(foo='bar', baz='bawlp') 23 | res = self.collector._get_distinct_attrs(distinct_ob) 24 | tools.eq_(res, distinct_ob) 25 | tools.assert_raises(NotImplementedError, 26 | self.collector._get_distinct_attrs, 27 | list()) 28 | 29 | def test_track_not_enabled(self): 30 | self.collector.enabled = False 31 | distinct_ob = dict(foo='bar', baz='bawlp') 32 | self.collector.track(distinct_ob, 'id') 33 | self.consumer.consume.assert_not_called() 34 | 35 | def test_track(self): 36 | distinct_ob = dict(foo='bar', baz='bawlp') 37 | self.collector.track(distinct_ob, 'id', hey='now') 38 | self.consumer.consume.assert_called_once() 39 | data = self.consumer.consume.call_args[0][0] 40 | tools.ok_(data.pop('when', False)) 41 | distinct_ob.update(dict(id='id', hey='now')) 42 | tools.eq_(data, distinct_ob) 43 | -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/skin/windows/overlay.css: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is Raindrop. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * Mozilla Messaging, Inc.. 18 | * Portions created by the Initial Developer are Copyright (C) 2009 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * */ 23 | 24 | #share-button { 25 | list-style-image: url("resource://ffshare/chrome/skin/windows/toolbar-button.png"); 26 | width: auto; 27 | height: auto; 28 | } 29 | 30 | #share-button[checked] { 31 | list-style-image: url("resource://ffshare/chrome/skin/windows/toolbar-button-active.png"); 32 | } 33 | 34 | #share-button[firstRun] { 35 | list-style-image: url("resource://ffshare/chrome/skin/windows/toolbar-button-glow.png"); 36 | } 37 | 38 | #share-button[status="start"] { 39 | list-style-image: url("resource://ffshare/chrome/skin/windows/status-sharing.png"); 40 | } 41 | 42 | #share-button[status="finished"] { 43 | list-style-image: url("resource://ffshare/chrome/skin/windows/status-shared.png"); 44 | } 45 | 46 | .ffshare-panel .panel-inner-arrowcontent { 47 | padding: 0; 48 | } 49 | -------------------------------------------------------------------------------- /linkdrop/tests/lib/test_shortener.py: -------------------------------------------------------------------------------- 1 | from cStringIO import StringIO 2 | from linkdrop.lib import shortener 3 | from mock import patch 4 | from nose import tools 5 | import json 6 | import urlparse 7 | 8 | config = {'bitly.userid': 'BITLY_USERID', 9 | 'bitly.key': 'BITLY_KEY', 10 | } 11 | 12 | 13 | @patch('linkdrop.lib.shortener.log') 14 | @patch('linkdrop.lib.shortener.urllib') 15 | def test_shorten_link_bad_response(mock_urllib, mock_log): 16 | longurl = 'http://example.com/long/long/really/no/i/mean/really/long/url' 17 | shortener_response = 'BAD RESPONSE' 18 | mock_urllib.urlopen.return_value = StringIO(shortener_response) 19 | res = shortener.shorten_link(config, longurl) 20 | tools.ok_(res is None) 21 | mock_urllib.urlopen.assert_called_once() 22 | urlopen_arg = mock_urllib.urlopen.call_args[0][0] 23 | tools.ok_('longUrl=%s' % longurl in urlopen_arg) 24 | mock_log.error.assert_called_once_with( 25 | "unexpected bitly response: %r", shortener_response) 26 | 27 | 28 | @patch('linkdrop.lib.shortener.urllib') 29 | def test_shorten_link(mock_urllib): 30 | longurl = 'http://example.com/long/long/really/no/i/mean/really/long/url' 31 | shorturl = 'http://sh.ort/url/%s/%s' 32 | 33 | def mock_shortener(url): 34 | query = urlparse.urlparse(url).query 35 | qdict = urlparse.parse_qs(query) 36 | bitly_userid = qdict.get('login')[0] 37 | bitly_key = qdict.get('apiKey')[0] 38 | result = shorturl % (bitly_userid, bitly_key) 39 | shortener_response = json.dumps({'data': {'url': result}}) 40 | return StringIO(shortener_response) 41 | mock_urllib.urlopen.side_effect = mock_shortener 42 | res = shortener.shorten_link(config, longurl) 43 | tools.eq_(shorturl % (config['bitly.userid'], config['bitly.key']), res) 44 | mock_urllib.urlopen.assert_called_once() 45 | urlopen_arg = mock_urllib.urlopen.call_args[0][0] 46 | tools.ok_('longUrl=%s' % longurl in urlopen_arg) 47 | -------------------------------------------------------------------------------- /linkdrop/templates/html_email.mako: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | ${c.subject} 6 | 7 | 8 |
9 | ${context.write(c.safeHTML(c.message))} 10 |
11 | 41 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /linkdrop/lib/shortener.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is Raindrop. 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Messaging, Inc.. 18 | # Portions created by the Initial Developer are Copyright (C) 2009 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # 23 | 24 | import cgi 25 | import json 26 | import urllib 27 | 28 | import logging 29 | log = logging.getLogger('__name__') 30 | 31 | 32 | def shorten_link(config, long_url): 33 | longUrl = cgi.escape(long_url.encode('utf-8')) 34 | bitly_userid = config.get('bitly.userid') 35 | bitly_key = config.get('bitly.key') 36 | bitly_result = urllib.urlopen( 37 | "http://api.bit.ly/v3/shorten?" 38 | "login=%(bitly_userid)s&apiKey=%(bitly_key)s&" 39 | "longUrl=%(longUrl)s&format=json" % dict(longUrl=longUrl, 40 | bitly_userid=bitly_userid, 41 | bitly_key=bitly_key)).read() 42 | shorturl = bitly_data = None 43 | try: 44 | bitly_data = json.loads(bitly_result)['data'] 45 | shorturl = bitly_data["url"] 46 | except (ValueError, TypeError): 47 | # bitly_data may be a list if there is an error, resulting in TypeError 48 | # when getting the url 49 | pass 50 | if not bitly_data or not shorturl: 51 | # The index of ['url'] is going to fail - it isn't clear what we 52 | # should do, but we might as well capture in the logs why. 53 | log.error("unexpected bitly response: %r", bitly_result) 54 | return shorturl 55 | -------------------------------------------------------------------------------- /linkdrop/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is Raindrop. 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Messaging, Inc.. 18 | # Portions created by the Initial Developer are Copyright (C) 2009 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # Rob Miller 23 | # 24 | # ***** END LICENSE BLOCK ***** 25 | 26 | from beaker.session import Session 27 | from linkdrop.wsgiapp import ShareServerApp 28 | from nose.tools import ok_ 29 | from paste.deploy import loadapp 30 | from webtest import TestApp 31 | 32 | __all__ = ['environ', 'TestController', 'testable_services'] 33 | 34 | testable_services = ["google.com", "facebook.com", "twitter.com", 35 | "linkedin.com", "yahoo.com"] 36 | 37 | # oh, this is just insane - re-enable all 'linkdrop' child loggers 38 | # due to http://bugs.python.org/issue11424 and the fact we have a logger 39 | # called 'linkdrop-metrics' 40 | # Note we don't see this in production due to when the logs are 41 | # initialized in a real app vs in tests. 42 | import logging 43 | for log_name in logging.getLogger().manager.loggerDict.keys(): 44 | if log_name.startswith("linkdrop."): 45 | logging.getLogger(log_name).disabled = False 46 | 47 | environ = {} 48 | 49 | 50 | # Note the base for our test cases is *not* a unittest.TestCase as some 51 | # nose features don't work with such classes. 52 | class TestController(object): 53 | def __init__(self, *args, **kwargs): 54 | # have to do a bit of digging to get to the app object that we actually 55 | # care about :P 56 | outer_app = loadapp('config:test.ini', relative_to='.') 57 | self.app = outer_app.app.application 58 | ok_(self.app.__class__ is ShareServerApp) 59 | session = Session(dict()) 60 | self.test_app = TestApp(self.app, extra_environ={'beaker.session': 61 | session}) 62 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-send-public-successful/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": null, 3 | "result": { 4 | "contributors": null, 5 | "coordinates": null, 6 | "created_at": "Wed Apr 13 02:28:18 +0000 2011", 7 | "favorited": false, 8 | "from": null, 9 | "geo": null, 10 | "id": 12345, 11 | "id_str": "12345", 12 | "in_reply_to_screen_name": null, 13 | "in_reply_to_status_id": null, 14 | "in_reply_to_status_id_str": null, 15 | "in_reply_to_user_id": null, 16 | "in_reply_to_user_id_str": null, 17 | "place": null, 18 | "retweet_count": 0, 19 | "retweeted": false, 20 | "shorturl": "http://bit.ly/eT3aUn", 21 | "source": "Mozilla Linkdrop", 22 | "text": "nerdy! http://bit.ly/eT3aUn", 23 | "to": null, 24 | "truncated": false, 25 | "user": { 26 | "contributors_enabled": false, 27 | "created_at": "Wed Apr 28 07:35:46 +0000 2010", 28 | "default_profile": true, 29 | "default_profile_image": true, 30 | "description": null, 31 | "favourites_count": 0, 32 | "follow_request_sent": false, 33 | "followers_count": 0, 34 | "following": false, 35 | "friends_count": 1, 36 | "geo_enabled": false, 37 | "id": 99999, 38 | "id_str": "99999", 39 | "is_translator": false, 40 | "lang": "en", 41 | "listed_count": 0, 42 | "location": null, 43 | "name": "my name", 44 | "notifications": false, 45 | "profile_background_color": "C0DEED", 46 | "profile_background_image_url": "http://a3.twimg.com/a/999/images/themes/theme1/bg.png", 47 | "profile_background_tile": false, 48 | "profile_image_url": "http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png", 49 | "profile_link_color": "0084B4", 50 | "profile_sidebar_border_color": "C0DEED", 51 | "profile_sidebar_fill_color": "DDEEF6", 52 | "profile_text_color": "333333", 53 | "profile_use_background_image": true, 54 | "protected": false, 55 | "screen_name": "twitterusername", 56 | "show_all_inline_media": false, 57 | "statuses_count": 67, 58 | "time_zone": "Melbourne", 59 | "url": null, 60 | "utc_offset": 36000, 61 | "verified": false 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-social.yahooapis.com-contacts-success/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | status: 200 3 | x-yahoo-social-contactsrev: 3 4 | via: HTTP/1.1 r1.ycpi.aue.yahoo.net (YahooTrafficServer/1.19.5 [cMsSf ]) 5 | content-location: http://social.yahooapis.com/v1/user/xxx/contacts 6 | -content-encoding: gzip 7 | age: 0 8 | x-yahoo-social-host: ws151.socdir.sp2.yahoo.com 9 | vary: Authorization,Accept-Encoding 10 | server: YTS/1.19.5 11 | connection: keep-alive 12 | etag: eccbc87e4b5ce2fe28308fd9f2a7baf3-gzip 13 | cache-control: private 14 | date: Mon, 21 Mar 2011 05:58:46 GMT 15 | p3p: policyref="http://info.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV" 16 | content-type: application/json; charset="utf-8" 17 | 18 | {"contacts":{ 19 | "start":0, 20 | "count":1, 21 | "total":1, 22 | "uri":"http://social.yahooapis.com/v1/user/xxx/contacts", 23 | "contact":[{ 24 | "uri":"http://social.yahooapis.com/v1/user/xxx/contact/1", 25 | "created":"2011-03-21T05:50:36Z", 26 | "updated":"2011-03-21T05:50:36Z", 27 | "isConnection":false, 28 | "id":1, 29 | "fields":[{ 30 | "uri":"http://social.yahooapis.com/v1/user/xxx/contact/1/nickname/2", 31 | "created":"2011-03-21T05:50:36Z", 32 | "updated":"2011-03-21T05:50:36Z", 33 | "id":2, 34 | "type":"nickname", 35 | "value":"test", 36 | "editedBy":"OWNER", 37 | "flags":[], 38 | "categories":[] 39 | },{ 40 | "uri":"http://social.yahooapis.com/v1/user/xxx/contact/1/email/3", 41 | "created":"2011-03-21T05:50:36Z", 42 | "updated":"2011-03-21T05:50:36Z", 43 | "id":3, 44 | "type":"email", 45 | "value":"test@test.com", 46 | "editedBy":"OWNER", 47 | "flags":[], 48 | "categories":[] 49 | },{ 50 | "uri":"http://social.yahooapis.com/v1/user/xxx/contact/1/name/1", 51 | "created":"2011-03-21T05:50:36Z", 52 | "updated":"2011-03-21T05:50:36Z", 53 | "id":1, 54 | "type":"name", 55 | "value":{ 56 | "givenName":"Test", 57 | "middleName":"", 58 | "familyName":"Test", 59 | "prefix":"", 60 | "suffix":"", 61 | "givenNameSound":"", 62 | "familyNameSound":"" 63 | }, 64 | "editedBy":"OWNER", 65 | "flags":[], 66 | "categories":[] 67 | } 68 | ], 69 | "categories":[] 70 | }]} 71 | } 72 | -------------------------------------------------------------------------------- /linkdrop/wsgiapp.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is Raindrop. 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Messaging, Inc.. 18 | # Portions created by the Initial Developer are Copyright (C) 2009 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # Rob Miller (rmiller@mozilla.com) 23 | # 24 | # ***** END LICENSE BLOCK ***** 25 | """ 26 | Application entry point. 27 | """ 28 | from linkdrop.controllers.account import AccountController 29 | from linkdrop.controllers.contacts import ContactsController 30 | from linkdrop.controllers.docs import DocsController 31 | from linkdrop.controllers.send import SendController 32 | from linkoauth.util import setup_config 33 | from routes.util import URLGenerator 34 | from services.baseapp import set_app, SyncServerApp 35 | from webob.dec import wsgify 36 | 37 | urls = [ 38 | ('GET', '/docs', 'docs', 'index'), 39 | ('POST', '/send', 'send', 'send'), 40 | ('POST', '/account/authorize', 'account', 'authorize'), 41 | (('GET', 'POST'), '/account/verify', 'account', 'verify'), 42 | ('POST', '/contacts/{domain}', 'contacts', 'get'), 43 | ] 44 | 45 | controllers = {'account': AccountController, 46 | 'contacts': ContactsController, 47 | 'docs': DocsController, 48 | 'send': SendController, 49 | } 50 | 51 | 52 | class ShareServerApp(SyncServerApp): 53 | """Share server WSGI application""" 54 | def __init__(self, urls, controllers, config, auth_class=None, 55 | *args, **kwargs): 56 | if auth_class is not None: 57 | raise ValueError("A ShareServerApp's ``auth_class`` must be None.") 58 | setup_config(config) 59 | super(ShareServerApp, self).__init__(urls, controllers, config, 60 | auth_class, *args, **kwargs) 61 | 62 | @wsgify 63 | def __call__(self, request, *args, **kwargs): 64 | """Construct an URLGenerator""" 65 | request.urlgen = URLGenerator(self.mapper, request.environ) 66 | superclass = super(ShareServerApp, self) 67 | return superclass.__call__.undecorated(request, *args, **kwargs) 68 | 69 | 70 | make_app = set_app(urls, controllers, klass=ShareServerApp, auth_class=None) 71 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-send-public-successful/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | status: 200 3 | x-transaction: 1302661698-34591-4129 4 | set-cookie: k=58.160.65.179.1302661698023708; path=/; expires=Wed, 20-Apr-11 02:28:18 GMT; domain=.twitter.com, guest_id=11111; path=/; expires=Fri, 13 May 2011 02:28:18 GMT, lang=en; path=/, _twitter_sess=some_session_id; domain=.twitter.com; path=/; HttpOnly 5 | expires: Tue, 31 Mar 1981 05:00:00 GMT 6 | vary: Accept-Encoding 7 | x-transaction-mask: a6183ffa5f8ca943ff1b53b5644ef114 8 | server: hi 9 | x-revision: DEV 10 | last-modified: Wed, 13 Apr 2011 02:28:18 GMT 11 | x-runtime: 0.07570 12 | etag: "5336c4db5eafb055f00feaddf322ef2c" 13 | pragma: no-cache 14 | cache-control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0 15 | date: Wed, 13 Apr 2011 02:28:18 GMT 16 | content-type: application/json; charset=utf-8 17 | 18 | {"text":"nerdy! http:\/\/bit.ly\/eT3aUn", 19 | "truncated":false, 20 | "place":null, 21 | "favorited":false, 22 | "id_str":"12345", 23 | "coordinates":null, 24 | "retweet_count":0, 25 | "source":"\u003Ca href=\"http:\/\/linkdrop.mozillamessaging.com\" rel=\"nofollow\"\u003EMozilla Linkdrop\u003C\/a\u003E", 26 | "in_reply_to_screen_name":null, 27 | "in_reply_to_status_id_str":null, 28 | "geo":null, 29 | "in_reply_to_status_id":null, 30 | "created_at":"Wed Apr 13 02:28:18 +0000 2011", 31 | "contributors":null, 32 | "retweeted":false, 33 | "in_reply_to_user_id_str":null, 34 | "user":{ 35 | "follow_request_sent":false, 36 | "profile_background_image_url":"http:\/\/a3.twimg.com\/a\/999\/images\/themes\/theme1\/bg.png", 37 | "url":null, 38 | "screen_name":"twitterusername", 39 | "description":null, 40 | "show_all_inline_media":false, 41 | "contributors_enabled":false, 42 | "lang":"en", 43 | "geo_enabled":false, 44 | "time_zone":"Melbourne", 45 | "profile_text_color":"333333", 46 | "location":null, 47 | "is_translator":false, 48 | "profile_sidebar_fill_color":"DDEEF6", 49 | "id_str":"99999", 50 | "listed_count":0, 51 | "profile_background_tile":false, 52 | "favourites_count":0, 53 | "statuses_count":67, 54 | "friends_count":1, 55 | "followers_count":0, 56 | "verified":false, 57 | "created_at":"Wed Apr 28 07:35:46 +0000 2010", 58 | "profile_link_color":"0084B4", 59 | "following":false, 60 | "notifications":false, 61 | "profile_sidebar_border_color":"C0DEED", 62 | "protected":false, 63 | "default_profile_image":true, 64 | "name":"my name", 65 | "profile_use_background_image":true, 66 | "profile_image_url":"http:\/\/a0.twimg.com\/sticky\/default_profile_images\/default_profile_3_normal.png", 67 | "id":99999, 68 | "default_profile":true, 69 | "utc_offset":36000, 70 | "profile_background_color":"C0DEED" 71 | }, 72 | "id":12345, 73 | "in_reply_to_user_id":null 74 | } 75 | -------------------------------------------------------------------------------- /tools/webbuild/requirejs/require/rhino.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license RequireJS rhino Copyright (c) 2010, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/jrburke/requirejs for details 5 | */ 6 | /*global require: false, readFile: false */ 7 | 8 | /* 9 | TODO: Work out relative paths, that use ./ and such, and allow loading normal 10 | CommonJS modules, by overriding require.get(). 11 | */ 12 | 13 | /*globals load: false, java: false */ 14 | "use strict"; 15 | 16 | (function () { 17 | 18 | var fileUtil = { 19 | backSlashRegExp: /\\/g, 20 | 21 | getLineSeparator: function () { 22 | return java.lang.System.getProperty("line.separator"); //Java String 23 | } 24 | }; 25 | 26 | require.load = function (context, moduleName, url) { 27 | //isDone is used by require.ready() 28 | require.s.isDone = false; 29 | 30 | //Indicate a the module is in process of loading. 31 | context.loaded[moduleName] = false; 32 | context.scriptCount += 1; 33 | 34 | load(url); 35 | 36 | //Support anonymous modules. 37 | context.completeLoad(moduleName); 38 | }; 39 | 40 | //Adapter to get text plugin to work. 41 | require.fetchText = function (url, callback) { 42 | var encoding = "utf-8", 43 | file = new java.io.File(url), 44 | lineSeparator = fileUtil.getLineSeparator(), 45 | input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)), 46 | stringBuffer, line, 47 | content = ''; 48 | try { 49 | stringBuffer = new java.lang.StringBuffer(); 50 | line = input.readLine(); 51 | 52 | // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324 53 | // http://www.unicode.org/faq/utf_bom.html 54 | 55 | // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK: 56 | // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 57 | if (line && line.length() && line.charAt(0) === 0xfeff) { 58 | // Eat the BOM, since we've already found the encoding on this file, 59 | // and we plan to concatenating this buffer with others; the BOM should 60 | // only appear at the top of a file. 61 | line = line.substring(1); 62 | } 63 | 64 | stringBuffer.append(line); 65 | 66 | while ((line = input.readLine()) !== null) { 67 | stringBuffer.append(lineSeparator); 68 | stringBuffer.append(line); 69 | } 70 | //Make sure we return a JavaScript string and not a Java string. 71 | content = String(stringBuffer.toString()); //String 72 | } finally { 73 | input.close(); 74 | } 75 | callback(content); 76 | }; 77 | 78 | }()); -------------------------------------------------------------------------------- /extensions/firefox-share/src/chrome/content/down.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | 27 | 28 | 29 | F1 not available 30 | 31 | 93 | 94 | 95 |
96 | 97 | 98 | 101 | 102 |
99 | Sorry, the F1 service is not available at the moment.
Please try later. 100 |
103 |
104 | 105 | 106 | -------------------------------------------------------------------------------- /linkdrop/tests/invalid_requests/test_send.py: -------------------------------------------------------------------------------- 1 | # Tests for invalid requests to the send end-point. 2 | # This is primarily exercising missing and invalid params. 3 | import json 4 | 5 | from linkdrop.lib import constants 6 | 7 | from linkdrop.tests import TestController 8 | from linkdrop.tests import testable_services 9 | from nose.tools import eq_ 10 | from routes.util import URLGenerator 11 | 12 | 13 | class TestSendInvalidParams(TestController): 14 | def getFullRequest(self, *except_for): 15 | account = {"oauth_token": "foo", "oauth_token_secret": "bar", 16 | "profile": {"emails": [{'value': 'me@example.com'}], 17 | "displayName": "Me", 18 | }, 19 | } 20 | result = {'domain': 'google.com', 21 | 'account': json.dumps(account), 22 | 'to': 'you@example.com', 23 | } 24 | for elt in except_for: 25 | del result[elt] 26 | return result 27 | 28 | def checkSend(self, request, 29 | expected_message=None, 30 | expected_code=constants.INVALID_PARAMS, 31 | expected_status=None, 32 | expected_http_code=200): 33 | # you must give *something* to check! 34 | assert expected_message or expected_code or expected_status 35 | url = URLGenerator(self.app.mapper, dict(HTTP_HOST='localhost')) 36 | response = self.test_app.post(url(controller='send', action='send'), 37 | params=request) 38 | 39 | assert response.status_int == expected_http_code, response.status_int 40 | try: 41 | got = json.loads(response.body) 42 | except ValueError: 43 | raise AssertionError("non-json response: %r" % (response.body,)) 44 | 45 | assert 'error' in got, response.body 46 | if expected_message: 47 | eq_(got['error'].get('message'), expected_message, response.body) 48 | if expected_code: 49 | eq_(got['error'].get('code'), expected_code, response.body) 50 | if expected_status: 51 | eq_(got['error'].get('status'), expected_status, response.body) 52 | 53 | def testNoDomain(self): 54 | self.checkSend(self.getFullRequest('domain')) 55 | 56 | def testUnknownDomain(self): 57 | req = self.getFullRequest() 58 | req['domain'] = "foo.com" 59 | self.checkSend(req) 60 | 61 | def testNoAccount(self): 62 | self.checkSend(self.getFullRequest('account'), expected_status=401, 63 | expected_code=None) 64 | 65 | # test missing OAuth params for each of the services. 66 | def checkMissingOAuth(self, service, missing_param): 67 | req = self.getFullRequest() 68 | req['domain'] = service 69 | acct = json.loads(req['account']) 70 | del acct[missing_param] 71 | req['account'] = json.dumps(acct) 72 | self.checkSend(req, expected_status=401, expected_code=None) 73 | 74 | def testMissingOAuth(self): 75 | for service in testable_services: 76 | yield self.checkMissingOAuth, service, "oauth_token" 77 | -------------------------------------------------------------------------------- /linkdrop/tests/invalid_requests/test_contacts.py: -------------------------------------------------------------------------------- 1 | # Tests for invalid requests to the contacts end-point. 2 | # This is primarily exercising missing and invalid params. 3 | import json 4 | 5 | from linkdrop.lib import constants 6 | 7 | from linkdrop.tests import TestController 8 | from linkdrop.tests import testable_services 9 | from nose.tools import eq_ 10 | from routes.util import URLGenerator 11 | 12 | 13 | class TestContactsInvalidParams(TestController): 14 | def getFullRequest(self, *except_for): 15 | account = {"oauth_token": "foo", "oauth_token_secret": "bar", 16 | "profile": {"emails": [{'value': 'me@example.com'}], 17 | "displayName": "Me", 18 | }, 19 | } 20 | request = {'domain': 'google.com', 21 | 'account': json.dumps(account), 22 | 'to': 'you@example.com', 23 | } 24 | for elt in except_for: 25 | del request[elt] 26 | return request 27 | 28 | def checkContacts(self, request, 29 | expected_message=None, 30 | expected_code=constants.INVALID_PARAMS, 31 | expected_status=None, 32 | expected_http_code=200): 33 | 34 | # you must give *something* to check! 35 | assert expected_message or expected_code or expected_status 36 | domain = request.pop('domain') 37 | url = URLGenerator(self.app.mapper, dict(HTTP_HOST='localhost')) 38 | response = self.test_app.post(url(controller='contacts', action='get', 39 | domain=domain), 40 | params=request) 41 | assert response.status_int == expected_http_code, response.status_int 42 | try: 43 | got = json.loads(response.body) 44 | except ValueError: 45 | raise AssertionError("non-json response: %r" % (response.body,)) 46 | 47 | assert 'error' in got, response.body 48 | if expected_message: 49 | eq_(got['error'].get('message'), expected_message, response.body) 50 | if expected_code: 51 | eq_(got['error'].get('code'), expected_code, response.body) 52 | if expected_status: 53 | eq_(got['error'].get('status'), expected_status, response.body) 54 | 55 | def testUnknownDomain(self): 56 | req = self.getFullRequest() 57 | req['domain'] = "foo.com" 58 | self.checkContacts(req) 59 | 60 | def testNoAccount(self): 61 | self.checkContacts(self.getFullRequest('account'), expected_status=401, 62 | expected_code=None) 63 | 64 | # test missing OAuth params for each of the services. 65 | def checkMissingOAuth(self, service, missing_param): 66 | req = self.getFullRequest() 67 | req['domain'] = service 68 | acct = json.loads(req['account']) 69 | del acct[missing_param] 70 | req['account'] = json.dumps(acct) 71 | self.checkContacts(req, expected_status=401, expected_code=None) 72 | 73 | def testMissingOAuth(self): 74 | for service in testable_services: 75 | yield self.checkMissingOAuth, service, "oauth_token" 76 | -------------------------------------------------------------------------------- /linkdrop/tests/services/test_headers.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is F1. 15 | # 16 | # The Initial Developer of the Original Code is Mozilla 17 | # Portions created by the Initial Developer are Copyright (C) 2011 18 | # the Initial Developer. All Rights Reserved. 19 | # 20 | # Contributor(s): 21 | # 22 | 23 | 24 | # Test that some request headers make it all the way to the service. 25 | # Currently the only such header is "Accept-Language" 26 | 27 | from linkdrop.tests import testable_services 28 | 29 | from test_playback import (HttpReplayer, setupReplayers, teardownReplayers, 30 | domain_to_test) 31 | 32 | from nose import with_setup 33 | from nose.tools import eq_ 34 | 35 | # A fake response object we return to the apis - we just pretend a 500 36 | # error occured every time after checking the header we expect is there. 37 | class FakeResponse(dict): 38 | def __init__(self): 39 | self['status'] = "500" 40 | 41 | def __getattr__(self, attr): 42 | try: 43 | return self[attr] 44 | except KeyError: 45 | raise AttributeError(attr) 46 | 47 | 48 | class SimpleHttpReplayer(HttpReplayer): 49 | _expected_language = None 50 | def request(self, uri, method="GET", body=None, headers=None, **kw): 51 | # check the header is (or is not) sent to the service. 52 | for k, v in (headers or {}).iteritems(): 53 | if k.lower()=="accept-language": 54 | eq_(v, self._expected_language) 55 | break 56 | else: 57 | assert self._expected_language is None 58 | # just return a 500 so things bail quickly. 59 | return FakeResponse(), "" 60 | 61 | 62 | def doSetup(): 63 | setupReplayers(SimpleHttpReplayer) 64 | 65 | 66 | @with_setup(doSetup, teardownReplayers) 67 | def check_with_headers(provider, req_type, exp_language, headers=None): 68 | SimpleHttpReplayer._expected_language = exp_language 69 | testclass = domain_to_test[provider] 70 | test = testclass() 71 | request = test.getDefaultRequest(req_type) 72 | response = test.getResponse(req_type, request, headers) 73 | 74 | def test_all(): 75 | for provider in testable_services: 76 | if provider in ["google.com", "googleapps.com"]: 77 | # these use SMTP which doesn't attempt to pass on the header. 78 | continue 79 | for req_type in ['send', 'contacts']: 80 | # test no header in f1 request means no header in service req. 81 | yield (check_with_headers, provider, req_type, None) 82 | # test "normal" case header makes it through. 83 | yield (check_with_headers, provider, req_type, 'something', 84 | {'Accept-Language': 'something'}) 85 | # test lower-case header names it through. 86 | yield (check_with_headers, provider, req_type, 'something-else', 87 | {'accept-language': 'something-else'}) 88 | -------------------------------------------------------------------------------- /f1.spec.in: -------------------------------------------------------------------------------- 1 | %define f1_prefix /opt/mozilla.org/f1 2 | %define f1_name_prefix mozilla-f1- 3 | 4 | # build with --define 'use_python_version 2.6' to pick what python to build against if not the system default 5 | %{!?use_python_version: %global use_python_version %{nil}} 6 | 7 | %if "%{use_python_version}" != "" 8 | %global pyver %( echo %{use_python_version} | sed -e's/\\.//g' ) 9 | %global python_version %{use_python_version} 10 | %global pyver_sys %pyver 11 | %else 12 | %global pyver %{nil} 13 | %{!?python_version: %global python_version %(%{__python} -c "import sys; sys.stdout.write(sys.version[:3])")} 14 | %global pyver_sys %( echo %{python_version} | sed -e's/\\.//g' ) 15 | %endif 16 | 17 | %global python_sitelib /lib/python%{python_version}/site-packages 18 | %global python_sitearch /%{_lib}/python%{python_version}/site-packages 19 | 20 | Name: %{f1_name_prefix}python%{pyver} 21 | Version: %%version%% 22 | Release: 6%%git%%%{?dist} 23 | Summary: Share Links Fast. 24 | 25 | Group: Applications/Internet 26 | License: MPL 27 | URL: http://f1.mozillamessaging.com/ 28 | Source0: linkdrop-%{version}.tar.gz 29 | BuildArch: noarch 30 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) 31 | 32 | BuildRequires: python%{pyver}-devel python%{pyver}-setuptools 33 | BuildRequires: /usr/bin/rsync, /usr/bin/cut, /bin/sed, /usr/bin/awk, /usr/bin/make 34 | BuildRequires: java 35 | BuildRequires: %{f1_name_prefix}python%{pyver}-paste-deploy, %{f1_name_prefix}python%{pyver}-paste-script 36 | Requires: %{f1_name_prefix}python%{pyver}-paste-deploy, %{f1_name_prefix}python%{pyver}-paste-script 37 | 38 | Requires: python%{pyver} 39 | 40 | %%buildrequires%% 41 | %%requires%% 42 | 43 | %description 44 | F1 is a browser extension that allows you to share links 45 | in a fast and fun way. Share links from within the browser, 46 | from any webpage, using the same services you already know 47 | and love. F1 is made by Mozilla Messaging. 48 | 49 | %prep 50 | %setup -q -n linkdrop-%{version} 51 | 52 | %build 53 | export PYTHONPATH=$(pwd):%{f1_prefix}%{python_sitelib}:%{f1_prefix}%{python_sitearch} 54 | mkdir web 55 | CFLAGS="%{optflags}" %{__python}%{pyver} setup.py build 56 | 57 | %install 58 | export PYTHONPATH=$(pwd):%{f1_prefix}%{python_sitelib}:%{f1_prefix}%{python_sitearch} 59 | rm -rf %{buildroot} 60 | %{__python}%{pyver} setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --prefix %{f1_prefix} --record=INSTALLED_FILES 61 | %{__install} -m 755 -d %{buildroot}%{_sysconfdir}/f1 62 | %{__install} -m 644 *.ini %{buildroot}%{_sysconfdir}/f1/ 63 | 64 | #XXX: Not ready yet 65 | #%check 66 | #export PYTHONPATH=$(pwd):%{f1_prefix}%{python_sitelib}:%{f1_prefix}%{python_sitearch} 67 | #%{__make} test NOSE="nosetests-%{python_version}" PYTHON="%{__python}%{pyver}" 68 | 69 | 70 | %clean 71 | rm -rf %{buildroot} 72 | 73 | %files -f INSTALLED_FILES 74 | %config(noreplace) %{_sysconfdir}/f1/*ini 75 | %defattr(-,root,root,-) 76 | %doc README.md LICENSE PKG-INFO docs/ 77 | 78 | %changelog 79 | * Tue Apr 26 2011 Philippe M. Chiasson - 0.3.7dev-7 80 | - Remove web content, moved to mozilla-f1-web 81 | * Wed Apr 20 2011 Philippe M. Chiasson - 0.3.7dev-6 82 | - Compile web content before packaging (make web) 83 | * Thu Apr 14 2011 Philippe M. Chiasson - 0.3.7dev-2 84 | - Include *.ini files 85 | * Fri Mar 18 2011 Philippe M. Chiasson - 0.3.2dev-1 86 | - Initial spec file 87 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is Raindrop. 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Messaging, Inc.. 18 | # Portions created by the Initial Developer are Copyright (C) 2009 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # 23 | 24 | try: 25 | from setuptools import setup, find_packages 26 | except ImportError: 27 | from ez_setup import use_setuptools 28 | use_setuptools() 29 | from setuptools import setup, find_packages 30 | 31 | VERSION = '0.3.7' 32 | 33 | setup( 34 | name='linkdrop', 35 | version=VERSION, 36 | description=('F1 is a browser extension that allows you to share links ' 37 | 'in a fast and fun way.'), 38 | author='Mozilla Messaging', 39 | author_email='linkdrop@googlegroups.com', 40 | url='http://f1.mozillamessaging.com/', 41 | install_requires=[ 42 | "PasteScript>=1.6.3", 43 | "beaker", 44 | "services", 45 | "decorator", 46 | "docutils", 47 | "nose", 48 | "coverage", 49 | "mock", 50 | "httplib2", 51 | "oauth2", 52 | "python-dateutil", 53 | "python-openid", 54 | "python-memcached", 55 | "linkoauth", 56 | ], 57 | packages=find_packages(exclude=['ez_setup']), 58 | include_package_data=True, 59 | test_suite='nose.collector', 60 | package_data={'linkdrop': ['i18n/*/LC_MESSAGES/*.mo']}, 61 | message_extractors={'linkdrop': [ 62 | ('**.py', 'python', None), 63 | ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}), 64 | ('public/**', 'ignore', None)]}, 65 | zip_safe=False, 66 | paster_plugins=['PasteScript'], 67 | entry_points=""" 68 | [paste.app_factory] 69 | main = linkdrop.wsgiapp:make_app 70 | static = linkdrop.static:make_static 71 | 72 | [paste.filter_app_factory] 73 | csrf = linkdrop.csrf:make_csrf_filter_app 74 | dbgp = linkdrop.debug:make_dbgp_middleware 75 | profiler = linkdrop.debug:make_profile_middleware 76 | 77 | [paste.app_install] 78 | main = paste.script.appinstall:Installer 79 | """, 80 | ) 81 | 82 | import os 83 | import stat 84 | basedir = os.path.join(os.getcwd(), "web") 85 | realdir = VERSION 86 | linkdir = os.path.join(basedir, "current") 87 | 88 | # Sanity check, nuke what isn't a symlink 89 | try: 90 | s = os.lstat(linkdir) 91 | if not stat.S_ISLNK(s.st_mode): 92 | if stat.S_ISDIR: 93 | os.rmdir(linkdir) 94 | else: 95 | os.unlink(linkdir) 96 | except OSError, e: 97 | if e.errno != 2: # file does not exist 98 | raise 99 | 100 | # Check what the symlink might already point to 101 | # and update if needed 102 | if hasattr(os, "readlink"): 103 | try: 104 | lver = os.readlink(linkdir) 105 | except OSError, e: 106 | lver = None 107 | if e.errno != 2: # file does not exist 108 | raise 109 | 110 | if lver != VERSION: 111 | if lver: 112 | os.unlink(linkdir) 113 | os.symlink(realdir, linkdir) 114 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-send-direct-successful/expected-f1-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": null, 3 | "result": { 4 | "created_at": "Wed Apr 13 06:47:54 +0000 2011", 5 | "from": null, 6 | "id": 2802052289, 7 | "id_str": "2802052289", 8 | "recipient": { 9 | "contributors_enabled": false, 10 | "created_at": "Sat Feb 21 05:55:31 +0000 2009", 11 | "default_profile": true, 12 | "default_profile_image": false, 13 | "description": "", 14 | "favourites_count": 0, 15 | "follow_request_sent": false, 16 | "followers_count": 207, 17 | "following": true, 18 | "friends_count": 138, 19 | "geo_enabled": false, 20 | "id": 333333333, 21 | "id_str": "333333333", 22 | "is_translator": false, 23 | "lang": "en", 24 | "listed_count": 21, 25 | "location": "Melbourne", 26 | "name": "My Follower", 27 | "notifications": false, 28 | "profile_background_color": "C0DEED", 29 | "profile_background_image_url": "http://a3.twimg.com/a/22222222/images/themes/theme1/bg.png", 30 | "profile_background_tile": false, 31 | "profile_image_url": "http://a1.twimg.com/profile_images/444444/mypic.png", 32 | "profile_link_color": "0084B4", 33 | "profile_sidebar_border_color": "C0DEED", 34 | "profile_sidebar_fill_color": "DDEEF6", 35 | "profile_text_color": "333333", 36 | "profile_use_background_image": true, 37 | "protected": false, 38 | "screen_name": "mytwitterfollower", 39 | "show_all_inline_media": true, 40 | "statuses_count": 172, 41 | "time_zone": "Melbourne", 42 | "url": "http://me.com", 43 | "utc_offset": 36000, 44 | "verified": false 45 | }, 46 | "recipient_id": 333333333, 47 | "recipient_screen_name": "mytwitterfollower", 48 | "sender": { 49 | "contributors_enabled": false, 50 | "created_at": "Wed Apr 28 07:35:46 +0000 2010", 51 | "default_profile": true, 52 | "default_profile_image": true, 53 | "description": null, 54 | "favourites_count": 0, 55 | "follow_request_sent": false, 56 | "followers_count": 1, 57 | "following": false, 58 | "friends_count": 1, 59 | "geo_enabled": false, 60 | "id": 111111111, 61 | "id_str": "111111111", 62 | "is_translator": false, 63 | "lang": "en", 64 | "listed_count": 0, 65 | "location": null, 66 | "name": "my name", 67 | "notifications": false, 68 | "profile_background_color": "C0DEED", 69 | "profile_background_image_url": "http://a3.twimg.com/a/1302639708/images/themes/theme1/bg.png", 70 | "profile_background_tile": false, 71 | "profile_image_url": "http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png", 72 | "profile_link_color": "0084B4", 73 | "profile_sidebar_border_color": "C0DEED", 74 | "profile_sidebar_fill_color": "DDEEF6", 75 | "profile_text_color": "333333", 76 | "profile_use_background_image": true, 77 | "protected": false, 78 | "screen_name": "mytwitterid", 79 | "show_all_inline_media": false, 80 | "statuses_count": 68, 81 | "time_zone": "Melbourne", 82 | "url": null, 83 | "utc_offset": 36000, 84 | "verified": false 85 | }, 86 | "sender_id": 111111111, 87 | "sender_screen_name": "mytwitterid", 88 | "shorturl": "http://bit.ly/eT3aUn", 89 | "text": "for a nerd http://bit.ly/eT3aUn", 90 | "to": "123456" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/README.txt: -------------------------------------------------------------------------------- 1 | This directory contains a corpus used for testing. Each test is in its 2 | own directory. The intent is that they allow full end-to-end testing of 3 | F1 - from the initial request into F1, the request and response F1 makes 4 | to the actual services, and the final response from F1. 5 | 6 | In general, these test cases were first generated by the "protocol capture" 7 | code (see linkoauth/protocap.py) - each capture was then edited and moved 8 | into this corpus with an appropriate name. 9 | 10 | Each directory uses a naming convention of "protocol-host-req_type-comments" 11 | where 12 | 13 | * protocol is either 'http' or 'smtp' 14 | * host is the actual hostname the connection is made to. 15 | * req_type is one of 'auth', 'contacts' or 'send'. 16 | * Comments are free form. 17 | 18 | The test runner parses the names so it can work out exactly how to test, so 19 | you must use the names above. Hopefully that will wind up going away and 20 | being smarter without making assumptions about the dir name. 21 | 22 | Test Types 23 | ---------- 24 | 25 | There are 2 types of tests this is designed to support. 26 | 27 | 1) 'Unexpected' service error response handling 28 | 29 | In this kind of test we are checking how F1 behaves when it makes a valid 30 | request but the service returns an unexpected error code due to a transient 31 | error on that service. In this type of test, the actual incoming F1 request 32 | and the content of the request made to the service isn't that important - the 33 | service just had a transient error unrelated to the input data. 34 | 35 | To make these tests more convenient, some tests don't have any input data 36 | defined - the test runner just synthesizez a simple request, ignores the 37 | content of the request F1 made to the service and just returns the error 38 | response. The final F1 response given that error response is checked and 39 | that's about it. 40 | 41 | 2) F1 functionality tests 42 | 43 | In this kind of test, the things we are testing depend directly on the 44 | incoming F1 request - the content of the request dictates the request we 45 | make to the service (eg, a 'direct' message versus a 'public' message.) 46 | 47 | These tests generally have the full set of input requests specified. In this 48 | case the test runner uses the specific incoming request, and then checks the 49 | request made to the service itself is as expected. It then replays the 50 | appropriate response to F1 and checks the outgoing F1 response is as expected. 51 | 52 | Corpus Contents 53 | --------------- 54 | Each directory may have the following files: 55 | 56 | * meta.json - currently unused. 57 | 58 | * f1-request.json - a file which holds the json body of an incoming request 59 | to F1. If this file doesn't exist, a "simple" request is synthesized which 60 | is useful for the "unexpected service errors" tests described above. 61 | 62 | * request-n - where n is an integer. These correspond to the requests we 63 | expect F1 to make on the external service. For example, if the 64 | f1-request.json file specifies a direct message on twitter, request-0 will 65 | hold the request F1 should make to the twitter directmessage API. This 66 | is checked by the test runner. If no request-n file exists, the test runner 67 | doesn't check the request at all - it just returns the appropriate response. 68 | 69 | * response-n - where n is an integer. This corresponds to the request-n file 70 | above. This is the response from the external service which the test runner 71 | returns to the F1 code. For the example above, this would be the response 72 | F1 gets from twitter after a successful direct message. 73 | 74 | * expected-f1-response.json - a json file which can describe a full F1 75 | response including header values and response code. 76 | 77 | * expected-f1-data.json - only used if 'expected-f1-response.json' does not 78 | exist. Holds only the response body portion of F1. 79 | 80 | The tests always check the final F1 response from the playback is as specified 81 | in the 'expected-f1-*' files. -------------------------------------------------------------------------------- /linkdrop/lib/metrics.py: -------------------------------------------------------------------------------- 1 | # A simple metrics gathering interface for F1. 2 | import time 3 | 4 | _emptydict = {} 5 | 6 | 7 | class MetricsConsumer(object): 8 | def consume(self, data): 9 | raise NotImplementedError 10 | 11 | 12 | class MetricsCollector(object): 13 | def __init__(self, consumer): 14 | self.consumer = consumer 15 | self.enabled = True 16 | 17 | def _get_distinct_attrs(self, distinct_ob): 18 | # 'distinct attributes' are anything we want to group the metrics by - 19 | # eg, it may include the concept of a 'user', a 'remote address', etc. 20 | # if it is already a dict, assume it is already a set of attributes. 21 | if distinct_ob is None: 22 | return _emptydict 23 | if isinstance(distinct_ob, dict): 24 | return distinct_ob 25 | return self._distinct_object_to_attrs(distinct_ob) 26 | 27 | def _distinct_object_to_attrs(self, distinct_ob): 28 | raise NotImplementedError 29 | 30 | def track(self, distinct_ob, id, **data): 31 | if not self.enabled: 32 | return 33 | data.update(self._get_distinct_attrs(distinct_ob)) 34 | # time can be formatted externally for lower perf impact here. 35 | data['when'] = time.time() 36 | data['id'] = id 37 | self.consumer.consume(data) 38 | 39 | def start_timer(self, distinct_ob, **init_data): 40 | init_data.update(self._get_distinct_attrs(distinct_ob)) 41 | return TimedMetricsCollector(self, init_data) 42 | 43 | 44 | class TimedMetricsCollector(object): 45 | def __init__(self, parent_collector, init_data): 46 | self.parent_collector = parent_collector 47 | self.init_data = init_data 48 | self.tracked = False 49 | self.started = time.time() 50 | 51 | def track(self, id, **data): 52 | assert not self.tracked 53 | self.tracked = True 54 | if self.init_data is not None: 55 | data.update(self.init_data) 56 | assert 'took' not in data, data 57 | data['took'] = time.time() - self.started 58 | self.parent_collector.track(None, id, **data) 59 | 60 | 61 | # F1 specific stuff - should probably go into its own module once it gets 62 | # more sophisticated or more options... 63 | import logging 64 | log = logging.getLogger('linkdrop-metrics') 65 | errlog = logging.getLogger('linkdrop') 66 | 67 | 68 | class F1MetricsConsumer(MetricsConsumer): 69 | def consume(self, data): 70 | # gozer has requested a simple format of name=value, space sep'd and 71 | # strings quoted. 72 | msg = " ".join(("%s=%r" % (n, v.encode("utf-8") 73 | if isinstance(v, unicode) else v) 74 | for n, v in data.iteritems())) 75 | log.info("%s", msg) 76 | 77 | 78 | class F1MetricsCollector(MetricsCollector): 79 | def _distinct_object_to_attrs(self, distinct_ob): 80 | # distinct_ob is expected to be a webob 'request' object 81 | # a proxy is used in production, so prefer HTTP_X_FORWARDED_FOR 82 | try: 83 | remote_address = distinct_ob.environ['HTTP_X_FORWARDED_FOR'] 84 | except KeyError: 85 | remote_address = distinct_ob.environ.get("REMOTE_ADDR") 86 | # discard the last octet of the IP address. Will almost certainly 87 | # need work for IPv6 :) 88 | result = {} 89 | if remote_address: 90 | try: 91 | remote_address = ".".join(remote_address.split(".", 3)[:-1]) \ 92 | + ".0" 93 | except (ValueError, IndexError), exc: 94 | errlog.error("failed to anonymize remote address %r: %s", 95 | remote_address, exc) 96 | else: 97 | result['remote_address'] = remote_address 98 | return result 99 | 100 | 101 | # the object used by the code. 102 | metrics = F1MetricsCollector(F1MetricsConsumer()) 103 | -------------------------------------------------------------------------------- /extensions/firefox-share/src/modules/addonutils.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is Raindrop. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * the Mozilla Foundation. 18 | * Portions created by the Initial Developer are Copyright (C) 2011 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * Anant Narayanan 23 | * Shane Caraveo 24 | * 25 | * Alternatively, the contents of this file may be used under the terms of 26 | * either the GNU General Public License Version 2 or later (the "GPL"), or 27 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | * in which case the provisions of the GPL or the LGPL are applicable instead 29 | * of those above. If you wish to allow use of your version of this file only 30 | * under the terms of either the GPL or the LGPL, and not to allow others to 31 | * use your version of this file under the terms of the MPL, indicate your 32 | * decision by deleting the provisions above and replace them with the notice 33 | * and other provisions required by the GPL or the LGPL. If you do not delete 34 | * the provisions above, a recipient may use your version of this file under 35 | * the terms of any one of the MPL, the GPL or the LGPL. 36 | * 37 | * ***** END LICENSE BLOCK ***** */ 38 | 39 | const {classes: Cc, interfaces: Ci, utils: Cu} = Components; 40 | 41 | Cu.import("resource://gre/modules/Services.jsm"); 42 | Cu.import("resource://gre/modules/AddonManager.jsm"); 43 | 44 | const EXPORTED_SYMBOLS = ["loadStylesheet", "getString"]; 45 | 46 | function loadStylesheet(win, uri) { 47 | let document = win.document; 48 | let pi = document.createProcessingInstruction( 49 | "xml-stylesheet", "href=\"" + uri + "\" type=\"text/css\""); 50 | document.insertBefore(pi, document.firstChild); 51 | return pi; 52 | } 53 | 54 | /* l10n support. See https://github.com/Mardak/restartless/examples/l10nDialogs */ 55 | function getString(name, args, plural) { 56 | let str; 57 | try { 58 | str = getString.bundle.GetStringFromName(name); 59 | } catch(ex) { 60 | str = getString.fallback.GetStringFromName(name); 61 | } 62 | if (args != null) { 63 | if (typeof args == "string" || args.length == null) 64 | args = [args]; 65 | str = str.replace(/%s/gi, args[0]); 66 | Array.forEach(args, function(replacement, index) { 67 | str = str.replace(RegExp("%" + (index + 1) + "\\$S", "gi"), replacement); 68 | }); 69 | } 70 | return str; 71 | } 72 | getString.init = function(addon, getAlternate) { 73 | if (typeof getAlternate != "function") 74 | getAlternate = function() "en-US"; 75 | 76 | function getBundle(locale) { 77 | let propertyPath = "chrome/locale/" + locale + ".properties"; 78 | let propertyFile = addon.getResourceURI(propertyPath); 79 | try { 80 | let uniqueFileSpec = propertyFile.spec + "#" + Math.random(); 81 | let bundle = Services.strings.createBundle(uniqueFileSpec); 82 | bundle.getSimpleEnumeration(); 83 | return bundle; 84 | } catch(ex) {} 85 | return null; 86 | } 87 | 88 | let locale = Cc["@mozilla.org/chrome/chrome-registry;1"]. 89 | getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global"); 90 | getString.bundle = getBundle(locale) || getBundle(getAlternate(locale)); 91 | getString.fallback = getBundle("en-US"); 92 | } 93 | -------------------------------------------------------------------------------- /misc/lsprofcalltree.py: -------------------------------------------------------------------------------- 1 | # lsprofcalltree.py: lsprof output which is readable by kcachegrind 2 | # David Allouche 3 | # Jp Calderone & Itamar Shtull-Trauring 4 | # Johan Dahlin 5 | 6 | import optparse 7 | import os 8 | import sys 9 | 10 | try: 11 | import cProfile 12 | except ImportError: 13 | raise SystemExit("This script requires cProfile from Python 2.5") 14 | 15 | def label(code): 16 | if isinstance(code, str): 17 | return ('~', 0, code) # built-in functions ('~' sorts at the end) 18 | else: 19 | return '%s %s:%d' % (code.co_name, 20 | code.co_filename, 21 | code.co_firstlineno) 22 | 23 | class KCacheGrind(object): 24 | def __init__(self, profiler): 25 | self.data = profiler.getstats() 26 | self.out_file = None 27 | 28 | def output(self, out_file): 29 | self.out_file = out_file 30 | print >> out_file, 'events: Ticks' 31 | self._print_summary() 32 | for entry in self.data: 33 | self._entry(entry) 34 | 35 | def _print_summary(self): 36 | max_cost = 0 37 | for entry in self.data: 38 | totaltime = int(entry.totaltime * 1000) 39 | max_cost = max(max_cost, totaltime) 40 | print >> self.out_file, 'summary: %d' % (max_cost,) 41 | 42 | def _entry(self, entry): 43 | out_file = self.out_file 44 | 45 | code = entry.code 46 | #print >> out_file, 'ob=%s' % (code.co_filename,) 47 | if isinstance(code, str): 48 | print >> out_file, 'fi=~' 49 | else: 50 | print >> out_file, 'fi=%s' % (code.co_filename,) 51 | print >> out_file, 'fn=%s' % (label(code),) 52 | 53 | inlinetime = int(entry.inlinetime * 1000) 54 | if isinstance(code, str): 55 | print >> out_file, '0 ', inlinetime 56 | else: 57 | print >> out_file, '%d %d' % (code.co_firstlineno, inlinetime) 58 | 59 | # recursive calls are counted in entry.calls 60 | if entry.calls: 61 | calls = entry.calls 62 | else: 63 | calls = [] 64 | 65 | if isinstance(code, str): 66 | lineno = 0 67 | else: 68 | lineno = code.co_firstlineno 69 | 70 | for subentry in calls: 71 | self._subentry(lineno, subentry) 72 | print >> out_file 73 | 74 | def _subentry(self, lineno, subentry): 75 | out_file = self.out_file 76 | code = subentry.code 77 | #print >> out_file, 'cob=%s' % (code.co_filename,) 78 | print >> out_file, 'cfn=%s' % (label(code),) 79 | if isinstance(code, str): 80 | print >> out_file, 'cfi=~' 81 | print >> out_file, 'calls=%d 0' % (subentry.callcount,) 82 | else: 83 | print >> out_file, 'cfi=%s' % (code.co_filename,) 84 | print >> out_file, 'calls=%d %d' % ( 85 | subentry.callcount, code.co_firstlineno) 86 | 87 | totaltime = int(subentry.totaltime * 1000) 88 | print >> out_file, '%d %d' % (lineno, totaltime) 89 | 90 | def main(args): 91 | usage = "%s [-o output_file_path] scriptfile [arg] ..." 92 | parser = optparse.OptionParser(usage=usage % sys.argv[0]) 93 | parser.allow_interspersed_args = False 94 | parser.add_option('-o', '--outfile', dest="outfile", 95 | help="Save stats to ", default=None) 96 | 97 | if not sys.argv[1:]: 98 | parser.print_usage() 99 | sys.exit(2) 100 | 101 | options, args = parser.parse_args() 102 | 103 | if not options.outfile: 104 | options.outfile = '%s.log' % os.path.basename(args[0]) 105 | 106 | sys.argv[:] = args 107 | 108 | prof = cProfile.Profile() 109 | try: 110 | try: 111 | prof = prof.run('execfile(%r)' % (sys.argv[0],)) 112 | except SystemExit: 113 | pass 114 | finally: 115 | kg = KCacheGrind(prof) 116 | kg.output(file(options.outfile, 'w')) 117 | 118 | if __name__ == '__main__': 119 | sys.exit(main(sys.argv)) 120 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-send-direct-successful/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | status: 200 3 | set-cookie: k=58.160.65.179.1302677273813408; path=/; expires=Wed, 20-Apr-11 06:47:53 GMT; domain=.twitter.com, guest_id=1234; path=/; expires=Fri, 13 May 2011 06:47:54 GMT, lang=en; path=/, _twitter_sess=session_value; domain=.twitter.com; path=/; HttpOnly 4 | expires: Tue, 31 Mar 1981 05:00:00 GMT 5 | vary: Accept-Encoding 6 | x-transaction-mask: a6183ffa5f8ca943ff1b53b5644ef114 7 | server: hi 8 | x-revision: DEV 9 | last-modified: Wed, 13 Apr 2011 06:47:54 GMT 10 | x-runtime: 0.08226 11 | pragma: no-cache 12 | cache-control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0 13 | date: Wed, 13 Apr 2011 06:47:54 GMT 14 | content-type: application/json; charset=utf-8 15 | 16 | { 17 | "text":"for a nerd http:\/\/bit.ly\/eT3aUn", 18 | "sender":{ 19 | "is_translator":false, 20 | "profile_background_image_url":"http:\/\/a3.twimg.com\/a\/1302639708\/images\/themes\/theme1\/bg.png", 21 | "friends_count":1, 22 | "url":null, 23 | "screen_name":"mytwitterid", 24 | "default_profile":true, 25 | "listed_count":0, 26 | "description":null, 27 | "contributors_enabled":false, 28 | "lang":"en", 29 | "statuses_count":68, 30 | "time_zone":"Melbourne", 31 | "profile_text_color":"333333", 32 | "location":null, 33 | "profile_sidebar_fill_color":"DDEEF6", 34 | "id_str":"111111111", 35 | "profile_background_tile":false, 36 | "favourites_count":0, 37 | "default_profile_image":true, 38 | "followers_count":1, 39 | "verified":false, 40 | "created_at":"Wed Apr 28 07:35:46 +0000 2010", 41 | "profile_link_color":"0084B4", 42 | "following":false, 43 | "notifications":false, 44 | "profile_sidebar_border_color":"C0DEED", 45 | "protected":false, 46 | "follow_request_sent":false, 47 | "name":"my name", 48 | "profile_use_background_image":true, 49 | "profile_image_url":"http:\/\/a0.twimg.com\/sticky\/default_profile_images\/default_profile_3_normal.png", 50 | "id":111111111, 51 | "show_all_inline_media":false, 52 | "geo_enabled":false, 53 | "utc_offset":36000, 54 | "profile_background_color":"C0DEED" 55 | }, 56 | "id_str":"2802052289", 57 | "created_at":"Wed Apr 13 06:47:54 +0000 2011", 58 | "recipient_id":333333333, 59 | "sender_screen_name":"mytwitterid", 60 | "sender_id":111111111, 61 | "recipient":{ 62 | "is_translator":false, 63 | "profile_background_image_url":"http:\/\/a3.twimg.com\/a\/22222222\/images\/themes\/theme1\/bg.png", 64 | "friends_count":138, 65 | "url":"http:\/\/me.com", 66 | "screen_name":"mytwitterfollower", 67 | "default_profile":true, 68 | "listed_count":21, 69 | "description":"", 70 | "contributors_enabled":false, 71 | "lang":"en", 72 | "statuses_count":172, 73 | "time_zone":"Melbourne", 74 | "profile_text_color":"333333", 75 | "location":"Melbourne", 76 | "profile_sidebar_fill_color":"DDEEF6", 77 | "id_str":"333333333", 78 | "profile_background_tile":false, 79 | "favourites_count":0, 80 | "default_profile_image":false, 81 | "followers_count":207, 82 | "verified":false, 83 | "created_at":"Sat Feb 21 05:55:31 +0000 2009", 84 | "profile_link_color":"0084B4", 85 | "following":true, 86 | "notifications":false, 87 | "profile_sidebar_border_color":"C0DEED", 88 | "protected":false, 89 | "follow_request_sent":false, 90 | "name":"My Follower", 91 | "profile_use_background_image":true, 92 | "profile_image_url":"http:\/\/a1.twimg.com\/profile_images\/444444\/mypic.png", 93 | "id":333333333, 94 | "show_all_inline_media":true, 95 | "geo_enabled":false, 96 | "utc_offset":36000, 97 | "profile_background_color":"C0DEED" 98 | }, 99 | "recipient_screen_name":"mytwitterfollower", 100 | "id":2802052289 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WARNING: THIS REPOSITORY IS DEPRECATED! 2 | 3 | The "F1" project, a way to share links implemented as a Firefox extension, has 4 | been converted to (and thus superseded by) Firefox's "Share" feature. The 5 | web app portion of this project is now available in the following repositories: 6 | 7 | - https://github.com/mozilla/server-share 8 | - https://github.com/mozilla/server-share-core 9 | - https://github.com/mozilla/client-share-web 10 | 11 | The browser extension portion of this project has been rolled into Firefox 12 | itself. 13 | 14 | ACTIVE DEVELOPMENT IS NO LONGER HAPPENING IN THIS REPOSITORY. 15 | 16 | Thanks! 17 | 18 | 19 | 20 | 21 | 22 | # f1 23 | 24 | A link sharing service that consists of a Firefox extension and a web service. 25 | 26 | The firefox extension creates an area to show the share UI served from the web service. 27 | 28 | The web service handles the OAuth work and sending of messages to different share servers. 29 | 30 | Some directory explanations: 31 | 32 | * **extensions**: holds the Firefox extension source. 33 | * **web**: holds the UI for the web service. 34 | * **grinder**: a load testing tool. 35 | * **tools**: deployment tools. 36 | * The rest of the files support the web service. 37 | 38 | ## Installation and Setup 39 | 40 | ### Get the f1 repository: 41 | 42 | git clone https://github.com/mozilla/f1.git 43 | cd f1 44 | 45 | ### Setup dependencies: 46 | 47 | make build 48 | 49 | If you are on OS X and you get errors or it does not work, see the OS X troubleshooting 50 | section below. 51 | 52 | ### Start the virtualenv 53 | 54 | source bin/activate 55 | 56 | ### Running f1 57 | 58 | Run the web server. 'reload' is useful for development, the webserver restarts on file changes, otherwise you can leave it off 59 | 60 | paster serve --reload development.ini 61 | 62 | Then visit: [http://127.0.0.1:5000/](http://127.0.0.1:5000/) for an index of api examples 63 | 64 | ## Troubleshooting OS X installs 65 | 66 | If the **make build** command produced errors or results in not being able to start 67 | up the server, use the following steps. It is suggested you re-clone F1 before 68 | doing the following steps, so that it starts out with a clean environment. 69 | 70 | 1. Make sure XCode 3 is installed. 71 | 72 | 2. Build your own version of Python: 73 | 74 | sudo svn co http://svn.plone.org/svn/collective/buildout/python/ 75 | sudo chown -R $USER ./python 76 | cd python 77 | vi buildout.cfg: then remove any references to python 2.4 and 2.5 78 | python bootstrap.py 79 | ./bin/buildout 80 | cd /usr/local/bin 81 | sudo ln -s /opt/python/bin/virtualenv-2.6 virtualenv 82 | 83 | 3. Now edit your .profile to make sure that if you have MacPorts installed, its PATH and MANPATH variables 84 | are last in the list for those environment variables. 85 | 86 | I also removed export PYTHONPATH=/Users/aaa/hg/raindrop/server/python:$PYTHONPATH 87 | and removed /Library/Frameworks/Python.framework/Versions/Current/bin from the $PATH variable. 88 | 89 | 4. Build C libraries via Homebrew: 90 | 91 | Homebrew installs into /usr/local by default, and it is best if you chown the files in there to you: 92 | 93 | sudo chown -R $USER /usr/local 94 | 95 | If installed things before in these directories, remove these directories: /usr/local/include and /usr/local/lib 96 | 97 | ruby -e "$(curl -fsSLk https://gist.github.com/raw/323731/install_homebrew.rb)" 98 | brew install memcached libmemcached 99 | 100 | Then try the **make build** command above and continue from there. 101 | 102 | ## Setting up a valid Google domain for OpenID+OAuth 103 | 104 | You have to have access to a valid domain that google can get to and where you can install an html file. 105 | 106 | Visit: [https://www.google.com/accounts/ManageDomains](https://www.google.com/accounts/ManageDomains) 107 | 108 | Add your domain, follow the rest of their instructions. 109 | 110 | To test: Once that is done, you can bypass normal access to your domain by adding to your /etc/hosts file: 111 | 112 | 127.0.0.1 your.host.com 113 | 114 | Update development.ini and add your key/secret for the google configuration, restart paster. 115 | 116 | Then in the web browser, hit f1 with http://your.host.com. 117 | -------------------------------------------------------------------------------- /production.ini: -------------------------------------------------------------------------------- 1 | # 2 | # linkdrop - Pylons configuration 3 | # 4 | # The %(here)s variable will be replaced with the parent directory of this file 5 | # 6 | [DEFAULT] 7 | import = %(here)s/private.ini 8 | # debug = true 9 | # Uncomment and replace with the address which should receive any error reports 10 | #email_to = you@yourdomain.com 11 | smtp_server = localhost 12 | error_email_from = paste@localhost 13 | 14 | oauth_failure = /0.3.7/auth.html#oauth_failure 15 | oauth_success = /0.3.7/auth.html#oauth_success 16 | 17 | # Register with twitter at http://dev.twitter.com/apps/new 18 | oauth.twitter.com.request = https://twitter.com/oauth/request_token 19 | oauth.twitter.com.access = https://twitter.com/oauth/access_token 20 | oauth.twitter.com.authorize = https://twitter.com/oauth/authenticate 21 | 22 | # Register with facebook at http://developers.facebook.com/setup/ 23 | oauth.facebook.com.scope = publish_stream,offline_access 24 | oauth.facebook.com.authorize = https://graph.facebook.com/oauth/authorize 25 | oauth.facebook.com.access = https://graph.facebook.com/oauth/access_token 26 | 27 | # Register with Google at https://www.google.com/accounts/ManageDomains 28 | oauth.google.com.scope = https://mail.google.com/ http://www.google.com/m8/feeds/ 29 | 30 | 31 | [server:main] 32 | use = egg:Paste#http 33 | host = 0.0.0.0 34 | port = 5000 35 | workers = 10 36 | worker_class = gevent 37 | 38 | [filter-app:main] 39 | use = egg:Beaker#beaker_session 40 | next = sessioned 41 | beaker.session.key = linkdrop 42 | beaker.session.secret = TMShmttWBvOMws810dW2nFB7k 43 | beaker.session.cookie_expires = false 44 | beaker.session.timeout = 2592000 45 | 46 | # XXX file sessions are slow, should change to memcached or database. be sure 47 | # to set lock_dir below if using memcached 48 | 49 | #beaker.session.type = file 50 | beaker.session.type = ext:memcached 51 | beaker.session.url = localhost:11211 52 | #beaker.session.type = ext:database 53 | #beaker.session.url = mysql+mysqldb://linkdrop:linkdrop@localhost/linkdrop 54 | 55 | # If you'd like to fine-tune the individual locations of the cache data dirs 56 | # for the Cache data, or the Session saves, un-comment the desired settings 57 | # here: 58 | beaker.cache.data_dir = %(here)s/data/cache 59 | beaker.session.data_dir = %(here)s/data/sessions 60 | beaker.session.lock_dir = %(here)s/data/sessions/lock 61 | 62 | [app:sessioned] 63 | use = egg:Paste#urlmap 64 | / = home 65 | /api = api 66 | 67 | # 68 | # This should be removed and the web pages directly served by nginx 69 | # 70 | [app:home] 71 | use = egg:linkdrop#static 72 | document_root = /var/www/f1 73 | 74 | [filter:proxy-prefix] 75 | use = egg:PasteDeploy#prefix 76 | prefix = /api 77 | 78 | [app:api] 79 | use = egg:linkdrop 80 | filter-with = proxy-prefix 81 | full_stack = true 82 | static_files = false 83 | session_middleware = false 84 | 85 | cache_dir = %(here)s/data 86 | app_instance_uuid = {94115436-4056-49c2-9af7-e12d3bbd9fbb} 87 | 88 | # WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* 89 | # Debug mode will enable the interactive debugging tool, allowing ANYONE to 90 | # execute malicious code after an exception is raised. 91 | set debug = false 92 | 93 | # Logging configuration 94 | [loggers] 95 | keys = root, routes, linkdrop 96 | 97 | [handlers] 98 | keys = console, file 99 | #keys = wsgi 100 | 101 | [formatters] 102 | keys = generic 103 | 104 | [logger_root] 105 | level = INFO 106 | handlers = console, file 107 | #handlers = wsgi 108 | 109 | [logger_routes] 110 | level = INFO 111 | handlers = 112 | qualname = routes.middleware 113 | # "level = DEBUG" logs the route matched and routing variables. 114 | 115 | [logger_linkdrop] 116 | level = INFO 117 | handlers = 118 | qualname = linkdrop 119 | 120 | [handler_console] 121 | class = StreamHandler 122 | args = (sys.stderr,) 123 | level = NOTSET 124 | formatter = generic 125 | 126 | [handler_wsgi] 127 | class = pylons.log.WSGIErrorsHandler 128 | args = () 129 | level = INFO 130 | formatter = generic 131 | 132 | [handler_file] 133 | class = FileHandler 134 | args = ('/var/log/linkdrop.log', 'a') 135 | level = INFO 136 | formatter = generic 137 | 138 | [formatter_generic] 139 | format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] [%(threadName)s] %(message)s 140 | datefmt = %H:%M:%S 141 | 142 | -------------------------------------------------------------------------------- /grinder/sendutil.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is Raindrop. 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Messaging, Inc.. 18 | # Portions created by the Initial Developer are Copyright (C) 2009 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # 23 | 24 | # The Grinder 3.4 25 | # HTTP script originally recorded by TCPProxy, but then hacked... 26 | 27 | from net.grinder.script import Test 28 | from net.grinder.script.Grinder import grinder 29 | from net.grinder.plugin.http import HTTPPluginControl, HTTPRequest 30 | from HTTPClient import NVPair 31 | from HTTPClient import Cookie, CookieModule, CookiePolicyHandler 32 | 33 | connectionDefaults = HTTPPluginControl.getConnectionDefaults() 34 | httpUtilities = HTTPPluginControl.getHTTPUtilities() 35 | 36 | log = grinder.logger.output 37 | 38 | # The URL of the server we want to hit. 39 | url0 = 'http://127.0.0.1:5000' 40 | 41 | # *sob* - failed to get json packages working. Using 're' is an option, 42 | # although it requires you install jython2.5 (which still doesn't have 43 | # json builtin) - so to avoid all that complication, hack 'eval' into 44 | # working for us... 45 | _json_ns = {'null': None} 46 | def json_loads(val): 47 | return eval(val, _json_ns) 48 | 49 | CookieModule.setCookiePolicyHandler(None) 50 | from net.grinder.plugin.http import HTTPPluginControl 51 | HTTPPluginControl.getConnectionDefaults().followRedirects = 1 52 | 53 | # To use a proxy server, uncomment the next line and set the host and port. 54 | # connectionDefaults.setProxyServer("localhost", 8001) 55 | 56 | # These definitions at the top level of the file are evaluated once, 57 | # when the worker process is started. 58 | connectionDefaults.defaultHeaders = \ 59 | [ NVPair('Accept-Language', 'en-us,en;q=0.5'), 60 | NVPair('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'), 61 | NVPair('Accept-Encoding', 'gzip, deflate'), 62 | NVPair('User-Agent', 'Mozilla/5.0 (Windows NT 6.0; WOW64; rv:2.0b6) Gecko/20100101 Firefox/4.0b6'), ] 63 | 64 | request1 = HTTPRequest() 65 | 66 | def getCSRF(): 67 | threadContext = HTTPPluginControl.getThreadHTTPClientContext() 68 | CookieModule.discardAllCookies(threadContext) 69 | result = request1.GET(url0 + '/api/account/get') 70 | assert result.getStatusCode()==200, result 71 | csrf = linkdrop = None 72 | for cookie in CookieModule.listAllCookies(threadContext): 73 | if cookie.name == "linkdrop": 74 | linkdrop = cookie 75 | if cookie.name == "csrf": 76 | csrf = cookie.value 77 | assert csrf and linkdrop 78 | return csrf, linkdrop 79 | 80 | def authTwitter(csrf): 81 | # Call authorize requesting we land back on /account/get - after 82 | # a couple of redirects for auth, we should wind up with the data from 83 | # account/get - which should now include our account info. 84 | result = request1.POST(url0 + '/api/account/authorize', 85 | ( NVPair('csrftoken', csrf), 86 | NVPair('domain', 'twitter.com'), 87 | NVPair('end_point_success', '/api/account/get'), 88 | NVPair('end_point_auth_failure', '/send/auth.html#oauth_failure'), ), 89 | ( NVPair('Content-Type', 'application/x-www-form-urlencoded'), )) 90 | assert result.getStatusCode()==200, result 91 | data = json_loads(result.getText()) 92 | assert data, 'account/get failed to return data' 93 | userid = data[0]['accounts'][0]['userid'] 94 | return userid 95 | 96 | def send(userid, csrf, domain="twitter.com", message="take that!"): 97 | """POST send.""" 98 | result = request1.POST(url0 + '/api/send', 99 | ( NVPair('domain', domain), 100 | NVPair('userid', userid), 101 | NVPair('csrftoken', csrf), 102 | NVPair('message', message), ), 103 | ( NVPair('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'), )) 104 | assert result.getStatusCode()==200, result 105 | assert '"error": null' in result.getText(), result.getText() 106 | return result 107 | -------------------------------------------------------------------------------- /staging.ini: -------------------------------------------------------------------------------- 1 | # 2 | # linkdrop - Pylons configuration 3 | # 4 | # The %(here)s variable will be replaced with the parent directory of this file 5 | # 6 | [DEFAULT] 7 | import = %(here)s/private.ini 8 | # debug = true 9 | # Uncomment and replace with the address which should receive any error reports 10 | #email_to = you@yourdomain.com 11 | smtp_server = localhost 12 | error_email_from = paste@localhost 13 | 14 | oauth_failure = /0.3.7/auth.html#oauth_failure 15 | oauth_success = /0.3.7/auth.html#oauth_success 16 | 17 | # Register with twitter at http://dev.twitter.com/apps/new 18 | oauth.twitter.com.request = https://twitter.com/oauth/request_token 19 | oauth.twitter.com.access = https://twitter.com/oauth/access_token 20 | oauth.twitter.com.authorize = https://twitter.com/oauth/authenticate 21 | 22 | # Register with facebook at http://developers.facebook.com/setup/ 23 | oauth.facebook.com.scope = publish_stream,offline_access 24 | oauth.facebook.com.authorize = https://graph.facebook.com/oauth/authorize 25 | oauth.facebook.com.access = https://graph.facebook.com/oauth/access_token 26 | 27 | # Register with Google at https://www.google.com/accounts/ManageDomains 28 | oauth.google.com.scope = https://mail.google.com/ http://www.google.com/m8/feeds/ 29 | 30 | 31 | [server:main] 32 | use = egg:Paste#http 33 | host = 0.0.0.0 34 | port = 5000 35 | 36 | [filter-app:main] 37 | use = egg:Beaker#beaker_session 38 | next = csrf 39 | beaker.session.key = linkdrop 40 | beaker.session.secret = TMShmttWBvOMws810dW2nFB7k 41 | beaker.session.cookie_expires = false 42 | beaker.session.timeout = 2592000 43 | 44 | # XXX file sessions are slow, should change to memcached or database. be sure 45 | # to set lock_dir below if using memcached 46 | 47 | #beaker.session.type = file 48 | beaker.session.type = ext:memcached 49 | beaker.session.url = localhost:11211 50 | #beaker.session.type = ext:database 51 | #beaker.session.url = mysql+mysqldb://linkdrop:linkdrop@localhost/linkdrop 52 | 53 | # If you'd like to fine-tune the individual locations of the cache data dirs 54 | # for the Cache data, or the Session saves, un-comment the desired settings 55 | # here: 56 | beaker.cache.data_dir = %(here)s/data/cache 57 | beaker.session.data_dir = %(here)s/data/sessions 58 | beaker.session.lock_dir = %(here)s/data/sessions/lock 59 | 60 | [filter-app:csrf] 61 | use = egg:linkdrop#csrf 62 | # allow access to account api's for oauth, which will not have csrf token 63 | # be sure to use the FULL path 64 | csrf.unprotected_path = /account 65 | next = api 66 | 67 | [composite:sessioned] 68 | use = egg:Paste#urlmap 69 | / = home 70 | /api = api 71 | 72 | [app:home] 73 | use = egg:Paste#static 74 | document_root = %(here)s/web 75 | 76 | [app:api] 77 | use = egg:linkdrop 78 | full_stack = true 79 | static_files = false 80 | session_middleware = false 81 | 82 | cache_dir = %(here)s/data 83 | app_instance_uuid = {94115436-4056-49c2-9af7-e12d3bbd9fbb} 84 | 85 | # WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* 86 | # Debug mode will enable the interactive debugging tool, allowing ANYONE to 87 | # execute malicious code after an exception is raised. 88 | set debug = false 89 | 90 | # Logging configuration 91 | [loggers] 92 | keys = root, routes, linkdrop 93 | 94 | [handlers] 95 | keys = console, file, metrics_file 96 | #keys = wsgi 97 | 98 | [formatters] 99 | keys = generic 100 | 101 | [logger_root] 102 | level = INFO 103 | handlers = file 104 | #handlers = wsgi 105 | 106 | [logger_routes] 107 | level = INFO 108 | handlers = 109 | qualname = routes.middleware 110 | # "level = DEBUG" logs the route matched and routing variables. 111 | 112 | [logger_linkdrop] 113 | # These log messages will propagate to the 'root' handler above... 114 | level = INFO 115 | handlers = 116 | qualname = linkdrop 117 | 118 | [logger_linkdrop_metrics] 119 | level = INFO 120 | handlers = metrics_file 121 | qualname = linkdrop-metrics 122 | # These messages go only to the metrics file - they will not propagate to the root 123 | propagate = 0 124 | 125 | [handler_console] 126 | class = StreamHandler 127 | args = (sys.stderr,) 128 | level = NOTSET 129 | formatter = generic 130 | 131 | [handler_wsgi] 132 | class = pylons.log.WSGIErrorsHandler 133 | args = () 134 | level = INFO 135 | formatter = generic 136 | 137 | [handler_file] 138 | class = FileHandler 139 | args = ('/var/log/httpd/linkdrop_log', 'a') 140 | level = NOTSET 141 | formatter = generic 142 | 143 | [handler_metrics_file] 144 | class = FileHandler 145 | args = ('/var/log/httpd/linkdrop_metrics_log', 'a') 146 | level = NOTSET 147 | formatter = generic 148 | 149 | [formatter_generic] 150 | format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] [%(threadName)s] %(message)s 151 | datefmt = %H:%M:%S 152 | -------------------------------------------------------------------------------- /linkdrop/tests/services/corpus/http-api.twitter.com-contacts-successful/response-0: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | vary: Accept-Encoding 3 | x-transaction-mask: a6183ffa5f8ca943ff1b53b5644ef114 4 | x-revision: DEV 5 | x-runtime: 0.02494 6 | etag: "bdd27e3cb2eee280c274599c40e6e074"-gzip 7 | cache-control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0 8 | x-ratelimit-class: api_identified 9 | status: 200 10 | x-ratelimit-remaining: 348 11 | set-cookie: k=58.160.65.179.1302673789529686; path=/; expires=Wed, 20-Apr-11 05:49:49 GMT; domain=.twitter.com, guest_id=12345; path=/; expires=Fri, 13 May 2011 05:49:49 GMT, lang=en; path=/, _twitter_sess=twitter_session_value; domain=.twitter.com; path=/; HttpOnly 12 | expires: Tue, 31 Mar 1981 05:00:00 GMT 13 | last-modified: Wed, 13 Apr 2011 05:49:49 GMT 14 | pragma: no-cache 15 | date: Wed, 13 Apr 2011 05:49:49 GMT 16 | content-location: https://api.twitter.com/1/statuses/followers.json?oauth_body_hash=the_body_hash&screen_name=mytwitterid&oauth_nonce=25327564&oauth_timestamp=1302673762&oauth_consumer_key=the_key&oauth_signature_method=HMAC-SHA1&oauth_version=1.0&oauth_token=the_token&oauth_signature=the_sig%3D 17 | x-transaction: 1302673789-50421-40553 18 | server: hi 19 | x-ratelimit-limit: 350 20 | content-type: application/json; charset=utf-8 21 | x-ratelimit-reset: 1302676561 22 | 23 | { 24 | "users": [ 25 | { 26 | "follow_request_sent":false, 27 | "profile_background_image_url":"http:\/\/a3.twimg.com\/a\/1302541726\/images\/themes\/theme1\/bg.png", 28 | "url":"http:\/\/me.com", 29 | "screen_name":"myfollower", 30 | "description":"", 31 | "status":{ 32 | "text":"RT @madeupstats: 0% of people suffer from pervidiphobia; a fear of responding to surveys.", 33 | "truncated":false, 34 | "place":null, 35 | "favorited":false, 36 | "id_str":"11111111111111111", 37 | "coordinates":null, 38 | "retweet_count":0, 39 | "source":"web", 40 | "in_reply_to_screen_name":null, 41 | "in_reply_to_status_id_str":null, 42 | "geo":null, 43 | "in_reply_to_status_id":null, 44 | "created_at":"Tue Apr 12 22:49:58 +0000 2011", 45 | "contributors":null, 46 | "retweeted":false, 47 | "in_reply_to_user_id_str":null, 48 | "retweeted_status":{ 49 | "text":"0% of people suffer from pervidiphobia; a fear of responding to surveys.", 50 | "truncated":false, 51 | "place":null, 52 | "favorited":false, 53 | "id_str":"2222222222222222", 54 | "coordinates":null, 55 | "retweet_count":0, 56 | "source":"web", 57 | "in_reply_to_screen_name":null, 58 | "in_reply_to_status_id_str":null, 59 | "geo":null, 60 | "in_reply_to_status_id":null, 61 | "created_at":"Tue Apr 12 12:11:23 +0000 2011", 62 | "contributors":null, 63 | "retweeted":false, 64 | "in_reply_to_user_id_str":null, 65 | "id":2222222222222222, 66 | "in_reply_to_user_id":null 67 | }, 68 | "id":11111111111111111, 69 | "in_reply_to_user_id":null 70 | }, 71 | "show_all_inline_media":true, 72 | "contributors_enabled":false, 73 | "lang":"en", 74 | "geo_enabled":false, 75 | "time_zone":"Melbourne", 76 | "profile_text_color":"333333", 77 | "location":"Melbourne", 78 | "is_translator":false, 79 | "profile_sidebar_fill_color":"DDEEF6", 80 | "id_str":"3333333", 81 | "listed_count":21, 82 | "profile_background_tile":false, 83 | "favourites_count":0, 84 | "default_profile":true, 85 | "statuses_count":172, 86 | "friends_count":138, 87 | "followers_count":207, 88 | "verified":false, 89 | "created_at":"Sat Feb 21 05:55:31 +0000 2009", 90 | "profile_link_color":"0084B4", 91 | "following":true, 92 | "notifications":false, 93 | "profile_sidebar_border_color":"C0DEED", 94 | "protected":false, 95 | "default_profile_image":false, 96 | "name":"My Follower", 97 | "profile_use_background_image":true, 98 | "profile_image_url":"http:\/\/a1.twimg.com\/profile_images\/123456\/my_pic.png", 99 | "id":3333333, 100 | "utc_offset":36000, 101 | "profile_background_color":"C0DEED" 102 | } 103 | ], 104 | "next_cursor":0, 105 | "previous_cursor":0, 106 | "next_cursor_str":"0", 107 | "previous_cursor_str":"0" 108 | } 109 | -------------------------------------------------------------------------------- /linkdrop/controllers/account.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is Raindrop. 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Messaging, Inc.. 18 | # Portions created by the Initial Developer are Copyright (C) 2009 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # Rob Miller (rmiller@mozilla.com) 23 | # 24 | 25 | import urllib 26 | import json 27 | from datetime import datetime 28 | from uuid import uuid1 29 | import hashlib 30 | 31 | from webob.exc import HTTPException 32 | from webob.exc import HTTPFound 33 | 34 | from linkoauth.errors import AccessException 35 | from linkdrop import log 36 | from linkdrop.controllers import get_services 37 | from linkdrop.lib.base import BaseController 38 | from linkdrop.lib.helpers import get_redirect_response 39 | from linkdrop.lib.metrics import metrics 40 | 41 | 42 | class AccountController(BaseController): 43 | """ 44 | Accounts 45 | ======== 46 | 47 | OAuth authorization api. 48 | 49 | """ 50 | __api_controller__ = True # for docs 51 | 52 | def _create_account(self, request, domain, userid, username): 53 | acct_hash = hashlib.sha1( 54 | "%s#%s" % ((username or '').encode('utf-8'), 55 | (userid or '').encode('utf-8'))).hexdigest() 56 | acct = dict(key=str(uuid1()), domain=domain, userid=userid, 57 | username=username) 58 | metrics.track(request, 'account-create', domain=domain, 59 | acct_id=acct_hash) 60 | return acct 61 | 62 | # this is not a rest api 63 | def authorize(self, request, *args, **kw): 64 | provider = request.POST['domain'] 65 | log.info("authorize request for %r", provider) 66 | services = get_services(self.app.config) 67 | session = request.environ.get('beaker.session', {}) 68 | return services.request_access(provider, request, request.urlgen, 69 | session) 70 | 71 | # this is not a rest api 72 | def verify(self, request, *args, **kw): 73 | provider = request.params.get('provider') 74 | log.info("verify request for %r", provider) 75 | 76 | acct = dict() 77 | try: 78 | services = get_services(self.app.config) 79 | session = request.environ.get('beaker.session', {}) 80 | user = services.verify(provider, request, request.urlgen, session) 81 | 82 | account = user['profile']['accounts'][0] 83 | if (not user.get('oauth_token') 84 | and not user.get('oauth_token_secret')): 85 | raise Exception('Unable to get OAUTH access') 86 | 87 | acct = self._create_account(request, 88 | provider, 89 | str(account['userid']), 90 | account['username']) 91 | acct['profile'] = user['profile'] 92 | acct['oauth_token'] = user.get('oauth_token', None) 93 | if 'oauth_token_secret' in user: 94 | acct['oauth_token_secret'] = user['oauth_token_secret'] 95 | acct['updated'] = datetime.now().isoformat() 96 | except AccessException, e: 97 | self._redirectException(request, e) 98 | # lib/oauth/*.py throws redirect exceptions in a number of 99 | # places and we don't want those "exceptions" to be logged as 100 | # errors. 101 | except HTTPException, e: 102 | log.info("account verification for %s caused a redirection: %s", 103 | provider, e) 104 | raise 105 | except Exception, e: 106 | log.exception('failed to verify the %s account', provider) 107 | self._redirectException(request, e) 108 | resp = get_redirect_response(request.config.get('oauth_success')) 109 | resp.set_cookie('account_tokens', urllib.quote(json.dumps(acct))) 110 | raise resp.exception 111 | 112 | def _redirectException(self, request, e): 113 | err = urllib.urlencode([('error', str(e))]) 114 | url = request.config.get('oauth_failure').split('#') 115 | raise HTTPFound(location='%s?%s#%s' % (url[0], err, url[1])) 116 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(OS),Windows_NT) 2 | BIN_DIR = Scripts 3 | else 4 | BIN_DIR = bin 5 | endif 6 | 7 | APPNAME = server-shared-send 8 | DEPS = mozilla:server-core,github:server-share-core,github:client-share-web 9 | DEV_DEPS = github:server-core,github:server-share-core,github:client-share-web 10 | VIRTUALENV = virtualenv 11 | NOSE = $(BIN_DIR)/nosetests 12 | NOSETESTS_ARGS = -s 13 | NOSETESTS_ARGS_C = -s --with-xunit --with-coverage --cover-package=linkdrop,linkoauth --cover-erase 14 | TESTS = linkdrop/tests deps/server-share-core/linkoauth/tests 15 | PYTHON = $(BIN_DIR)/python 16 | version = $(shell $(PYTHON) setup.py --version) 17 | tag = $(shell grep tag_build setup.cfg | cut -d= -f2 | xargs echo ) 18 | 19 | # *sob* - just running easy_install on Windows prompts for UAC... 20 | ifeq ($(OS),Windows_NT) 21 | EZ = $(PYTHON) $(BIN_DIR)/easy_install-script.py 22 | else 23 | EZ = $(BIN_DIR)/easy_install 24 | endif 25 | COVEROPTS = --cover-html --cover-html-dir=html --with-coverage --cover-package=linkdrop 26 | COVERAGE := coverage 27 | PYLINT = $(BIN_DIR)/pylint 28 | PKGS = linkdrop 29 | 30 | GIT_DESCRIBE := `git describe --long` 31 | 32 | ifeq ($(TOPSRCDIR),) 33 | export TOPSRCDIR = $(shell pwd) 34 | endif 35 | srcdir=$(TOPSRCDIR)/extensions/firefox-share/src/ 36 | objdir=$(TOPSRCDIR)/extensions/firefox-share/dist/ 37 | dist_dir=$(TOPSRCDIR)/dist 38 | stage_dir=$(objdir)/stage 39 | xpi_dir=$(TOPSRCDIR)/web/dev 40 | web_dir=$(TOPSRCDIR)/web/dev 41 | static_dir=$(TOPSRCDIR)/web/$(version) 42 | webbuild_dir=$(TOPSRCDIR)/tools/webbuild 43 | requirejs_dir=$(webbuild_dir)/requirejs 44 | 45 | xpi_name := ffshare.xpi 46 | xpi_files := bootstrap.js chrome install.rdf modules 47 | dep_files := Makefile $(shell find $(srcdir) -type f) 48 | 49 | SLINK = ln -sf 50 | ifneq ($(findstring MINGW,$(shell uname -s)),) 51 | SLINK = cp -r 52 | export NO_SYMLINK = 1 53 | endif 54 | 55 | all: xpi 56 | 57 | xpi: $(xpi_dir)/$(xpi_name) 58 | 59 | $(xpi_dir): 60 | mkdir -p $(xpi_dir) 61 | 62 | stage_files = $(stage_dir)/chrome $(stage_dir)/install.rdf $(stage_dir)/bootstrap.js $(stage_dir)/modules 63 | 64 | $(stage_dir): 65 | mkdir -p $(stage_dir) 66 | $(MAKE) $(stage_files) 67 | 68 | $(stage_dir)/bootstrap.js: $(srcdir)/bootstrap.js 69 | $(SLINK) $(srcdir)/bootstrap.js $(stage_dir)/bootstrap.js 70 | 71 | $(stage_dir)/install.rdf: $(srcdir)/install.rdf 72 | $(SLINK) $(srcdir)/install.rdf $(stage_dir)/install.rdf 73 | 74 | $(stage_dir)/chrome: $(srcdir)/chrome 75 | $(SLINK) $(srcdir)/chrome $(stage_dir)/chrome 76 | 77 | $(stage_dir)/modules: $(srcdir)/modules 78 | $(SLINK) $(srcdir)/modules $(stage_dir)/modules 79 | 80 | $(xpi_dir)/$(xpi_name): $(xpi_dir) $(stage_dir) $(dep_files) 81 | rm -f $(xpi_dir)/$(xpi_name) 82 | cd $(stage_dir) && zip -9r $(xpi_name) $(xpi_files) 83 | mv $(stage_dir)/$(xpi_name) $(xpi_dir)/$(xpi_name) 84 | 85 | web: $(static_dir) 86 | 87 | $(static_dir): 88 | rsync -av $(web_dir)/ $(static_dir)/ 89 | 90 | perl -i -pe "s:VERSION='[^']+':VERSION='$(version)':" $(TOPSRCDIR)/setup.py 91 | perl -i -pe 's:/[^/]+/auth.html:/$(version)/auth.html:go' $(TOPSRCDIR)/staging.ini 92 | perl -i -pe 's:/[^/]+/auth.html:/$(version)/auth.html:go' $(TOPSRCDIR)/production.ini 93 | 94 | find $(static_dir) -name \*.html | xargs perl -i -pe 's:/dev/:/$(version)/:go' 95 | perl -i -pe 's:/dev/:/$(version)/:go' $(static_dir)/scripts/oauth.js 96 | 97 | cd $(static_dir) && $(requirejs_dir)/build/build.sh build.js 98 | cd $(static_dir)/settings && $(requirejs_dir)/build/build.sh build.js 99 | cd $(static_dir)/share && $(requirejs_dir)/build/build.sh build.js 100 | cd $(static_dir)/share/panel && $(requirejs_dir)/build/build.sh build.js 101 | 102 | clean: 103 | rm -rf $(objdir) 104 | rm -rf $(static_dir) 105 | rm -rf $(dist_dir) 106 | rm -f f1.spec 107 | 108 | dist: f1.spec 109 | $(PYTHON) setup.py sdist --formats gztar,zip 110 | # This is so Hudson can get stable urls to this tarball 111 | ln -sf linkdrop-$(version)$(tag).tar.gz dist/linkdrop-current.tar.gz 112 | 113 | rpm: f1.spec 114 | $(PYTHON) setup.py bdist_rpm 115 | 116 | f1.spec: f1.spec.in Makefile tools/makespec 117 | tools/makespec $(version)$(tag) linkdrop.egg-info/requires.txt $(GIT_DESCRIBE) < f1.spec.in > f1.spec 118 | 119 | build: 120 | $(VIRTUALENV) --no-site-packages --distribute . 121 | $(PYTHON) build.py $(APPNAME) $(DEPS) 122 | $(EZ) nose 123 | $(EZ) WebTest 124 | $(EZ) Funkload 125 | $(EZ) pylint 126 | $(EZ) coverage 127 | 128 | dev: 129 | $(VIRTUALENV) --no-site-packages --distribute . 130 | $(PYTHON) build.py $(APPNAME) $(DEV_DEPS) 131 | $(EZ) nose 132 | $(EZ) WebTest 133 | $(EZ) Funkload 134 | $(EZ) pylint 135 | $(EZ) coverage 136 | 137 | test: 138 | $(NOSE) $(NOSETESTS_ARGS) $(TESTS) 139 | 140 | coverage: 141 | $(NOSE) $(NOSETESTS_ARGS_C) $(TESTS) 142 | $(COVERAGE) xml 143 | 144 | .PHONY: xpi clean dist rpm build test coverage web $(static_dir) 145 | -------------------------------------------------------------------------------- /linkdrop/tests/controllers/test_contacts.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is Raindrop. 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Messaging, Inc.. 18 | # Portions created by the Initial Developer are Copyright (C) 2009 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # Rob Miller 23 | # 24 | 25 | from linkdrop.controllers import contacts 26 | from linkdrop.tests import TestController 27 | from mock import Mock 28 | from mock import patch 29 | from nose import tools 30 | import json 31 | 32 | 33 | class TestContactsController(TestController): 34 | def setUp(self): 35 | self.gserv_patcher = patch( 36 | 'linkdrop.controllers.contacts.get_services') 37 | self.metrics_patcher = patch( 38 | 'linkdrop.controllers.contacts.metrics') 39 | self.gserv_patcher.start() 40 | self.metrics_patcher.start() 41 | self.request = Mock() 42 | self.request.POST = dict() 43 | self.domain = 'example.com' 44 | self.request.sync_info = dict(domain=self.domain) 45 | self.controller = contacts.ContactsController(self.app) 46 | self.real_get = self.controller.get.undecorated.undecorated 47 | 48 | def tearDown(self): 49 | self.gserv_patcher.stop() 50 | self.metrics_patcher.stop() 51 | 52 | def test_get_no_acct(self): 53 | res = self.real_get(self.controller, self.request) 54 | contacts.metrics.track.assert_called_with(self.request, 55 | 'contacts-noaccount', 56 | domain=self.domain) 57 | tools.ok_(res['result'] is None) 58 | tools.eq_(res['error']['provider'], self.domain) 59 | tools.eq_(res['error']['status'], 401) 60 | tools.ok_(res['error']['message'].startswith('not logged in')) 61 | 62 | def _setup_acct_data(self): 63 | acct_json = json.dumps({'username': 'USERNAME', 64 | 'userid': 'USERID'}) 65 | self.request.POST['account'] = acct_json 66 | 67 | def test_get_no_provider(self): 68 | from linkoauth.errors import DomainNotRegisteredError 69 | mock_services = contacts.get_services() 70 | mock_services.getcontacts.side_effect = DomainNotRegisteredError() 71 | self._setup_acct_data() 72 | res = self.real_get(self.controller, self.request) 73 | tools.ok_(res['result'] is None) 74 | tools.eq_(res['error']['message'], "'domain' is invalid") 75 | 76 | def test_get_oauthkeysexception(self): 77 | from linkoauth.errors import OAuthKeysException 78 | oauthexc = OAuthKeysException('OAUTHKEYSEXCEPTION') 79 | mock_services = contacts.get_services() 80 | mock_services.getcontacts.side_effect = oauthexc 81 | domain = 'example.com' 82 | self._setup_acct_data() 83 | res = self.real_get(self.controller, self.request) 84 | contacts.metrics.track.assert_called_with( 85 | self.request, 'contacts-oauth-keys-missing', 86 | domain=self.domain) 87 | tools.ok_(res['result'] is None) 88 | tools.eq_(res['error']['provider'], self.domain) 89 | tools.eq_(res['error']['status'], 401) 90 | tools.ok_(res['error']['message'].startswith('not logged in')) 91 | 92 | def test_get_serviceunavailexception(self): 93 | from linkoauth.errors import ServiceUnavailableException 94 | servexc = ServiceUnavailableException('SERVUNAVAIL') 95 | mock_services = contacts.get_services() 96 | mock_services.getcontacts.side_effect = servexc 97 | self._setup_acct_data() 98 | res = self.real_get(self.controller, self.request) 99 | contacts.metrics.track.assert_called_with( 100 | self.request, 'contacts-service-unavailable', 101 | domain=self.domain) 102 | tools.ok_(res['result'] is None) 103 | tools.eq_(res['error']['provider'], self.domain) 104 | tools.eq_(res['error']['status'], 503) 105 | tools.ok_(res['error']['message'].startswith( 106 | 'The service is temporarily unavailable')) 107 | 108 | def test_get_success(self): 109 | self._setup_acct_data() 110 | mock_services = contacts.get_services() 111 | mock_services.getcontacts.return_value = ('SUCCESS', None) 112 | res = self.real_get(self.controller, self.request) 113 | tools.eq_(res['result'], 'SUCCESS') 114 | tools.ok_(res['error'] is None) 115 | -------------------------------------------------------------------------------- /grinder.ini: -------------------------------------------------------------------------------- 1 | # 2 | # linkdrop - Pylons testing environment configuration 3 | # 4 | # The %(here)s variable will be replaced with the parent directory of this file 5 | # 6 | [DEFAULT] 7 | debug = true 8 | # Uncomment and replace with the address which should receive any error reports 9 | #email_to = you@yourdomain.com 10 | smtp_server = localhost 11 | error_email_from = paste@localhost 12 | 13 | # there is no test shortener any more :( But DO NOT 14 | # use the production bitly id and key or we will hit the bitly rate-limiter 15 | # which will impact production!! 16 | bitly.userid = ??? 17 | bitly.key = ??? 18 | 19 | # We specify our local test server is used for all service connections. 20 | stub_service_server=127.0.0.1:8327 21 | 22 | # See comments in main .ini file for additional comments about these. 23 | oauth.twitter.com.consumer_key = 2r1qbed58DAaNMe142msTg 24 | oauth.twitter.com.consumer_secret = prh6A961516mJ3XEjd7eERsGxuVZqycrBB6lV7LQ 25 | oauth.twitter.com.request = http://%(stub_service_server)s/fake_oauth/twitter/request_token 26 | oauth.twitter.com.access = http://%(stub_service_server)s/fake_oauth/twitter/access_token 27 | oauth.twitter.com.authorize = http://%(stub_service_server)s/fake_oauth/twitter/authenticate 28 | oauth.twitter.com.host = %(stub_service_server)s 29 | oauth.twitter.com.secure = False 30 | 31 | oauth.facebook.com.app_id = 158102624846 32 | oauth.facebook.com.app_secret = 4203f7f23803f405e06509ec4d4b9729 33 | oauth.facebook.com.scope = publish_stream 34 | oauth.facebook.com.authorize = http://%(stub_service_server)s/fake_oauth/facebook/authorize 35 | oauth.facebook.com.access = http://%(stub_service_server)s/fake_oauth/facebook/access_token 36 | oauth.facebook.com.profile = http://%(stub_service_server)s/facebook/me 37 | oauth.facebook.com.feed = http://%(stub_service_server)s/facebook/feed 38 | 39 | # XXX This will not work without registering a domain! See README 40 | oauth.google.com.consumer_key = anonymous 41 | oauth.google.com.consumer_secret = anonymous 42 | oauth.google.com.scope = https://mail.google.com/ http://www.google.com/m8/feeds/ 43 | 44 | oauth.yahoo.com.consumer_key = FILL_ME_IN 45 | oauth.yahoo.com.consumer_secret = FILL_ME_IN 46 | oauth.yahoo.com.app_id = FILL_ME_IN 47 | # set to true if you have completed domain verification with Yahoo 48 | oauth.yahoo.com.verified = 0 49 | 50 | [server:main] 51 | use = egg:Paste#http 52 | host = 127.0.0.1 53 | port = 5000 54 | 55 | [filter-app:main] 56 | use = egg:Beaker#beaker_session 57 | next = csrf 58 | beaker.session.key = linkdrop 59 | beaker.session.secret = somesecret 60 | beaker.session.type = memory 61 | 62 | [filter-app:csrf] 63 | use = egg:linkdrop#csrf 64 | # allow access to account api's for oauth, which will not have csrf token 65 | # be sure to use the FULL path 66 | csrf.unprotected_path = /api/account 67 | next = sessioned 68 | 69 | [composite:sessioned] 70 | use = egg:Paste#urlmap 71 | / = home 72 | /api = api 73 | 74 | [app:home] 75 | use = egg:Paste#static 76 | document_root = %(here)s/web 77 | 78 | [app:api] 79 | #use: config:api.ini 80 | 81 | use = egg:linkdrop 82 | full_stack = true 83 | static_files = true 84 | 85 | cache_dir = %(here)s/data 86 | beaker.session.key = linkdrop 87 | beaker.session.secret = somesecret 88 | 89 | # If you'd like to fine-tune the individual locations of the cache data dirs 90 | # for the Cache data, or the Session saves, un-comment the desired settings 91 | # here: 92 | #beaker.cache.data_dir = %(here)s/data/cache 93 | #beaker.session.data_dir = %(here)s/data/sessions 94 | 95 | # SQLAlchemy database URL 96 | #sqlalchemy.url = sqlite:///%(here)s/test.db 97 | sqlalchemy.echo = False 98 | sqlalchemy.url = mysql+mysqldb://linkdrop:linkdrop@localhost/linkdrop 99 | 100 | # SQLAlchemy migration 101 | # if managed, the migration repository is here 102 | migrate.repository = %(here)s/changes 103 | # automatically do database upgrades 104 | migrate.auto = 1 105 | 106 | # WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* 107 | # Debug mode will enable the interactive debugging tool, allowing ANYONE to 108 | # execute malicious code after an exception is raised. 109 | set debug = false 110 | 111 | # Logging configuration 112 | [loggers] 113 | keys = root, routes, linkdrop, sqlalchemy 114 | 115 | [handlers] 116 | keys = console 117 | 118 | [formatters] 119 | keys = generic 120 | 121 | [logger_root] 122 | level = WARNING 123 | handlers = console 124 | 125 | [logger_routes] 126 | level = INFO 127 | handlers = 128 | qualname = routes.middleware 129 | # "level = DEBUG" logs the route matched and routing variables. 130 | 131 | [logger_linkdrop] 132 | level = WARNING 133 | handlers = 134 | qualname = linkdrop 135 | 136 | [logger_sqlalchemy] 137 | level = INFO 138 | handlers = 139 | qualname = sqlalchemy.engine 140 | # "level = INFO" logs SQL queries. 141 | # "level = DEBUG" logs SQL queries and results. 142 | # "level = WARN" logs neither. (Recommended for production systems.) 143 | 144 | [handler_console] 145 | class = StreamHandler 146 | args = (sys.stderr,) 147 | level = NOTSET 148 | formatter = generic 149 | 150 | [formatter_generic] 151 | format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] [%(threadName)s] %(message)s 152 | datefmt = %H:%M:%S 153 | -------------------------------------------------------------------------------- /tools/webbuild/requirejs/build/jslib/pragma.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright (c) 2010, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/jrburke/requirejs for details 5 | */ 6 | 7 | /*jslint regexp: false */ 8 | /*global */ 9 | 10 | "use strict"; 11 | 12 | var pragma = { 13 | conditionalRegExp: /(exclude|include)Start\s*\(\s*["'](\w+)["']\s*,(.*)\)/, 14 | useStrictRegExp: /['"]use strict['"];/g, 15 | hasRegExp: /has\s*\(\s*['"]([^'"]+)['"]\)/g, 16 | 17 | removeStrict: function (contents, config) { 18 | return config.useStrict ? contents : contents.replace(pragma.useStrictRegExp, ''); 19 | }, 20 | 21 | /** 22 | * processes the fileContents for some //>> conditional statements 23 | */ 24 | process: function (fileName, fileContents, config) { 25 | /*jslint evil: true */ 26 | var foundIndex = -1, startIndex = 0, lineEndIndex, conditionLine, 27 | matches, type, marker, condition, isTrue, endRegExp, endMatches, 28 | endMarkerIndex, shouldInclude, startLength, pragmas = config.pragmas, 29 | //Legacy arg defined to help in dojo conversion script. Remove later 30 | //when dojo no longer needs conversion: 31 | kwArgs = pragmas; 32 | 33 | //Replace has references if desired 34 | if (config.has) { 35 | fileContents = fileContents.replace(pragma.hasRegExp, function (match, test) { 36 | if (test in config.has) { 37 | return !!config.has[test]; 38 | } 39 | return match; 40 | }); 41 | } 42 | 43 | //If pragma work is not desired, skip it. 44 | if (config.skipPragmas) { 45 | return pragma.removeStrict(fileContents, config); 46 | } 47 | 48 | while ((foundIndex = fileContents.indexOf("//>>", startIndex)) !== -1) { 49 | //Found a conditional. Get the conditional line. 50 | lineEndIndex = fileContents.indexOf("\n", foundIndex); 51 | if (lineEndIndex === -1) { 52 | lineEndIndex = fileContents.length - 1; 53 | } 54 | 55 | //Increment startIndex past the line so the next conditional search can be done. 56 | startIndex = lineEndIndex + 1; 57 | 58 | //Break apart the conditional. 59 | conditionLine = fileContents.substring(foundIndex, lineEndIndex + 1); 60 | matches = conditionLine.match(pragma.conditionalRegExp); 61 | if (matches) { 62 | type = matches[1]; 63 | marker = matches[2]; 64 | condition = matches[3]; 65 | isTrue = false; 66 | //See if the condition is true. 67 | try { 68 | isTrue = !!eval("(" + condition + ")"); 69 | } catch (e) { 70 | throw "Error in file: " + 71 | fileName + 72 | ". Conditional comment: " + 73 | conditionLine + 74 | " failed with this error: " + e; 75 | } 76 | 77 | //Find the endpoint marker. 78 | endRegExp = new RegExp('\\/\\/\\>\\>\\s*' + type + 'End\\(\\s*[\'"]' + marker + '[\'"]\\s*\\)', "g"); 79 | endMatches = endRegExp.exec(fileContents.substring(startIndex, fileContents.length)); 80 | if (endMatches) { 81 | endMarkerIndex = startIndex + endRegExp.lastIndex - endMatches[0].length; 82 | 83 | //Find the next line return based on the match position. 84 | lineEndIndex = fileContents.indexOf("\n", endMarkerIndex); 85 | if (lineEndIndex === -1) { 86 | lineEndIndex = fileContents.length - 1; 87 | } 88 | 89 | //Should we include the segment? 90 | shouldInclude = ((type === "exclude" && !isTrue) || (type === "include" && isTrue)); 91 | 92 | //Remove the conditional comments, and optionally remove the content inside 93 | //the conditional comments. 94 | startLength = startIndex - foundIndex; 95 | fileContents = fileContents.substring(0, foundIndex) + 96 | (shouldInclude ? fileContents.substring(startIndex, endMarkerIndex) : "") + 97 | fileContents.substring(lineEndIndex + 1, fileContents.length); 98 | 99 | //Move startIndex to foundIndex, since that is the new position in the file 100 | //where we need to look for more conditionals in the next while loop pass. 101 | startIndex = foundIndex; 102 | } else { 103 | throw "Error in file: " + 104 | fileName + 105 | ". Cannot find end marker for conditional comment: " + 106 | conditionLine; 107 | 108 | } 109 | } 110 | } 111 | 112 | return pragma.removeStrict(fileContents, config); 113 | } 114 | }; 115 | -------------------------------------------------------------------------------- /linkdrop/static.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | from paste import request 5 | from paste import fileapp 6 | from paste import httpexceptions 7 | from paste.httpheaders import ETAG 8 | 9 | version_re = re.compile("/\d+.\d+.\d+/", re.U) 10 | 11 | 12 | class StaticURLParser(object): 13 | """ 14 | based on Paste.urlparser.StaticURLParser, however we handle an internal 15 | redirect for versioning of static files. This is only intended for 16 | development work, as we serve production from apache/mod_wsgi. 17 | """ 18 | # @@: Should URLParser subclass from this? 19 | 20 | def __init__(self, directory, root_directory=None, 21 | cache_max_age=None, version="/dev/"): 22 | self.directory = self.normpath(directory) 23 | self.root_directory = self.normpath(root_directory or directory) 24 | self.cache_max_age = cache_max_age 25 | self.version = version 26 | 27 | def normpath(path): 28 | return os.path.normcase(os.path.abspath(path)) 29 | normpath = staticmethod(normpath) 30 | 31 | def __call__(self, environ, start_response): 32 | path_info = environ.get('PATH_INFO', '') 33 | if not path_info: 34 | return self.add_slash(environ, start_response) 35 | directory = "%s" % self.directory 36 | if (not path_info.startswith('/%s/' % self.version) 37 | and version_re.match(path_info) is None 38 | and directory == self.root_directory): 39 | directory = os.path.join(directory, self.version) 40 | if path_info == '/': 41 | # @@: This should obviously be configurable 42 | filename = 'index.html' 43 | else: 44 | filename = request.path_info_pop(environ) 45 | full = self.normpath(os.path.join(directory, filename)) 46 | if not full.startswith(self.root_directory): 47 | # Out of bounds 48 | return self.not_found(environ, start_response) 49 | if not os.path.exists(full): 50 | return self.not_found(environ, start_response) 51 | if os.path.isdir(full): 52 | # @@: Cache? 53 | return self.__class__(full, root_directory=self.root_directory, 54 | version=self.version, 55 | cache_max_age=self.cache_max_age)( 56 | environ, start_response) 57 | if environ.get('PATH_INFO') and environ.get('PATH_INFO') != '/': 58 | return self.error_extra_path(environ, start_response) 59 | if_none_match = environ.get('HTTP_IF_NONE_MATCH') 60 | if if_none_match: 61 | mytime = os.stat(full).st_mtime 62 | if str(mytime) == if_none_match: 63 | headers = [] 64 | ## FIXME: probably should be 65 | ## ETAG.update(headers, '"%s"' % mytime) 66 | ETAG.update(headers, mytime) 67 | start_response('304 Not Modified', headers) 68 | return [''] # empty body 69 | 70 | fa = self.make_app(full) 71 | if self.cache_max_age: 72 | fa.cache_control(max_age=self.cache_max_age) 73 | return fa(environ, start_response) 74 | 75 | def make_app(self, filename): 76 | return fileapp.FileApp(filename) 77 | 78 | def add_slash(self, environ, start_response): 79 | """ 80 | This happens when you try to get to a directory 81 | without a trailing / 82 | """ 83 | url = request.construct_url(environ, with_query_string=False) 84 | url += '/' 85 | if environ.get('QUERY_STRING'): 86 | url += '?' + environ['QUERY_STRING'] 87 | exc = httpexceptions.HTTPMovedPermanently( 88 | 'The resource has moved to %s - you should be redirected ' 89 | 'automatically.' % url, 90 | headers=[('location', url)]) 91 | return exc.wsgi_application(environ, start_response) 92 | 93 | def not_found(self, environ, start_response, debug_message=None): 94 | exc = httpexceptions.HTTPNotFound( 95 | 'The resource at %s could not be found' 96 | % request.construct_url(environ), 97 | comment='SCRIPT_NAME=%r; PATH_INFO=%r; looking in %r; debug: %s' 98 | % (environ.get('SCRIPT_NAME'), environ.get('PATH_INFO'), 99 | self.directory, debug_message or '(none)')) 100 | return exc.wsgi_application(environ, start_response) 101 | 102 | def error_extra_path(self, environ, start_response): 103 | exc = httpexceptions.HTTPNotFound( 104 | 'The trailing path %r is not allowed' % environ['PATH_INFO']) 105 | return exc.wsgi_application(environ, start_response) 106 | 107 | def __repr__(self): 108 | return '<%s %r>' % (self.__class__.__name__, self.directory) 109 | 110 | 111 | def make_static(global_conf, document_root, cache_max_age=None, version="dev"): 112 | """ 113 | Return a WSGI application that serves a directory (configured 114 | with document_root) 115 | 116 | cache_max_age - integer specifies CACHE_CONTROL max_age in seconds 117 | """ 118 | if cache_max_age is not None: 119 | cache_max_age = int(cache_max_age) 120 | return StaticURLParser( 121 | document_root, cache_max_age=cache_max_age, version=version) 122 | -------------------------------------------------------------------------------- /tools/webbuild/requirejs/require/text.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license RequireJS text Copyright (c) 2010, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/jrburke/requirejs for details 5 | */ 6 | /*jslint regexp: false, nomen: false, plusplus: false */ 7 | /*global require: false, XMLHttpRequest: false, ActiveXObject: false, 8 | define: false */ 9 | "use strict"; 10 | 11 | (function () { 12 | var progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], 13 | xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, 14 | bodyRegExp = /]*>\s*([\s\S]+)\s*<\/body>/im, 15 | buildMap = []; 16 | 17 | if (!require.textStrip) { 18 | require.textStrip = function (text) { 19 | //Strips declarations so that external SVG and XML 20 | //documents can be added to a document without worry. Also, if the string 21 | //is an HTML document, only the part inside the body tag is returned. 22 | if (text) { 23 | text = text.replace(xmlRegExp, ""); 24 | var matches = text.match(bodyRegExp); 25 | if (matches) { 26 | text = matches[1]; 27 | } 28 | } else { 29 | text = ""; 30 | } 31 | return text; 32 | }; 33 | } 34 | 35 | if (!require.jsEscape) { 36 | require.jsEscape = function (text) { 37 | return text.replace(/(['\\])/g, '\\$1') 38 | .replace(/[\f]/g, "\\f") 39 | .replace(/[\b]/g, "\\b") 40 | .replace(/[\n]/g, "\\n") 41 | .replace(/[\t]/g, "\\t") 42 | .replace(/[\r]/g, "\\r"); 43 | }; 44 | } 45 | 46 | //Upgrade require to add some methods for XHR handling. But it could be that 47 | //this require is used in a non-browser env, so detect for existing method 48 | //before attaching one. 49 | if (!require.getXhr) { 50 | require.getXhr = function () { 51 | //Would love to dump the ActiveX crap in here. Need IE 6 to die first. 52 | var xhr, i, progId; 53 | if (typeof XMLHttpRequest !== "undefined") { 54 | return new XMLHttpRequest(); 55 | } else { 56 | for (i = 0; i < 3; i++) { 57 | progId = progIds[i]; 58 | try { 59 | xhr = new ActiveXObject(progId); 60 | } catch (e) {} 61 | 62 | if (xhr) { 63 | progIds = [progId]; // so faster next time 64 | break; 65 | } 66 | } 67 | } 68 | 69 | if (!xhr) { 70 | throw new Error("require.getXhr(): XMLHttpRequest not available"); 71 | } 72 | 73 | return xhr; 74 | }; 75 | } 76 | 77 | if (!require.fetchText) { 78 | require.fetchText = function (url, callback) { 79 | var xhr = require.getXhr(); 80 | xhr.open('GET', url, true); 81 | xhr.onreadystatechange = function (evt) { 82 | //Do not explicitly handle errors, those should be 83 | //visible via console output in the browser. 84 | if (xhr.readyState === 4) { 85 | callback(xhr.responseText); 86 | } 87 | }; 88 | xhr.send(null); 89 | }; 90 | } 91 | 92 | define({ 93 | load: function (name, req, onLoad, config) { 94 | //Name has format: some.module.filext!strip 95 | //The strip part is optional. 96 | //if strip is present, then that means only get the string contents 97 | //inside a body tag in an HTML string. For XML/SVG content it means 98 | //removing the declarations so the content can be inserted 99 | //into the current doc without problems. 100 | 101 | var strip = false, url, index = name.indexOf("."), 102 | modName = name.substring(0, index), 103 | ext = name.substring(index + 1, name.length); 104 | 105 | index = ext.indexOf("!"); 106 | if (index !== -1) { 107 | //Pull off the strip arg. 108 | strip = ext.substring(index + 1, ext.length); 109 | strip = strip === "strip"; 110 | ext = ext.substring(0, index); 111 | } 112 | 113 | //Load the text. 114 | url = req.nameToUrl(modName, "." + ext); 115 | require.fetchText(url, function (text) { 116 | text = strip ? require.textStrip(text) : text; 117 | if (config.isBuild && config.inlineText) { 118 | buildMap[name] = text; 119 | } 120 | onLoad(text); 121 | }); 122 | }, 123 | 124 | write: function (pluginName, moduleName, write) { 125 | if (moduleName in buildMap) { 126 | var text = require.jsEscape(buildMap[moduleName]); 127 | write("define('" + pluginName + "!" + moduleName + 128 | "', function () { return '" + text + "';});\n"); 129 | } 130 | } 131 | }); 132 | }()); 133 | -------------------------------------------------------------------------------- /linkdrop/controllers/contacts.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is Raindrop. 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Messaging, Inc.. 18 | # Portions created by the Initial Developer are Copyright (C) 2009 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # Rob Miller (rmiller@mozilla.com) 23 | # 24 | 25 | import json 26 | 27 | from linkoauth.errors import (OAuthKeysException, ServiceUnavailableException, 28 | DomainNotRegisteredError) 29 | 30 | from linkdrop.controllers import get_services 31 | from linkdrop.lib.base import BaseController 32 | from linkdrop.lib.helpers import json_exception_response, api_response 33 | from linkdrop.lib.helpers import api_entry, api_arg, get_passthrough_headers 34 | from linkdrop.lib import constants 35 | from linkdrop.lib.metrics import metrics 36 | 37 | 38 | class ContactsController(BaseController): 39 | """ 40 | Contacts 41 | ======== 42 | 43 | A proxy for retrieving contacts from a service. 44 | 45 | """ 46 | __api_controller__ = True # for docs 47 | 48 | @api_response 49 | @json_exception_response 50 | @api_entry( 51 | doc=""" 52 | contacts 53 | -------- 54 | 55 | Get contacts from a service. 56 | """, 57 | urlargs=[ 58 | api_arg('domain', 'string', True, None, None, """ 59 | The domain of the service to get contacts from (e.g. google.com) 60 | """), 61 | ], 62 | queryargs=[ 63 | # name, type, required, default, allowed, doc 64 | api_arg('username', 'string', False, None, None, """ 65 | The user name used by the service. The username or userid is required if more 66 | than one account is setup for the service. 67 | """), 68 | api_arg('userid', 'string', False, None, None, """ 69 | The user id used by the service. The username or userid is required if more 70 | than one account is setup for the service. 71 | """), 72 | api_arg('startindex', 'integer', False, 0, None, """ 73 | Start index used for paging results. 74 | """), 75 | api_arg('maxresults', 'integer', False, 25, None, """ 76 | Max results to be returned per request, used with startindex for paging. 77 | """), 78 | api_arg('group', 'string', False, 'Contacts', None, """ 79 | Name of the group to return. 80 | """), 81 | ], 82 | response={'type': 'object', 'doc': 'Portable Contacts Collection'}) 83 | def get(self, request): 84 | domain = str(request.sync_info['domain']) 85 | page_data = request.POST.get('pageData', None) 86 | account_data = request.POST.get('account', None) 87 | 88 | acct = None 89 | if account_data: 90 | acct = json.loads(account_data) 91 | if not acct: 92 | metrics.track(request, 'contacts-noaccount', domain=domain) 93 | error = {'provider': domain, 94 | 'message': ("not logged in or no user account " 95 | "for that domain"), 96 | 'status': 401, 97 | } 98 | return {'result': None, 'error': error} 99 | 100 | headers = get_passthrough_headers(request) 101 | page_data = page_data and json.loads(page_data) or {} 102 | try: 103 | services = get_services(self.app.config) 104 | result, error = services.getcontacts(domain, acct, page_data, 105 | headers) 106 | except DomainNotRegisteredError: 107 | error = { 108 | 'message': "'domain' is invalid", 109 | 'code': constants.INVALID_PARAMS, 110 | } 111 | return {'result': None, 'error': error} 112 | 113 | except OAuthKeysException, e: 114 | # more than likely we're missing oauth tokens for some reason. 115 | error = {'provider': domain, 116 | 'message': ("not logged in or no user account " 117 | "for that domain"), 118 | 'status': 401, 119 | } 120 | result = None 121 | metrics.track(request, 'contacts-oauth-keys-missing', 122 | domain=domain) 123 | except ServiceUnavailableException, e: 124 | error = {'provider': domain, 125 | 'message': ("The service is temporarily unavailable " 126 | "- please try again later."), 127 | 'status': 503, 128 | } 129 | if e.debug_message: 130 | error['debug_message'] = e.debug_message 131 | result = None 132 | metrics.track(request, 'contacts-service-unavailable', 133 | domain=domain) 134 | return {'result': result, 'error': error} 135 | --------------------------------------------------------------------------------