Bug 1215063 - Add os.path.isAbsolute() and as.path.join() shell utilities r=sfink
authorJon Coppeard <jcoppeard@mozilla.com>
Tue, 10 Nov 2015 09:44:52 +0000
changeset 308026 548a09b1a4e6af5e098c4b560ee637b1cc6d624e
parent 308025 cc01ca9f3dff6577aa0aaad1f3c68e0bdfa79760
child 308027 26f84ff40db55f203fe0e5b322d5819fa1892671
push id7422
push userpehrsons@gmail.com
push dateWed, 11 Nov 2015 04:19:53 +0000
reviewerssfink
bugs1215063
milestone45.0a1
Bug 1215063 - Add os.path.isAbsolute() and as.path.join() shell utilities r=sfink
js/src/shell/OSObject.cpp
--- a/js/src/shell/OSObject.cpp
+++ b/js/src/shell/OSObject.cpp
@@ -25,62 +25,86 @@
 #include "jsobj.h"
 #ifdef XP_WIN
 # include "jswin.h"
 #endif
 #include "jswrapper.h"
 
 #include "js/Conversions.h"
 #include "shell/jsshell.h"
+#include "vm/StringBuffer.h"
 #include "vm/TypedArrayObject.h"
 
 #include "jsobjinlines.h"
 
 #ifdef XP_WIN
 # define PATH_MAX (MAX_PATH > _MAX_DIR ? MAX_PATH : _MAX_DIR)
 # define getcwd _getcwd
 #else
 # include <libgen.h>
 #endif
 
 using namespace JS;
 
 namespace js {
 namespace shell {
 
+#ifdef XP_WIN
+const char PathSeparator = '\\';
+#else
+const char PathSeparator = '/';
+#endif
+
+static bool
+IsAbsolutePath(const JSAutoByteString& filename)
+{
+    const char* pathname = filename.ptr();
+
+    if (pathname[0] == PathSeparator)
+        return true;
+
+#ifdef XP_WIN
+    // On Windows there are various forms of absolute paths (see
+    // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
+    // for details):
+    //
+    //   "\..."
+    //   "\\..."
+    //   "C:\..."
+    //
+    // The first two cases are handled by the test above so we only need a test
+    // for the last one here.
+
+    if ((strlen(pathname) > 3 &&
+        isalpha(pathname[0]) && pathname[1] == ':' && pathname[2] == '\\'))
+    {
+        return true;
+    }
+#endif
+
+    return false;
+}
+
 /*
  * Resolve a (possibly) relative filename to an absolute path. If
  * |scriptRelative| is true, then the result will be relative to the directory
  * containing the currently-running script, or the current working directory if
  * the currently-running script is "-e" (namely, you're using it from the
  * command line.) Otherwise, it will be relative to the current working
  * directory.
  */
 JSString*
 ResolvePath(JSContext* cx, HandleString filenameStr, PathResolutionMode resolveMode)
 {
     JSAutoByteString filename(cx, filenameStr);
     if (!filename)
         return nullptr;
 
-    const char* pathname = filename.ptr();
-    if (pathname[0] == '/')
-        return filenameStr;
-#ifdef XP_WIN
-    // Various forms of absolute paths per http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
-    // "\..."
-    if (pathname[0] == '\\')
+    if (IsAbsolutePath(filename))
         return filenameStr;
-    // "C:\..."
-    if (strlen(pathname) > 3 && isalpha(pathname[0]) && pathname[1] == ':' && pathname[2] == '\\')
-        return filenameStr;
-    // "\\..."
-    if (strlen(pathname) > 2 && pathname[1] == '\\' && pathname[2] == '\\')
-        return filenameStr;
-#endif
 
     /* Get the currently executing script's name. */
     JS::AutoFilename scriptFilename;
     if (!DescribeScriptedCaller(cx, &scriptFilename))
         return nullptr;
 
     if (!scriptFilename.get())
         return nullptr;
@@ -105,17 +129,17 @@ ResolvePath(JSContext* cx, HandleString 
     } else {
         const char* cwd = getcwd(buffer, PATH_MAX);
         if (!cwd)
             return nullptr;
     }
 
     size_t len = strlen(buffer);
     buffer[len] = '/';
-    strncpy(buffer + len + 1, pathname, sizeof(buffer) - (len+1));
+    strncpy(buffer + len + 1, filename.ptr(), sizeof(buffer) - (len+1));
     if (buffer[PATH_MAX] != '\0')
         return nullptr;
 
     return JS_NewStringCopyZ(cx, buffer);
 }
 
 static JSObject*
 FileAsTypedArray(JSContext* cx, const char* pathname)
@@ -323,16 +347,90 @@ static const JSFunctionSpecWithHelp osfi
 "redirect(stdoutFilename[, stderrFilename])",
 "  Redirect stdout and/or stderr to the named file. Pass undefined to avoid\n"
 "   redirecting. Filenames are relative to the current working directory."),
 
     JS_FS_HELP_END
 };
 
 static bool
