FYSETC / SD-WIFI-PRO

The hard ware of SD WIFI PRO
31 stars 4 forks source link

No Directory Access #5

Open kknight-tx opened 1 year ago

kknight-tx commented 1 year ago

Only root level file access is enabled with no way to manage directories at all. You can only view directories at the root level but all other directory access / management is not working. I get the same results on a new out of the box device or after running the updater using the "SdWiFiBrowser\tools\Flash\install-all-8M.bat" script.

cc46808 commented 2 months ago

Screenshot 2024-08-30 114334

Step 1

*Open FSWebServer.cpp and within `void FSWebServer::onHttpList(AsyncWebServerRequest request)` replace:**

if (path != "/" && !SD.exists((char *)path.c_str())) {
    request->send(500, "text/plain","LIST:BADPATH");
    return;
  }

with:

 if (path.indexOf('/') == -1 && !SD.exists((char *)path.c_str()) ) {
    request->send(500, "text/plain", "LIST:BADPATH");
    return;
}

Step 2

Replace the index.htm file contents with the following:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>SD WIFI PRO</title>

  <!-- load CSS -->
  <link rel="stylesheet" href="css/bootstrap.min.css">
  <link rel="stylesheet" href="css/fontawesome-all.min.css">
  <link rel="stylesheet" href="css/index.css">

  <script src="js/jquery-3.2.1.slim.min.js"></script>
  <script src="js/index.js"></script>
</head>

<body>
    <div class="container">
      <div class="tm-file-form-container mt-4">
        <form action="index.html" method="GET" class="form-inline tm-file-form">
          <div method="GET" class="text-uppercase tm-setting" onclick="javascript:window.location.href='wifi.htm'"><i class="fas fa-wifi"></i></div>
          <div class="form-group tm-file-box">
            <input type="file" id="Choose" value="Choose" class="tm-file-input form-control" name="data" >
            <button id="uploadButton" type="submit" class="form-control tm-file-submit" onclick="javascript:onClickUpload()"><i class="fa fa-upload"></i></button>
            <div id="updateButton" class="form-control tm-file-submit" onclick="javascript:onClickUpdateList()"><i class="fa fa-sync-alt"></i></div>
            <!-- <div id = "relinquishControlButton" class="form-control tm-file-submit" onclick="javascript:httpRelinquishSD()"><i class="fa fa-sd-card"></i></div>   -->
            <div id="backButton" class="form-control tm-file-submit" onclick="javascript:onClickBack()"><i class="fa fa-history"></i></div>
          </div>
        </form>
      </div>

      <div class="file-boxes progress-stripes" id="probar">
          <progress class="file-boxes" id="progressbar" value="0" max="100"></progress>
      </div>

      <div id="filelist" class="row mt-4 mb-1">
        <div class="col-xl-12">
          <div id="filelistbox" class="file-boxes"></div>
        </div>
      </div>

      <footer class="row">
        <div class="col-xl-12">
          <p class="text-center p-4">Copyright &copy; <span class="tm-current-year"></span></p>
        </div>
      </footer>
    </div> <!-- .container -->

    <script>
      $(function () {
        $('.tm-current-year').text(new Date().getFullYear()); 
      });
    </script>
</body>
</html>

Step 3

Replace the index.js file contents with the following:

var renderPage = true;
var sdbusy = false;
var gListPath = '';
var dirStack = ['/'];
var activeRequests = [];

if (navigator.userAgent.indexOf('MSIE') !== -1
    || navigator.appVersion.indexOf('Trident/') > 0) {
    /* Microsoft Internet Explorer detected in. */
    alert("Please view this in a modern browser such as Chrome or Microsoft Edge.");
    renderPage = false;
}

function httpPost(filename, data, type) {
    xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = httpPostProcessRequest;
    var formData = new FormData();
    formData.append("data", new Blob([data], { type: type }), filename);
    xmlHttp.open("POST", "/edit");
    xmlHttp.send(formData);
}

function httpGetList(path) {
    xmlHttp = new XMLHttpRequest(path);
    xmlHttp.onload = function () {
        sdbusy = false;
    }
    xmlHttp.onreadystatechange = function () {
        var resp = xmlHttp.responseText;
        if (xmlHttp.readyState == 4) {
            console.log("Get response of path:");
            console.log(resp);

            if (xmlHttp.status == 200)
                onHttpList(resp);

            if( resp.startsWith('LIST:')) {
                if(resp.includes('SDBUSY')) {
                    alert("Printer is busy, wait for 10s and try again");
                } else if(resp.includes('BADARGS') || 
                            resp.includes('BADPATH') ||
                            resp.includes('NOTDIR')) {
                    alert("Bad args, please try again or reset the module");
                }
            }
        }
    };
    xmlHttp.open('GET', '/list?dir=' + path, true);
    xmlHttp.send(null);
}

