shell bypass 403
Cubjrnet7 Shell
: /home/eklavya/www/administrator/components/com_ffexplorer/assets/explorer/src/components/ [ drwxr-xr-x ]
<template>
<div class="e-folders" :style="{ height: this.treeHeight }" @contextmenu.prevent>
<ul class="e-folders-list">
<ETreeItem
v-for="item in treeData"
:key="item.path"
:item="item"
/>
</ul>
<vue-context ref="menu" :close-on-scroll="true">
<li style="border-bottom: dashed 1px #ddd;" v-if="!isRoot" @click="compress">
<a>Compress to Zip</a>
</li>
<li style="border-bottom: dashed 1px #ddd;" v-if="isArchive" @click="extract">
<a>Extract to ...</a>
</li>
<li v-if="contextItem.type === 'folder'" @click="newFile">
<a>New File</a>
</li>
<li style="border-bottom: dashed 1px #ddd;" v-if="contextItem.type === 'folder'" @click="newFolder">
<a>New Folder</a>
</li>
<li v-if="contextItem.type === 'folder' && !isRoot" @click="delayCall('renameFolder')">
<a>Rename Folder</a>
</li>
<li style="border-bottom: dashed 1px #ddd;" v-if="contextItem.type === 'folder' && !isRoot" @click="delayCall('deleteFolder')">
<a>Delete Folder</a>
</li>
<li v-if="contextItem.type === 'file'" @click="delayCall('openFile')">
<a>Open</a>
</li>
<li style="border-bottom: dashed 1px #ddd;" v-if="contextItem.type === 'file'" @click="delayCall('download')">
<a>Download File</a>
</li>
<li v-if="contextItem.type === 'file'" @click="delayCall('renameFile')">
<a>Rename File</a>
</li>
<li style="border-bottom: dashed 1px #ddd;" v-if="contextItem.type === 'file'" @click="delayCall('deleteFile')">
<a>Delete File</a>
</li>
<li v-if="contextItem.type === 'folder'" @click="uploadDialog = true">
<a>Upload Files</a>
</li>
<li style="border-bottom: dashed 1px #ddd;" v-if="contextItem.type === 'folder'" @click="refresh">
<a>Refresh</a>
</li>
<li v-if="!isRoot" @click="openPermissionDialog">
<a>Permission</a>
</li>
<li v-if="!isRoot" @click="copyPathToClipboard">
<a>Copy Relative Path</a>
</li>
</vue-context>
<el-dialog
title="Upload Files"
:append-to-body="true"
:destroy-on-close="true"
:show-close="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
:visible.sync="uploadDialog">
<el-upload
class="upload-file"
:action="uploadUrl"
:data="uploadData"
:on-success="onSuccessUpload"
:multiple="true">
<el-button size="small" type="primary">Click to upload</el-button>
<div slot="tip" class="el-upload__tip">Files with a size less than {{maxFileSizeUpload}}B</div>
</el-upload>
<span slot="footer" class="dialog-footer">
<el-button
size="small"
:loading="uploadDialogBusy"
@click="onCloseUploadDialog">Close</el-button>
</span>
</el-dialog>
<el-dialog
class="permission-dialog"
title="Permission"
width="400px"
:append-to-body="true"
:destroy-on-close="true"
:show-close="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
:visible.sync="permissionDialog">
<div v-loading="permissionLoading">
<table class="permission-config">
<tr>
<td></td>
<td>User</td>
<td>Group</td>
<td>World</td>
</tr>
<tr>
<td>Read</td>
<td><input type="checkbox" v-model="chmod.userRead"></td>
<td><input type="checkbox" v-model="chmod.groupRead"></td>
<td><input type="checkbox" v-model="chmod.worldRead"></td>
</tr>
<tr>
<td>Write</td>
<td><input type="checkbox" v-model="chmod.userWrite"></td>
<td><input type="checkbox" v-model="chmod.groupWrite"></td>
<td><input type="checkbox" v-model="chmod.worldWrite"></td>
</tr>
<tr>
<td>Execute</td>
<td><input type="checkbox" v-model="chmod.userExecute"></td>
<td><input type="checkbox" v-model="chmod.groupExecute"></td>
<td><input type="checkbox" v-model="chmod.worldExecute"></td>
</tr>
<tr>
<td>Permission</td>
<td>{{permission.user}}</td>
<td>{{permission.group}}</td>
<td>{{permission.world}}</td>
</tr>
</table>
</div>
<span slot="footer" class="dialog-footer">
<el-button
size="small"
:disabled="permissionLoading"
@click="closePermissionDialog">Close</el-button>
<el-button
size="small"
type="primary"
:disabled="permissionLoading"
@click="savePermission">Save</el-button>
</span>
</el-dialog>
<el-dialog
style="text-align: center;"
:title="this.previewImage.name"
:append-to-body="true"
:visible.sync="previewImageDialog">
<img style="box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.75);"
:key="this.previewImage.path"
:src="this.previewImage.path">
</el-dialog>
</div>
</template>
<script>
import 'vue-context/src/sass/vue-context.scss';
import Vue from 'vue';
import ETreeItem from "./ETreeItem.vue";
import debounce from 'lodash/debounce';
import VueContext from 'vue-context';
import { EventBus } from '../event-bus';
import { arrange } from '../utils';
export default {
components: {
ETreeItem,
VueContext,
},
data() {
const {
path,
maxFileSizeUpload,
uploadForm
} = FF_EXPLORER_DATA;
const rootUri = path.root;
const uploadUrl = path.ajax;
return {
treeData: [{
name: 'root',
path: '\\',
type: 'folder',
}],
treeHeight: '0px',
contextItem: {},
uploadDialog: false,
uploadDialogBusy: false,
uploadUrl,
maxFileSizeUpload,
uploadForm,
permissionDialog: false,
permissionLoading: false,
chmod: {
userRead: false,
userWrite: false,
userExecute: false,
groupRead: false,
groupWrite: false,
groupExecute: false,
worldRead: false,
worldWrite: false,
worldExecute: false,
},
rootUri,
previewImageDialog: false,
previewImage: {
path: '',
name: '',
},
}
},
mounted() {
this.$ajax({
task: 'explorer.explodeFolder',
path: '\\',
})
.then(res => {
if (res.error) {
return alert(res.error);
}
const root = this.treeData.find(item => item.path === '\\');
Vue.set(root, 'children', arrange(res));
})
.catch(error => {
alert('init root folder error');
console.log(error);
});
setTimeout(() => {
this.setTreeHeight();
});
jQuery(window).on('resize.ffexplorer', () => {
this.setTreeHeight();
});
this.$el.addEventListener('scroll', () => {
this.$refs.menu.close();
});
EventBus.$on('openContextMenu', data => {
this.$refs.menu.open(data.event);
Vue.set(this, 'contextItem', data.item);
});
EventBus.$on('openImage', ({name, path}) => {
Vue.set(this, 'previewImage', {
path: this.rootUri + path.substring(1),
name,
});
setTimeout(() => {
this.previewImageDialog = true;
});
});
},
computed: {
isRoot() {
return this.contextItem.path === '\\';
},
uploadData() {
const {params} = FF_EXPLORER_DATA;
const uploadData = {
task: 'explorer.upload',
path: this.contextItem.path,
};
return jQuery.extend(uploadData, params);
},
isArchive() {
if (!this.contextItem.type || this.contextItem.type === 'folder') {
return false;
}
const frags = this.contextItem.name.split('.');
if (frags.length < 2) {
return false;
}
if (frags.length === 2 && frags[0] === '') {
return false;
}
const ext = frags.pop();
const archiveExt = ['zip', 'tar', 'tgz', 'gz', 'gzip', 'tbz2', 'bz2', 'bzip2'];
if (archiveExt.indexOf(ext) === -1) {
return false;
}
return true;
},
permission() {
let user = 0;
let group = 0;
let world = 0;
for (const key in this.chmod) {
const val = this.chmod[key];
if (val) {
user = key === 'userRead' ? user + 4 : user;
user = key === 'userWrite' ? user + 2 : user;
user = key === 'userExecute' ? user + 1 : user;
group = key === 'groupRead' ? group + 4 : group;
group = key === 'groupWrite' ? group + 2 : group;
group = key === 'groupExecute' ? group + 1 : group;
world = key === 'worldRead' ? world + 4 : world;
world = key === 'worldWrite' ? world + 2 : world;
world = key === 'worldExecute' ? world + 1 : world;
}
}
return {user, group, world};
},
},
methods: {
extract() {
const source = this.contextItem.path;
const frags = source.split('\\');
frags.pop();
const path = (frags.length === 1 ? '\\' : '') + frags.join('\\');
this.$prompt('', 'Relative path to extract:', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
inputPattern: /^[^,:\*\?"<>|]+$/,
inputValue: path,
inputErrorMessage: 'Path should not contain ^ , : * ? " < > |',
showClose: false,
closeOnClickModal: false,
closeOnPressEscape: false,
closeOnHashChange: false,
confirmButtonLoading: false,
showCancelButton: true,
beforeClose: (action, instance, done) => {
if (action == 'confirm') {
const target = instance.inputValue.replace('/', '\\');
instance.showCancelButton = false;
instance.confirmButtonLoading = true;
this.$ajax({
task: 'explorer.extract',
source,
target,
})
.then(res => {
if (res.error) {
return alert(res.error);
}
const item = this.findItemByPath(this.treeData[0], target);
if (item) {
return this.refreshNode(item).then(() => {
done();
});
}
done();
})
.catch(error => {
alert('extract error');
})
.finally(() => {
instance.showCancelButton = true;
instance.confirmButtonLoading = false;
});
} else {
done();
}
}
}).then(({ value }) => {
this.$message({
type: 'success',
message: 'Extract done.',
});
}).catch(() => {});;
},
download() {
jQuery('.context-download-file').remove();
const $body = jQuery('body');
const $form = jQuery(this.uploadForm);
$form.find('.file-path').val(this.contextItem.path);
$body.append($form);
$form.submit();
},
compress() {
const loading = this.$loading({
lock: true,
text: 'Compressing',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)',
customClass: 'compress-loading'
});
this.$ajax({
task: 'explorer.compress',
path: this.contextItem.path,
})
.then(res => {
if (res.error) {
alert(res.error);
return;
}
const parent = this.getParent(this.treeData[0], this.contextItem.path);
return new Promise((resolve, reject) => {
this.refreshNode(parent).then(() => {
this.$message({
type: 'success',
message: 'Compress successfully'
});
resolve();
});
});
})
.catch(error => {
alert('error');
})
.finally(() => {
loading.close();
});
},
savePermission() {
this.permissionLoading = true;
const {permission} = this;
const {user, group, world} = permission;
const mode = '0' + user + group + world;
this.$ajax({
task: 'explorer.setPermission',
path: this.contextItem.path,
mode,
})
.then(res => {
if (res.error) {
alert(res.error);
return;
}
this.$message({
type: 'success',
message: 'Save permission successfully!',
});
})
.catch(error => {
console.log(error);
alert('save error');
})
.finally(() => {
this.permissionLoading = false;
});
},
closePermissionDialog() {
this.permissionDialog = false;
const chmod = {
userRead: false,
userWrite: false,
userExecute: false,
groupRead: false,
groupWrite: false,
groupExecute: false,
worldRead: false,
worldWrite: false,
worldExecute: false,
};
setTimeout(() => {
Vue.set(this, 'chmod', chmod);
}, 300);
},
openPermissionDialog() {
this.permissionDialog = true;
this.permissionLoading = true;
this.$ajax({
task: 'explorer.getPermission',
path: this.contextItem.path,
})
.then(res => {
if (res.error) {
alert(res.error);
return;
}
if (res.permission) {
const pieces = res.permission.split('');
if (pieces.length !== 9) {
alert('permission error');
}
pieces.forEach((val, i) => {
if (i === 0 && val === 'r') {
this.chmod.userRead = true;
}
if (i === 1 && val === 'w') {
this.chmod.userWrite = true;
}
if (i === 2 && val === 'x') {
this.chmod.userExecute = true;
}
if (i === 3 && val === 'r') {
this.chmod.groupRead = true;
}
if (i === 4 && val === 'w') {
this.chmod.groupWrite = true;
}
if (i === 5 && val === 'x') {
this.chmod.groupExecute = true;
}
if (i === 6 && val === 'r') {
this.chmod.worldRead = true;
}
if (i === 7 && val === 'w') {
this.chmod.worldWrite = true;
}
if (i === 8 && val === 'x') {
this.chmod.worldExecute = true;
}
})
}
})
.catch(error => {
console.log(error);
alert('error');
})
.finally(() => {
this.permissionLoading = false;
});
},
copyPathToClipboard() {
navigator.clipboard.writeText(this.contextItem.path).then(() => {
this.$message({
type: 'success',
message: 'Copied'
});
})
},
onSuccessUpload(res, file, fileList) {
if (res.error) {
alert(res.error);
const idx = fileList.findIndex(f => f.uid === file.uid);
fileList.splice(idx, 1);
}
},
onCloseUploadDialog() {
this.uploadDialogBusy = true;
this.refreshNode(this.contextItem).then(() => {
this.uploadDialogBusy = false;
this.uploadDialog = false;
});
},
delayCall(action) {
setTimeout(() => {
this[action] && this[action]();
});
},
isLockedFile(path) {
const {lockedFiles} = this.$store.state;
return lockedFiles.indexOf(path) > -1;
},
isLockedFolder(path) {
const {lockedFiles} = this.$store.state;
return lockedFiles.some(file => {
return file.indexOf(path) === 0;
});
},
refresh() {
const loading = this.$loading({
lock: true,
text: 'Refreshing',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)',
customClass: 'compress-loading'
});
this.refreshNode(this.contextItem)
.catch(error => {
console.log(error);
alert('refresh error');
})
.finally(() => {
setTimeout(() => {
loading.close();
}, 300);
});
},
refreshNode(item) {
return new Promise((resolve, reject) => {
this.$ajax({
task: 'explorer.explodeFolder',
path: item.path,
})
.then(res => {
if (res.error) {
alert(res.error)
reject(res.error);
} else {
const children = item.children || [];
const newItems = res.filter(r => {
return !children.find(child => child.path === r.path );
});
newItems.forEach(i => children.push(i));
const deletedItems = children.filter(child => {
return !res.find(r => r.path === child.path);
});
deletedItems.forEach(i => {
const idx = children.findIndex(child => child.path === i.path);
children.splice(idx, 1);
});
Vue.set(item, 'children', arrange(children));
}
resolve();
})
.catch(error => {
reject(error);
});
});
},
newFile() {
this.$prompt('', 'New File', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
inputPattern: /^[^,\\\/:\*\?"<>|]+$/,
inputErrorMessage: 'File name should not contain ^ , \\ / : * ? " < > |',
showClose: false,
closeOnClickModal: false,
closeOnPressEscape: false,
closeOnHashChange: false,
confirmButtonLoading: false,
showCancelButton: true,
beforeClose: (action, instance, done) => {
if (action == 'confirm') {
this.doCreateNew('explorer.newFile', instance, done);
} else {
done();
}
}
}).then(({ value }) => {
this.$message({
type: 'success',
message: 'New file has been created: ' + value
});
}).catch(() => {});
},
newFolder() {
this.$prompt('', 'New Folder', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
inputPattern: /^[^,\\\/:\*\?"<>|]+$/,
inputErrorMessage: 'Folder name should not contain ^ , \\ / : * ? " < > |',
showClose: false,
closeOnClickModal: false,
closeOnPressEscape: false,
closeOnHashChange: false,
confirmButtonLoading: false,
showCancelButton: true,
beforeClose: (action, instance, done) => {
if (action == 'confirm') {
this.doCreateNew('explorer.newFolder', instance, done);
} else {
done();
}
}
}).then(({ value }) => {
this.$message({
type: 'success',
message: 'New Folder has been created: ' + value
});
}).catch(() => {});
},
renameFolder() {
if (this.isLockedFolder(this.contextItem.path)) {
alert('Folder is locked. Having some files are opening or saving. Please wait till process done then try again.');
return;
}
this.$prompt('Rename folder "'+this.contextItem.name+'"', 'Rename Folder', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
inputValue: this.contextItem.name,
inputPattern: /^[^,\\\/:\*\?"<>|]+$/,
inputErrorMessage: 'Folder name should not contain ^ , \\ / : * ? " < > |',
showClose: false,
closeOnClickModal: false,
closeOnPressEscape: false,
closeOnHashChange: false,
confirmButtonLoading: false,
showCancelButton: true,
beforeClose: (action, instance, done) => {
if (action == 'confirm') {
this.doRename('explorer.renameFolder', instance, done);
} else {
done();
}
}
}).then(({ value }) => {
this.$message({
type: 'success',
message: 'Your folder name has been change to ' + value
});
}).catch(() => {});
},
deleteFolder() {
if (this.isLockedFolder(this.contextItem.path)) {
alert('Folder is locked. Having some files are opening or saving. Please wait till process done then try again.');
return;
}
this.$confirm('This will permanently delete this folder and its files. Continue?', 'Delete folder "' + this.contextItem.name + '"', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
showClose: false,
closeOnClickModal: false,
closeOnPressEscape: false,
closeOnHashChange: false,
confirmButtonLoading: false,
showCancelButton: true,
beforeClose: (action, instance, done) => {
if (action == 'confirm') {
this.doDelete('explorer.deleteFolder', instance, done);
} else {
done();
}
}
}).then(() => {
this.$message({
type: 'success',
message: 'Folder is deleted'
});
}).catch(() => {});
},
openFile() {
if (this.isLockedFile(this.contextItem.path)) {
alert('File is locked for opening or saving. Please try again later.');
return;
}
EventBus.$emit('openFileEditor', {
item: this.contextItem,
force: true
});
},
renameFile() {
if (this.isLockedFile(this.contextItem.path)) {
alert('File is locked for opening or saving. Please try again later.');
return;
}
this.$prompt('Rename file "'+this.contextItem.name+'"', 'Rename File', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
inputValue: this.contextItem.name,
inputPattern: /^[^,\\\/:\*\?"<>|]+$/,
inputErrorMessage: 'File name should not contain ^ , \\ / : * ? " < > |',
showClose: false,
closeOnClickModal: false,
closeOnPressEscape: false,
closeOnHashChange: false,
confirmButtonLoading: false,
showCancelButton: true,
beforeClose: (action, instance, done) => {
if (action == 'confirm') {
this.doRename('explorer.renameFile', instance, done);
} else {
done();
}
}
}).then(({ value }) => {
this.$message({
type: 'success',
message: 'Success'
});
}).catch(() => {});
},
deleteFile() {
if (this.isLockedFile(this.contextItem.path)) {
alert('File is locked for opening or saving. Please try again later.');
return;
}
this.$confirm('This will permanently delete this file. Continue?', 'Delete file "'+this.contextItem.name+'"', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
showClose: false,
closeOnClickModal: false,
closeOnPressEscape: false,
closeOnHashChange: false,
confirmButtonLoading: false,
showCancelButton: true,
beforeClose: (action, instance, done) => {
if (action == 'confirm') {
this.doDelete('explorer.deleteFile', instance, done);
} else {
done();
}
}
}).then(({ value }) => {
this.$message({
type: 'success',
message: 'File is deleted'
});
}).catch(() => {});
},
doCreateNew(task, instance, done) {
instance.showCancelButton = false;
instance.confirmButtonLoading = true;
this.$ajax({
task: task,
name: instance.inputValue,
path: this.contextItem.path,
})
.then(res => {
if (res.error) {
instance.showCancelButton = true;
instance.confirmButtonLoading = false;
alert(res.error);
} else {
return this.refreshNode(this.contextItem).then(() => done());
}
})
.catch(error => {
alert('create new error');
});
},
doRename(task, instance, done) {
instance.showCancelButton = false;
instance.confirmButtonLoading = true;
this.$ajax({
task: task,
newName: instance.inputValue,
oldPath: this.contextItem.path,
})
.then(res => {
if (res.error) {
instance.showCancelButton = true;
instance.confirmButtonLoading = false;
alert(res.error);
} else {
const parent = this.getParent(this.treeData[0], this.contextItem.path);
this.refreshNode(parent).then(() => {
const eventName = this.contextItem.type === 'file' ? 'fileNameChanged' : 'folderNameChanged';
EventBus.$emit(eventName, res.data, this.contextItem);
done();
});
}
})
.catch(error => {
alert('rename error');
});
},
doDelete(task, instance, done) {
instance.showCancelButton = false;
instance.confirmButtonLoading = true;
this.$ajax({
task: task,
path: this.contextItem.path,
})
.then(res => {
if (res.error) {
instance.showCancelButton = true;
instance.confirmButtonLoading = false;
alert(res.error);
} else {
const parent = this.getParent(this.treeData[0], this.contextItem.path);
const index = parent.children.findIndex(i => i.path === this.contextItem.path);
parent.children.splice(index, 1);
return this.refreshNode(parent).then(() => {
const eventName = this.contextItem.type === 'file' ? 'fileDeleted' : 'folderDeleted';
EventBus.$emit(eventName, this.contextItem);
done();
});
}
})
.catch(error => {
alert('delete error.');
});
},
setTreeHeight: debounce(function() {
const $ = jQuery;
const wHeight = $(window).height();
if ($('.e-folders').length) {
this.treeHeight = (wHeight - $('.e-folders').offset().top - 35) + 'px';
}
}, 100),
getParent(root, path) {
let node;
root.children.some(n => {
if (n.path === path) {
return node = root;
}
if (n.children) {
return node = this.getParent(n, path);
}
});
return node;
},
findItemByPath(root, path) {
let node;
if (root.path === path) {
return root;
}
root.children.some(n => {
if (n.path === path) {
return node = n;
}
if (n.children) {
return node = this.findItemByPath(n, path);
}
});
return node;
},
}
}
</script>
<style lang="scss">
.e-folders {
overflow: auto;
border: solid 1px #ddd;
ul.e-folders-list {
padding: 5px;
min-width: max-content;
margin: 0;
list-style: none;
font-family: monospace;
ul {
margin-left: 10px;
padding-left: 10px;
border-left: dashed 1px #ccc;
list-style: none;
}
}
.v-context {
min-width: 5rem;
padding: 0;
li {
cursor: pointer;
user-select: none;
}
}
}
.permission-dialog {
.permission-config {
width: 100%;
td {
padding: 8px;
line-height: 18px;
text-align: left;
vertical-align: top;
border-bottom: 1px solid #ddd;
}
}
}
.context-download-file {
display: none;
}
.el-loading-mask.compress-loading {
.el-icon-loading,
.el-loading-text {
color: #ccc;
}
}
</style>