+ospath_isAbsolute(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() != 1 || !args[0].isString()) {
+        JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "isAbsolute");
+        return false;
+    }
+
+    JSAutoByteString path(cx, args[0].toString());
+    if (!path)
+        return false;
+
+    args.rval().setBoolean(IsAbsolutePath(path));
+    return true;
+}
+
+static bool
+ospath_join(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() < 1) {
+        JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "join");
+        return false;
+    }
+
+    // This function doesn't take into account some aspects of Windows paths,
+    // e.g. the drive letter is always reset when an absolute path is appended.
+
+    StringBuffer buffer(cx);
+
+    for (unsigned i = 0; i < args.length(); i++) {
+        if (!args[i].isString()) {
+            JS_ReportError(cx, "join expects string arguments only");
+            return false;
+        }
+
+        JSAutoByteString path(cx, args[i].toString());
+        if (!path)
+            return false;
+
+        if (IsAbsolutePath(path)) {
+            MOZ_ALWAYS_TRUE(buffer.resize(0));
+        } else if (i != 0) {
+            if (!buffer.append(PathSeparator))
+                return false;
+        }
+
+        if (!buffer.append(args[i].toString()))
+            return false;
+    }
+
+    JSString* result = buffer.finishString();
+    if (!result)
+        return false;
+
+    args.rval().setString(result);
+    return true;
+}
+
+static const JSFunctionSpecWithHelp ospath_functions[] = {
+    JS_FN_HELP("isAbsolute", ospath_isAbsolute, 1, 0,
+"isAbsolute(path)",
+"  Return whether the given path is absolute."),
+
+    JS_FN_HELP("join", ospath_join, 1, 0,
+"join(paths...)",
+"  Join one or more path components in a platform independent way."),
+
+    JS_FS_HELP_END
+};
+
+static bool
 os_getenv(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() < 1) {
         JS_ReportError(cx, "os.getenv requires 1 argument");
         return false;
     }
     RootedString key(cx, ToString(cx, args[0]));
@@ -618,16 +716,24 @@ DefineOS(JSContext* cx, HandleObject glo
         return false;
     }
 
     if (!fuzzingSafe) {
         if (!JS_DefineFunctionsWithHelp(cx, osfile, osfile_unsafe_functions))
             return false;
     }
 
+    RootedObject ospath(cx, JS_NewPlainObject(cx));
+    if (!ospath ||
+        !JS_DefineFunctionsWithHelp(cx, ospath, ospath_functions) ||
+        !JS_DefineProperty(cx, obj, "path", ospath, 0))
+    {
+        return false;
+    }
+
     // For backwards compatibility, expose various os.file.* functions as
     // direct methods on the global.
     RootedValue val(cx);
 
     struct {
         const char* src;
         const char* dst;
     } osfile_exports[] = {