diff --git a/.vscode/launch.json b/.vscode/launch.json
index fb8f310ff731baaca8b5f9bafa1b1c2f74e222c4..109b8b6c5d6ff3e21505cc02f45126750e9688cb 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -44,7 +44,7 @@
       "cwd": "${workspaceFolder}",
       "program": "${workspaceFolder}/.pyenv/bin/pytest",
       "args": [
-        "-sv", "tests/test_api.py::TestRepo::test_search[2-user-other_test_user]"
+        "-sv", "tests/test_api.py::TestRaw::test_raw_file_from_calc"
       ]
     },
     {
diff --git a/gui/src/components/api.js b/gui/src/components/api.js
index f6fca331f0dc52084750f18ea5bf845aa715db3e..84d5018a16e688609234875aaef041e1295a13ec 100644
--- a/gui/src/components/api.js
+++ b/gui/src/components/api.js
@@ -242,6 +242,27 @@ class Api {
       .finally(this.onFinishLoading)
   }
 
+  async getRawFileListFromCalc(uploadId, calcId) {
+    this.onStartLoading()
+    return this.swaggerPromise
+      .then(client => {
+        try {
+          return client.apis.raw.get_file_list_from_calc({
+            upload_id: uploadId,
+            calc_id: calcId,
+            path: null
+          })
+        } catch (e) {
+          console.log(e)
+        }
+      })
+      .catch(this.handleApiError)
+      .then(response => {
+        return response.body
+      })
+      .finally(this.onFinishLoading)
+  }
+
   async repo(uploadId, calcId) {
     this.onStartLoading()
     return this.swaggerPromise
diff --git a/gui/src/components/entry/RawFiles.js b/gui/src/components/entry/RawFiles.js
index ced4189abe98f0d830a476bf82d48408039a9326..d408ca11009201c7cac20a2ca293a66b10e7f8f7 100644
--- a/gui/src/components/entry/RawFiles.js
+++ b/gui/src/components/entry/RawFiles.js
@@ -9,11 +9,11 @@ import Download from './Download'
 class RawFiles extends React.Component {
   static propTypes = {
     classes: PropTypes.object.isRequired,
-    uploadId: PropTypes.str.isRequired,
-    mainfile: PropTypes.str.isRequired,
+    data: PropTypes.object.isRequired,
     api: PropTypes.object.isRequired,
     user: PropTypes.object,
-    loading: PropTypes.number.isRequired
+    loading: PropTypes.number.isRequired,
+    raiseError: PropTypes.func.isRequired
   }
 
   static styles = theme => ({
@@ -25,6 +25,7 @@ class RawFiles extends React.Component {
 
   state = {
     selectedFiles: [],
+    uploadDirectory: null,
     files: null
   }
 
@@ -39,9 +40,9 @@ class RawFiles extends React.Component {
   }
 
   update() {
-    const {uploadId, mainfile} = this.props
-    this.props.api.raw_file_list(uploadId, mainfile).then(data => {
-      this.setState({files: data.files})
+    const {data: {uploadId, calcId}} = this.props
+    this.props.api.getRawFileListFromCalc(uploadId, calcId).then(data => {
+      this.setState({files: data.contents, uploadDirectory: data.directory})
     }).catch(error => {
       this.setState({files: null})
       this.props.raiseError(error)
@@ -49,7 +50,7 @@ class RawFiles extends React.Component {
   }
 
   label(file) {
-    return file.substring(file.lastIndexOf('/') + 1)
+    return file
   }
 
   onSelectFile(file) {
@@ -64,21 +65,10 @@ class RawFiles extends React.Component {
   }
 
   render() {
-    const {classes, uploadId, mainfile, loading} = this.props
-    const {selectedFiles, files} = this.state
+    const {classes, data: {upload_id, calc_id}, loading} = this.props
+    const {selectedFiles, files, uploadDirectory} = this.state
 
-    const mainfileLocal = mainfile.split('/')[-1]
-
-    let availableFiles = [
-      {
-        file: mainfileLocal,
-        size: -1
-      }
-    ]
-
-    if (files) {
-      const mainfileIndex = files.findIndex(file => file.file === mainfileLocal)
-    }
+    const availableFiles = files ? files.map(file => file.name) : []
 
     const someSelected = selectedFiles.length > 0
     const allSelected = availableFiles.length === selectedFiles.length && someSelected
@@ -100,7 +90,7 @@ class RawFiles extends React.Component {
           </FormLabel>
           <Download component={IconButton} disabled={selectedFiles.length === 0}
             tooltip="download selected files"
-            url={(selectedFiles.length === 1) ? `raw/${uploadId}/${selectedFiles[0]}` : `raw/${calc_id}?files=${encodeURIComponent(selectedFiles.join(','))}`}
+            url={(selectedFiles.length === 1) ? `raw/${upload_id}/${uploadDirectory}/${selectedFiles[0]}` : `raw/${upload_id}?files=${encodeURIComponent(selectedFiles.map(file => `${uploadDirectory}/${file}`).join(','))}`}
             fileName={selectedFiles.length === 1 ? this.label(selectedFiles[0]) : `${calc_id}.zip`}
           >
             <DownloadIcon />
diff --git a/nomad/api/raw.py b/nomad/api/raw.py
index ffa3564da995b213401a1529eb48e0131758ecdf..145170dd6b8e9df7b60c83adeced6874be11f305 100644
--- a/nomad/api/raw.py
+++ b/nomad/api/raw.py
@@ -16,19 +16,27 @@
 The raw API of the nomad@FAIRDI APIs. Can be used to retrieve raw calculation files.
 """
 
+from typing import IO, Any, Union
 import os.path
-from zipfile import ZIP_DEFLATED, ZIP_STORED
-
 import zipstream
 from flask import Response, request, send_file, stream_with_context
 from flask_restplus import abort, Resource, fields
+import magic
+import sys
 
 from nomad.files import UploadFiles, Restricted
+from nomad.processing import Calc
 
 from .app import api
 from .auth import login_if_available, create_authorization_predicate, \
     signature_token_argument, with_signature_token
 
+if sys.version_info >= (3, 7):
+    import zipfile
+else:
+    import zipfile37 as zipfile
+
+
 ns = api.namespace('raw', description='Downloading raw data files.')
 
 raw_file_list_model = api.model('RawFileList', {
@@ -46,65 +54,147 @@ raw_file_compress_argument = dict(
 raw_file_from_path_parser = api.parser()
 raw_file_from_path_parser.add_argument(**raw_file_compress_argument)
 raw_file_from_path_parser.add_argument(**signature_token_argument)
+raw_file_from_path_parser.add_argument(
+    name='length', type=int, help='Download only x bytes from the given file.',
+    location='args')
+raw_file_from_path_parser.add_argument(
+    name='offset', type=int, help='Start downloading a file\' content from the given offset.',
+    location='args')
 
 
-@ns.route('/list/<string:upload_id>/<path:directory>')
-@api.doc(params={
-    'upload_id': 'The unique id for the requested upload.',
-    'directory': 'The directory in the upload with the desired contents.'
-})
-@api.header('Content-Type', 'application/json')
-class RawFileList(Resource):
-    @api.doc('get')
-    @api.response(404, 'The upload or path does not exist')
-    @api.response(401, 'Not authorized to access the data.')
-    @api.response(200, 'File(s) send', headers={'Content-Type': 'application/json'})
-    @api.marshal_with(raw_file_list_model, skip_none=True, code=200, description='File list send')
-    @login_if_available
-    @with_signature_token
-    def get(self, upload_id: str, directory: str):
-        """
-        Get the contents of the given directory for the given upload.
-
-        If the path points to a file a single entry is returned. If the path
-        points to a directory, information on all files in the directory are returned.
-        """
+class FileView:
+    """
+    File-like wrapper that restricts the contents to a portion of the file.
+    Arguments:
+        f: the file-like
+        offset: the offset
+        length: the amount of bytes
+    """
+    def __init__(self, f, offset, length):
+        self.f = f
+        self.f_offset = offset
+        self.offset = 0
+        self.length = length
+
+    def seek(self, offset, whence=0):
+        if whence == os.SEEK_SET:
+            self.offset = offset
+        elif whence == os.SEEK_CUR:
+            self.offset += offset
+        elif whence == os.SEEK_END:
+            self.offset = self.length + offset
+        else:
+            # Other values of whence should raise an IOError
+            return self.f.seek(offset, whence)
+        return self.f.seek(self.offset + self.f_offset, os.SEEK_SET)
+
+    def tell(self):
+        return self.offset
+
+    def read(self, size=-1):
+        self.seek(self.offset)
+        if size < 0:
+            size = self.length - self.offset
+        size = max(0, min(size, self.length - self.offset))
+        self.offset += size
+        return self.f.read(size)
+
+
+def get_raw_file_from_upload_path(upload_files, upload_filepath, authorization_predicate):
+    """
+    Helper method used by func:`RawFileFromUploadPathResource.get` and
+    func:`RawFileFromCalcPathResource.get`.
+    """
+    if upload_filepath[-1:] == '*':
+        upload_filepath = upload_filepath[0:-1]
+        wildcarded_files = list(upload_files.raw_file_manifest(path_prefix=upload_filepath))
+        if len(wildcarded_files) == 0:
+            abort(404, message='There are no files for %s.' % upload_filepath)
+        else:
+            compress = request.args.get('compress', None) is not None
+            return respond_to_get_raw_files(upload_files.upload_id, wildcarded_files, compress)
 
-        upload_files = UploadFiles.get(upload_id, create_authorization_predicate(upload_id))
-        if upload_files is None:
-            abort(404, message='The upload with id %s does not exist.' % upload_id)
+    try:
+        with upload_files.raw_file(upload_filepath, 'br') as raw_file:
+            buffer = raw_file.read(2048)
+        mime_type = magic.from_buffer(buffer, mime=True)
 
-        files = upload_files.raw_file_list(directory=directory)
-        if len(files) == 0:
-            abort(404, message='There are no files for %s.' % directory)
+        try:
+            offset = int(request.args.get('offset', 0))
+            length = int(request.args.get('length', -1))
+        except Exception:
+            abort(400, message='bad parameter types')
+
+        if offset < 0:
+            abort(400, message='bad offset, length values')
+        if offset > 0 and length <= 0:
+            abort(400, message='bad offset, length values')
+
+        raw_file = upload_files.raw_file(upload_filepath, 'br')
+        raw_file_view: Union[FileView, IO[Any]] = None
+        if length > 0:
+            raw_file_view = FileView(raw_file, offset, length)
         else:
-            return {
-                'upload_id': upload_id,
-                'directory': directory,
-                'contents': [dict(file=file, size=size) for file, size in files]
-            }
+            raw_file_view = raw_file
+
+        return send_file(
+            raw_file_view,
+            mimetype=mime_type,
+            as_attachment=True,
+            attachment_filename=os.path.basename(upload_filepath))
+    except Restricted:
+        abort(401, message='Not authorized to access all files in %s.' % upload_files.upload_id)
+    except KeyError:
+        directory_files = upload_files.raw_file_list(upload_filepath)
+        if len(directory_files) == 0:
+            abort(404, message='There is nothing to be found at %s.' % upload_filepath)
+        return {
+            'upload_id': upload_files.upload_id,
+            'directory': upload_filepath,
+            'contents': [
+                dict(name=name, size=size) for name, size in directory_files]
+        }, 200
 
 
 @ns.route('/<string:upload_id>/<path:path>')
 @api.doc(params={
     'upload_id': 'The unique id for the requested upload.',
-    'path': 'The path to a file or directory.'
+    'path': 'The path to a file or directory with optional wildcard.'
 })
-@api.header('Content-Type', 'application/gz')
-class RawFileFromPathResource(Resource):
+class RawFileFromUploadPathResource(Resource):
     @api.doc('get')
     @api.response(404, 'The upload or path does not exist')
-    @api.response(401, 'Not authorized to access the data.')
-    @api.response(200, 'File(s) send', headers={'Content-Type': 'application/gz'})
+    @api.response(401, 'Not authorized to access the requested files.')
+    @api.response(200, 'File(s) send')
     @api.expect(raw_file_from_path_parser, validate=True)
     @login_if_available
     @with_signature_token
     def get(self, upload_id: str, path: str):
         """
-        Get a single raw calculation file or whole directory from a given upload.
-
-        If the given path points to a file, the file is provided. If the given path
-        points to an directory, the directory and all contents is provided as .zip file.
+        Get a single raw calculation file, directory contents, or whole directory sub-tree
+        from a given upload.
+
+        The 'upload_id' parameter needs to identify an existing upload.
+
+        If the upload
+        is not yet published or contains requested data with embargo, proper authentication
+        is required. This can be done via HTTP headers as usual. But, if you need to
+        access files via plain URLs (e.g. for curl, download link, etc.), URLs for
+        this endpoint can be token signed (see also /auth/token). For unpublished
+        uploads, authentication is required regardless. For (partially) embargoed data,
+        multi file downloads work, but will not contain any embargoed data.
+
+        If the given path points to a file, the file is provided with the appropriate
+        Content-Type header. A 401 is returned for staging, embargo files with unsigned
+        or wrongly signed URLs. When accessing a file, the additional query parameters 'length'
+        and 'offset' can be used to partially download a file's content.
+
+        If the given path points to a directory, the content (names, sizes, type) is returned
+        as a json body. Only visible items (depending on authenticated user, token) are
+        returned.
+
+        If the given path ends with the '*' wildcard character, all upload contents that
+        match the given path at the start, will be returned as a .zip file body.
         Zip files are streamed; instead of 401 errors, the zip file will just not contain
         any files that the user is not authorized to access.
         """
@@ -121,29 +211,67 @@ class RawFileFromPathResource(Resource):
         if upload_files is None:
             abort(404, message='The upload with id %s does not exist.' % upload_id)
 
-        if upload_filepath[-1:] == '*':
-            upload_filepath = upload_filepath[0:-1]
-            files = list(upload_files.raw_file_manifest(path_prefix=upload_filepath))
-            if len(files) == 0:
-                abort(404, message='There are no files for %s.' % upload_filepath)
-            else:
-                compress = request.args.get('compress', None) is not None
-                return respond_to_get_raw_files(upload_id, files, compress)
+        return get_raw_file_from_upload_path(upload_files, upload_filepath, authorization_predicate)
 
-        try:
-            return send_file(
-                upload_files.raw_file(upload_filepath, 'br'),
-                mimetype='application/octet-stream',
-                as_attachment=True,
-                attachment_filename=os.path.basename(upload_filepath))
-        except Restricted:
-            abort(401, message='Not authorized to access upload %s.' % upload_id)
-        except KeyError:
-            files = list(file for file in upload_files.raw_file_manifest(upload_filepath))
-            if len(files) == 0:
-                abort(404, message='The file %s does not exist.' % upload_filepath)
-            else:
-                abort(404, message='The file %s does not exist, but there are files with matching paths' % upload_filepath, files=files)
+
+@ns.route('/calc/<string:upload_id>/<string:calc_id>/<path:path>')
+@api.doc(params={
+    'upload_id': 'The unique id for the requested calc\'s upload.',
+    'calc_id': 'The unique calc id for the requested calc',
+    'path': 'The path to a file or directory with optional wildcard.'
+})
+class RawFileFromCalcPathResource(Resource):
+    @api.doc('get_file_from_calc')
+    @api.response(404, 'The upload or path does not exist')
+    @api.response(401, 'Not authorized to access the requested files.')
+    @api.response(200, 'File(s) send')
+    @api.expect(raw_file_from_path_parser, validate=True)
+    @login_if_available
+    @with_signature_token
+    def get(self, upload_id: str, calc_id: str, path: str):
+        """
+        Get a single raw calculation file, calculation contents, or all files for a
+        given calculation.
+
+        The 'upload_id' parameter needs to identify an existing upload.
+        The 'calc_id' parameter needs to identify a calculation within in the upload.
+
+        This endpoint behaves exactly like /raw/<upload_id>/<path>, but the path is
+        now relative to the calculation and not the upload.
+        """
+        calc_filepath = path if path is not None else ''
+        authorization_predicate = create_authorization_predicate(upload_id)
+        upload_files = UploadFiles.get(upload_id, authorization_predicate)
+        if upload_files is None:
+            abort(404, message='The upload with id %s does not exist.' % upload_id)
+
+        calc = Calc.get(calc_id)
+        if calc is None:
+            abort(404, message='The calc with id %s does not exist.' % calc_id)
+        if calc.upload_id != upload_id:
+            abort(404, message='The calc with id %s is not part of the upload with id %s.' % (calc_id, upload_id))
+
+        upload_filepath = os.path.join(os.path.dirname(calc.mainfile), calc_filepath)
+        return get_raw_file_from_upload_path(upload_files, upload_filepath, authorization_predicate)
+
+
+@ns.route('/calc/<string:upload_id>/<string:calc_id>/')
+class RawFileFromCalcEmptyPathResource(RawFileFromCalcPathResource):
+    @api.doc('get_file_list_from_calc')
+    @api.response(404, 'The upload or path does not exist')
+    @api.response(401, 'Not authorized to access the requested files.')
+    @api.response(200, 'File(s) send')
+    @api.expect(raw_file_from_path_parser, validate=True)
+    @login_if_available
+    @with_signature_token
+    def get(self, upload_id: str, calc_id: str):
+        """
+        Get calculation contents.
+
+        This is basically /raw/calc/<upload_id>/<calc_id>/<path> with an empty path, since
+        having an empty path parameter is not possible.
+        """
+        return super().get(upload_id, calc_id, None)
 
 
 raw_files_request_model = api.model('RawFilesRequest', {
@@ -236,7 +364,7 @@ def respond_to_get_raw_files(upload_id, files, compress=False):
                     # we just leave it out in the download
                     pass
 
-        compression = ZIP_DEFLATED if compress else ZIP_STORED
+        compression = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED
         zip_stream = zipstream.ZipFile(mode='w', compression=compression, allowZip64=True)
         zip_stream.paths_to_write = iterator()
 
diff --git a/nomad/files.py b/nomad/files.py
index bd7d2d4957dbda5f9fe3223a1519d1eaaac51174..71e1b2e0a7c043644e434e0ba868e0eaec31ef93 100644
--- a/nomad/files.py
+++ b/nomad/files.py
@@ -49,11 +49,11 @@ being other mainfiles. Therefore, the aux files of a restricted calc might becom
 """
 
 from abc import ABCMeta
+import sys
 from typing import IO, Generator, Dict, Iterable, Callable, List, Tuple
 import os.path
 import os
 import shutil
-from zipfile import ZipFile, BadZipFile
 import tarfile
 import hashlib
 import io
@@ -64,6 +64,14 @@ from nomad import config, utils
 from nomad.datamodel import UploadWithMetadata
 
 
+# TODO this should become obsolete, once we are going beyong python 3.6. For now
+# python 3.6's zipfile does not allow to seek/tell within a file-like opened from a
+# file in a zipfile.
+if sys.version_info >= (3, 7):
+    import zipfile
+else:
+    import zipfile37 as zipfile
+
 user_metadata_filename = 'user_metadata.pickle'
 
 
@@ -320,6 +328,8 @@ class StagingUploadFiles(UploadFiles):
             return open(path_object.os_path, *args, **kwargs)
         except FileNotFoundError:
             raise KeyError()
+        except IsADirectoryError:
+            raise KeyError()
 
     def raw_file(self, file_path: str, *args, **kwargs) -> IO:
         if not self._is_authorized():
@@ -370,12 +380,12 @@ class StagingUploadFiles(UploadFiles):
         ext = os.path.splitext(path)[1]
         if force_archive or ext == '.zip':
             try:
-                with ZipFile(path) as zf:
+                with zipfile.ZipFile(path) as zf:
                     zf.extractall(target_dir.os_path)
                 if move:
                     os.remove(path)
                 return
-            except BadZipFile:
+            except zipfile.BadZipFile:
                 pass
 
         if force_archive or ext in ['.tgz', '.tar.gz', '.tar.bz2']:
@@ -440,9 +450,9 @@ class StagingUploadFiles(UploadFiles):
                 self._user_metadata_file.os_path,
                 target_metadata_file.os_path)
 
-        def create_zipfile(kind: str, prefix: str, ext: str) -> ZipFile:
+        def create_zipfile(kind: str, prefix: str, ext: str) -> zipfile.ZipFile:
             file = target_dir.join_file('%s-%s.%s.zip' % (kind, prefix, ext))
-            return ZipFile(file.os_path, mode='w')
+            return zipfile.ZipFile(file.os_path, mode='w')
 
         # In prior versions we used bagit on raw files. There was not much purpose for
         # it, so it was removed. Check 0.3.x for the implementation
@@ -513,6 +523,9 @@ class StagingUploadFiles(UploadFiles):
                     yield path
 
     def raw_file_list(self, directory: str) -> List[Tuple[str, int]]:
+        if not self._is_authorized():
+            raise Restricted
+
         if directory is None or directory == '':
             prefix = self._raw_dir.os_path
         else:
@@ -647,9 +660,9 @@ class PublicUploadFiles(UploadFiles):
         super().__init__(config.fs.public, *args, **kwargs)
 
     @cachetools.cached(cache=__zip_file_cache)
-    def get_zip_file(self, prefix: str, access: str, ext: str) -> ZipFile:
+    def get_zip_file(self, prefix: str, access: str, ext: str) -> zipfile.ZipFile:
         zip_file = self.join_file('%s-%s.%s.zip' % (prefix, access, ext))
-        return ZipFile(zip_file.os_path)
+        return zipfile.ZipFile(zip_file.os_path)
 
     def _file(self, prefix: str, ext: str, path: str, *args, **kwargs) -> IO:
         mode = kwargs.get('mode') if len(args) == 0 else args[0]
@@ -670,6 +683,8 @@ class PublicUploadFiles(UploadFiles):
                     return f
             except FileNotFoundError:
                 pass
+            except IsADirectoryError:
+                pass
             except KeyError:
                 pass
 
@@ -711,6 +726,9 @@ class PublicUploadFiles(UploadFiles):
 
         results = []
         for access in ['public', 'restricted']:
+            if access == 'restricted' and not self._is_authorized():
+                continue
+
             try:
                 zf = self.get_zip_file('raw', access, 'plain')
                 for path in zf.namelist():
diff --git a/nomad/metainfo/bootstrap.py b/nomad/metainfo/bootstrap.py
index b53b908b161693d2958c07a6db201310772b7ef5..4118eccf1e90d43831eb00a5a68ae983d20ee3af 100644
--- a/nomad/metainfo/bootstrap.py
+++ b/nomad/metainfo/bootstrap.py
@@ -64,7 +64,7 @@ class MProperty(MObject):
 
 
 class MElementDef(MSection):
-    def __init__(self, m_definition: 'MElementDef', name: str): # more **kwargs
+    def __init__(self, m_definition: 'MElementDef', name: str):  # more **kwargs
         super().__init__(m_definition=m_definition)
         self.name = name
 
diff --git a/nomad/migration.py b/nomad/migration.py
index 58bb2f5c38c7f807fd1c99ab911be6d36f5251a7..44cd7e87bf8cc94cf911946eb37a36ea44d38daa 100644
--- a/nomad/migration.py
+++ b/nomad/migration.py
@@ -26,7 +26,7 @@ import multiprocessing.pool
 import time
 import os
 import os.path
-import zipfile
+import sys
 import tarfile
 import math
 from mongoengine import Document, IntField, StringField, DictField, BooleanField
@@ -47,6 +47,11 @@ from nomad.datamodel import CalcWithMetadata
 from nomad.processing import FAILURE
 
 
+if sys.version_info >= (3, 7):
+    import zipfile
+else:
+    import zipfile37 as zipfile
+
 default_pid_prefix = 7000000
 """ The default pid prefix for new non migrated calculations """
 
diff --git a/rawapi.requirements.txt b/rawapi.requirements.txt
index 6cb2a7908b48461d0ca22746004b1e8032c98f8f..10922cf7121a3668d7771e23111106a3e324b96a 100644
--- a/rawapi.requirements.txt
+++ b/rawapi.requirements.txt
@@ -28,3 +28,4 @@ matid
 ase==3.15.0
 filelock
 ujson
+zipfile37
diff --git a/tests/test_api.py b/tests/test_api.py
index 8e0b84eaddceff8c9161618ff071fe3baa546792..acc8a947ac4e4da5b3c855babead6096823b3665 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -22,6 +22,7 @@ import io
 import inspect
 from passlib.hash import bcrypt
 import datetime
+import os.path
 
 from nomad.api.app import rfc3339DateTime
 from nomad import coe_repo, search, parsing, files, config
@@ -840,6 +841,20 @@ class TestRepo():
 
 class TestRaw(UploadFilesBasedTests):
 
+    def test_raw_file_from_calc(self, client, non_empty_processed, test_user_auth):
+        calc = list(non_empty_processed.calcs)[0]
+        url = '/raw/calc/%s/%s/%s' % (
+            non_empty_processed.upload_id, calc.calc_id, os.path.basename(calc.mainfile))
+        rv = client.get(url, headers=test_user_auth)
+        assert rv.status_code == 200
+        assert len(rv.data) > 0
+
+        url = '/raw/calc/%s/%s/' % (non_empty_processed.upload_id, calc.calc_id)
+        rv = client.get(url, headers=test_user_auth)
+        assert rv.status_code == 200
+        result = json.loads(rv.data)
+        assert len(result['contents']) > 0
+
     @UploadFilesBasedTests.check_authorizaton
     def test_raw_file(self, client, upload, auth_headers):
         url = '/raw/%s/%s' % (upload, example_file_mainfile)
@@ -847,6 +862,21 @@ class TestRaw(UploadFilesBasedTests):
         assert rv.status_code == 200
         assert len(rv.data) > 0
 
+    @UploadFilesBasedTests.check_authorizaton
+    def test_raw_file_partial(self, client, upload, auth_headers):
+        url = '/raw/%s/%s?offset=0&length=20' % (upload, example_file_mainfile)
+        rv = client.get(url, headers=auth_headers)
+        assert rv.status_code == 200
+        start_data = rv.data
+        assert len(start_data) == 20
+
+        url = '/raw/%s/%s?offset=10&length=10' % (upload, example_file_mainfile)
+        rv = client.get(url, headers=auth_headers)
+        assert rv.status_code == 200
+        next_data = rv.data
+        assert len(rv.data) == 10
+        assert start_data[10:] == next_data
+
     @UploadFilesBasedTests.ignore_authorization
     def test_raw_file_signed(self, client, upload, _, test_user_signature_token):
         url = '/raw/%s/%s?token=%s' % (upload, example_file_mainfile, test_user_signature_token)
@@ -862,14 +892,6 @@ class TestRaw(UploadFilesBasedTests):
         data = json.loads(rv.data)
         assert 'files' not in data
 
-    @UploadFilesBasedTests.ignore_authorization
-    def test_raw_file_list_alternatives(self, client, upload, auth_headers):
-        url = '/raw/%s/examples' % upload
-        rv = client.get(url, headers=auth_headers)
-        assert rv.status_code == 404
-        data = json.loads(rv.data)
-        assert len(data['files']) == 5
-
     @pytest.mark.parametrize('compress', [True, False])
     @UploadFilesBasedTests.ignore_authorization
     def test_raw_file_wildcard(self, client, upload, auth_headers, compress):
@@ -961,7 +983,7 @@ class TestRaw(UploadFilesBasedTests):
 
     @UploadFilesBasedTests.ignore_authorization
     def test_raw_files_list(self, client, upload, auth_headers):
-        url = '/raw/list/%s/examples_template' % upload
+        url = '/raw/%s/examples_template' % upload
         rv = client.get(url, headers=auth_headers)
         assert rv.status_code == 200
         data = json.loads(rv.data)
@@ -970,12 +992,12 @@ class TestRaw(UploadFilesBasedTests):
         assert data['upload_id'] == upload
         assert data['directory'] == 'examples_template'
         for content in data['contents']:
-            assert content['file'] is not None
+            assert content['name'] is not None
             assert content['size'] >= 0
 
     @UploadFilesBasedTests.ignore_authorization
     def test_raw_files_list_missing(self, client, upload, auth_headers):
-        url = '/raw/list/%s/examples_' % upload
+        url = '/raw/%s/examples_' % upload
         rv = client.get(url, headers=auth_headers)
         assert rv.status_code == 404
 
diff --git a/tests/test_files.py b/tests/test_files.py
index c35f03466c4edb66fe6777a71bbbd5acaeaed7c6..eb9a2a3ec4767dbd48de0b827e4cb9254d20085c 100644
--- a/tests/test_files.py
+++ b/tests/test_files.py
@@ -227,7 +227,7 @@ class UploadFilesContract(UploadFilesFixtures):
         raw_files = list(upload_files.raw_file_list(directory=prefix))
         if prefix is None:
             assert len(raw_files) == 0
-        else:
+        elif upload_files._is_authorized() or len(raw_files) > 0:
             assert '1.aux' in list(path for path, _ in raw_files)
             for file, size in raw_files:
                 if file.endswith('.aux'):