ソースを参照

Add path builder

KernelDeimos 1 年間 前
コミット
e3f57ee20e

+ 9 - 2
packages/backend/src/routers/_default.js

@@ -26,6 +26,7 @@ const auth = require('../middleware/auth.js');
 const { generate_puter_page_html } = require('../temp/puter_page_loader');
 const { Context } = require('../util/context');
 const { DB_READ } = require('../services/database/consts');
+const { PathBuilder } = require('../util/pathutil.js');
 
 let auth_user;
 
@@ -246,6 +247,7 @@ router.all('*', async function(req, res, next) {
         // /assets/
         // ------------------------
         else if (path.startsWith('/assets/')) {
+            path = PathBuilder.resolve(path);
             return res.sendFile(path, { root: __dirname + '../../public' }, function (err) {
                 if (err && err.statusCode) {
                     return res.status(err.statusCode).send('Error /public/')
@@ -338,7 +340,7 @@ router.all('*', async function(req, res, next) {
 
             // /dist/...
             else if(path.startsWith('/dist/') || path.startsWith('/src/')){
-                path = _path.resolve(path);
+                path = PathBuilder.resolve(path);
                 return res.sendFile(path, {root: config.assets.gui}, function(err){
                     if(err && err.statusCode){
                         return res.status(err.statusCode).send('Error /gui/dist/')
@@ -348,6 +350,7 @@ router.all('*', async function(req, res, next) {
 
             // All other paths
             else{
+                path = PathBuilder.resolve(path);
                 return res.sendFile(path, {root: _path.join(config.assets.gui, 'src')}, function(err){
                     if(err && err.statusCode){
                         return res.status(err.statusCode).send('Error /gui/')
@@ -364,7 +367,11 @@ router.all('*', async function(req, res, next) {
             subdomain === 'draw' || subdomain === 'camera' || subdomain === 'recorder' ||
             subdomain === 'dev-center' || subdomain === 'terminal'){
 
-        let root = _path.join(__dirname, config.defaultjs_asset_path, 'apps', subdomain);
+        let root = PathBuilder
+            .add(__dirname)
+            .add(config.defaultjs_asset_path, { allow_traversal: true })
+            .add('apps').add(subdomain)
+            .build();
         if ( subdomain === 'docs' ) root += '/dist';
         root = _path.normalize(root);
 

+ 60 - 0
packages/backend/src/util/pathutil.js

@@ -0,0 +1,60 @@
+const { AdvancedBase } = require("@heyputer/puter-js-common");
+
+/**
+ * PathBuilder implements the builder pattern for building paths.
+ * This makes it clear which path fragments are allowed to traverse
+ * to parent directories.
+ */
+class PathBuilder extends AdvancedBase {
+    static MODULES = {
+        path: require('path'),
+    }
+
+    constructor() {
+        super();
+        this.path_ = '';
+    }
+
+    static create () {
+        return new PathBuilder();
+    }
+
+    static add (fragment, options) {
+        return PathBuilder.create().add(fragment, options);
+    }
+
+    static resolve (fragment) {
+        const p = PathBuilder.create();
+        const require = p.require;
+        const node_path = require('path');
+        fragment = node_path.resolve(fragment);
+        return p.add(fragment).build();
+    }
+    
+    add (fragment, options) {
+        options = options || {};
+        if ( ! options.allow_traversal ) {
+            fragment = fragment.replace(/(\.\.\/|\.\.\\)/g, '');
+            if ( fragment === '..' ) {
+                fragment = '';
+            }
+        }
+
+        const require = this.require;
+        const node_path = require('path');
+
+        this.path_ = this.path_
+            ? node_path.join(this.path_, fragment)
+            : fragment;
+
+        return this;
+    }
+
+    build () {
+        return this.path_;
+    }
+}
+
+module.exports = {
+    PathBuilder,
+};