[Libguestfs] [PATCH v9 03/11] daemon: utils: New functions unix_canonical_path, utf16le_to_utf8 and tests.

Richard W.M. Jones rjones at redhat.com
Mon Jul 17 16:55:23 UTC 2017


These utility functions will be used in the OCaml inspection code.
---
 daemon/daemon_utils_tests.ml |  15 +++++++
 daemon/utils.ml              | 100 +++++++++++++++++++++++++++++++++++++++++++
 daemon/utils.mli             |  12 ++++++
 3 files changed, 127 insertions(+)

diff --git a/daemon/daemon_utils_tests.ml b/daemon/daemon_utils_tests.ml
index 892509d89..b1f02de30 100644
--- a/daemon/daemon_utils_tests.ml
+++ b/daemon/daemon_utils_tests.ml
@@ -46,3 +46,18 @@ let () =
 let () =
   assert (proc_unmangle_path "\\040" = " ");
   assert (proc_unmangle_path "\\040\\040" = "  ")
+
+(* Test unix_canonical_path. *)
+let () =
+  assert (unix_canonical_path "/" = "/");
+  assert (unix_canonical_path "/usr" = "/usr");
+  assert (unix_canonical_path "/usr/" = "/usr");
+  assert (unix_canonical_path "/usr/local" = "/usr/local");
+  assert (unix_canonical_path "///" = "/");
+  assert (unix_canonical_path "///usr//local//" = "/usr/local");
+  assert (unix_canonical_path "/usr///" = "/usr")
+
+(* Test utf16le_to_utf8. *)
+let () =
+  assert (utf16le_to_utf8 "\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00" = "Windows");
+  assert (utf16le_to_utf8 "\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\xae\x00" = "Windows\xc2\xae")
diff --git a/daemon/utils.ml b/daemon/utils.ml
index 808e575fd..e53b4bf02 100644
--- a/daemon/utils.ml
+++ b/daemon/utils.ml
@@ -247,3 +247,103 @@ let proc_unmangle_path path =
 let is_small_file path =
   is_regular_file path &&
     (stat path).st_size <= 2 * 1048 * 1024
+
+let unix_canonical_path path =
+  let is_absolute = String.length path > 0 && path.[0] = '/' in
+  let path = String.nsplit "/" path in
+  let path = List.filter ((<>) "") path in
+  (if is_absolute then "/" else "") ^ String.concat "/" path
+
+(* Note that we cannot use iconv here because inside the appliance
+ * all i18n databases are deleted.  For the same reason we cannot
+ * use functions like hivex_value_string, as they also use iconv
+ * internally.
+ *
+ * https://en.wikipedia.org/wiki/UTF-16
+ * Also inspired by functions in glib's glib/gutf8.c
+ *)
+let rec utf16le_to_utf8 instr =
+  (* If the length is odd and the last character is ASCII NUL, just
+   * drop that.  (If it's not ASCII NUL, then there's an error)
+   *)
+  let len = String.length instr in
+  let instr =
+    if len mod 1 = 1 then (
+      if instr.[len-1] = '\000' then String.sub instr 0 (len-1)
+      else invalid_arg "input is not a valid UTF16-LE string: length is odd"
+    ) else instr in
+
+  (* The length should now be even.  If the last two bytes are
+   * '\0\0' then assume it's a NUL-terminated string from the
+   * Windows registry and drop both characters.
+   *)
+  let len = String.length instr in
+  let instr =
+    if len >= 2 && instr.[len-2] = '\000' && instr.[len-1] = '\000' then
+      String.sub instr 0 (len-2)
+    else instr in
+
+  let outbuf = Buffer.create len in
+
+  (* Encode a wide character as UTF-8 and write to outbuf.
+   * Basically this is g_unichar_to_utf8 implemented in OCaml.
+   *)
+  let encode_utf8 c =
+    let first, len =
+      if c < 0x80 then
+        (0, 1)
+      else if c < 0x800 then
+        (0xc0, 2)
+      else if c < 0x10000 then
+        (0xe0, 3)
+      else if c < 0x200000 then
+        (0xf0, 4)
+      else if c < 0x4000000 then
+        (0xf8, 5)
+      else
+        (0xfc, 6) in
+    let rec loop i c =
+      if i = 0 then Buffer.add_char outbuf (Char.chr (c lor first))
+      else if i > 0 then (
+        loop (i-1) (c lsr 6);
+        Buffer.add_char outbuf (Char.chr ((c land 0x3f) lor 0x80))
+      )
+    in
+    loop (len-1) c
+  in
+
+  (* Loop over the input UTF16-LE characters. *)
+  let is_high_surrogate c = c >= 0xd800 && c < 0xdc00
+  and is_low_surrogate c = c >= 0xdc00 && c < 0xe000
+  and surrogate_value highc lowc =
+    0x1_0000 + (highc - 0xd800) * 0x400 + lowc - 0xdc00
+  in
+
+  let len = String.length instr in
+  let rec loop i =
+    if i+1 >= len then ()
+    else (
+      let c = Char.code instr.[i] + (Char.code instr.[i+1] lsl 8) in
+
+      let wc, skip =
+        (* High surrogate - must come first. *)
+        if is_high_surrogate c then (
+          if i+3 >= len then
+            invalid_arg "input is not a valid UTF16-LE string: high surrogate at end of string";
+          let lowc = Char.code instr.[i+2] + (Char.code instr.[i+3] lsl 8) in
+          if not (is_low_surrogate lowc) then
+            invalid_arg "input is not a valid UTF16-LE string: high surrogate not followed by low surrogate";
+          (surrogate_value c lowc, 4)
+        )
+        else if is_low_surrogate c then
+          invalid_arg "input is not a valid UTF16-LE string: unexpected low surrogate"
+        else
+          (c, 2) in
+
+      encode_utf8 wc;
+      loop (i+skip)
+    )
+  in
+  loop 0;
+
+  Buffer.contents outbuf
diff --git a/daemon/utils.mli b/daemon/utils.mli
index d3c8bdf4d..94a77de01 100644
--- a/daemon/utils.mli
+++ b/daemon/utils.mli
@@ -85,3 +85,15 @@ val commandr : ?flags:command_flag list -> string -> string list -> (int * strin
 
 val is_small_file : string -> bool
 (** Return true if the path is a small regular file. *)
+
+val unix_canonical_path : string -> string
+(** Canonicalize a Unix path, so "///usr//local//" -> "/usr/local"
+
+    The path is modified in place because the result is always
+    the same length or shorter than the argument passed. *)
+
+val utf16le_to_utf8 : string -> string
+(** Convert a UTF16-LE string to UTF-8.
+
+    This uses a simple internal implementation since we cannot use
+    iconv inside the daemon. *)
-- 
2.13.2




More information about the Libguestfs mailing list