def display(request, path): """ Implements displaying part of a file. GET arguments are length, offset, mode, compression and encoding with reasonable defaults chosen. Note that display by length and offset are on bytes, not on characters. TODO(philip): Could easily built-in file type detection (perhaps using something similar to file(1)), as well as more advanced binary-file viewing capability (de-serialize sequence files, decompress gzipped text files, etc.). There exists a python-magic package to interface with libmagic. """ path = _unquote_path(path) if not request.fs.isfile(path): raise PopupException("Not a file: '%s'" % (path,)) stats = request.fs.stats(path) encoding = request.GET.get('encoding') or i18n.get_site_encoding() # I'm mixing URL-based parameters and traditional # HTTP GET parameters, since URL-based parameters # can't naturally be optional. # Need to deal with possibility that length is not present # because the offset came in via the toolbar manual byte entry. end = request.GET.get("end") if end: end = int(end) begin = request.GET.get("begin", 1) if begin: # Subtract one to zero index for file read begin = int(begin) - 1 if end: offset = begin length = end - begin if begin >= end: raise PopupException("First byte to display must be before last byte to display.") else: length = int(request.GET.get("length", DEFAULT_CHUNK_SIZE_BYTES)) # Display first block by default. offset = int(request.GET.get("offset", 0)) mode = request.GET.get("mode") compression = request.GET.get("compression") if mode and mode not in ["binary", "text"]: raise PopupException("Mode must be one of 'binary' or 'text'.") if offset < 0: raise PopupException("Offset may not be less than zero.") if length < 0: raise PopupException("Length may not be less than zero.") if length > MAX_CHUNK_SIZE_BYTES: raise PopupException("Cannot request chunks greater than %d bytes" % MAX_CHUNK_SIZE_BYTES) # Auto gzip detection, unless we are explicitly told to view binary if not compression and mode != 'binary': if path.endswith('.gz') and detect_gzip(request.fs.open(path).read(2)): compression = 'gzip' offset = 0 else: compression = 'none' f = request.fs.open(path) if compression == 'gzip': if offset and offset != 0: raise PopupException("We don't support offset and gzip Compression") try: try: contents = GzipFile('', 'r', 0, StringIO(f.read())).read(length) except: logging.warn("Could not decompress file at %s" % path, exc_info=True) contents = '' raise PopupException("Failed to decompress file") finally: f.close() else: try: f.seek(offset) contents = f.read(length) finally: f.close() # Get contents as string for text mode, or at least try uni_contents = None if not mode or mode == 'text': uni_contents = unicode(contents, encoding, errors='replace') is_binary = uni_contents.find(i18n.REPLACEMENT_CHAR) != -1 # Auto-detect mode if not mode: mode = is_binary and 'binary' or 'text' # Get contents as bytes if mode == "binary": xxd_out = list(xxd.xxd(offset, contents, BYTES_PER_LINE, BYTES_PER_SENTENCE)) dirname = posixpath.dirname(path) # Start with index-like data: data = _massage_stats(request, request.fs.stats(path)) # And add a view structure: data["success"] = True data["view"] = { 'offset': offset, 'length': length, 'end': offset + len(contents), 'dirname': dirname, 'mode': mode, 'compression': compression, 'size': stats['size'] } data["filename"] = os.path.basename(path) data["editable"] = stats['size'] < MAX_FILEEDITOR_SIZE if mode == "binary": # This might be the wrong thing for ?format=json; doing the # xxd'ing in javascript might be more compact, or sending a less # intermediate representation... logger.debug("xxd: " + str(xxd_out)) data['view']['xxd'] = xxd_out data['view']['masked_binary_data'] = False else: data['view']['contents'] = uni_contents data['view']['masked_binary_data'] = is_binary return render_with_toolbars("display.mako", request, data)
def display(request, path): """ Implements displaying part of a file. GET arguments are length, offset, mode, compression and encoding with reasonable defaults chosen. Note that display by length and offset are on bytes, not on characters. TODO(philip): Could easily built-in file type detection (perhaps using something similar to file(1)), as well as more advanced binary-file viewing capability (de-serialize sequence files, decompress gzipped text files, etc.). There exists a python-magic package to interface with libmagic. """ if not request.fs.isfile(path): raise PopupException(_("Not a file: '%(path)s'") % {'path': path}) mimetype = mimetypes.guess_type(path)[0] if mimetype is not None and INLINE_DISPLAY_MIMETYPE.search(mimetype): path_enc = urlencode(path) return redirect(reverse('filebrowser.views.download', args=[path_enc]) + '?disposition=inline') stats = request.fs.stats(path) encoding = request.GET.get('encoding') or i18n.get_site_encoding() # I'm mixing URL-based parameters and traditional # HTTP GET parameters, since URL-based parameters # can't naturally be optional. # Need to deal with possibility that length is not present # because the offset came in via the toolbar manual byte entry. end = request.GET.get("end") if end: end = int(end) begin = request.GET.get("begin", 1) if begin: # Subtract one to zero index for file read begin = int(begin) - 1 if end: offset = begin length = end - begin if begin >= end: raise PopupException(_("First byte to display must be before last byte to display.")) else: length = int(request.GET.get("length", DEFAULT_CHUNK_SIZE_BYTES)) # Display first block by default. offset = int(request.GET.get("offset", 0)) mode = request.GET.get("mode") compression = request.GET.get("compression") if mode and mode not in ["binary", "text"]: raise PopupException(_("Mode must be one of 'binary' or 'text'.")) if offset < 0: raise PopupException(_("Offset may not be less than zero.")) if length < 0: raise PopupException(_("Length may not be less than zero.")) if length > MAX_CHUNK_SIZE_BYTES: raise PopupException(_("Cannot request chunks greater than %(bytes)d bytes.") % {'bytes': MAX_CHUNK_SIZE_BYTES}) # Do not decompress in binary mode. if mode == 'binary': compression = 'none' # Read out based on meta. compression, offset, length, contents =\ read_contents(compression, path, request.fs, offset, length) # Get contents as string for text mode, or at least try uni_contents = None if not mode or mode == 'text': uni_contents = unicode(contents, encoding, errors='replace') is_binary = uni_contents.find(i18n.REPLACEMENT_CHAR) != -1 # Auto-detect mode if not mode: mode = is_binary and 'binary' or 'text' # Get contents as bytes if mode == "binary": xxd_out = list(xxd.xxd(offset, contents, BYTES_PER_LINE, BYTES_PER_SENTENCE)) dirname = posixpath.dirname(path) # Start with index-like data: data = _massage_stats(request, request.fs.stats(path)) # And add a view structure: data["success"] = True data["view"] = { 'offset': offset, 'length': length, 'end': offset + len(contents), 'dirname': dirname, 'mode': mode, 'compression': compression, 'size': stats['size'], 'max_chunk_size': str(MAX_CHUNK_SIZE_BYTES) } data["filename"] = os.path.basename(path) data["editable"] = stats['size'] < MAX_FILEEDITOR_SIZE if mode == "binary": # This might be the wrong thing for ?format=json; doing the # xxd'ing in javascript might be more compact, or sending a less # intermediate representation... logger.debug("xxd: " + str(xxd_out)) data['view']['xxd'] = xxd_out data['view']['masked_binary_data'] = False else: data['view']['contents'] = uni_contents data['view']['masked_binary_data'] = is_binary data['breadcrumbs'] = parse_breadcrumbs(path) return render("display.mako", request, data)
def display(request, path): """ Implements displaying part of a file. GET arguments are length, offset, mode, compression and encoding with reasonable defaults chosen. Note that display by length and offset are on bytes, not on characters. TODO(philip): Could easily built-in file type detection (perhaps using something similar to file(1)), as well as more advanced binary-file viewing capability (de-serialize sequence files, decompress gzipped text files, etc.). There exists a python-magic package to interface with libmagic. """ path = _unquote_path(path) if not request.fs.isfile(path): raise PopupException("Not a file: '%s'" % (path, )) stats = request.fs.stats(path) encoding = request.GET.get('encoding') or i18n.get_site_encoding() # I'm mixing URL-based parameters and traditional # HTTP GET parameters, since URL-based parameters # can't naturally be optional. # Need to deal with possibility that length is not present # because the offset came in via the toolbar manual byte entry. end = request.GET.get("end") if end: end = int(end) begin = request.GET.get("begin", 1) if begin: # Subtract one to zero index for file read begin = int(begin) - 1 if end: offset = begin length = end - begin if begin >= end: raise PopupException( "First byte to display must be before last byte to display.") else: length = int(request.GET.get("length", DEFAULT_CHUNK_SIZE_BYTES)) # Display first block by default. offset = int(request.GET.get("offset", 0)) mode = request.GET.get("mode") compression = request.GET.get("compression") if mode and mode not in ["binary", "text"]: raise PopupException("Mode must be one of 'binary' or 'text'.") if offset < 0: raise PopupException("Offset may not be less than zero.") if length < 0: raise PopupException("Length may not be less than zero.") if length > MAX_CHUNK_SIZE_BYTES: raise PopupException("Cannot request chunks greater than %d bytes" % MAX_CHUNK_SIZE_BYTES) # Auto gzip detection, unless we are explicitly told to view binary if not compression and mode != 'binary': if path.endswith('.gz') and detect_gzip(request.fs.open(path).read(2)): compression = 'gzip' offset = 0 else: compression = 'none' f = request.fs.open(path) if compression == 'gzip': if offset and offset != 0: raise PopupException( "We don't support offset and gzip Compression") try: try: contents = GzipFile('', 'r', 0, StringIO(f.read())).read(length) except: logging.warn("Could not decompress file at %s" % path, exc_info=True) contents = '' raise PopupException("Failed to decompress file") finally: f.close() else: try: f.seek(offset) contents = f.read(length) finally: f.close() # Get contents as string for text mode, or at least try uni_contents = None if not mode or mode == 'text': uni_contents = unicode(contents, encoding, errors='replace') is_binary = uni_contents.find(i18n.REPLACEMENT_CHAR) != -1 # Auto-detect mode if not mode: mode = is_binary and 'binary' or 'text' # Get contents as bytes if mode == "binary": xxd_out = list( xxd.xxd(offset, contents, BYTES_PER_LINE, BYTES_PER_SENTENCE)) dirname = posixpath.dirname(path) # Start with index-like data: data = _massage_stats(request, request.fs.stats(path)) # And add a view structure: data["success"] = True data["view"] = { 'offset': offset, 'length': length, 'end': offset + len(contents), 'dirname': dirname, 'mode': mode, 'compression': compression, 'size': stats['size'] } data["filename"] = os.path.basename(path) data["editable"] = stats['size'] < MAX_FILEEDITOR_SIZE if mode == "binary": # This might be the wrong thing for ?format=json; doing the # xxd'ing in javascript might be more compact, or sending a less # intermediate representation... logger.debug("xxd: " + str(xxd_out)) data['view']['xxd'] = xxd_out data['view']['masked_binary_data'] = False else: data['view']['contents'] = uni_contents data['view']['masked_binary_data'] = is_binary return render_with_toolbars("display.mako", request, data)