diff --git a/mincss/main.py b/mincss/main.py index 0f79534..9735991 100644 --- a/mincss/main.py +++ b/mincss/main.py @@ -3,11 +3,24 @@ import io import os import time +from urllib.parse import urlparse from mincss import __version__ from .processor import Processor +def filename_from_href(href): + """Return a filesystem-safe filename derived from a URL's href. + + Using the raw last path segment of a URL (including any query string) + as a filename fails on Windows with OSError: [Errno 22] Invalid + argument, since characters like '?' and '=' are not valid in Windows + filenames. This drops the query string and fragment, keeping only the + URL's path basename. + """ + return os.path.basename(urlparse(href).path) + + def run(args): if args.version: print(__version__) @@ -37,10 +50,10 @@ def run(args): os.mkdir(output_dir) for link in p.links: print('FOR', link.href) - orig_name = link.href.split('/')[-1] + orig_name = filename_from_href(link.href) with io.open(os.path.join(output_dir, orig_name), 'w') as f: f.write(link.after) - before_name = 'before_' + link.href.split('/')[-1] + before_name = 'before_' + orig_name with io.open(os.path.join(output_dir, before_name), 'w') as f: f.write(link.before) print('Files written to', output_dir) diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..560dd52 --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,41 @@ +""" +Regression test for GH issue #53 (peterbe/mincss): + + OSError: [Errno 22] Invalid argument: './output\\banner-styles.css?v=...' + +main.run() derived the output filename for a downloaded CSS link +directly from `link.href.split('/')[-1]`, which includes the URL's +query string verbatim. Characters like '?' and '=' are not valid in +Windows filenames, so writing that file raised OSError (Windows) or +produced a surprising/wrong filename (POSIX, where '?' is legal but +clearly not intended to be part of a filename). + +filename_from_href() now parses the URL properly and uses only the +path's basename, dropping the query string and fragment. +""" + +import unittest + +from mincss.main import filename_from_href + + +class FilenameFromHrefTest(unittest.TestCase): + def test_strips_query_string(self): + href = "http://example.com/static/css/banner-styles.css?v=1516052760.0" + self.assertEqual(filename_from_href(href), "banner-styles.css") + + def test_strips_query_string_and_fragment(self): + href = "http://example.com/style.css?v=1#section" + self.assertEqual(filename_from_href(href), "style.css") + + def test_plain_url_without_query_string_unaffected(self): + href = "http://example.com/static/css/plain.css" + self.assertEqual(filename_from_href(href), "plain.css") + + def test_relative_path_url(self): + href = "/static/css/relative.css?v=2" + self.assertEqual(filename_from_href(href), "relative.css") + + +if __name__ == "__main__": + unittest.main()