mirror of
https://github.com/rikkaneko/paste.git
synced 2025-06-06 16:45:41 +00:00
Add custom file title
Remove redundant API_SPEC_TEXT Update paste.html formatting style Fix bugs Fix typo Signed-off-by: Joe Ma <rikkaneko23@gmail.com>
This commit is contained in:
parent
3562fe2d24
commit
1bf8f08ee6
5 changed files with 80 additions and 105 deletions
3
.idea/codeStyles/Project.xml
generated
3
.idea/codeStyles/Project.xml
generated
|
@ -7,9 +7,6 @@
|
||||||
<option name="GROUP_STDLIB_IMPORTS" value="true" />
|
<option name="GROUP_STDLIB_IMPORTS" value="true" />
|
||||||
<option name="LOCAL_PACKAGE_PREFIXES" />
|
<option name="LOCAL_PACKAGE_PREFIXES" />
|
||||||
</GoCodeStyleSettings>
|
</GoCodeStyleSettings>
|
||||||
<HTMLCodeStyleSettings>
|
|
||||||
<option name="HTML_ALIGN_TEXT" value="true" />
|
|
||||||
</HTMLCodeStyleSettings>
|
|
||||||
<JSCodeStyleSettings version="0">
|
<JSCodeStyleSettings version="0">
|
||||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
<option name="USE_DOUBLE_QUOTES" value="false" />
|
<option name="USE_DOUBLE_QUOTES" value="false" />
|
||||||
|
|
1
.idea/inspectionProfiles/Project_Default.xml
generated
1
.idea/inspectionProfiles/Project_Default.xml
generated
|
@ -3,6 +3,7 @@
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="ES6MissingAwait" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
<inspection_tool class="ES6MissingAwait" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="HtmlFormInputWithoutLabel" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
|
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
|
||||||
<option name="processCode" value="false" />
|
<option name="processCode" value="false" />
|
||||||
<option name="processLiterals" value="false" />
|
<option name="processLiterals" value="false" />
|
||||||
|
|
|
@ -119,6 +119,7 @@ Add `?qr=1` to enable QR code generation for paste link.
|
||||||
|`read-limit`|The maximum access count|
|
|`read-limit`|The maximum access count|
|
||||||
|`qrcode`|Toggle QR code generation|
|
|`qrcode`|Toggle QR code generation|
|
||||||
|`paste-type`|Set paste type|
|
|`paste-type`|Set paste type|
|
||||||
|
|`title`|File's title|
|
||||||
|
|
||||||
#### For raw request,
|
#### For raw request,
|
||||||
|
|
||||||
|
@ -129,6 +130,7 @@ Add `?qr=1` to enable QR code generation for paste link.
|
||||||
|`x-pass`|Paste's password|
|
|`x-pass`|Paste's password|
|
||||||
|`x-read-limit`|The maximum access count|
|
|`x-read-limit`|The maximum access count|
|
||||||
|`x-paste-type`|Set paste type|
|
|`x-paste-type`|Set paste type|
|
||||||
|
|`x-qr`|Toggle QR code generation|
|
||||||
|
|
||||||
The request body contains the upload content.
|
The request body contains the upload content.
|
||||||
|
|
||||||
|
@ -139,7 +141,7 @@ The request body contains the upload content.
|
||||||
|`paste`|Normal paste content|
|
|`paste`|Normal paste content|
|
||||||
|`link`|URL link to be redirected|
|
|`link`|URL link to be redirected|
|
||||||
|
|
||||||
### GET /\<uuid\>/\<option\> (Not implemented)
|
### GET /\<uuid\>/\<option\>
|
||||||
|
|
||||||
Fetch the paste (code) in rendered HTML with syntax highlighting
|
Fetch the paste (code) in rendered HTML with syntax highlighting
|
||||||
Add `?qr=1` to enable QR code generation for paste link.
|
Add `?qr=1` to enable QR code generation for paste link.
|
||||||
|
|
90
paste.html
90
paste.html
|
@ -34,51 +34,75 @@
|
||||||
The source code is available in my GitHub repository <a href="https://github.com/rikkaneko/paste">[here]</a>.<br>
|
The source code is available in my GitHub repository <a href="https://github.com/rikkaneko/paste">[here]</a>.<br>
|
||||||
This webpage is designed for upload files only.
|
This webpage is designed for upload files only.
|
||||||
For other operations like changing paste settings and deleting paste, please make use of the
|
For other operations like changing paste settings and deleting paste, please make use of the
|
||||||
<a href="https://pb.nekoul.com/api">API call</a> with <a href="https://wiki.archlinux.org/title/CURL">curl</a>.
|
<a href="https://github.com/rikkaneko/paste#api-specification">API call</a> with <a
|
||||||
|
href="https://wiki.archlinux.org/title/CURL">curl</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<form id="upload_file_form" action="https://pb.nekoul.com" method="POST" enctype=multipart/form-data>
|
<form id="upload_file_form" action="https://pb.nekoul.com" method="POST" enctype=multipart/form-data>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<h4>Upload file</h4>
|
<h4>Upload file</h4>
|
||||||
<input id="upload_file" type="file" name="u">
|
<input id="upload_file" type="file" name="u" style="font-size: 14px">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>Upload text</h4>
|
<h4>Upload text</h4>
|
||||||
<textarea id="text_input" style="width: 30%; max-width: 100%; " rows="5" cols="50"
|
<div>
|
||||||
name="u" placeholder="Paste text or URL link here..." spellcheck="false"></textarea>
|
<textarea id="text_input"
|
||||||
</div>
|
style="width: 50%; max-width: 80%; font-size: 14px; margin-top: 2px"
|
||||||
<div>
|
rows="10" name="u" spellcheck="false"></textarea><br>
|
||||||
<h4>Settings</h4>
|
(<span id="text_length">0 characters</span>)
|
||||||
<label for="pass_input">Password: </label>
|
</div>
|
||||||
<input id="pass_input" type="password" name="pass" style="margin-left: 7px;">
|
|
||||||
<input id="show_pass_button" type="checkbox">
|
|
||||||
<label for="show_pass_button">Show</label>
|
|
||||||
</div>
|
|
||||||
<div style="margin-top: 1px;">
|
|
||||||
<label for="read_limit_input">Read limit: </label>
|
|
||||||
<input id="read_limit_input" type="number" name="read-limit" min="1" style="margin-left: 2px; width: 3em">
|
|
||||||
</div>
|
|
||||||
<div style="margin-top: 2px;">
|
|
||||||
<input id="show_qr_checkbox" type="checkbox" name="qrcode" checked>
|
|
||||||
<label for="show_qr_checkbox">Show QR code on sumbitted</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<input id="gen_link" type="checkbox" name="paste-type" value="link">
|
|
||||||
<label for="gen_link">Generate as shorten URL link</label>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<div>
|
|
||||||
<input id="reset_button" type="reset" value="Reset">
|
|
||||||
<input id="sumbit_form_button" type="submit" value="Sumbit"> (<span id="file_stats">0 bytes</span>)
|
|
||||||
</div>
|
</div>
|
||||||
|
<h4>Settings</h4>
|
||||||
|
<table style="padding-bottom: 2px;">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="title_input">Title: </label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input id="title_input" type="text" name="title" spellcheck="false">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="pass_input">Password: </label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input id="pass_input" type="password" name="pass">
|
||||||
|
<input id="show_pass_button" type="checkbox">
|
||||||
|
<label for="show_pass_button">Show</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="read_limit_input">Read limit: </label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input id="read_limit_input" type="number" name="read-limit" min="1" style="width: 3em">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<input id="show_qr_checkbox" type="checkbox" name="qrcode" checked>
|
||||||
|
<label for="show_qr_checkbox">Show QR code on sumbitted</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input id="gen_link" type="checkbox" name="paste-type" value="link">
|
||||||
|
<label for="gen_link">Generate as shorten URL link</label>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div>
|
||||||
|
<input id="reset_button" type="reset" value="Reset" style="font-size: 14px">
|
||||||
|
<input id="sumbit_form_button" type="submit" value="Sumbit" style="font-size: 14px"> (<span
|
||||||
|
id="file_stats">0 bytes</span>)
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function update_textarea() {
|
function update_textarea() {
|
||||||
this.style.height = 'auto';
|
const length = document.getElementById('text_length');
|
||||||
this.style.height = this.scrollHeight + 'px';
|
length.textContent = `${this.value.length} characters`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_file_status() {
|
function update_file_status() {
|
||||||
|
@ -111,8 +135,8 @@
|
||||||
for (let i = 0; i < elements.length; i++) {
|
for (let i = 0; i < elements.length; i++) {
|
||||||
elements[i].disabled = false;
|
elements[i].disabled = false;
|
||||||
}
|
}
|
||||||
let size = document.getElementById('file_size');
|
let size = document.getElementById('file_stats');
|
||||||
size.innerHTML = '0 bytes';
|
size.textContent = '0 bytes';
|
||||||
}
|
}
|
||||||
|
|
||||||
function handle_submit_form(event) {
|
function handle_submit_form(event) {
|
||||||
|
@ -144,7 +168,7 @@
|
||||||
document.getElementById('upload_file_form').addEventListener('submit', handle_submit_form, false);
|
document.getElementById('upload_file_form').addEventListener('submit', handle_submit_form, false);
|
||||||
</script>
|
</script>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://nekoul.com">[Homepage]</a><a href="https://pb.nekoul.com/api">[API]</a>
|
<a href="https://nekoul.com">[Homepage]</a><a href="https://github.com/rikkaneko/paste#api-specification">[API]</a>
|
||||||
<p>© 2022 rikkaneko</p>
|
<p>© 2022 rikkaneko</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
87
src/index.ts
87
src/index.ts
|
@ -34,54 +34,6 @@ export interface Env {
|
||||||
ENDPOINT: string;
|
ENDPOINT: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const API_SPEC_TEXT =
|
|
||||||
`Paste service https://${SERVICE_URL}
|
|
||||||
|
|
||||||
[API Specification]
|
|
||||||
GET / Fetch the Web frontpage for uploading text/file [x]
|
|
||||||
GET /api Fetch API specification
|
|
||||||
|
|
||||||
# Authentication support HTTP Basic access authentication (RFC 7617) or the x-pass header
|
|
||||||
GET /<uuid> Fetch the paste by uuid [x]
|
|
||||||
|
|
||||||
# Currently, only the following options is supported for <option>,
|
|
||||||
# "settings": Fetch the paste information, add \`?qr=1\` to enable QR code generation for paste link.
|
|
||||||
# "download": Download paste as attachment
|
|
||||||
# "raw": Display paste as plain text
|
|
||||||
GET /<uuid>/<option> Fetch the paste (code) in rendered HTML with syntax highlighting [ ]
|
|
||||||
|
|
||||||
# Only support multipart/form-data and raw request
|
|
||||||
# For form-data, u=<upload-data>, both title and content-type is deduced from the u
|
|
||||||
# Add \`?qr=1\` or qrcode=(on|true) using form-data to enable QR code generation for paste link.
|
|
||||||
# The following key is supported for both HTTP form request and headers, add the prefix "x-" for header keys in raw request
|
|
||||||
# title: File title, i.e. main.py
|
|
||||||
# content-type: The media type (MIME) of the data and encoding, i.e., text/plain; charset=UTF-8;
|
|
||||||
# pass: Paste password
|
|
||||||
# read-limit: Limit access times to paste to <read-limit>
|
|
||||||
# paste-type: Set paste type (Available: paste, link)
|
|
||||||
POST / Create new paste [x]
|
|
||||||
|
|
||||||
DELETE /<uuid> Delete paste by uuid [x]
|
|
||||||
POST /<uuid>/settings Update paste setting, i.e., passcode and valid time [ ]
|
|
||||||
|
|
||||||
# For paste with password protected, all API call related to the pastes requires additional x-pass header
|
|
||||||
|
|
||||||
* uuid: [A-z0-9]{${UUID_LENGTH}}
|
|
||||||
|
|
||||||
Supported Features
|
|
||||||
* Password protection
|
|
||||||
* Limit access times
|
|
||||||
* Generate QR code for paste link
|
|
||||||
|
|
||||||
[ ] indicated not implemented
|
|
||||||
|
|
||||||
Limitation
|
|
||||||
* Max. 10MB file size upload
|
|
||||||
* Paste will be kept for 28 days only by default
|
|
||||||
|
|
||||||
Last update on 11 Sept.
|
|
||||||
`;
|
|
||||||
|
|
||||||
const gen_id = customAlphabet(
|
const gen_id = customAlphabet(
|
||||||
'1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', UUID_LENGTH);
|
'1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', UUID_LENGTH);
|
||||||
|
|
||||||
|
@ -115,14 +67,6 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path === '/api' && method == 'GET') {
|
|
||||||
return new Response(API_SPEC_TEXT, {
|
|
||||||
headers: {
|
|
||||||
'cache-control': 'public, max-age=172800',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path === '/') {
|
if (path === '/') {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
// Fetch the HTML for uploading text/file
|
// Fetch the HTML for uploading text/file
|
||||||
|
@ -159,6 +103,7 @@ export default {
|
||||||
const formdata = await request.formData();
|
const formdata = await request.formData();
|
||||||
const data = formdata.get('u');
|
const data = formdata.get('u');
|
||||||
const type = formdata.get('paste-type');
|
const type = formdata.get('paste-type');
|
||||||
|
const file_title = formdata.get('title');
|
||||||
if (data === null) {
|
if (data === null) {
|
||||||
return new Response('Invalid request.\n', {
|
return new Response('Invalid request.\n', {
|
||||||
status: 422,
|
status: 422,
|
||||||
|
@ -175,6 +120,7 @@ export default {
|
||||||
mime_type = 'text/plain; charset=UTF-8;';
|
mime_type = 'text/plain; charset=UTF-8;';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof file_title === 'string') title = file_title;
|
||||||
if (typeof type === 'string') paste_type = type;
|
if (typeof type === 'string') paste_type = type;
|
||||||
|
|
||||||
// Set password
|
// Set password
|
||||||
|
@ -206,16 +152,15 @@ export default {
|
||||||
mime_type = headers.get('x-content-type') || undefined;
|
mime_type = headers.get('x-content-type') || undefined;
|
||||||
password = headers.get('x-pass') || undefined;
|
password = headers.get('x-pass') || undefined;
|
||||||
paste_type = headers.get('x-paste-type') || undefined;
|
paste_type = headers.get('x-paste-type') || undefined;
|
||||||
const count = headers.get('x-read-limit') || undefined;
|
need_qrcode = headers.get('x-qr') === '1';
|
||||||
if (typeof count === 'string') {
|
const count = headers.get('x-read-limit') || '';
|
||||||
const n = parseInt(count);
|
const n = parseInt(count);
|
||||||
if (isNaN(n) || n <= 0) {
|
if (isNaN(n) || n <= 0) {
|
||||||
return new Response('x-read-limit must be a positive integer.\n', {
|
return new Response('x-read-limit must be a positive integer.\n', {
|
||||||
status: 422,
|
status: 422,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
read_limit = n;
|
|
||||||
}
|
}
|
||||||
|
read_limit = n;
|
||||||
buffer = await request.arrayBuffer();
|
buffer = await request.arrayBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,6 +187,12 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check file title rules
|
||||||
|
if (title && /^.*[\\\/]/.test(title))
|
||||||
|
return new Response('Invalid title', {
|
||||||
|
status: 422,
|
||||||
|
});
|
||||||
|
|
||||||
// Check password rules
|
// Check password rules
|
||||||
if (password && !check_password_rules(password)) {
|
if (password && !check_password_rules(password)) {
|
||||||
return new Response('Invalid password. ' +
|
return new Response('Invalid password. ' +
|
||||||
|
@ -321,7 +272,7 @@ export default {
|
||||||
if (option === 'settings') {
|
if (option === 'settings') {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'GET': {
|
case 'GET': {
|
||||||
const need_qrcode = searchParams.get('qr') === '1';
|
const need_qrcode = searchParams.get('qr') === '1' || headers.get('x-qr') === '1';
|
||||||
return await get_paste_info(uuid, descriptor, env, is_browser, need_qrcode);
|
return await get_paste_info(uuid, descriptor, env, is_browser, need_qrcode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -518,11 +469,11 @@ async function get_paste_info(uuid: string, descriptor: PasteIndexEntry, env: En
|
||||||
let content = dedent`
|
let content = dedent`
|
||||||
id: ${uuid}
|
id: ${uuid}
|
||||||
link: ${link}
|
link: ${link}
|
||||||
|
type: ${descriptor.type}
|
||||||
title: ${descriptor.title?.trim() || '-'}
|
title: ${descriptor.title?.trim() || '-'}
|
||||||
mime-type: ${descriptor.mime_type ?? '-'}
|
mime-type: ${descriptor.mime_type ?? '-'}
|
||||||
size: ${descriptor.size} bytes (${to_human_readable_size(descriptor.size)})
|
size: ${descriptor.size} bytes (${to_human_readable_size(descriptor.size)})
|
||||||
password: ${(!!descriptor.password)}
|
password: ${(!!descriptor.password)}
|
||||||
editable: ${descriptor.editable ? descriptor.editable : true}
|
|
||||||
remaining read count: ${descriptor.read_count_remain !== undefined ?
|
remaining read count: ${descriptor.read_count_remain !== undefined ?
|
||||||
descriptor.read_count_remain ? descriptor.read_count_remain : `0 (expired)` : '-'}
|
descriptor.read_count_remain ? descriptor.read_count_remain : `0 (expired)` : '-'}
|
||||||
created at ${created.toISOString()}
|
created at ${created.toISOString()}
|
||||||
|
@ -623,7 +574,7 @@ interface PasteIndexEntry {
|
||||||
last_modified: number,
|
last_modified: number,
|
||||||
size: number,
|
size: number,
|
||||||
password?: string,
|
password?: string,
|
||||||
editable?: boolean, // Default: True
|
editable?: boolean, // Default: False (unsupported)
|
||||||
read_count_remain?: number
|
read_count_remain?: number
|
||||||
type?: string;
|
type?: string;
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue