We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Unknown error with no read permissions
If you do not have read permission on a notebook (e.g., it has `-rw-------` and you are not the owner) and you try to open it, rather than saying permission denied, you get an error that implies there's something wrong with the notebook format:

![image](https://cloud.githubusercontent.com/assets/83444/6356023/53956f6e-bc0f-11e4-884b-aea89261a9e2.png)

In the notebook server, there is an unhandled permission denied traceback:

```
[E 11:09:31.650 NotebookApp] Unhandled error in API request
    Traceback (most recent call last):
      File "/Users/jhamrick/project/tools/ipython/IPython/html/base/handlers.py", line 365, in wrapper
        result = yield gen.maybe_future(method(self, *args, **kwargs))
      File "/usr/local/lib/python2.7/site-packages/tornado/gen.py", line 807, in run
        value = future.result()
      File "/usr/local/lib/python2.7/site-packages/tornado/concurrent.py", line 209, in result
        raise_exc_info(self._exc_info)
      File "/usr/local/lib/python2.7/site-packages/tornado/gen.py", line 212, in wrapper
        yielded = next(result)
      File "/Users/jhamrick/project/tools/ipython/IPython/html/services/contents/handlers.py", line 126, in get
        path=path, type=type, format=format, content=content,
      File "/Users/jhamrick/project/tools/ipython/IPython/html/services/contents/filemanager.py", line 347, in get
        model = self._notebook_model(path, content=content)
      File "/Users/jhamrick/project/tools/ipython/IPython/html/services/contents/filemanager.py", line 306, in _notebook_model
        nb = self._read_notebook(os_path, as_version=4)
      File "/Users/jhamrick/project/tools/ipython/IPython/html/services/contents/fileio.py", line 104, in _read_notebook
        with self.open(os_path, 'r', encoding='utf-8') as f:
      File "/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py", line 17, in __enter__
        return self.gen.next()
      File "/Users/jhamrick/project/tools/ipython/IPython/html/services/contents/fileio.py", line 49, in open
        with io.open(os_path, *args, **kwargs) as f:
    IOError: [Errno 13] Permission denied: '/Users/jhamrick/project/ipynb/Untitled9.ipynb'
```

Perhaps it would be possible to return a more informative generic error message, like just the last line of the traceback or something along those lines?


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `IPython/html/services/contents/fileio.py`
Content:
```
1 """
2 Utilities for file-based Contents/Checkpoints managers.
3 """
4 
5 # Copyright (c) IPython Development Team.
6 # Distributed under the terms of the Modified BSD License.
7 
8 import base64
9 from contextlib import contextmanager
10 import errno
11 import io
12 import os
13 import shutil
14 
15 from tornado.web import HTTPError
16 
17 from IPython.html.utils import (
18     to_api_path,
19     to_os_path,
20 )
21 from IPython import nbformat
22 from IPython.utils.io import atomic_writing
23 from IPython.utils.py3compat import str_to_unicode
24 
25 
26 class FileManagerMixin(object):
27     """
28     Mixin for ContentsAPI classes that interact with the filesystem.
29 
30     Provides facilities for reading, writing, and copying both notebooks and
31     generic files.
32 
33     Shared by FileContentsManager and FileCheckpoints.
34 
35     Note
36     ----
37     Classes using this mixin must provide the following attributes:
38 
39     root_dir : unicode
40         A directory against against which API-style paths are to be resolved.
41 
42     log : logging.Logger
43     """
44 
45     @contextmanager
46     def open(self, os_path, *args, **kwargs):
47         """wrapper around io.open that turns permission errors into 403"""
48         with self.perm_to_403(os_path):
49             with io.open(os_path, *args, **kwargs) as f:
50                 yield f
51 
52     @contextmanager
53     def atomic_writing(self, os_path, *args, **kwargs):
54         """wrapper around atomic_writing that turns permission errors to 403"""
55         with self.perm_to_403(os_path):
56             with atomic_writing(os_path, *args, **kwargs) as f:
57                 yield f
58 
59     @contextmanager
60     def perm_to_403(self, os_path=''):
61         """context manager for turning permission errors into 403."""
62         try:
63             yield
64         except OSError as e:
65             if e.errno in {errno.EPERM, errno.EACCES}:
66                 # make 403 error message without root prefix
67                 # this may not work perfectly on unicode paths on Python 2,
68                 # but nobody should be doing that anyway.
69                 if not os_path:
70                     os_path = str_to_unicode(e.filename or 'unknown file')
71                 path = to_api_path(os_path, root=self.root_dir)
72                 raise HTTPError(403, u'Permission denied: %s' % path)
73             else:
74                 raise
75 
76     def _copy(self, src, dest):
77         """copy src to dest
78 
79         like shutil.copy2, but log errors in copystat
80         """
81         shutil.copyfile(src, dest)
82         try:
83             shutil.copystat(src, dest)
84         except OSError:
85             self.log.debug("copystat on %s failed", dest, exc_info=True)
86 
87     def _get_os_path(self, path):
88         """Given an API path, return its file system path.
89 
90         Parameters
91         ----------
92         path : string
93             The relative API path to the named file.
94 
95         Returns
96         -------
97         path : string
98             Native, absolute OS path to for a file.
99         """
100         return to_os_path(path, self.root_dir)
101 
102     def _read_notebook(self, os_path, as_version=4):
103         """Read a notebook from an os path."""
104         with self.open(os_path, 'r', encoding='utf-8') as f:
105             try:
106                 return nbformat.read(f, as_version=as_version)
107             except Exception as e:
108                 raise HTTPError(
109                     400,
110                     u"Unreadable Notebook: %s %r" % (os_path, e),
111                 )
112 
113     def _save_notebook(self, os_path, nb):
114         """Save a notebook to an os_path."""
115         with self.atomic_writing(os_path, encoding='utf-8') as f:
116             nbformat.write(nb, f, version=nbformat.NO_CONVERT)
117 
118     def _read_file(self, os_path, format):
119         """Read a non-notebook file.
120 
121         os_path: The path to be read.
122         format:
123           If 'text', the contents will be decoded as UTF-8.
124           If 'base64', the raw bytes contents will be encoded as base64.
125           If not specified, try to decode as UTF-8, and fall back to base64
126         """
127         if not os.path.isfile(os_path):
128             raise HTTPError(400, "Cannot read non-file %s" % os_path)
129 
130         with self.open(os_path, 'rb') as f:
131             bcontent = f.read()
132 
133         if format is None or format == 'text':
134             # Try to interpret as unicode if format is unknown or if unicode
135             # was explicitly requested.
136             try:
137                 return bcontent.decode('utf8'), 'text'
138             except UnicodeError:
139                 if format == 'text':
140                     raise HTTPError(
141                         400,
142                         "%s is not UTF-8 encoded" % os_path,
143                         reason='bad format',
144                     )
145         return base64.encodestring(bcontent).decode('ascii'), 'base64'
146 
147     def _save_file(self, os_path, content, format):
148         """Save content of a generic file."""
149         if format not in {'text', 'base64'}:
150             raise HTTPError(
151                 400,
152                 "Must specify format of file contents as 'text' or 'base64'",
153             )
154         try:
155             if format == 'text':
156                 bcontent = content.encode('utf8')
157             else:
158                 b64_bytes = content.encode('ascii')
159                 bcontent = base64.decodestring(b64_bytes)
160         except Exception as e:
161             raise HTTPError(
162                 400, u'Encoding error saving %s: %s' % (os_path, e)
163             )
164 
165         with self.atomic_writing(os_path, text=False) as f:
166             f.write(bcontent)
167
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/IPython/html/services/contents/fileio.py b/IPython/html/services/contents/fileio.py
--- a/IPython/html/services/contents/fileio.py
+++ b/IPython/html/services/contents/fileio.py
@@ -61,7 +61,7 @@
         """context manager for turning permission errors into 403."""
         try:
             yield
-        except OSError as e:
+        except (OSError, IOError) as e:
             if e.errno in {errno.EPERM, errno.EACCES}:
                 # make 403 error message without root prefix
                 # this may not work perfectly on unicode paths on Python 2,

{"golden_diff": "diff --git a/IPython/html/services/contents/fileio.py b/IPython/html/services/contents/fileio.py\n--- a/IPython/html/services/contents/fileio.py\n+++ b/IPython/html/services/contents/fileio.py\n@@ -61,7 +61,7 @@\n         \"\"\"context manager for turning permission errors into 403.\"\"\"\n         try:\n             yield\n-        except OSError as e:\n+        except (OSError, IOError) as e:\n             if e.errno in {errno.EPERM, errno.EACCES}:\n                 # make 403 error message without root prefix\n                 # this may not work perfectly on unicode paths on Python 2,\n", "issue": "Unknown error with no read permissions\nIf you do not have read permission on a notebook (e.g., it has `-rw-------` and you are not the owner) and you try to open it, rather than saying permission denied, you get an error that implies there's something wrong with the notebook format:\n\n![image](https://cloud.githubusercontent.com/assets/83444/6356023/53956f6e-bc0f-11e4-884b-aea89261a9e2.png)\n\nIn the notebook server, there is an unhandled permission denied traceback:\n\n```\n[E 11:09:31.650 NotebookApp] Unhandled error in API request\n    Traceback (most recent call last):\n      File \"/Users/jhamrick/project/tools/ipython/IPython/html/base/handlers.py\", line 365, in wrapper\n        result = yield gen.maybe_future(method(self, *args, **kwargs))\n      File \"/usr/local/lib/python2.7/site-packages/tornado/gen.py\", line 807, in run\n        value = future.result()\n      File \"/usr/local/lib/python2.7/site-packages/tornado/concurrent.py\", line 209, in result\n        raise_exc_info(self._exc_info)\n      File \"/usr/local/lib/python2.7/site-packages/tornado/gen.py\", line 212, in wrapper\n        yielded = next(result)\n      File \"/Users/jhamrick/project/tools/ipython/IPython/html/services/contents/handlers.py\", line 126, in get\n        path=path, type=type, format=format, content=content,\n      File \"/Users/jhamrick/project/tools/ipython/IPython/html/services/contents/filemanager.py\", line 347, in get\n        model = self._notebook_model(path, content=content)\n      File \"/Users/jhamrick/project/tools/ipython/IPython/html/services/contents/filemanager.py\", line 306, in _notebook_model\n        nb = self._read_notebook(os_path, as_version=4)\n      File \"/Users/jhamrick/project/tools/ipython/IPython/html/services/contents/fileio.py\", line 104, in _read_notebook\n        with self.open(os_path, 'r', encoding='utf-8') as f:\n      File \"/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py\", line 17, in __enter__\n        return self.gen.next()\n      File \"/Users/jhamrick/project/tools/ipython/IPython/html/services/contents/fileio.py\", line 49, in open\n        with io.open(os_path, *args, **kwargs) as f:\n    IOError: [Errno 13] Permission denied: '/Users/jhamrick/project/ipynb/Untitled9.ipynb'\n```\n\nPerhaps it would be possible to return a more informative generic error message, like just the last line of the traceback or something along those lines?\n\n", "before_files": [{"content": "\"\"\"\nUtilities for file-based Contents/Checkpoints managers.\n\"\"\"\n\n# Copyright (c) IPython Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nimport base64\nfrom contextlib import contextmanager\nimport errno\nimport io\nimport os\nimport shutil\n\nfrom tornado.web import HTTPError\n\nfrom IPython.html.utils import (\n    to_api_path,\n    to_os_path,\n)\nfrom IPython import nbformat\nfrom IPython.utils.io import atomic_writing\nfrom IPython.utils.py3compat import str_to_unicode\n\n\nclass FileManagerMixin(object):\n    \"\"\"\n    Mixin for ContentsAPI classes that interact with the filesystem.\n\n    Provides facilities for reading, writing, and copying both notebooks and\n    generic files.\n\n    Shared by FileContentsManager and FileCheckpoints.\n\n    Note\n    ----\n    Classes using this mixin must provide the following attributes:\n\n    root_dir : unicode\n        A directory against against which API-style paths are to be resolved.\n\n    log : logging.Logger\n    \"\"\"\n\n    @contextmanager\n    def open(self, os_path, *args, **kwargs):\n        \"\"\"wrapper around io.open that turns permission errors into 403\"\"\"\n        with self.perm_to_403(os_path):\n            with io.open(os_path, *args, **kwargs) as f:\n                yield f\n\n    @contextmanager\n    def atomic_writing(self, os_path, *args, **kwargs):\n        \"\"\"wrapper around atomic_writing that turns permission errors to 403\"\"\"\n        with self.perm_to_403(os_path):\n            with atomic_writing(os_path, *args, **kwargs) as f:\n                yield f\n\n    @contextmanager\n    def perm_to_403(self, os_path=''):\n        \"\"\"context manager for turning permission errors into 403.\"\"\"\n        try:\n            yield\n        except OSError as e:\n            if e.errno in {errno.EPERM, errno.EACCES}:\n                # make 403 error message without root prefix\n                # this may not work perfectly on unicode paths on Python 2,\n                # but nobody should be doing that anyway.\n                if not os_path:\n                    os_path = str_to_unicode(e.filename or 'unknown file')\n                path = to_api_path(os_path, root=self.root_dir)\n                raise HTTPError(403, u'Permission denied: %s' % path)\n            else:\n                raise\n\n    def _copy(self, src, dest):\n        \"\"\"copy src to dest\n\n        like shutil.copy2, but log errors in copystat\n        \"\"\"\n        shutil.copyfile(src, dest)\n        try:\n            shutil.copystat(src, dest)\n        except OSError:\n            self.log.debug(\"copystat on %s failed\", dest, exc_info=True)\n\n    def _get_os_path(self, path):\n        \"\"\"Given an API path, return its file system path.\n\n        Parameters\n        ----------\n        path : string\n            The relative API path to the named file.\n\n        Returns\n        -------\n        path : string\n            Native, absolute OS path to for a file.\n        \"\"\"\n        return to_os_path(path, self.root_dir)\n\n    def _read_notebook(self, os_path, as_version=4):\n        \"\"\"Read a notebook from an os path.\"\"\"\n        with self.open(os_path, 'r', encoding='utf-8') as f:\n            try:\n                return nbformat.read(f, as_version=as_version)\n            except Exception as e:\n                raise HTTPError(\n                    400,\n                    u\"Unreadable Notebook: %s %r\" % (os_path, e),\n                )\n\n    def _save_notebook(self, os_path, nb):\n        \"\"\"Save a notebook to an os_path.\"\"\"\n        with self.atomic_writing(os_path, encoding='utf-8') as f:\n            nbformat.write(nb, f, version=nbformat.NO_CONVERT)\n\n    def _read_file(self, os_path, format):\n        \"\"\"Read a non-notebook file.\n\n        os_path: The path to be read.\n        format:\n          If 'text', the contents will be decoded as UTF-8.\n          If 'base64', the raw bytes contents will be encoded as base64.\n          If not specified, try to decode as UTF-8, and fall back to base64\n        \"\"\"\n        if not os.path.isfile(os_path):\n            raise HTTPError(400, \"Cannot read non-file %s\" % os_path)\n\n        with self.open(os_path, 'rb') as f:\n            bcontent = f.read()\n\n        if format is None or format == 'text':\n            # Try to interpret as unicode if format is unknown or if unicode\n            # was explicitly requested.\n            try:\n                return bcontent.decode('utf8'), 'text'\n            except UnicodeError:\n                if format == 'text':\n                    raise HTTPError(\n                        400,\n                        \"%s is not UTF-8 encoded\" % os_path,\n                        reason='bad format',\n                    )\n        return base64.encodestring(bcontent).decode('ascii'), 'base64'\n\n    def _save_file(self, os_path, content, format):\n        \"\"\"Save content of a generic file.\"\"\"\n        if format not in {'text', 'base64'}:\n            raise HTTPError(\n                400,\n                \"Must specify format of file contents as 'text' or 'base64'\",\n            )\n        try:\n            if format == 'text':\n                bcontent = content.encode('utf8')\n            else:\n                b64_bytes = content.encode('ascii')\n                bcontent = base64.decodestring(b64_bytes)\n        except Exception as e:\n            raise HTTPError(\n                400, u'Encoding error saving %s: %s' % (os_path, e)\n            )\n\n        with self.atomic_writing(os_path, text=False) as f:\n            f.write(bcontent)\n", "path": "IPython/html/services/contents/fileio.py"}], "after_files": [{"content": "\"\"\"\nUtilities for file-based Contents/Checkpoints managers.\n\"\"\"\n\n# Copyright (c) IPython Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nimport base64\nfrom contextlib import contextmanager\nimport errno\nimport io\nimport os\nimport shutil\n\nfrom tornado.web import HTTPError\n\nfrom IPython.html.utils import (\n    to_api_path,\n    to_os_path,\n)\nfrom IPython import nbformat\nfrom IPython.utils.io import atomic_writing\nfrom IPython.utils.py3compat import str_to_unicode\n\n\nclass FileManagerMixin(object):\n    \"\"\"\n    Mixin for ContentsAPI classes that interact with the filesystem.\n\n    Provides facilities for reading, writing, and copying both notebooks and\n    generic files.\n\n    Shared by FileContentsManager and FileCheckpoints.\n\n    Note\n    ----\n    Classes using this mixin must provide the following attributes:\n\n    root_dir : unicode\n        A directory against against which API-style paths are to be resolved.\n\n    log : logging.Logger\n    \"\"\"\n\n    @contextmanager\n    def open(self, os_path, *args, **kwargs):\n        \"\"\"wrapper around io.open that turns permission errors into 403\"\"\"\n        with self.perm_to_403(os_path):\n            with io.open(os_path, *args, **kwargs) as f:\n                yield f\n\n    @contextmanager\n    def atomic_writing(self, os_path, *args, **kwargs):\n        \"\"\"wrapper around atomic_writing that turns permission errors to 403\"\"\"\n        with self.perm_to_403(os_path):\n            with atomic_writing(os_path, *args, **kwargs) as f:\n                yield f\n\n    @contextmanager\n    def perm_to_403(self, os_path=''):\n        \"\"\"context manager for turning permission errors into 403.\"\"\"\n        try:\n            yield\n        except (OSError, IOError) as e:\n            if e.errno in {errno.EPERM, errno.EACCES}:\n                # make 403 error message without root prefix\n                # this may not work perfectly on unicode paths on Python 2,\n                # but nobody should be doing that anyway.\n                if not os_path:\n                    os_path = str_to_unicode(e.filename or 'unknown file')\n                path = to_api_path(os_path, root=self.root_dir)\n                raise HTTPError(403, u'Permission denied: %s' % path)\n            else:\n                raise\n\n    def _copy(self, src, dest):\n        \"\"\"copy src to dest\n\n        like shutil.copy2, but log errors in copystat\n        \"\"\"\n        shutil.copyfile(src, dest)\n        try:\n            shutil.copystat(src, dest)\n        except OSError:\n            self.log.debug(\"copystat on %s failed\", dest, exc_info=True)\n\n    def _get_os_path(self, path):\n        \"\"\"Given an API path, return its file system path.\n\n        Parameters\n        ----------\n        path : string\n            The relative API path to the named file.\n\n        Returns\n        -------\n        path : string\n            Native, absolute OS path to for a file.\n        \"\"\"\n        return to_os_path(path, self.root_dir)\n\n    def _read_notebook(self, os_path, as_version=4):\n        \"\"\"Read a notebook from an os path.\"\"\"\n        with self.open(os_path, 'r', encoding='utf-8') as f:\n            try:\n                return nbformat.read(f, as_version=as_version)\n            except Exception as e:\n                raise HTTPError(\n                    400,\n                    u\"Unreadable Notebook: %s %r\" % (os_path, e),\n                )\n\n    def _save_notebook(self, os_path, nb):\n        \"\"\"Save a notebook to an os_path.\"\"\"\n        with self.atomic_writing(os_path, encoding='utf-8') as f:\n            nbformat.write(nb, f, version=nbformat.NO_CONVERT)\n\n    def _read_file(self, os_path, format):\n        \"\"\"Read a non-notebook file.\n\n        os_path: The path to be read.\n        format:\n          If 'text', the contents will be decoded as UTF-8.\n          If 'base64', the raw bytes contents will be encoded as base64.\n          If not specified, try to decode as UTF-8, and fall back to base64\n        \"\"\"\n        if not os.path.isfile(os_path):\n            raise HTTPError(400, \"Cannot read non-file %s\" % os_path)\n\n        with self.open(os_path, 'rb') as f:\n            bcontent = f.read()\n\n        if format is None or format == 'text':\n            # Try to interpret as unicode if format is unknown or if unicode\n            # was explicitly requested.\n            try:\n                return bcontent.decode('utf8'), 'text'\n            except UnicodeError:\n                if format == 'text':\n                    raise HTTPError(\n                        400,\n                        \"%s is not UTF-8 encoded\" % os_path,\n                        reason='bad format',\n                    )\n        return base64.encodestring(bcontent).decode('ascii'), 'base64'\n\n    def _save_file(self, os_path, content, format):\n        \"\"\"Save content of a generic file.\"\"\"\n        if format not in {'text', 'base64'}:\n            raise HTTPError(\n                400,\n                \"Must specify format of file contents as 'text' or 'base64'\",\n            )\n        try:\n            if format == 'text':\n                bcontent = content.encode('utf8')\n            else:\n                b64_bytes = content.encode('ascii')\n                bcontent = base64.decodestring(b64_bytes)\n        except Exception as e:\n            raise HTTPError(\n                400, u'Encoding error saving %s: %s' % (os_path, e)\n            )\n\n        with self.atomic_writing(os_path, text=False) as f:\n            f.write(bcontent)\n", "path": "IPython/html/services/contents/fileio.py"}]}