This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

configs.services.pad: upload any file to p.krebsco.de, drop external link warning

+237
+11
configs/services/pad/module.nix
··· 21 21 22 22 services.hedgedoc = { 23 23 enable = true; 24 + # Adds the krebspaste upload type (images to p.krebsco.de/image, other 25 + # files as pastes to p.krebsco.de, GCed after 30 days), 26 + # allowedUploadMimeTypes = [ "*" ] for any file type, and links 27 + # non-image uploads as [name](url) instead of embedding them. 28 + package = pkgs.hedgedoc.overrideAttrs (old: { 29 + patches = (old.patches or [ ]) ++ [ ./upload-any-file.patch ]; 30 + }); 24 31 settings = { 25 32 allowOrigin = [ 26 33 "localhost" ··· 31 38 domain = config.clan.hedgedoc.domain; 32 39 allowFreeURL = true; 33 40 defaultPermission = "freely"; 41 + # No "you are leaving HedgeDoc" interstitial on external links. 42 + externalLinkWarning = false; 43 + allowedUploadMimeTypes = [ "*" ]; 44 + imageUploadType = "krebspaste"; 34 45 # debug = true; 35 46 # useSSL = true; 36 47 # hsts.enable = false;
+226
configs/services/pad/upload-any-file.patch
··· 1 + diff -r a/lib/config/default.js b/lib/config/default.js 2 + --- a/lib/config/default.js 2026-10-11 08:22:28.833011861 +0200 3 + +++ b/lib/config/default.js 2026-10-11 08:22:52.996391760 +0200 4 + @@ -71,11 +71,14 @@ 5 + tooBusyLag: 70, 6 + // document 7 + documentMaxLength: 100000, 8 + - // image upload setting, available options are imgur/s3/filesystem/azure/lutim 9 + + // image upload setting, available options are imgur/s3/filesystem/azure/lutim/krebspaste 10 + imageUploadType: 'filesystem', 11 + lutim: { 12 + url: 'https://framapic.org/' 13 + }, 14 + + krebspaste: { 15 + + url: 'https://p.krebsco.de' 16 + + }, 17 + imgur: { 18 + clientID: undefined 19 + }, 20 + diff -r a/lib/config/index.js b/lib/config/index.js 21 + --- a/lib/config/index.js 2026-10-11 08:22:28.832388404 +0200 22 + +++ b/lib/config/index.js 2026-10-11 08:22:52.994753055 +0200 23 + @@ -170,8 +170,8 @@ 24 + } 25 + 26 + // Validate upload upload providers 27 + -if (['filesystem', 's3', 'minio', 'imgur', 'azure', 'lutim'].indexOf(config.imageUploadType) === -1) { 28 + - logger.error('"imageuploadtype" is not correctly set. Please use "filesystem", "s3", "minio", "azure", "lutim" or "imgur". Defaulting to "filesystem"') 29 + +if (['filesystem', 's3', 'minio', 'imgur', 'azure', 'lutim', 'krebspaste'].indexOf(config.imageUploadType) === -1) { 30 + + logger.error('"imageuploadtype" is not correctly set. Please use "filesystem", "s3", "minio", "azure", "lutim", "krebspaste" or "imgur". Defaulting to "filesystem"') 31 + config.imageUploadType = 'filesystem' 32 + } 33 + 34 + @@ -179,23 +179,26 @@ 35 + logger.error('You can only deactivate one of "saml.wantAssertionsSigned" and "saml.wantAuthnResponseSigned"') 36 + } 37 + 38 + -// figure out mime types for image uploads 39 + -switch (config.imageUploadType) { 40 + - case 'imgur': 41 + - config.allowedUploadMimeTypes = [ 42 + - 'image/jpeg', 43 + - 'image/png', 44 + - 'image/gif' 45 + - ] 46 + - break 47 + - default: 48 + - config.allowedUploadMimeTypes = [ 49 + - 'image/jpeg', 50 + - 'image/png', 51 + - 'image/gif', 52 + - 'image/svg+xml', 53 + - 'image/webp' 54 + - ] 55 + +// figure out mime types for image uploads, unless set explicitly. 56 + +// ['*'] allows any file type. 57 + +if (!Array.isArray(config.allowedUploadMimeTypes)) { 58 + + switch (config.imageUploadType) { 59 + + case 'imgur': 60 + + config.allowedUploadMimeTypes = [ 61 + + 'image/jpeg', 62 + + 'image/png', 63 + + 'image/gif' 64 + + ] 65 + + break 66 + + default: 67 + + config.allowedUploadMimeTypes = [ 68 + + 'image/jpeg', 69 + + 'image/png', 70 + + 'image/gif', 71 + + 'image/svg+xml', 72 + + 'image/webp' 73 + + ] 74 + + } 75 + } 76 + 77 + // generate correct path 78 + diff -r a/lib/web/imageRouter/index.js b/lib/web/imageRouter/index.js 79 + --- a/lib/web/imageRouter/index.js 2026-10-11 08:22:28.837198185 +0200 80 + +++ b/lib/web/imageRouter/index.js 2026-10-11 08:22:29.024913829 +0200 81 + @@ -32,6 +32,9 @@ 82 + export default imageRouter 83 + 84 + async function checkUploadType (filePath) { 85 + + if (config.allowedUploadMimeTypes.includes('*')) { 86 + + return true 87 + + } 88 + const extension = path.extname(filePath).toLowerCase() 89 + const FileType = await import('file-type') 90 + let typeFromMagic = await FileType.fileTypeFromFile(filePath) 91 + diff -r a/lib/web/imageRouter/krebspaste.js b/lib/web/imageRouter/krebspaste.js 92 + --- a/lib/web/imageRouter/krebspaste.js 1970-01-01 01:00:00.000000000 +0100 93 + +++ b/lib/web/imageRouter/krebspaste.js 2026-10-11 08:22:41.108085483 +0200 94 + @@ -0,0 +1,62 @@ 95 + +'use strict' 96 + +// Uploads to a krebs paste service (https://p.krebsco.de): images to its 97 + +// imgur-compatible /image endpoint, everything else (and images /image 98 + +// rejects) as a plain paste to /. 99 + +const fs = require('fs') 100 + + 101 + +const config = require('../../config') 102 + +const logger = require('../../logger') 103 + + 104 + +async function post (url, body) { 105 + + const res = await fetch(url, { method: 'POST', body }) 106 + + const text = await res.text() 107 + + return { status: res.status, ok: res.ok, text } 108 + +} 109 + + 110 + +// The service reports links with the scheme/host it saw; keep only the path. 111 + +function rebase (link, base) { 112 + + return new URL(new URL(link).pathname, base).href 113 + +} 114 + + 115 + +async function upload (filePath) { 116 + + const base = config.krebspaste.url 117 + + const body = fs.readFileSync(filePath) 118 + + const FileType = await import('file-type') 119 + + const type = await FileType.fileTypeFromBuffer(body) 120 + + 121 + + if (type && type.mime.startsWith('image/')) { 122 + + const res = await post(new URL('/image', base), body) 123 + + if (res.ok) { 124 + + return rebase(JSON.parse(res.text).data.link, base) 125 + + } 126 + + if (res.status !== 400) { 127 + + throw new Error(`krebspaste /image: ${res.status} ${res.text}`) 128 + + } 129 + + logger.debug(`krebspaste: /image rejected ${type.mime}, pasting instead`) 130 + + } 131 + + 132 + + const res = await post(new URL('/', base), body) 133 + + if (!res.ok) { 134 + + throw new Error(`krebspaste /: ${res.status} ${res.text}`) 135 + + } 136 + + // one link per line: full hash, then the short one if unambiguous 137 + + const links = res.text.trim().split('\n') 138 + + return rebase(links[links.length - 1], base) 139 + +} 140 + + 141 + +exports.uploadImage = function (imagePath, callback) { 142 + + if (!callback || typeof callback !== 'function') { 143 + + logger.error('Callback has to be a function') 144 + + return 145 + + } 146 + + 147 + + if (!imagePath || typeof imagePath !== 'string') { 148 + + callback(new Error('Image path is missing or wrong'), null) 149 + + return 150 + + } 151 + + 152 + + upload(imagePath).then( 153 + + (url) => callback(null, url), 154 + + (err) => callback(err, null) 155 + + ) 156 + +} 157 + diff -r a/public/js/lib/editor/toolbar.html b/public/js/lib/editor/toolbar.html 158 + --- a/public/js/lib/editor/toolbar.html 2026-10-11 08:22:28.849861030 +0200 159 + +++ b/public/js/lib/editor/toolbar.html 2026-10-11 08:22:29.025254870 +0200 160 + @@ -34,8 +34,8 @@ 161 + <a id="makeImage" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Image"> 162 + <i class="fa fa-image fa-fw"></i> 163 + </a> 164 + - <a id="uploadImage" class="btn btn-sm btn-file ui-upload-image" title="Upload Image"> 165 + - <i class="fa fa-upload fa-fw"></i><input type="file" accept="image/*" name="upload" multiple title="Upload Image"> 166 + + <a id="uploadImage" class="btn btn-sm btn-file ui-upload-image" title="Upload File"> 167 + + <i class="fa fa-upload fa-fw"></i><input type="file" name="upload" multiple title="Upload File"> 168 + </a> 169 + <a id="makeTable" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Table"> 170 + <i class="fa fa-table fa-fw"></i> 171 + diff -r a/public/vendor/inlineAttachment/inline-attachment.js b/public/vendor/inlineAttachment/inline-attachment.js 172 + --- a/public/vendor/inlineAttachment/inline-attachment.js 2026-10-11 08:22:28.904847610 +0200 173 + +++ b/public/vendor/inlineAttachment/inline-attachment.js 2026-10-11 08:22:29.025553536 +0200 174 + @@ -232,6 +232,8 @@ 175 + var fileNameMatches = file.name.match(/\.(.+)$/); 176 + if (fileNameMatches) { 177 + extension = fileNameMatches[1]; 178 + + } else if (file.type.indexOf('image/') !== 0) { 179 + + extension = 'bin'; 180 + } 181 + } 182 + 183 + @@ -265,7 +267,7 @@ 184 + xhr.onload = function() { 185 + // If HTTP status is OK or Created 186 + if (xhr.status === 200 || xhr.status === 201) { 187 + - me.onFileUploadResponse(xhr, id); 188 + + me.onFileUploadResponse(xhr, id, file); 189 + } else { 190 + me.onFileUploadError(xhr, id); 191 + } 192 + @@ -295,7 +297,7 @@ 193 + * @param {XMLHttpRequest} xhr 194 + * @return {Void} 195 + */ 196 + - inlineAttachment.prototype.onFileUploadResponse = function(xhr, id) { 197 + + inlineAttachment.prototype.onFileUploadResponse = function(xhr, id, file) { 198 + if (this.settings.onFileUploadResponse.call(this, xhr) !== false) { 199 + var result = JSON.parse(xhr.responseText), 200 + filename = result[this.settings.jsonFieldName]; 201 + @@ -305,6 +307,11 @@ 202 + var string = this.settings.progressText.replace(this.filenameTag, id); 203 + var lines = this.editor.getValue().split('\n'); 204 + var newValue = this.settings.urlText.replace(this.filenameTag, filename); 205 + + // Non-image files become a plain link named after the file. 206 + + if (file && file.type.indexOf('image/') !== 0) { 207 + + var name = (file.name || 'file').replace(/([\\\[\]])/g, '\\$1'); 208 + + newValue = '[' + name + '](' + filename + ')'; 209 + + } 210 + for(var i = 0; i < lines.length; i++) { 211 + var ch = lines[i].indexOf(string); 212 + if(ch != -1) 213 + @@ -368,10 +375,12 @@ 214 + for (var i = 0; i < items.length; i++) { 215 + var item = items[i]; 216 + if (this.isFileAllowed(item)) { 217 + - result = true; 218 + var id = ID(); 219 + var file = item.getAsFile(); 220 + + // Only file items count; with allowedTypes '*' string items 221 + + // (plain text, html) also pass isFileAllowed. 222 + if (file !== null) { 223 + + result = true; 224 + this.onFileInserted(file, id); 225 + this.uploadFile(file, id); 226 + }