[viewvc-dev] Re: [viewvc-users] Don't display binary files

JJ <[email protected]> Wed, 12 Nov 2008 09:52:43 -0600
Newsgroups gmane.comp.version-control.cvs.viewcvs.devel
Message-ID <[email protected]>
Here's another patch (includes the previous one) that checks if the file is
binary based on its contents.  It considers the file binary if more than 30%
of its characters are non-text characters, per
http://code.activestate.com/recipes/173220/.

I'd prefer to not open and close the file the additional time to check the
threshold.  I had to though since vclib.svn.svn_repo.FileContentsPipe didn't
have anyway to reset to position 0 after reading the first 512 bytes.
Also, maybe we could make the threshold for non-text characters an option in
the conf file.

What do you think?

JJ
On Wed, Nov 12, 2008 at 9:05 AM, JJ <[email protected]> wrote:

> Here's a smaller patch against HEAD that just adds the ignore_svn_mimetype
> option.
>
> I think the next step is adding the correct logic to markup_or_annotate
> that detects if the file is viewable (meaning it is a viewable image or some
> other text file).  Since the file is already being opened at that time,
> perhaps using something like http://code.activestate.com/recipes/173220/ and
> setting a is_viewable variable would be best.  Then the template can check
> this value and display some appropriate message about the file not being
> viewable, similar to
> http://viewvc.tigris.org/source/browse/viewvc/trunk/viewvc.org/favicon.ico?rev=1946&view=markup
> .
>
>   On Tue, Nov 11, 2008 at 11:21 AM, JJ <[email protected]> wrote:
>
>>  On Tue, Nov 11, 2008 at 10:30 AM, C. Michael Pilato <[email protected]
>> > wrote:
>>
>>> JJ wrote:
>>> > My patch is attached to this email.  I made the following decisions,
>>> > which you can feel free to disagree with.
>>> >
>>> > 1) I do not set prefer_markup to true for any images.  There isn't much
>>> > value in showing the image in the markup view as opposed to the
>>> download
>>> > view.  In addition it causes problems because things such as diff,
>>> > annotate and download as text were enabled for image files.
>>>
>>> Well, there is *some* value, in that (as for text files) you can see the
>>> most recent change metadata associated with the image, and you also have
>>> handy links to other views of that thing (which can don't get with the
>>> download view).
>>>
>>
>>
>> You could make the case that ANY file should be viewable then, since any
>> file can have properties.
>>
>> In any event, the templates need some way of telling if the file is binary
>> or text (i.e. something other than prefer_markup, since prefer_markup could
>> then be set for text files or images) so they don't allow diffs or other
>> things that don't make sense for binary files.
>>
>>
>>>
>>> > The other
>>> > problem is that an image file may have prefer_markup set to true, but
>>> > then when displaying it in the markup view it looks like garbled text
>>> if
>>> > the svn:mime-type is set to something like application/octet-stream
>>> > (something which is quite common).
>>>
>>> Hrm.  I wonder if the change to make ViewVC honor the svn:mime-type
>>> property
>>> is going to cause widespread annoyance because so many people haven't
>>> taken
>>> the time to set that property correctly?  Those folks will, in ViewVC
>>> 1.1,
>>> lose the ability to see their files in the browser altogether.
>>>
>>> Do you think this behavior should be configurable?  ignore_svn_mimetype =
>>> 0?
>>>
>>
>>
>> Yes, I personally would like this option.  I didn't realize this behaviour
>> was new to 1.1.  If someone did enable it they should still be informed that
>> the guessed mime type will not take the property into account.
>>
>>
>>>
>>> > 2) I changed the default template to never link to the markup view if
>>> > prefer_markup is false.
>>> >
>>> > 3) I changed the default template to never link to things that apply
>>> > only to text files (diff, download as text, view) if prefer_markup is
>>> false.
>>> > It seems like the check for whether it is a text file needs to be
>>> > smarter now, to include more than just files with text/* mime types in
>>> > this category.  Opening the files would be a performance hit of course.
>>>
>>> ViewVC can't afford to be any slower. :-)
>>
>>
>> Agreed.
>>
>>
>>>
>>>
>>> One comment about your patch:  I had mentioned that get_file_view_info()
>>> would probably be handy as you built out the somewhat standard set of
>>> links
>>> for each file in view_revision(), but I see that you didn't take
>>> advantage
>>> of that function.  Why?  Did it not meet your needs in some way I didn't
>>> expect?
>>>
>> Probably because I don't know what I'm doing and wasn't listening very
>> well.  :-(
>>
>> Another thought on detecting if a mime type is for a text file...
>>
>> This is ugly, but for experimentation sake I expanded is_text as follows.
>>
>> def is_text(mime_type):
>>   rtn = not mime_type or mime_type[:5] == 'text/'
>>   if not rtn:
>>     from pygments.lexers import ClassNotFound, get_lexer_for_mimetype
>>     try:
>>       lexer = get_lexer_for_mimetype(mime_type)
>>       rtn = True
>>     except ClassNotFound:
>>       pass
>>   return rtn
>> This works pretty well, though it is specific to the use of Pygments.
>> Perhaps there is a more specific module in Python that can detect text files
>> based on the mime type?  It looked like the mimetypes module can't, but
>> perhaps I'm wrong.
>>
>> JJ
>>
>>
>>
>
>

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
patch.txt (text/plain, 3.8 KB)
Index: viewvc.conf.dist
===================================================================
--- viewvc.conf.dist    (revision 2047)
+++ viewvc.conf.dist    (working copy)
@@ -269,6 +269,13 @@
 # Possible values: "tar", "annotate", "co", "markup", "roots"
 allowed_views = markup, annotate, roots

+# Don't use the svn:mime-type property to determine how to display a
+# file in the markup view.  This is helpful when, for example, an image
+# has svn:mime-type set to application/octet-stream.  If ViewVC tries
+# to display the image using the mime type application/octet-stream,
+# the image will look unrecognizeable.
+ignore_svn_mimetype = 0
+
 # authorizer: The name of the ViewVC authorizer plugin to use when
 # authorizing access to repository contents.  This value must be the
 # name of a Python module addressable as vcauth.MODULENAME (most
Index: templates/file.ezt
===================================================================
--- templates/file.ezt  (revision 2047)
+++ templates/file.ezt  (working copy)
@@ -102,5 +102,12 @@
 [end]
 [end]

+[if-any is_viewable]
+[else]
+<h3>
+<em>Non-viewable file contents hidden</em>
+</h3>
+[end]
+
 [include "include/props.ezt"]
 [include "include/footer.ezt"]
Index: lib/viewvc.py
===================================================================
--- lib/viewvc.py       (revision 2047)
+++ lib/viewvc.py       (working copy)
@@ -972,6 +972,24 @@
 def is_text(mime_type):
   return not mime_type or mime_type[:5] == 'text/'

+text_characters = "".join(map(chr, range(32, 127)) + list("\n\r\t\b"))
+_null_trans = string.maketrans("", "")
+
+def istext(s):
+  if "\0" in s:
+    return 0
+
+  if not s:  # Empty files are considered text
+    return 1
+
+  # Get the non-text characters (maps a character to itself then
+  # use the 'remove' option to get rid of the text characters.)
+  t = s.translate(_null_trans, text_characters)
+
+  # If more than 30% non-text characters, then
+  # this is considered a binary file
+  return float(len(t))/len(s) <= 0.30
+
 def is_cvsroot_path(roottype, path_parts):
   return roottype == 'cvs' and path_parts and path_parts[0] == 'CVSROOT'

@@ -1465,7 +1483,8 @@
   mime_type = None
   if not path_parts:
     return None
-  if request.roottype == 'svn':
+  if request.roottype == 'svn' and \
+     not request.cfg.options.ignore_svn_mimetype:
     try:
       itemprops = request.repos.itemprops(path_parts, rev)
       mime_type = itemprops.get('svn:mime-type')
@@ -1481,6 +1500,7 @@
   lines = fp = image_src_href = None
   annotation = None
   revision = None
+  is_viewable = None
   mime_type = calculate_mime_type(request, path, rev)

   # Is this a viewable image type?
@@ -1493,6 +1513,7 @@
     annotation = 'binary'
     image_src_href = request.get_url(view_func=view_checkout,
                                      params={'revision': rev}, escape=1)
+    is_viewable = True

   # Not a viewable image.
   else:
@@ -1513,10 +1534,18 @@
     if check_freshness(request, None, revision, weak=1):
       fp.close()
       return
-    lines = markup_stream_pygments(request, cfg, blame_source, fp,
-                                   path[-1], mime_type)
-    fp.close()

+    # Make sure its a text file.
+    if istext(fp.read(512)):
+      is_viewable = True
+      fp.close()
+      fp, revision = request.repos.openfile(path, rev)
+      lines = markup_stream_pygments(request, cfg, blame_source, fp,
+                                     path[-1], mime_type)
+      fp.close()
+    else:
+      is_viewable = False
+
   data = common_template_data(request, revision)
   data.update({
     'mime_type' : mime_type,
@@ -1538,6 +1567,7 @@
     'lines' : lines,
     'properties' : get_itemprops(request, path, rev),
     'annotation' : annotation,
+    'is_viewable' : ezt.boolean(is_viewable),
     })

   if cfg.options.show_log_in_markup: