├── 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 |
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 |
12 |
19 |
20 |
21 | |
22 | % if c.description:
23 |
24 | ${context.write(c.safeHTML(c.description))}
25 |
26 | % endif
27 | |
28 | % if c.thumbnail:
29 |
30 | % if c.shorturl:
31 |
32 | % elif c.longurl:
33 |
34 | % endif
35 |
36 | |
37 | % endif
38 |
39 |
40 |
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 |
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 |
--------------------------------------------------------------------------------