Compare commits

...

89 Commits
main ... 5.7.x

Author SHA1 Message Date
RRosio 1b363e0431 Back to dev version
3 years ago
RRosio 90cf12c48e Release 5.7.16
3 years ago
Rosio ae66301abe
Merge pull request #6554 from kostyafarber/issue-6524
3 years ago
Kostya Farber 796a1693df
Update notebook/notebookapp.py
3 years ago
Kostya Farber 0e5239d756 add warning to upgrade notebook version
3 years ago
RRosio 77198fa6b8 Back to dev version
3 years ago
RRosio fab76230a7 Release 5.7.15
3 years ago
Eric Charles a8d5ca7a38
Merge pull request #6522 from RRosio/update-version
3 years ago
RRosio dc0c8c47c7 using group method to match regex items, which works for Python versions < 3.6. Fix so users can install extensions
3 years ago
RRosio 71d48bf50b Back to dev version
3 years ago
RRosio 6944302f77 Release 5.7.14
3 years ago
RRosio b132283d32 Back to dev version
4 years ago
RRosio 60c38c02a2 Release 5.7.14a0
4 years ago
Eric Charles 03ada465b2
Merge pull request #6502 from RRosio/update_changelog
4 years ago
RRosio 9bdc741223 update changelog for 5.7.14a0 release
4 years ago
Eric Charles 44f3012a8d
Merge pull request #6497 from RRosio/updates
4 years ago
RRosio 986c88317d update version to account for alpha,beta and release candidate as is done in the 6.5.x branch
4 years ago
RRosio 7f4a78a6df pinning jupyter_client<7 to prevent asyncio error when opening notebook
4 years ago
Rosio a02fe351df
Merge pull request #6471 from dylanramzn/jquery-upgrade
4 years ago
Dylan Ross 37387bec8e Bump minimum version of jQuery to 3.6.0
4 years ago
MeeseeksMachine 69f6cbee94
Backport PR #6197: Fix crypto handling (#6198)
4 years ago
Steven Silvester 96b6d54315 Release 5.7.13
5 years ago
MeeseeksMachine 6d65298595
Backport PR #6133: Add @babel/core dependency (#6134)
5 years ago
Zachary Sailer f194d49d4c
Merge pull request #6132 from meeseeksmachine/auto-backport-of-pr-6131-on-5.7.x
5 years ago
Zachary Sailer f300e92670 Backport PR #6131: Switch webpack to production mode
5 years ago
Steven Silvester f9195b25d7 Release 5.7.12
5 years ago
Afshin Taylor Darian 42d4ed516c
Ship *.js in manifest (#6123)
5 years ago
Steven Silvester 48018b2fea Release 5.7.11
5 years ago
Afshin Taylor Darian e26a465822 Merge pull request from GHSA-hwvq-6gjx-j797
5 years ago
Steven Silvester 7aa0b1b571 Release 5.7.10
6 years ago
Steven Silvester 9788d52d46 Release 5.7.9
6 years ago
Steven Silvester 0b99a19dee
[5.7.x] Fix duplicates in download as menu (#5597)
6 years ago
MeeseeksMachine 127180855b
Backport PR #5433: Add release instructions (#5439)
6 years ago
Steven Silvester f297ce6803 pin nbconvert
6 years ago
Steven Silvester d9eef5ddc9 Revert "Update CodeMirror to version 5.48.4 (#4858)"
6 years ago
Steven Silvester 5fcb9e912e Revert "Upgraded typeahead library. (#4944)"
6 years ago
ahangsleben dee53a1148 Upgraded typeahead library. (#4944)
6 years ago
bermani f113632a29 Update CodeMirror to version 5.48.4 (#4858)
6 years ago
Grant Nestor b3e9c6cedb Use react vs. preact
6 years ago
Scott Sanderson fef839dee4 BUG: Pin preact and friends using ~ rather than ^. (#4681)
6 years ago
Luciano Resende ca30421c62 Update to JQuery 3.4.1 (#4958)
6 years ago
Min RK b8e30ea84b 5.7.8
7 years ago
Min RK 8f2ab101aa changelog for 5.7.8
7 years ago
Min RK 979e0bd15e even more careful with login redirect checks
7 years ago
Min RK 16cf97cf3c release 5.7.7
7 years ago
Min RK c6fafc38cb protect against chrome mishandling backslash as slash in URLs
7 years ago
Min RK 5bc968c8d6 changelog for redirect check
7 years ago
Min RK 70fe9f0ddb parse urls when validating redirect targets
7 years ago
Min RK 9e5c6ef360
Merge pull request #4522 from meeseeksmachine/auto-backport-of-pr-4513-on-5.7.x
7 years ago
Min RK fd8586c4c1 Backport PR #4513: Fix regressions in 5.7.x
7 years ago
Min RK deb4d4a663
Merge pull request #4521 from meeseeksmachine/auto-backport-of-pr-4468-on-5.7.x
7 years ago
Min RK 197e568828 Backport PR #4468: Fix incorrect MIME/Content-Type for JavaScript on misconfigured Windows systems
7 years ago
Min RK 05aa4b2cff release 5.7.6
7 years ago
Min RK 0ff8b86d53 changelog for 5.7.6
7 years ago
Min RK bfaa613857 Block cross-origin GET,HEAD requests with mismatched Referer
7 years ago
Min RK b5105814fc add xsrf checks on files endpoints
7 years ago
Min RK cfc335b764 Set X-Content-Options: nosniff on all handlers
7 years ago
Min RK f3f00df5b0
Merge pull request #4454 from minrk/5.7.x
7 years ago
Min RK d1e8d4df6c release 5.7.5
7 years ago
Min RK 8244468dc3 test py27 with tornado 4 on travis
7 years ago
Min RK 667b459070 pin tornado<7
7 years ago
Min RK 1b5b60357a Merge pull request #4449 from minrk/unpin-tornado
7 years ago
Min RK ab6445233e Backport PR #4412: Enable restart_kernel for async usage
7 years ago
Min RK daa1bde4f2 Backport PR #4392: Call tornado WebSocketHandler.get() as a coroutine
7 years ago
Thomas Kluyver cf3622c1ff
Merge pull request #4349 from takluyver/io-open-fd
7 years ago
Thomas Kluyver bc459d05d1 Limit tornado version to <6
7 years ago
Samuel Lelièvre f8d7d4086f
Use io.open in notebookapp.py to fix #4303
7 years ago
Thomas Kluyver 536c30ef97 Back to development
7 years ago
Thomas Kluyver fe7c29096f release 5.7.4
7 years ago
Thomas Kluyver 3e4bc670c4
Merge pull request #4288 from meeseeksmachine/auto-backport-of-pr-4286-on-5.7.x
7 years ago
Thomas Kluyver d624d00477 Backport PR #4286: Add release notes for 5.7.4
7 years ago
Thomas Kluyver 7b0ed75141
Merge pull request #4285 from meeseeksmachine/auto-backport-of-pr-4284-on-5.7.x
7 years ago
Thomas Kluyver 3941536b22 Backport PR #4284: More selective filename test in list_running_servers
7 years ago
Thomas Kluyver 546ac5956d back to development for JS version too
7 years ago
Thomas Kluyver aec34c4869 Back to development
7 years ago
Thomas Kluyver c0175f63d9 release 5.7.3
7 years ago
Thomas Kluyver 9310eb2ee5
Merge pull request #4279 from meeseeksmachine/auto-backport-of-pr-4278-on-5.7.x
7 years ago
Thomas Kluyver 75117d3dac Backport PR #4278: Write release notes for 5.7.3
7 years ago
Min RK f6c230e1be
Merge pull request #4272 from meeseeksmachine/auto-backport-of-pr-4271-on-5.7.x
7 years ago
Thomas Kluyver 4ca3f41c2a Backport PR #4271: bootstrap 3.4
7 years ago
Thomas Kluyver 8619d32719
Merge pull request #4265 from meeseeksmachine/auto-backport-of-pr-4260-on-5.7.x
7 years ago
Thomas Kluyver db5c2fecbe Use io.open() for Python 2
7 years ago
Min RK e7868e670c Backport PR #4260: Launch the browser with a redirect file
7 years ago
Min RK 94ac73c466 5.7.2
7 years ago
Min RK 1485fc2be5 changes for 5.7.1, 5.7.2
7 years ago
Min RK ad25be985c assemble breadcrumb html safely
7 years ago
Thomas Kluyver dd56b4ec11 Back to development
7 years ago
Thomas Kluyver 154fa08c48 release 5.7.1
7 years ago
Thomas Kluyver 4026ef0107 Apply CSP sandboxing for nbconvert responses
7 years ago

@ -49,7 +49,8 @@ before_install:
fi
install:
- pip install --pre .[test]
- pip install --pre .[test] $EXTRA_PIP
- pip freeze
- wget https://github.com/jgm/pandoc/releases/download/1.19.1/pandoc-1.19.1-1-amd64.deb && sudo dpkg -i pandoc-1.19.1-1-amd64.deb
@ -92,14 +93,21 @@ matrix:
addons:
firefox: 57.0
- python: 3.4
env: GROUP=python
- python: 3.5
env: GROUP=python
- python: "3.7-dev"
- python: 3.7
dist: xenial
env: GROUP=python
- python: 3.6
env: GROUP=docs
- python: 3.6
env:
- GROUP=python
- EXTRA_PIP="tornado<5"
- python: 2.7
env:
- GROUP=python
- EXTRA_PIP="tornado<5"
after_success:
- codecov

@ -1,6 +1,7 @@
include COPYING.md
include CONTRIBUTING.rst
include README.md
include *.js
include package.json
include bower.json
include .bowerrc

@ -0,0 +1,73 @@
# Making a Release of Notebook
## Start from a fresh git checkout and conda environment
### Set the release branch
```bash
export release_branch=master
```
### Create the git checkout
```bash
git clone git@github.com:jupyter/notebook.git
cd notebook
git checkout ${release_banch}
```
### Create and activate the conda environment
```bash
conda create -n notebook-release -c conda-forge jupyter
conda activate notebook-release
```
## Perform a local dev install
```bash
pip install -ve .
```
## Install release dependencies
```bash
conda install -c conda-forge nodejs babel twine
npm install -g po2json
```
## Update the version
```bash
vim notebook/_version.py
python setup.py jsversion
git commit -am "Release $(python setup.py --version)"
git tag $(python setup.py --version)
```
## Create the artifacts
```bash
rm -rf dist
python setup.py sdist
python setup.py bdist_wheel
```
## Upload the artifacts
```bash
twine check dist/* && twine upload dist/*
```
## Change back to dev version
```bash
vim notebook/_version.py # Add the .dev suffix
git commit -am "Back to dev version"
```
## Push the commits and tags
```bash
git push origin ${release_branch} --tags
```

@ -3,22 +3,20 @@
"version": "0.0.1",
"dependencies": {
"backbone": "components/backbone#~1.2",
"bootstrap": "components/bootstrap#~3.3",
"bootstrap": "bootstrap#~3.4",
"bootstrap-tour": "0.9.0",
"codemirror": "components/codemirror#~5.37",
"create-react-class": "https://cdn.jsdelivr.net/npm/create-react-class@15.6.3/create-react-class.min.js",
"es6-promise": "~1.0",
"font-awesome": "components/font-awesome#~4.7.0",
"google-caja": "5669",
"jed": "~1.1.1",
"jquery": "components/jquery#~3.3",
"jquery": "components/jquery#~3.6.0",
"jquery-typeahead": "~2.0.0",
"jquery-ui": "components/jqueryui#~1.12",
"marked": "~0.4",
"MathJax": "^2.7.4",
"moment": "~2.19.3",
"preact": "https://unpkg.com/preact@^7.2.0/dist/preact.min.js",
"preact-compat": "https://unpkg.com/preact-compat@^3.14.3/dist/preact-compat.min.js",
"proptypes": "https://unpkg.com/proptypes@^0.14.4/index.js",
"react": "~16.0.0",
"requirejs": "~2.2",
"requirejs-text": "~2.0.15",
"requirejs-plugins": "~1.0.3",

@ -1,3 +1,3 @@
sphinx>=1.3.6
sphinx<2.0
sphinx-rtd-theme
nbsphinx

@ -21,6 +21,110 @@ We strongly recommend that you upgrade pip to version 9+ of pip before upgrading
Use ``pip install pip --upgrade`` to upgrade pip. Check pip version with
``pip --version``.
.. _release-5.7.15:
5.7.15
--------
- Fix compatibility with Python < 3.6(:ghpull:`6522`).
.. _release-5.7.14a0:
5.7.14a0
--------
- Update JQuery dependency to version 3.6.0
- Pin jinja2 <= 3.0.0
- Pin jupyter_client < 7.0.0
- Update versioning mechanism
.. _release-5.7.9:
5.7.9
------
- Update JQuery dependency to version 3.4.1 to fix security vulnerability (CVE-2019-11358)
- Update from preact to React
.. _release-5.7.8:
5.7.8
-----
- Fix regression in restarting kernels in 5.7.5.
The restart handler would return before restart was completed.
- Further improve compatibility with tornado 6 with improved
checks for when websockets are closed.
- Fix regression in 5.7.6 on Windows where .js files could have the wrong mime-type.
- Fix Open Redirect vulnerability (CVE-2019-10255)
where certain malicious URLs could redirect from the Jupyter login page
to a malicious site after a successful login.
5.7.7 contained only a partial fix for this issue.
.. _release-5.7.6:
5.7.6
-----
5.7.6 contains a security fix for a cross-site inclusion (XSSI) vulnerability (CVE-20199644),
where files at a known URL could be included in a page from an unauthorized website if the user is logged into a Jupyter server.
The fix involves setting the ``X-Content-Type-Options: nosniff``
header, and applying CSRF checks previously on all non-GET
API requests to GET requests to API endpoints and the /files/ endpoint.
The attacking page is able to access some contents of files when using Internet Explorer through script errors,
but this has not been demonstrated with other browsers.
.. _release-5.7.5:
5.7.5
-----
- Fix compatibility with tornado 6 (:ghpull:`4392`, :ghpull:`4449`).
- Fix opening integer filedescriptor during startup on Python 2 (:ghpull:`4349`)
- Fix compatibility with asynchronous `KernelManager.restart_kernel` methods (:ghpull:`4412`)
.. _release-5.7.4:
5.7.4
-----
5.7.4 fixes a bug introduced in 5.7.3, in which the ``list_running_servers()``
function attempts to parse HTML files as JSON, and consequently crashes
(:ghpull:`4284`).
.. _release-5.7.3:
5.7.3
-----
5.7.3 contains one security improvement and one security fix:
- Launch the browser with a local file which redirects to the server address
including the authentication token (:ghpull:`4260`).
This prevents another logged-in user from stealing the token from command line
arguments and authenticating to the server.
The single-use token previously used to mitigate this has been removed.
Thanks to Dr. Owain Kenway for suggesting the local file approach.
- Upgrade bootstrap to 3.4, fixing an XSS vulnerability, which has been
assigned `CVE-2018-14041 <https://nvd.nist.gov/vuln/detail/CVE-2018-14041>`_
(:ghpull:`4271`).
.. _release-5.7.2:
5.7.2
-----
5.7.2 contains a security fix preventing malicious directory names
from being able to execute javascript. CVE request pending.
.. _release-5.7.1:
5.7.1
-----
5.7.1 contains a security fix preventing nbconvert endpoints from executing javascript with access to the server API. CVE request pending.
.. _release-5.7.0:
5.7.0

@ -1,67 +0,0 @@
.. _notebook_release:
Making a Notebook release
=========================
This document guides a contributor through creating a release of the Jupyter
notebook.
Check installed tools
---------------------
Review ``CONTRIBUTING.rst``. Make sure all the tools needed to generate the
minified JavaScript and CSS files are properly installed.
Clean the repository
--------------------
You can remove all non-tracked files with:
.. code:: bash
git clean -xfdi
This would ask you for confirmation before removing all untracked files. Make
sure the ``dist/`` folder is clean and avoid stale build from
previous attempts.
Create the release
------------------
#. Update version number in ``notebook/_version.py``.
#. Run this command:
.. code:: bash
python setup.py jsversion
It will modify (at least) ``notebook/static/base/js/namespace.js`` which
makes the notebook version available from within JavaScript.
#. Commit and tag the release with the current version number:
.. code:: bash
git commit -am "release $VERSION"
git tag $VERSION
#. You are now ready to build the ``sdist`` and ``wheel``:
.. code:: bash
python setup.py sdist
python setup.py bdist_wheel
#. You can now test the ``wheel`` and the ``sdist`` locally before uploading
to PyPI. Make sure to use `twine <https://github.com/pypa/twine>`_ to
upload the archives over SSL.
.. code:: bash
twine upload dist/*
#. If all went well, change the ``notebook/_version.py`` back adding the
``.dev`` suffix.
#. Push directly on master, not forgetting to push ``--tags`` too.

@ -33,7 +33,6 @@ The Jupyter Notebook
:caption: Contributor Documentation
contributing
development_release
development_faq
.. toctree::

@ -2,12 +2,15 @@
store the current version info of the notebook.
"""
import re
# Downstream maintainer, when running `python.setup.py jsversion`,
# the version string is propagated to the JavaScript files, do not forget to
# patch the JavaScript files in `.postN` release done by distributions.
# Next beta (b)/ alpha (a)/ release candidate (rc) release: The version number for alpha is X.Y.ZaN
__version__ = '5.7.16.dev'
# Next beta/alpha/rc release: The version number for beta is X.Y.ZbN **without dots**.
version_info = (5, 7, 0)
__version__ = '.'.join(map(str, version_info[:3])) + ''.join(version_info[3:])
# Build up version_info tuple for backwards compatibility
pattern = r'(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)'
match = re.match(pattern, __version__)
parts = [int(match.group(part)) for part in ['major', 'minor', 'patch']]
if match.group('rest'):
parts.append(match.group('rest'))
version_info = tuple(parts)

@ -7,9 +7,9 @@ import re
import os
try:
from urllib.parse import urlparse # Py 3
from urllib.parse import urlparse, urlunparse # Py 3
except ImportError:
from urlparse import urlparse # Py 2
from urlparse import urlparse, urlunparse # Py 2
import uuid
from tornado.escape import url_escape
@ -39,15 +39,23 @@ class LoginHandler(IPythonHandler):
"""
if default is None:
default = self.base_url
if not url.startswith(self.base_url):
# protect chrome users from mishandling unescaped backslashes.
# \ is not valid in urls, but some browsers treat it as /
# instead of %5C, causing `\\` to behave as `//`
url = url.replace("\\", "%5C")
parsed = urlparse(url)
path_only = urlunparse(parsed._replace(netloc='', scheme=''))
if url != path_only or not (parsed.path + '/').startswith(self.base_url):
# require that next_url be absolute path within our path
allow = False
# OR pass our cross-origin check
if '://' in url:
if url != path_only:
# if full URL, run our cross-origin check:
parsed = urlparse(url.lower())
origin = '%s://%s' % (parsed.scheme, parsed.netloc)
if self.allow_origin:
origin = origin.lower()
if origin == '%s://%s' % (self.request.protocol, self.request.host):
allow = True
elif self.allow_origin:
allow = self.allow_origin == origin
elif self.allow_origin_pat:
allow = bool(self.allow_origin_pat.match(origin))
@ -204,17 +212,11 @@ class LoginHandler(IPythonHandler):
return
# check login token from URL argument or Authorization header
user_token = cls.get_token(handler)
one_time_token = handler.one_time_token
authenticated = False
if user_token == token:
# token-authenticated, set the login cookie
handler.log.debug("Accepting token-authenticated connection from %s", handler.request.remote_ip)
authenticated = True
elif one_time_token and user_token == one_time_token:
# one-time-token-authenticated, only allow this token once
handler.settings.pop('one_time_token', None)
handler.log.info("Accepting one-time-token-authenticated connection from %s", handler.request.remote_ip)
authenticated = True
if authenticated:
return uuid.uuid4().hex

@ -0,0 +1,54 @@
"""Tests for login redirects"""
import requests
from tornado.httputil import url_concat
from notebook.tests.launchnotebook import NotebookTestBase
class LoginTest(NotebookTestBase):
def login(self, next):
first = requests.get(self.base_url() + "login")
first.raise_for_status()
resp = requests.post(
url_concat(
self.base_url() + "login",
{'next': next},
),
allow_redirects=False,
data={
"password": self.token,
"_xsrf": first.cookies.get("_xsrf", ""),
},
cookies=first.cookies,
)
resp.raise_for_status()
return resp.headers['Location']
def test_next_bad(self):
for bad_next in (
"//some-host",
"//host" + self.url_prefix + "tree",
"https://google.com",
"/absolute/not/base_url",
"///jupyter.org",
"/\\some-host",
):
url = self.login(next=bad_next)
self.assertEqual(url, self.url_prefix)
assert url
def test_next_ok(self):
for next_path in (
"tree/",
self.base_url() + "has/host",
"notebooks/notebook.ipynb",
"tree//something",
):
if "://" in next_path:
expected = next_path
else:
expected = self.url_prefix + next_path
actual = self.login(next=expected)
self.assertEqual(actual, expected)

@ -82,6 +82,7 @@ class AuthenticatedHandler(web.RequestHandler):
def set_default_headers(self):
headers = {}
headers["X-Content-Type-Options"] = "nosniff"
headers.update(self.settings.get('headers', {}))
headers["Content-Security-Policy"] = self.content_security_policy
@ -180,11 +181,6 @@ class AuthenticatedHandler(web.RequestHandler):
"""Return the login token for this application, if any."""
return self.settings.get('token', None)
@property
def one_time_token(self):
"""Return the one-time-use token for this application, if any."""
return self.settings.get('one_time_token', None)
@property
def login_available(self):
"""May a user proceed to log in?
@ -404,13 +400,69 @@ class IPythonHandler(AuthenticatedHandler):
)
return allow
def check_referer(self):
"""Check Referer for cross-site requests.
Disables requests to certain endpoints with
external or missing Referer.
If set, allow_origin settings are applied to the Referer
to whitelist specific cross-origin sites.
Used on GET for api endpoints and /files/
to block cross-site inclusion (XSSI).
"""
host = self.request.headers.get("Host")
referer = self.request.headers.get("Referer")
if not host:
self.log.warning("Blocking request with no host")
return False
if not referer:
self.log.warning("Blocking request with no referer")
return False
referer_url = urlparse(referer)
referer_host = referer_url.netloc
if referer_host == host:
return True
# apply cross-origin checks to Referer:
origin = "{}://{}".format(referer_url.scheme, referer_url.netloc)
if self.allow_origin:
allow = self.allow_origin == origin
elif self.allow_origin_pat:
allow = bool(self.allow_origin_pat.match(origin))
else:
# No CORS settings, deny the request
allow = False
if not allow:
self.log.warning("Blocking Cross Origin request for %s. Referer: %s, Host: %s",
self.request.path, origin, host,
)
return allow
def check_xsrf_cookie(self):
"""Bypass xsrf cookie checks when token-authenticated"""
if self.token_authenticated or self.settings.get('disable_check_xsrf', False):
# Token-authenticated requests do not need additional XSRF-check
# Servers without authentication are vulnerable to XSRF
return
return super(IPythonHandler, self).check_xsrf_cookie()
try:
return super(IPythonHandler, self).check_xsrf_cookie()
except web.HTTPError as e:
if self.request.method in {'GET', 'HEAD'}:
# Consider Referer a sufficient cross-origin check for GET requests
if not self.check_referer():
referer = self.request.headers.get('Referer')
if referer:
msg = "Blocking Cross Origin request from {}.".format(referer)
else:
msg = "Blocking request from unknown origin"
raise web.HTTPError(403, msg)
else:
raise
def check_host(self):
"""Check the host header if remote access disallowed.
@ -475,7 +527,7 @@ class IPythonHandler(AuthenticatedHandler):
logged_in=self.logged_in,
allow_password_change=self.settings.get('allow_password_change'),
login_available=self.login_available,
token_available=bool(self.token or self.one_time_token),
token_available=bool(self.token),
static_url=self.static_url,
sys_info=json_sys_info(),
contents_js_source=self.contents_js_source,
@ -654,14 +706,21 @@ class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
return super(AuthenticatedFileHandler, self).content_security_policy + \
"; sandbox allow-scripts"
@web.authenticated
def head(self, path):
self.check_xsrf_cookie()
return super(AuthenticatedFileHandler, self).head(path)
@web.authenticated
def get(self, path):
self.check_xsrf_cookie()
if os.path.splitext(path)[1] == '.ipynb' or self.get_argument("download", False):
name = path.rsplit('/', 1)[-1]
self.set_attachment_header(name)
return web.StaticFileHandler.get(self, path)
def get_content_type(self):
path = self.absolute_path.strip('/')
if '/' in path:

@ -17,7 +17,8 @@ except ImportError:
import tornado
from tornado import gen, ioloop, web
from tornado.websocket import WebSocketHandler
from tornado.iostream import StreamClosedError
from tornado.websocket import WebSocketHandler, WebSocketClosedError
from jupyter_client.session import Session
from jupyter_client.jsonutil import date_default, extract_dates
@ -172,7 +173,7 @@ class WebSocketMixin(object):
def send_ping(self):
"""send a ping to keep the websocket alive"""
if self.stream.closed() and self.ping_callback is not None:
if self.ws_connection is None and self.ping_callback is not None:
self.ping_callback.stop()
return
@ -185,8 +186,13 @@ class WebSocketMixin(object):
self.log.warning("WebSocket ping timeout after %i ms.", since_last_pong)
self.close()
return
try:
self.ping(b'')
except (StreamClosedError, WebSocketClosedError):
# websocket has been closed, stop pinging
self.ping_callback.stop()
return
self.ping(b'')
self.last_ping = now
def on_pong(self, data):
@ -237,7 +243,7 @@ class ZMQStreamHandler(WebSocketMixin, WebSocketHandler):
def _on_zmq_reply(self, stream, msg_list):
# Sometimes this gets triggered when the on_close method is scheduled in the
# eventloop but hasn't been called.
if self.stream.closed() or stream.closed():
if self.ws_connection is None or stream.closed():
self.log.warning("zmq message arrived on closed channel")
self.close()
return
@ -246,8 +252,14 @@ class ZMQStreamHandler(WebSocketMixin, WebSocketHandler):
msg = self._reserialize_reply(msg_list, channel=channel)
except Exception:
self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
else:
return
try:
self.write_message(msg, binary=isinstance(msg, bytes))
except (StreamClosedError, WebSocketClosedError):
self.log.warning("zmq message arrived on closed channel")
self.close()
return
class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
@ -281,7 +293,8 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
# assign and yield in two step to avoid tornado 3 issues
res = self.pre_get()
yield gen.maybe_future(res)
super(AuthenticatedZMQStreamHandler, self).get(*args, **kwargs)
res = super(AuthenticatedZMQStreamHandler, self).get(*args, **kwargs)
yield gen.maybe_future(res)
def initialize(self):
self.log.debug("Initializing websocket connection %s", self.request.path)

@ -35,10 +35,13 @@ class FilesHandler(IPythonHandler):
@web.authenticated
def head(self, path):
self.get(path, include_body=False)
self.check_xsrf_cookie()
return self.get(path, include_body=False)
@web.authenticated
def get(self, path, include_body=True):
# /files/ requests must originate from the same site
self.check_xsrf_cookie()
cm = self.contents_manager
if cm.is_hidden(path) and not cm.allow_hidden:

@ -78,6 +78,13 @@ class NbconvertFileHandler(IPythonHandler):
SUPPORTED_METHODS = ('GET',)
@property
def content_security_policy(self):
# In case we're serving HTML/SVG, confine any Javascript to a unique
# origin so it can't interact with the notebook server.
return super(NbconvertFileHandler, self).content_security_policy + \
"; sandbox allow-scripts"
@web.authenticated
def get(self, format, path):
@ -145,6 +152,13 @@ class NbconvertFileHandler(IPythonHandler):
class NbconvertPostHandler(IPythonHandler):
SUPPORTED_METHODS = ('POST',)
@property
def content_security_policy(self):
# In case we're serving HTML/SVG, confine any Javascript to a unique
# origin so it can't interact with the notebook server.
return super(NbconvertPostHandler, self).content_security_policy + \
"; sandbox allow-scripts"
@web.authenticated
def post(self, format):
exporter = get_exporter(format, config=self.config)

@ -15,17 +15,54 @@ from ..utils import url_escape
from ..transutils import _
def get_custom_frontend_exporters():
def get_frontend_exporters():
from nbconvert.exporters.base import get_export_names, get_exporter
# name=exporter_name, display=export_from_notebook+extension
ExporterInfo = namedtuple('ExporterInfo', ['name', 'display'])
for name in sorted(get_export_names()):
exporter = get_exporter(name)()
ux_name = getattr(exporter, 'export_from_notebook', None)
if ux_name is not None:
display = _('{} ({})'.format(ux_name, exporter.file_extension))
yield ExporterInfo(name, display)
default_exporters = [
ExporterInfo(name='html', display='HTML (.html)'),
ExporterInfo(name='latex', display='LaTeX (.tex)'),
ExporterInfo(name='markdown', display='Markdown (.md)'),
ExporterInfo(name='notebook', display='Notebook (.ipynb)'),
ExporterInfo(name='pdf', display='PDF via LaTeX (.pdf)'),
ExporterInfo(name='rst', display='reST (.rst)'),
ExporterInfo(name='script', display='Script (.txt)'),
ExporterInfo(name='slides', display='Reveal.js slides (.slides.html)')
]
frontend_exporters = []
for name in get_export_names():
exporter_class = get_exporter(name)
exporter_instance = exporter_class()
ux_name = getattr(exporter_instance, 'export_from_notebook', None)
super_uxname = getattr(super(exporter_class, exporter_instance),
'export_from_notebook', None)
# Ensure export_from_notebook is explicitly defined & not inherited
if ux_name is not None and ux_name != super_uxname:
display = _('{} ({})'.format(ux_name,
exporter_instance.file_extension))
frontend_exporters.append(ExporterInfo(name, display))
# Ensure default_exporters are in frontend_exporters if not already
# This protects against nbconvert versions lower than 5.5
names = set(exporter.name.lower() for exporter in frontend_exporters)
for exporter in default_exporters:
if exporter.name not in names:
frontend_exporters.append(exporter)
# Protect against nbconvert 5.5.0
python_exporter = ExporterInfo(name='python', display='python (.py)')
if python_exporter in frontend_exporters:
frontend_exporters.remove(python_exporter)
# Protect against nbconvert 5.4.x
template_exporter = ExporterInfo(name='custom', display='custom (.txt)')
if template_exporter in frontend_exporters:
frontend_exporters.remove(template_exporter)
return sorted(frontend_exporters)
class NotebookHandler(IPythonHandler):
@ -56,7 +93,7 @@ class NotebookHandler(IPythonHandler):
kill_kernel=False,
mathjax_url=self.mathjax_url,
mathjax_config=self.mathjax_config,
get_custom_frontend_exporters=get_custom_frontend_exporters
get_frontend_exporters=get_frontend_exporters
)
)

@ -26,6 +26,7 @@ import select
import signal
import socket
import sys
import tempfile
import threading
import time
import warnings
@ -107,7 +108,7 @@ from jupyter_core.paths import jupyter_runtime_dir, jupyter_path
from notebook._sysinfo import get_sys_info
from ._tz import utcnow, utcfromtimestamp
from .utils import url_path_join, check_pid, url_escape
from .utils import url_path_join, check_pid, url_escape, urljoin, pathname2url
#-----------------------------------------------------------------------------
# Module globals
@ -210,6 +211,7 @@ class NotebookWebApplication(web.Application):
log.warning(_("""Alternatively use `%s` when working on the notebook's Javascript and LESS""") % 'npm run build:watch')
warnings.warn(_("The `ignore_minified_js` flag is deprecated and will be removed in Notebook 6.0"), DeprecationWarning)
log.warning("Notebook version 5 is no longer maintained. Please upgrade to version 6 or later.")
now = utcnow()
root_dir = contents_manager.root_dir
@ -754,12 +756,6 @@ class NotebookApp(JupyterApp):
""")
).tag(config=True)
one_time_token = Unicode(
help=_("""One-time token used for opening a browser.
Once used, this token cannot be used again.
""")
)
_token_generated = True
@default('token')
@ -1178,6 +1174,13 @@ class NotebookApp(JupyterApp):
def _default_info_file(self):
info_file = "nbserver-%s.json" % os.getpid()
return os.path.join(self.runtime_dir, info_file)
browser_open_file = Unicode()
@default('browser_open_file')
def _default_browser_open_file(self):
basename = "nbserver-%s-open.html" % os.getpid()
return os.path.join(self.runtime_dir, basename)
pylab = Unicode('disabled', config=True,
help=_("""
@ -1357,9 +1360,6 @@ class NotebookApp(JupyterApp):
self.tornado_settings['cookie_options'] = self.cookie_options
self.tornado_settings['get_secure_cookie_kwargs'] = self.get_secure_cookie_kwargs
self.tornado_settings['token'] = self.token
if (self.open_browser or self.file_to_run) and not self.password:
self.one_time_token = binascii.hexlify(os.urandom(24)).decode('ascii')
self.tornado_settings['one_time_token'] = self.one_time_token
# ensure default_url starts with base_url
if not self.default_url.startswith(self.base_url):
@ -1582,10 +1582,12 @@ class NotebookApp(JupyterApp):
def init_mime_overrides(self):
# On some Windows machines, an application has registered an incorrect
# mimetype for CSS in the registry. Tornado uses this when serving
# .css files, causing browsers to reject the stylesheet. We know the
# mimetype always needs to be text/css, so we override it here.
# mimetype for CSS and JavaScript in the registry.
# Tornado uses this when serving .css and .js files, causing browsers to
# reject these files. We know the mimetype always needs to be text/css for css
# and application/javascript for JS, so we override it here.
mimetypes.add_type('text/css', '.css')
mimetypes.add_type('application/javascript', '.js')
def shutdown_no_activity(self):
@ -1689,6 +1691,67 @@ class NotebookApp(JupyterApp):
if e.errno != errno.ENOENT:
raise
def write_browser_open_file(self):
"""Write an nbserver-<pid>-open.html file
This can be used to open the notebook in a browser
"""
# default_url contains base_url, but so does connection_url
open_url = self.default_url[len(self.base_url):]
with io.open(self.browser_open_file, 'w', encoding='utf-8') as f:
self._write_browser_open_file(open_url, f)
def _write_browser_open_file(self, url, fh):
if self.token:
url = url_concat(url, {'token': self.token})
url = url_path_join(self.connection_url, url)
jinja2_env = self.web_app.settings['jinja2_env']
template = jinja2_env.get_template('browser-open.html')
fh.write(template.render(open_url=url))
def remove_browser_open_file(self):
"""Remove the nbserver-<pid>-open.html file created for this server.
Ignores the error raised when the file has already been removed.
"""
try:
os.unlink(self.browser_open_file)
except OSError as e:
if e.errno != errno.ENOENT:
raise
def launch_browser(self):
try:
browser = webbrowser.get(self.browser or None)
except webbrowser.Error as e:
self.log.warning(_('No web browser found: %s.') % e)
browser = None
if not browser:
return
if self.file_to_run:
if not os.path.exists(self.file_to_run):
self.log.critical(_("%s does not exist") % self.file_to_run)
self.exit(1)
relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
uri = url_escape(url_path_join('notebooks', *relpath.split(os.sep)))
# Write a temporary file to open in the browser
fd, open_file = tempfile.mkstemp(suffix='.html')
with io.open(fd, 'w', encoding='utf-8') as fh:
self._write_browser_open_file(uri, fh)
else:
open_file = self.browser_open_file
b = lambda: browser.open(
urljoin('file:', pathname2url(open_file)),
new=self.webbrowser_open_new)
threading.Thread(target=b).start()
def start(self):
""" Start the Notebook server app, after initialization
@ -1718,38 +1781,19 @@ class NotebookApp(JupyterApp):
"resources section at https://jupyter.org/community.html."))
self.write_server_info_file()
self.write_browser_open_file()
if self.open_browser or self.file_to_run:
try:
browser = webbrowser.get(self.browser or None)
except webbrowser.Error as e:
self.log.warning(_('No web browser found: %s.') % e)
browser = None
if self.file_to_run:
if not os.path.exists(self.file_to_run):
self.log.critical(_("%s does not exist") % self.file_to_run)
self.exit(1)
relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
uri = url_escape(url_path_join('notebooks', *relpath.split(os.sep)))
else:
# default_url contains base_url, but so does connection_url
uri = self.default_url[len(self.base_url):]
if self.one_time_token:
uri = url_concat(uri, {'token': self.one_time_token})
if browser:
b = lambda : browser.open(url_path_join(self.connection_url, uri),
new=self.webbrowser_open_new)
threading.Thread(target=b).start()
self.launch_browser()
if self.token and self._token_generated:
# log full URL with generated token, so there's a copy/pasteable link
# with auth info.
self.log.critical('\n'.join([
'\n',
'Copy/paste this URL into your browser when you connect for the first time,',
'to login with a token:',
'To access the notebook, open this file in a browser:',
' %s' % urljoin('file:', pathname2url(self.browser_open_file)),
'Or copy and paste one of these URLs:',
' %s' % self.display_url,
]))
@ -1765,6 +1809,7 @@ class NotebookApp(JupyterApp):
info(_("Interrupted..."))
finally:
self.remove_server_info_file()
self.remove_browser_open_file()
self.cleanup_kernels()
def stop(self):
@ -1789,7 +1834,7 @@ def list_running_servers(runtime_dir=None):
return
for file_name in os.listdir(runtime_dir):
if file_name.startswith('nbserver-'):
if re.match('nbserver-(.+).json', file_name):
with io.open(os.path.join(runtime_dir, file_name), encoding='utf-8') as f:
info = json.load(f)

@ -280,10 +280,11 @@ class MappingKernelManager(MultiKernelManager):
self.last_kernel_activity = utcnow()
return super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
@gen.coroutine
def restart_kernel(self, kernel_id):
"""Restart a kernel by kernel_id"""
self._check_kernel_id(kernel_id)
super(MappingKernelManager, self).restart_kernel(kernel_id)
yield gen.maybe_future(super(MappingKernelManager, self).restart_kernel(kernel_id))
kernel = self.get_kernel(kernel_id)
# return a Future that will resolve when the kernel has successfully restarted
channel = kernel.connect_shell()
@ -319,7 +320,8 @@ class MappingKernelManager(MultiKernelManager):
channel.on_recv(on_reply)
loop = IOLoop.current()
timeout = loop.add_timeout(loop.time() + self.kernel_info_timeout, on_timeout)
return future
# wait for restart to complete
yield future
def notify_connect(self, kernel_id):
"""Notice a new connection to a kernel"""

@ -9,6 +9,7 @@ class NbconvertRootHandler(APIHandler):
@web.authenticated
def get(self):
self.check_xsrf_cookie()
try:
from nbconvert.exporters import base
except ImportError as e:

@ -32,7 +32,7 @@ define(function(){
// expose modules
jprop('utils','base/js/utils')
//Jupyter.load_extensions = Jupyter.utils.load_extensions;
//
jprop('security','base/js/security');
@ -73,7 +73,7 @@ define(function(){
// tree
jglobal('SessionList','tree/js/sessionlist');
Jupyter.version = "5.7.0";
Jupyter.version = "5.7.16";
Jupyter._target = '_blank';
return Jupyter;
});

@ -3,124 +3,24 @@
define([
'jquery',
'components/google-caja/html-css-sanitizer-minified',
], function($, sanitize) {
'components/sanitizer/index',
], function($, sanitizer) {
"use strict";
var noop = function (x) { return x; };
var caja;
if (window && window.html) {
caja = window.html;
caja.html4 = window.html4;
caja.sanitizeStylesheet = window.sanitizeStylesheet;
}
var sanitizeAttribs = function (tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger) {
/**
* add trusting data-attributes to the default sanitizeAttribs from caja
* this function is mostly copied from the caja source
*/
var ATTRIBS = caja.html4.ATTRIBS;
for (var i = 0; i < attribs.length; i += 2) {
var attribName = attribs[i];
if (attribName.substr(0,5) == 'data-') {
var attribKey = '*::' + attribName;
if (!ATTRIBS.hasOwnProperty(attribKey)) {
ATTRIBS[attribKey] = 0;
}
}
}
// Caja doesn't allow data uri for img::src, see
// https://github.com/google/caja/issues/1558
// This is not a security issue for browser post ie6 though, so we
// disable the check
// https://www.owasp.org/index.php/Script_in_IMG_tags
ATTRIBS['img::src'] = 0;
return caja.sanitizeAttribs(tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger);
};
var sanitize_css = function (css, tagPolicy) {
/**
* sanitize CSS
* like sanitize_html, but for CSS
* called by sanitize_stylesheets
*/
return caja.sanitizeStylesheet(
window.location.pathname,
css,
{
containerClass: null,
idSuffix: '',
tagPolicy: tagPolicy,
virtualizeAttrName: noop
},
noop
);
};
var sanitize_stylesheets = function (html, tagPolicy) {
/**
* sanitize just the css in style tags in a block of html
* called by sanitize_html, if allow_css is true
*/
var h = $("<div/>").append(html);
var style_tags = h.find("style");
if (!style_tags.length) {
// no style tags to sanitize
return html;
}
style_tags.each(function(i, style) {
style.innerHTML = sanitize_css(style.innerHTML, tagPolicy);
});
return h.html();
};
var defaultSanitizer = sanitizer.defaultSanitizer;
var sanitize_html = function (html, allow_css) {
/**
* sanitize HTML
* if allow_css is true (default: false), CSS is sanitized as well.
* otherwise, CSS elements and attributes are simply removed.
*/
var html4 = caja.html4;
if (allow_css) {
// allow sanitization of style tags,
// not just scrubbing
html4.ELEMENTS.style &= ~html4.eflags.UNSAFE;
html4.ATTRIBS.style = html4.atype.STYLE;
} else {
// scrub all CSS
html4.ELEMENTS.style |= html4.eflags.UNSAFE;
html4.ATTRIBS.style = html4.atype.SCRIPT;
}
var record_messages = function (msg, opts) {
console.log("HTML Sanitizer", msg, opts);
};
var policy = function (tagName, attribs) {
if (!(html4.ELEMENTS[tagName] & html4.eflags.UNSAFE)) {
return {
'attribs': sanitizeAttribs(tagName, attribs,
noop, noop, record_messages)
};
} else {
record_messages(tagName + " removed", {
change: "removed",
tagName: tagName
});
}
};
var sanitized = caja.sanitizeWithPolicy(html, policy);
if (allow_css) {
// sanitize style tags as stylesheets
sanitized = sanitize_stylesheets(sanitized, policy);
}
return sanitized;
const options = {};
if (!allow_css) {
options.allowedStyles = {};
}
return defaultSanitizer.sanitize(html, options);
};
var sanitize_html_and_parse = function (html, allow_css) {
@ -141,9 +41,8 @@ define([
$.htmlPrefilter = prev_htmlPrefilter; // Set it back again
}
};
var security = {
caja: caja,
sanitize_html_and_parse: sanitize_html_and_parse,
sanitize_html: sanitize_html
};

@ -12,9 +12,6 @@ define([
dialog,
marked
) {
var render = preact.render;
var createClass = preactCompat.createClass;
var createElement = preactCompat.createElement;
/**
@ -33,7 +30,7 @@ var humanize_action_id = function(str) {
* Wether an action have a keybinding or not.
**/
var KeyBinding = createClass({
var KeyBinding = createReactClass({
displayName: 'KeyBindings',
getInitialState: function() {
return {shrt:''};
@ -53,13 +50,13 @@ var KeyBinding = createClass({
event.preventDefault();
return false;
};
return createElement('form', {className:'jupyter-keybindings',
return React.createElement('form', {className:'jupyter-keybindings',
onSubmit: binding_setter
},
createElement('i', {className: "pull-right fa fa-plus", alt: 'add-keyboard-shortcut',
React.createElement('i', {className: "pull-right fa fa-plus", alt: 'add-keyboard-shortcut',
onClick: binding_setter
}),
createElement('input', {
React.createElement('input', {
type:'text',
placeholder:'add shortcut',
className:'pull-right'+((available||empty)?'':' alert alert-danger'),
@ -67,10 +64,10 @@ var KeyBinding = createClass({
onChange:that.handleShrtChange
}),
that.props.shortcuts ? that.props.shortcuts.map(function (item, index) {
return createElement('span', {className: 'pull-right'},
createElement('kbd', {}, [
return React.createElement('span', {className: 'pull-right'},
React.createElement('kbd', {}, [
item.h,
createElement('i', {className: "fa fa-times", alt: 'remove '+item.h,
React.createElement('i', {className: "fa fa-times", alt: 'remove '+item.h,
onClick:function () {
that.props.unbind(item.raw);
}
@ -78,13 +75,13 @@ var KeyBinding = createClass({
])
);
}): null,
createElement('div', {title: '(' + that.props.ckey + ')' ,
React.createElement('div', {title: '(' + that.props.ckey + ')' ,
className:'jupyter-keybindings-text'}, that.props.display )
);
}
});
var KeyBindingList = createClass({
var KeyBindingList = createReactClass({
displayName: 'KeyBindingList',
getInitialState: function(){
return {data:[]};
@ -95,7 +92,7 @@ var KeyBindingList = createClass({
render: function() {
var that = this;
var children = this.state.data.map(function (binding) {
return createElement(KeyBinding, Object.assign({}, binding, {
return React.createElement(KeyBinding, Object.assign({}, binding, {
onAddBindings: function (shortcut, action) {
that.props.bind(shortcut, action);
that.setState({data:that.props.callback()});
@ -107,7 +104,7 @@ var KeyBindingList = createClass({
}
}));
});
children.unshift(createElement('div', {className:'well', key:'disclamer', id:'short-key-binding-intro', dangerouslySetInnerHTML:
children.unshift(React.createElement('div', {className:'well', key:'disclamer', id:'short-key-binding-intro', dangerouslySetInnerHTML:
{__html:
marked(
@ -116,7 +113,7 @@ var KeyBindingList = createClass({
"See more [**details of defining keyboard shortcuts**](#long-key-binding-intro) below."
)}
}));
children.push(createElement('div', {className:'well', key:'disclamer', id:'long-key-binding-intro', dangerouslySetInnerHTML:
children.push(React.createElement('div', {className:'well', key:'disclamer', id:'long-key-binding-intro', dangerouslySetInnerHTML:
{__html:
marked(
@ -165,7 +162,7 @@ var KeyBindingList = createClass({
"Changing the keybindings of edit mode is not currently available."
)}
}));
return createElement('div',{}, children);
return React.createElement('div',{}, children);
}
});
@ -217,8 +214,8 @@ var ShortcutEditor = function(notebook) {
mod.addClass("modal_stretch");
mod.modal('show');
render(
createElement(KeyBindingList, {
ReactDOM.render(
React.createElement(KeyBindingList, {
callback: function () { return get_shortcuts_data(notebook);},
bind: function (shortcut, command) {
return notebook.keyboard_manager.command_shortcuts._persist_shortcut(shortcut, command);

@ -383,18 +383,28 @@ define([
breadcrumb.empty();
var list_item = $('<li/>');
var root_url = utils.url_path_join(that.base_url, '/tree');
var root = $('<li/>').append('<a href="' + root_url + '"><i class="fa fa-folder"></i></a>').click(function(e) {
// Allow the default browser action when the user holds a modifier (e.g., Ctrl-Click)
if(e.altKey || e.metaKey || e.shiftKey) {
return true;
}
var path = '';
window.history.pushState({
path: path
}, 'Home', utils.url_path_join(that.base_url, 'tree'));
that.update_location(path);
return false;
});
var root = $('<li/>').append(
$("<a/>")
.attr('href', root_url)
.append(
$("<i/>")
.addClass('fa fa-folder')
)
.click(function(e) {
// Allow the default browser action when the user holds a modifier (e.g., Ctrl-Click)
if(e.altKey || e.metaKey || e.shiftKey) {
return true;
}
var path = '';
window.history.pushState(
{path: path},
'Home',
utils.url_path_join(that.base_url, 'tree')
);
that.update_location(path);
return false;
})
);
breadcrumb.append(root);
var path_parts = [];
this.notebook_path.split('/').forEach(function(path_part) {
@ -405,17 +415,24 @@ define([
'/tree',
utils.encode_uri_components(path)
);
var crumb = $('<li/>').append('<a href="' + url + '">' + path_part + '</a>').click(function(e) {
// Allow the default browser action when the user holds a modifier (e.g., Ctrl-Click)
if(e.altKey || e.metaKey || e.shiftKey) {
return true;
}
window.history.pushState({
path: path
}, path, url);
that.update_location(path);
return false;
});
var crumb = $('<li/>').append(
$('<a/>')
.attr('href', url)
.text(path_part)
.click(function(e) {
// Allow the default browser action when the user holds a modifier (e.g., Ctrl-Click)
if(e.altKey || e.metaKey || e.shiftKey) {
return true;
}
window.history.pushState(
{path: path},
path,
url
);
that.update_location(path);
return false;
})
);
breadcrumb.append(crumb);
});
this.contents.list_contents(that.notebook_path).then(

@ -0,0 +1,18 @@
{# This template is not served, but written as a file to open in the browser,
passing the token without putting it in a command-line argument. #}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="1;url={{ open_url }}" />
<title>Opening Jupyter Notebook</title>
</head>
<body>
<p>
This page should redirect you to Jupyter Notebook. If it doesn't,
<a href="{{ open_url }}">click here to go to Jupyter</a>.
</p>
</body>
</html>

@ -109,15 +109,7 @@ data-notebook-path="{{notebook_path | urlencode}}"
<li id="print_preview"><a href="#">{% trans %}Print Preview{% endtrans %}</a></li>
<li class="dropdown-submenu"><a href="#">{% trans %}Download as{% endtrans %}</a>
<ul id="download_menu" class="dropdown-menu">
<li id="download_ipynb"><a href="#">{% trans %}Notebook (.ipynb){% endtrans %}</a></li>
<li id="download_script"><a href="#">{% trans %}Script{% endtrans %}</a></li>
<li id="download_html"><a href="#">{% trans %}HTML (.html){% endtrans %}</a></li>
<li id="download_slides"><a href="#">{% trans %}Reveal.js slides (.html){% endtrans %}</a></li>
<li id="download_markdown"><a href="#">{% trans %}Markdown (.md){% endtrans %}</a></li>
<li id="download_rst"><a href="#">{% trans %}reST (.rst){% endtrans %}</a></li>
<li id="download_latex"><a href="#">{% trans %}LaTeX (.tex){% endtrans %}</a></li>
<li id="download_pdf"><a href="#">{% trans %}PDF via LaTeX (.pdf){% endtrans %}</a></li>
{% for exporter in get_custom_frontend_exporters() %}
{% for exporter in get_frontend_exporters() %}
<li id="download_{{ exporter.name }}">
<a href="#">{{ exporter.display }}</a>
</li>

@ -16,9 +16,9 @@
{% endblock %}
<link rel="stylesheet" href="{{ base_url }}custom/custom.css" type="text/css" />
<script src="{{static_url("components/es6-promise/promise.min.js")}}" type="text/javascript" charset="utf-8"></script>
<script src="{{static_url('components/preact/index.js')}}" type="text/javascript"></script>
<script src="{{static_url('components/proptypes/index.js')}}" type="text/javascript"></script>
<script src="{{static_url('components/preact-compat/index.js')}}" type="text/javascript"></script>
<script src="{{static_url('components/react/react.production.min.js')}}" type="text/javascript"></script>
<script src="{{static_url('components/react/react-dom.production.min.js')}}" type="text/javascript"></script>
<script src="{{static_url('components/create-react-class/index.js')}}" type="text/javascript"></script>
<script src="{{static_url('components/requirejs/require.js') }}" type="text/javascript" charset="utf-8"></script>
<script>
require.config({
@ -37,7 +37,7 @@
jquery: 'components/jquery/jquery.min',
json: 'components/requirejs-plugins/src/json',
text: 'components/requirejs-text/text',
bootstrap: 'components/bootstrap/js/bootstrap.min',
bootstrap: 'components/bootstrap/dist/js/bootstrap.min',
bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
'jquery-ui': 'components/jquery-ui/jquery-ui.min',
moment: 'components/moment/min/moment-with-locales',

@ -25,6 +25,8 @@ from notebook import notebookapp, __version__
from notebook.auth.security import passwd_check
NotebookApp = notebookapp.NotebookApp
from .launchnotebook import NotebookTestBase
def test_help_output():
"""ipython notebook --help-all works"""
@ -183,3 +185,10 @@ def test_notebook_stop():
app.start()
nt.assert_equal(exc.exception.code, 1)
nt.assert_equal(len(app.servers_shut_down), 0)
class NotebookAppTests(NotebookTestBase):
def test_list_running_servers(self):
servers = list(notebookapp.list_running_servers())
assert len(servers) >= 1
assert self.port in {info['port'] for info in servers}

@ -13,11 +13,30 @@ import sys
from distutils.version import LooseVersion
try:
from urllib.parse import quote, unquote, urlparse
from inspect import isawaitable
except ImportError:
from urllib import quote, unquote
from urlparse import urlparse
def isawaitable(f):
"""If isawaitable is undefined, nothing is awaitable"""
return False
try:
from concurrent.futures import Future as ConcurrentFuture
except ImportError:
class ConcurrentFuture:
"""If concurrent.futures isn't importable, nothing will be a c.f.Future"""
pass
try:
from urllib.parse import quote, unquote, urlparse, urljoin
from urllib.request import pathname2url
except ImportError:
from urllib import quote, unquote, pathname2url
from urlparse import urlparse, urljoin
# tornado.concurrent.Future is asyncio.Future
# in tornado >=5 with Python 3
from tornado.concurrent import Future as TornadoFuture
from tornado import gen
from ipython_genutils import py3compat
# UF_HIDDEN is a stat flag not defined in the stat module.
@ -305,3 +324,33 @@ if sys.platform == 'win32':
check_pid = _check_pid_win32
else:
check_pid = _check_pid_posix
def maybe_future(obj):
"""Like tornado's gen.maybe_future
but more compatible with asyncio for recent versions
of tornado
"""
if isinstance(obj, TornadoFuture):
return obj
elif isawaitable(obj):
return asyncio.ensure_future(obj)
elif isinstance(obj, ConcurrentFuture):
return asyncio.wrap_future(obj)
else:
# not awaitable, wrap scalar in future
f = TornadoFuture()
f.set_result(obj)
return f
# monkeypatch tornado gen.maybe_future
# on Python 3
# TODO: remove monkeypatch after backporting smaller fix to 5.x
try:
import asyncio
except ImportError:
pass
else:
import tornado.gen
tornado.gen.maybe_future = maybe_future

@ -11,13 +11,22 @@
"scripts": {
"bower": "bower install",
"build": "python setup.py js css",
"build:watch": "set -e; while true; do npm run build; sleep 3; done"
"build:webpack": "webpack --mode production",
"build:watch": "npm run watch",
"watch": "onchange 'notebook/static/**/!(*.min).js' 'notebook/static/**/*.less' 'bower.json' -- npm run build"
},
"devDependencies": {
"bower": "*",
"@babel/core": "^7.15.0",
"@babel/preset-env": "^7.15.0",
"@jupyterlab/apputils": "^3.1.3",
"babel-loader": "^8.2.2",
"babel-polyfill": "^6.26.0",
"bower": "^1.8.8",
"less": "~2",
"requirejs": "^2.1.17",
"po2json": "^0.4.5"
},
"dependencies": {}
"onchange": "^6.0.0",
"po2json": "^0.4.5",
"requirejs": "^2.3.6",
"webpack": "^5.46.0",
"webpack-cli": "^4.7.2"
}
}

@ -78,17 +78,17 @@ for more information.
],
zip_safe = False,
install_requires = [
'jinja2',
'tornado>=4',
'jinja2<=3.0.0',
'tornado>=4.1,<7',
# pyzmq>=17 is not technically necessary,
# but hopefully avoids incompatibilities with Tornado 5. April 2018
'pyzmq>=17',
'ipython_genutils',
'traitlets>=4.2.1',
'jupyter_core>=4.4.0',
'jupyter_client>=5.2.0',
'jupyter_client>=5.2.0,<7.0.0',
'nbformat',
'nbconvert',
'nbconvert<6.0',
'ipykernel', # bless IPython kernel for now
'Send2Trash',
'terminado>=0.8.1',

@ -133,13 +133,13 @@ def find_package_data():
# (there are lots of resources we bundle for sdist-reasons that we don't actually use)
static_data.extend([
pjoin(components, "backbone", "backbone-min.js"),
pjoin(components, "bootstrap", "js", "bootstrap.min.js"),
pjoin(components, "bootstrap", "dist", "js", "bootstrap.min.js"),
pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
pjoin(components, "create-react-class", "index.js"),
pjoin(components, "font-awesome", "css", "*.css"),
pjoin(components, "es6-promise", "*.js"),
pjoin(components, "font-awesome", "fonts", "*.*"),
pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
pjoin(components, "jed", "jed.js"),
pjoin(components, "jquery", "jquery.min.js"),
pjoin(components, "jquery-typeahead", "dist", "jquery.typeahead.min.js"),
@ -148,12 +148,11 @@ def find_package_data():
pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
pjoin(components, "marked", "lib", "marked.js"),
pjoin(components, "preact", "index.js"),
pjoin(components, "preact-compat", "index.js"),
pjoin(components, "proptypes", "index.js"),
pjoin(components, "react", "react.production.min.js"),
pjoin(components, "requirejs", "require.js"),
pjoin(components, "requirejs-plugins", "src", "json.js"),
pjoin(components, "requirejs-text", "text.js"),
pjoin(components, "sanitizer", "index.js"),
pjoin(components, "underscore", "underscore-min.js"),
pjoin(components, "moment", "moment.js"),
pjoin(components, "moment", "min", "*.js"),
@ -377,14 +376,21 @@ class Bower(Command):
bower_dir = pjoin(static, 'components')
node_modules = pjoin(repo_root, 'node_modules')
sanitizer_dir = pjoin(bower_dir, 'sanitizer')
def should_run(self):
if self.force:
return True
if not os.path.exists(self.bower_dir):
return True
return mtime(self.bower_dir) < mtime(pjoin(repo_root, 'bower.json'))
if not os.path.exists(self.sanitizer_dir):
return True
bower_stale = mtime(self.bower_dir) < mtime(pjoin(repo_root, 'bower.json'))
if bower_stale:
return True
return mtime(self.sanitizer_dir) < mtime(pjoin(repo_root, 'webpack.config.js'))
def should_run_npm(self):
if not which('npm'):
@ -418,6 +424,8 @@ class Bower(Command):
print("You can install js dependencies with `npm install`", file=sys.stderr)
raise
# self.npm_components()
if not os.path.exists(self.sanitizer_dir):
run(['npm', 'run', 'build:webpack'], cwd=repo_root, env=env)
os.utime(self.bower_dir, None)
# update package data in case this created new files
update_package_data(self.distribution)

@ -19,7 +19,7 @@ var rjs_config = {
jquery: 'components/jquery/jquery.min',
json: 'components/requirejs-plugins/src/json',
text: 'components/requirejs-text/text',
bootstrap: 'components/bootstrap/js/bootstrap.min',
bootstrap: 'components/bootstrap/dist/js/bootstrap.min',
bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
"jquery-ui": 'components/jquery-ui/jquery-ui.min',
moment: 'components/moment/min/moment-with-locales',

@ -0,0 +1,34 @@
const path = require('path');
const crypto = require('crypto');
// Workaround for loaders using "md4" by default, which is not supported in FIPS-compliant OpenSSL
// See https://github.com/jupyterlab/jupyterlab/issues/11248
const cryptoOrigCreateHash = crypto.createHash;
crypto.createHash = (algorithm) =>
cryptoOrigCreateHash(algorithm == 'md4' ? 'sha256' : algorithm);
module.exports = {
entry: ['babel-polyfill', '@jupyterlab/apputils/lib/sanitizer'],
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'notebook/static/components/sanitizer'),
libraryTarget: "amd",
},
devtool: false,
optimization: {
minimize: false
},
module: {
rules: [
{
test: /\.m?jsx?$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
}
}
}
]
}
}
Loading…
Cancel
Save