Skip to content

Commit 625fd92

Browse files
committed
Fix URKL encoding hellscape; closes pterodactyl#2664 closes pterodactyl#2663
1 parent 009f9c2 commit 625fd92

File tree

9 files changed

+32
-17
lines changed

9 files changed

+32
-17
lines changed

app/Http/Controllers/Api/Client/Servers/FileController.php

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Http\Response;
77
use Pterodactyl\Models\Server;
88
use Illuminate\Http\JsonResponse;
9+
use Illuminate\Support\Collection;
910
use Pterodactyl\Services\Nodes\NodeJWTService;
1011
use Illuminate\Contracts\Routing\ResponseFactory;
1112
use Pterodactyl\Repositories\Wings\DaemonFileRepository;
@@ -70,7 +71,7 @@ public function directory(ListFilesRequest $request, Server $server): array
7071
{
7172
$contents = $this->fileRepository
7273
->setServer($server)
73-
->getDirectory(urlencode(urldecode($request->get('directory') ?? '/')));
74+
->getDirectory($this->encode($request->get('directory') ?? '/'));
7475

7576
return $this->fractal->collection($contents)
7677
->transformWith($this->getTransformer(FileObjectTransformer::class))
@@ -91,7 +92,7 @@ public function contents(GetFileContentsRequest $request, Server $server): Respo
9192
{
9293
return new Response(
9394
$this->fileRepository->setServer($server)->getContent(
94-
urlencode(urldecode($request->get('file'))), config('pterodactyl.files.max_edit_size')
95+
$this->encode($request->get('file')), config('pterodactyl.files.max_edit_size')
9596
),
9697
Response::HTTP_OK,
9798
['Content-Type' => 'text/plain']
@@ -113,7 +114,7 @@ public function download(GetFileContentsRequest $request, Server $server)
113114
$token = $this->jwtService
114115
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
115116
->setClaims([
116-
'file_path' => $request->get('file'),
117+
'file_path' => rawurldecode($request->get('file')),
117118
'server_uuid' => $server->uuid,
118119
])
119120
->handle($server->node, $request->user()->id . $server->uuid);
@@ -142,7 +143,7 @@ public function download(GetFileContentsRequest $request, Server $server)
142143
public function write(WriteFileContentRequest $request, Server $server): JsonResponse
143144
{
144145
$this->fileRepository->setServer($server)->putContent(
145-
$request->get('file'),
146+
$this->encode($request->get('file')),
146147
$request->getContent()
147148
);
148149

@@ -261,4 +262,18 @@ public function delete(DeleteFileRequest $request, Server $server): JsonResponse
261262

262263
return new JsonResponse([], Response::HTTP_NO_CONTENT);
263264
}
265+
266+
/**
267+
* Encodes a given file name & path in a format that should work for a good majority
268+
* of file names without too much confusing logic.
269+
*
270+
* @param string $path
271+
* @return string
272+
*/
273+
private function encode(string $path): string
274+
{
275+
return Collection::make(explode('/', rawurldecode($path)))->map(function ($value) {
276+
return rawurlencode($value);
277+
})->join('/');
278+
}
264279
}

resources/scripts/api/server/files/getFileContents.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import http from '@/api/http';
33
export default (server: string, file: string): Promise<string> => {
44
return new Promise((resolve, reject) => {
55
http.get(`/api/client/servers/${server}/files/contents`, {
6-
params: { file: file.split('/').map(item => encodeURIComponent(item)).join('/') },
6+
params: { file: encodeURI(decodeURI(file)) },
77
transformResponse: res => res,
88
responseType: 'text',
99
})

resources/scripts/api/server/files/loadDirectory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface FileObject {
1717

1818
export default async (uuid: string, directory?: string): Promise<FileObject[]> => {
1919
const { data } = await http.get(`/api/client/servers/${uuid}/files/list`, {
20-
params: { directory: directory?.split('/').map(item => encodeURIComponent(item)).join('/') },
20+
params: { directory: encodeURI(directory ?? '/') },
2121
});
2222

2323
return (data.data || []).map(rawDataToFileObject);

resources/scripts/api/server/files/saveFileContents.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import http from '@/api/http';
22

33
export default async (uuid: string, file: string, content: string): Promise<void> => {
44
await http.post(`/api/client/servers/${uuid}/files/write`, content, {
5-
params: { file },
5+
params: { file: encodeURI(decodeURI(file)) },
66
headers: {
77
'Content-Type': 'text/plain',
88
},

resources/scripts/components/server/files/FileEditContainer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export default () => {
6161
setLoading(true);
6262
clearFlashes('files:view');
6363
fetchFileContent()
64-
.then(content => saveFileContents(uuid, encodeURIComponent(name || hash.replace(/^#/, '')), content))
64+
.then(content => saveFileContents(uuid, name || hash.replace(/^#/, ''), content))
6565
.then(() => {
6666
if (name) {
6767
history.push(`/server/${id}/files/edit#/${name}`);

resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ export default ({ withinFileEditor, isNewFile }: Props) => {
3333
.filter(directory => !!directory)
3434
.map((directory, index, dirs) => {
3535
if (!withinFileEditor && index === dirs.length - 1) {
36-
return { name: decodeURIComponent(encodeURIComponent(directory)) };
36+
return { name: directory };
3737
}
3838

39-
return { name: decodeURIComponent(encodeURIComponent(directory)), path: `/${dirs.slice(0, index + 1).join('/')}` };
39+
return { name: directory, path: `/${dirs.slice(0, index + 1).join('/')}` };
4040
});
4141

4242
const onSelectAllClick = (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -79,7 +79,7 @@ export default ({ withinFileEditor, isNewFile }: Props) => {
7979
}
8080
{file &&
8181
<React.Fragment>
82-
<span css={tw`px-1 text-neutral-300`}>{decodeURIComponent(encodeURIComponent(file))}</span>
82+
<span css={tw`px-1 text-neutral-300`}>{decodeURI(file)}</span>
8383
</React.Fragment>
8484
}
8585
</div>

resources/scripts/components/server/files/FileManagerContainer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default () => {
3636
useEffect(() => {
3737
clearFlashes('files');
3838
setSelectedFiles([]);
39-
setDirectory(hash.length > 0 ? hash : '/');
39+
setDirectory(hash.length > 0 ? decodeURI(hash) : '/');
4040
}, [ hash ]);
4141

4242
useEffect(() => {

resources/scripts/components/server/files/FileObjectRow.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const Clickable: React.FC<{ file: FileObject }> = memo(({ file, children }) => {
2424
const history = useHistory();
2525
const match = useRouteMatch();
2626

27+
const destination = cleanDirectoryPath(`${directory}/${file.name}`).split('/').map(v => encodeURI(v)).join('/');
28+
2729
const onRowClick = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
2830
// Don't rely on the onClick to work with the generated URL. Because of the way this
2931
// 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 }) => {
3234
// Just trust me future me, leave this be.
3335
if (!file.isFile) {
3436
e.preventDefault();
35-
history.push(`#${cleanDirectoryPath(`${directory}/${file.name}`)}`);
37+
history.push(`#${destination}`);
3638
}
3739
};
3840

@@ -43,7 +45,7 @@ const Clickable: React.FC<{ file: FileObject }> = memo(({ file, children }) => {
4345
</div>
4446
:
4547
<NavLink
46-
to={`${match.url}/${file.isFile ? 'edit/' : ''}#${cleanDirectoryPath(`${directory}/${file.name}`)}`}
48+
to={`${match.url}/${file.isFile ? 'edit/' : ''}#${destination}`}
4749
css={tw`flex flex-1 text-neutral-300 no-underline p-3 overflow-hidden truncate`}
4850
onClick={onRowClick}
4951
>

resources/scripts/components/server/files/NewDirectoryButton.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,7 @@ export default ({ className }: WithClassname) => {
9292
<span css={tw`text-neutral-200`}>This directory will be created as</span>
9393
&nbsp;/home/container/
9494
<span css={tw`text-cyan-200`}>
95-
{decodeURIComponent(encodeURIComponent(
96-
join(directory, values.directoryName).replace(/^(\.\.\/|\/)+/, ''),
97-
))}
95+
{join(directory, values.directoryName).replace(/^(\.\.\/|\/)+/, '')}
9896
</span>
9997
</p>
10098
<div css={tw`flex justify-end`}>

0 commit comments

Comments
 (0)