parent
009f9c297d
commit
625fd92130
9 changed files with 32 additions and 17 deletions
|
@ -6,6 +6,7 @@ use Carbon\CarbonImmutable;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Pterodactyl\Models\Server;
|
use Pterodactyl\Models\Server;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Pterodactyl\Services\Nodes\NodeJWTService;
|
use Pterodactyl\Services\Nodes\NodeJWTService;
|
||||||
use Illuminate\Contracts\Routing\ResponseFactory;
|
use Illuminate\Contracts\Routing\ResponseFactory;
|
||||||
use Pterodactyl\Repositories\Wings\DaemonFileRepository;
|
use Pterodactyl\Repositories\Wings\DaemonFileRepository;
|
||||||
|
@ -70,7 +71,7 @@ class FileController extends ClientApiController
|
||||||
{
|
{
|
||||||
$contents = $this->fileRepository
|
$contents = $this->fileRepository
|
||||||
->setServer($server)
|
->setServer($server)
|
||||||
->getDirectory(urlencode(urldecode($request->get('directory') ?? '/')));
|
->getDirectory($this->encode($request->get('directory') ?? '/'));
|
||||||
|
|
||||||
return $this->fractal->collection($contents)
|
return $this->fractal->collection($contents)
|
||||||
->transformWith($this->getTransformer(FileObjectTransformer::class))
|
->transformWith($this->getTransformer(FileObjectTransformer::class))
|
||||||
|
@ -91,7 +92,7 @@ class FileController extends ClientApiController
|
||||||
{
|
{
|
||||||
return new Response(
|
return new Response(
|
||||||
$this->fileRepository->setServer($server)->getContent(
|
$this->fileRepository->setServer($server)->getContent(
|
||||||
urlencode(urldecode($request->get('file'))), config('pterodactyl.files.max_edit_size')
|
$this->encode($request->get('file')), config('pterodactyl.files.max_edit_size')
|
||||||
),
|
),
|
||||||
Response::HTTP_OK,
|
Response::HTTP_OK,
|
||||||
['Content-Type' => 'text/plain']
|
['Content-Type' => 'text/plain']
|
||||||
|
@ -113,7 +114,7 @@ class FileController extends ClientApiController
|
||||||
$token = $this->jwtService
|
$token = $this->jwtService
|
||||||
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
|
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
|
||||||
->setClaims([
|
->setClaims([
|
||||||
'file_path' => $request->get('file'),
|
'file_path' => rawurldecode($request->get('file')),
|
||||||
'server_uuid' => $server->uuid,
|
'server_uuid' => $server->uuid,
|
||||||
])
|
])
|
||||||
->handle($server->node, $request->user()->id . $server->uuid);
|
->handle($server->node, $request->user()->id . $server->uuid);
|
||||||
|
@ -142,7 +143,7 @@ class FileController extends ClientApiController
|
||||||
public function write(WriteFileContentRequest $request, Server $server): JsonResponse
|
public function write(WriteFileContentRequest $request, Server $server): JsonResponse
|
||||||
{
|
{
|
||||||
$this->fileRepository->setServer($server)->putContent(
|
$this->fileRepository->setServer($server)->putContent(
|
||||||
$request->get('file'),
|
$this->encode($request->get('file')),
|
||||||
$request->getContent()
|
$request->getContent()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -261,4 +262,18 @@ class FileController extends ClientApiController
|
||||||
|
|
||||||
return new JsonResponse([], Response::HTTP_NO_CONTENT);
|
return new JsonResponse([], Response::HTTP_NO_CONTENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a given file name & path in a format that should work for a good majority
|
||||||
|
* of file names without too much confusing logic.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function encode(string $path): string
|
||||||
|
{
|
||||||
|
return Collection::make(explode('/', rawurldecode($path)))->map(function ($value) {
|
||||||
|
return rawurlencode($value);
|
||||||
|
})->join('/');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import http from '@/api/http';
|
||||||
export default (server: string, file: string): Promise<string> => {
|
export default (server: string, file: string): Promise<string> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
http.get(`/api/client/servers/${server}/files/contents`, {
|
http.get(`/api/client/servers/${server}/files/contents`, {
|
||||||
params: { file: file.split('/').map(item => encodeURIComponent(item)).join('/') },
|
params: { file: encodeURI(decodeURI(file)) },
|
||||||
transformResponse: res => res,
|
transformResponse: res => res,
|
||||||
responseType: 'text',
|
responseType: 'text',
|
||||||
})
|
})
|
||||||
|
|
|
@ -17,7 +17,7 @@ export interface FileObject {
|
||||||
|
|
||||||
export default async (uuid: string, directory?: string): Promise<FileObject[]> => {
|
export default async (uuid: string, directory?: string): Promise<FileObject[]> => {
|
||||||
const { data } = await http.get(`/api/client/servers/${uuid}/files/list`, {
|
const { data } = await http.get(`/api/client/servers/${uuid}/files/list`, {
|
||||||
params: { directory: directory?.split('/').map(item => encodeURIComponent(item)).join('/') },
|
params: { directory: encodeURI(directory ?? '/') },
|
||||||
});
|
});
|
||||||
|
|
||||||
return (data.data || []).map(rawDataToFileObject);
|
return (data.data || []).map(rawDataToFileObject);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import http from '@/api/http';
|
||||||
|
|
||||||
export default async (uuid: string, file: string, content: string): Promise<void> => {
|
export default async (uuid: string, file: string, content: string): Promise<void> => {
|
||||||
await http.post(`/api/client/servers/${uuid}/files/write`, content, {
|
await http.post(`/api/client/servers/${uuid}/files/write`, content, {
|
||||||
params: { file },
|
params: { file: encodeURI(decodeURI(file)) },
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/plain',
|
'Content-Type': 'text/plain',
|
||||||
},
|
},
|
||||||
|
|
|
@ -61,7 +61,7 @@ export default () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
clearFlashes('files:view');
|
clearFlashes('files:view');
|
||||||
fetchFileContent()
|
fetchFileContent()
|
||||||
.then(content => saveFileContents(uuid, encodeURIComponent(name || hash.replace(/^#/, '')), content))
|
.then(content => saveFileContents(uuid, name || hash.replace(/^#/, ''), content))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (name) {
|
if (name) {
|
||||||
history.push(`/server/${id}/files/edit#/${name}`);
|
history.push(`/server/${id}/files/edit#/${name}`);
|
||||||
|
|
|
@ -33,10 +33,10 @@ export default ({ withinFileEditor, isNewFile }: Props) => {
|
||||||
.filter(directory => !!directory)
|
.filter(directory => !!directory)
|
||||||
.map((directory, index, dirs) => {
|
.map((directory, index, dirs) => {
|
||||||
if (!withinFileEditor && index === dirs.length - 1) {
|
if (!withinFileEditor && index === dirs.length - 1) {
|
||||||
return { name: decodeURIComponent(encodeURIComponent(directory)) };
|
return { name: directory };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { name: decodeURIComponent(encodeURIComponent(directory)), path: `/${dirs.slice(0, index + 1).join('/')}` };
|
return { name: directory, path: `/${dirs.slice(0, index + 1).join('/')}` };
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSelectAllClick = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const onSelectAllClick = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
@ -79,7 +79,7 @@ export default ({ withinFileEditor, isNewFile }: Props) => {
|
||||||
}
|
}
|
||||||
{file &&
|
{file &&
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<span css={tw`px-1 text-neutral-300`}>{decodeURIComponent(encodeURIComponent(file))}</span>
|
<span css={tw`px-1 text-neutral-300`}>{decodeURI(file)}</span>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -36,7 +36,7 @@ export default () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
clearFlashes('files');
|
clearFlashes('files');
|
||||||
setSelectedFiles([]);
|
setSelectedFiles([]);
|
||||||
setDirectory(hash.length > 0 ? hash : '/');
|
setDirectory(hash.length > 0 ? decodeURI(hash) : '/');
|
||||||
}, [ hash ]);
|
}, [ hash ]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -24,6 +24,8 @@ const Clickable: React.FC<{ file: FileObject }> = memo(({ file, children }) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const match = useRouteMatch();
|
const match = useRouteMatch();
|
||||||
|
|
||||||
|
const destination = cleanDirectoryPath(`${directory}/${file.name}`).split('/').map(v => encodeURI(v)).join('/');
|
||||||
|
|
||||||
const onRowClick = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
const onRowClick = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||||
// Don't rely on the onClick to work with the generated URL. Because of the way this
|
// Don't rely on the onClick to work with the generated URL. Because of the way this
|
||||||
// component re-renders you'll get redirected into a nested directory structure since
|
// component re-renders you'll get redirected into a nested directory structure since
|
||||||
|
@ -32,7 +34,7 @@ const Clickable: React.FC<{ file: FileObject }> = memo(({ file, children }) => {
|
||||||
// Just trust me future me, leave this be.
|
// Just trust me future me, leave this be.
|
||||||
if (!file.isFile) {
|
if (!file.isFile) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
history.push(`#${cleanDirectoryPath(`${directory}/${file.name}`)}`);
|
history.push(`#${destination}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,7 +45,7 @@ const Clickable: React.FC<{ file: FileObject }> = memo(({ file, children }) => {
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
<NavLink
|
<NavLink
|
||||||
to={`${match.url}/${file.isFile ? 'edit/' : ''}#${cleanDirectoryPath(`${directory}/${file.name}`)}`}
|
to={`${match.url}/${file.isFile ? 'edit/' : ''}#${destination}`}
|
||||||
css={tw`flex flex-1 text-neutral-300 no-underline p-3 overflow-hidden truncate`}
|
css={tw`flex flex-1 text-neutral-300 no-underline p-3 overflow-hidden truncate`}
|
||||||
onClick={onRowClick}
|
onClick={onRowClick}
|
||||||
>
|
>
|
||||||
|
|
|
@ -92,9 +92,7 @@ export default ({ className }: WithClassname) => {
|
||||||
<span css={tw`text-neutral-200`}>This directory will be created as</span>
|
<span css={tw`text-neutral-200`}>This directory will be created as</span>
|
||||||
/home/container/
|
/home/container/
|
||||||
<span css={tw`text-cyan-200`}>
|
<span css={tw`text-cyan-200`}>
|
||||||
{decodeURIComponent(encodeURIComponent(
|
{join(directory, values.directoryName).replace(/^(\.\.\/|\/)+/, '')}
|
||||||
join(directory, values.directoryName).replace(/^(\.\.\/|\/)+/, ''),
|
|
||||||
))}
|
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<div css={tw`flex justify-end`}>
|
<div css={tw`flex justify-end`}>
|
||||||
|
|
Loading…
Reference in a new issue