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:
Joe Ma 2022-10-15 13:56:31 +08:00
parent 3562fe2d24
commit 1bf8f08ee6
No known key found for this signature in database
GPG key ID: 7A0ECF5F5EDC587F
5 changed files with 80 additions and 105 deletions

View file

@ -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" />

View file

@ -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" />

View file

@ -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.

View file

@ -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>&copy; 2022 rikkaneko</p> <p>&copy; 2022 rikkaneko</p>
</body> </body>
</html> </html>

View file

@ -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;
} }