function httpGetGcode(path) {
    xmlHttp = new XMLHttpRequest(path);
    xmlHttp.onreadystatechange = function () {
        var resp = xmlHttp.responseText;
        if (xmlHttp.readyState == 4) {

            console.log("Get download response:");
            console.log(xmlHttp.responseText);

            if( resp.startsWith('DOWNLOAD:')) {
                if(resp.includes('SDBUSY')) {
                    alert("Printer is busy, wait for 10s and try again");
                } else if(resp.includes('BADARGS')) {
                    alert("Bad args, please try again or reset the module");
                }
            }
        }
    };
    xmlHttp.open('GET', '/download?dir=' + gListPath + "/" + path, true);
    xmlHttp.send(null);
}

function httpRelinquishSD() {
    let relinquishRequest = new XMLHttpRequest();
    relinquishRequest.open('GET', '/relinquish', true);
    relinquishRequest.send();

    console.log('Aborting all active requests...');
    activeRequests.forEach(function(xhr, index) {
        xhr.abort();
        console.log(`Aborted request #${index + 1}`);
    });
    activeRequests = [];
    sdbusy = false;

    logActiveRequests();
}

function onClickSelect() {
    var obj = document.getElementById('filelistbox').innerHTML = "";
}

function onClickDelete(filename) {
    if(sdbusy) {
        alert("SD card is busy");
        return
    }
    sdbusy = true;

    console.log('delete: %s', filename);
    xmlHttp = new XMLHttpRequest();
    xmlHttp.onload = function () {
        sdbusy = false;
        updateList();
    };
    xmlHttp.onreadystatechange = function () {
        var resp = xmlHttp.responseText;

        if( resp.startsWith('DELETE:')) {
            if(resp.includes('SDBUSY')) {
                alert("Printer is busy, wait for 10s and try again");
            } else if(resp.includes('BADARGS') || 
                        resp.includes('BADPATH')) {
                alert("Bad args, please try again or reset the module");
            }
        }
    };
    xmlHttp.open('GET', '/delete?path=' + gListPath + '/' + filename, true);
    xmlHttp.send();
}

function getContentType(filename) {
    filename = filename.toLowerCase();
    console.log("Filename: " + filename);
    if (filename.endsWith(".htm")) return "text/html";
    else if (filename.endsWith(".html")) return "text/html";
    else if (filename.endsWith(".css")) return "text/css";
    else if (filename.endsWith(".js")) return "application/javascript";
    else if (filename.endsWith(".json")) return "application/json";
    else if (filename.endsWith(".png")) return "image/png";
    else if (filename.endsWith(".gif")) return "image/gif";
    else if (filename.endsWith(".jpg")) return "image/jpeg";
    else if (filename.endsWith(".jpeg")) return "image/jpeg";
    else if (filename.endsWith(".ico")) return "image/x-icon";
    else if (filename.endsWith(".xml")) return "text/xml";
    else if (filename.endsWith(".pdf")) return "application/x-pdf";
    else if (filename.endsWith(".zip")) return "application/x-zip";
    else if (filename.endsWith(".gz")) return "application/x-gzip";
    return "text/plain";
}

function onClickDownload(filename) {
    if (sdbusy) {
        alert("SD card is busy");
        return;
    }
    sdbusy = true;

    document.getElementById('probar').style.display = "block";

    var type = getContentType(filename);
    let urlData = "/download?path=" + gListPath + "/" + filename;
    let xhr = new XMLHttpRequest();

    xhr.open('GET', urlData, true);
    xhr.setRequestHeader("Content-Type", type);
    xhr.setRequestHeader("Accept", "*");
    xhr.responseType = 'blob';
    activeRequests.push(xhr);

    xhr.addEventListener('progress', event => {
        const percent = ((event.loaded / event.total) * 100).toFixed(2);
        console.log(`downloaded: ${percent} %`);

        var progressBar = document.getElementById('progressbar');
        if (event.lengthComputable) {
            progressBar.max = event.total;
            progressBar.value = event.loaded;
        }
    }, false);

    xhr.onload = function (e) {
        if (this.status == 200) {
            let blob = this.response;
            let downloadElement = document.createElement('a');
            let url = window.URL.createObjectURL(blob);
            downloadElement.href = url;
            downloadElement.download = filename;
            downloadElement.click();
            sdbusy = false;
            console.log("download finished");
            document.getElementById('probar').style.display = "none";
            httpRelinquishSD();
            window.URL.revokeObjectURL(url);
        }
        // Remove the request from activeRequests once completed
        activeRequests = activeRequests.filter(req => req !== xhr);
    };

    xhr.onerror = function (e) {
        alert('Download failed!');
        document.getElementById('probar').style.display = "none";
        // Also remove on error
        activeRequests = activeRequests.filter(req => req !== xhr);
    };

    xhr.send();
}

function onUploaded(evt) {
    $("div[role='progressbar']").css("width",0);
    $("div[role='progressbar']").attr('aria-valuenow',0);
    document.getElementById('probar').style.display="none";
    updateList();
    sdbusy = true;
    document.getElementById('uploadButton').disabled = false;
    alert('Upload done!');
}

function onUploadFailed(evt) {
    document.getElementById('probar').style.display="none";
    document.getElementById('uploadButton').disabled = false;
    alert('Upload failed!');
}

function onUploading(evt) {
    var progressBar = document.getElementById('progressbar');
    if (evt.lengthComputable) {
      progressBar.max = evt.total;
      progressBar.value = evt.loaded;
    }
}

function onClickUpload() {
    if(sdbusy) {
        alert("SD card is busy");
        return
    }

    var input = document.getElementById('Choose');
    if (input.files.length === 0) {
        alert("Please choose a file first");
        return;
    }

    sdbusy = true;

    document.getElementById('uploadButton').disabled = true;
    document.getElementById('probar').style.display="block";

    xmlHttp = new XMLHttpRequest();
    xmlHttp.onload = onUploaded;
    xmlHttp.onerror = onUploadFailed;
    xmlHttp.upload.onprogress = onUploading;
    var formData = new FormData();
    var savePath = '';
    savePath = gListPath + '/' + input.files[0].name;
    formData.append('data', input.files[0], savePath);
    xmlHttp.open('POST', '/upload');
    xmlHttp.send(formData);
}

function niceBytes(x){
    const units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
    let l = 0, n = parseInt(x, 10) || 0;

    while(n >= 1024 && ++l){
        n = n/1024;
    }
    return(n.toFixed(n < 10 && l > 0 ? 1 : 0) + ' ' + units[l]);
}

function createFilelistItem(i, type, filename, size) {
    var isDir = type === "dir";
    var data =  "<div class=\"media\">\n" + 
                    "<div class=\"file-index\">" + i + "</div>\n" +
                    "<div class=\"media-body tm-bg-gray\">\n" +
                        "<div class=\"tm-description-box\">\n" +
                            "<h5 id='filename-" + i + "' class='tm-text-blue'>" + filename + "</h5>\n" +
                            "<p class='mb-0'>Type: " + type + " | Size: " + size + " Bytes</p>\n" +
                        "</div>\n" +
                        "<div class='tm-dd-box'>\n" +
                            (isDir ? "<div id='dir-" + i + "' type='button' value='Browse' class='btn tm-bg-blue tm-text-white tm-dd' onclick='javascript:onClickBrowse(\"" + filename + "\")'><i class=\"fa fa-folder-open\"></i></div>" : "") +
                            (!isDir ? "<div id='" + filename + "' type='button' value='Delete' class='btn tm-bg-blue tm-text-white tm-dd' onclick='javascript:onClickDelete(id)'><i class=\"fa fa-trash-alt\"></i></div>" : "") +
                            (!isDir ? "<div id='" + filename + "' type='button' value='Download' class='btn tm-bg-blue tm-text-white tm-dd' onclick='javascript:onClickDownload(id)'><i class=\"fa fa-download\"></i></div>" : "") +
                        "</div>\n" +
                    "</div>\n" +
                "</div>";
    return data;
}

function onHttpList(response) {
    var list = JSON.parse(response);
    for (var i = 0; i < list.length; i++) {
        $("#filelistbox").append(createFilelistItem(i+1,list[i].type,list[i].name,niceBytes(list[i].size)));
    }
}

function updateList() {
    document.getElementById('filelistbox').innerHTML = "";
    // Ensure gListPath is set to '/' if it's an empty string
    if (gListPath === ""){gListPath = "/";}
    httpGetList(gListPath);
}

function onClickUpdateList() {
    if(sdbusy) {
        alert("SD card is busy");
        return
    }
    sdbusy = true;

    updateList();
}

function onClickBrowse(dirName) {
    if (sdbusy) {
        alert("SD card is busy");
        return;
    }

    // Construct the new path with a leading '/' if needed
    if (gListPath === "/" || gListPath === "") {
        gListPath = "/" + dirName;
    } else {
        gListPath = gListPath + "/" + dirName;
    }

    dirStack.push(gListPath);
    updateList(); 
}

function onClickBack() {
    if (sdbusy) {
        alert("SD card is busy");
        return;
    }

    if (dirStack.length > 1) {
        dirStack.pop(); // Remove the current directory
        gListPath = dirStack[dirStack.length - 1]; // Get the previous directory path
        updateList();
    } else {
        alert("You are already at the root directory.");
    }
}

function logActiveRequests() {
    console.log(`Total Active Requests: ${activeRequests.length}`);
}

Notes

To get the icons to work you will need to create a webfonts directory within your data folder. The download and add fa-regular-400.woff2 to the webfonts folder.