From 1ac64de502631a946e68013a612418a301b76c4e Mon Sep 17 00:00:00 2001 From: Agus Hilman Date: Thu, 22 Aug 2024 19:02:20 +0700 Subject: [PATCH 1/4] Hide date widget fake input when rendering pdf --- packages/enketo-core/src/widget/text-print/text-print.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/enketo-core/src/widget/text-print/text-print.js b/packages/enketo-core/src/widget/text-print/text-print.js index d379e0a90..7cd1d34ec 100644 --- a/packages/enketo-core/src/widget/text-print/text-print.js +++ b/packages/enketo-core/src/widget/text-print/text-print.js @@ -36,6 +36,15 @@ class TextPrintWidget extends Widget { this.element.after(printElement); this.element.classList.add('print-hide'); + // If previous element is a fake input in date widget, hide it as well + const previousElement = this.element.previousElementSibling; + if ( + previousElement !== null && + previousElement.classList.contains('date') + ) { + previousElement.classList.add('print-hide'); + } + this.widget = this.element.parentElement.querySelector( `.${className}` ); From 0f37c1f310b28487ae604ff2b56a4afd75976c1f Mon Sep 17 00:00:00 2001 From: Agus Hilman Date: Wed, 4 Sep 2024 21:15:53 +0700 Subject: [PATCH 2/4] Fixed editing display of uploaded file with # character in it --- packages/enketo-express/app/lib/media.js | 6 +- packages/enketo-express/app/lib/utils.js | 26 ++++++++ packages/enketo-express/config/express.js | 12 ++++ packages/enketo-express/package.json | 1 + yarn.lock | 79 +++++++++++++++++++++++ 5 files changed, 122 insertions(+), 2 deletions(-) diff --git a/packages/enketo-express/app/lib/media.js b/packages/enketo-express/app/lib/media.js index 138f9e82a..2ad249c4f 100644 --- a/packages/enketo-express/app/lib/media.js +++ b/packages/enketo-express/app/lib/media.js @@ -84,6 +84,7 @@ const markupEntities = { /** * @param {string} fileName */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars const escapeFileName = (fileName) => transformer .escapeURLPath(fileName) @@ -132,6 +133,7 @@ const getMediaMap = async (resourceId, media, options) => { await Promise.all( mediaEntries.map(({ filename, hash, downloadUrl }) => { + filename = encodeURIComponent(filename); const mediaURL = createMediaURL({ basePath, fileName: filename, @@ -149,7 +151,7 @@ const getMediaMap = async (resourceId, media, options) => { * escaping logic here to ensure keys match file names when they have * special URL characters. */ - result[escapeFileName(filename)] = mediaURL; + result[filename] = mediaURL; if (resourceType === ResourceType.MANIFEST) { return cacheModel.cacheManifestItem( @@ -213,7 +215,7 @@ const getInstanceAttachments = async (instanceId) => { return Object.fromEntries( Object.entries(instanceAttachments).map(([key, value]) => [ - escapeFileName(key), + encodeURIComponent(key), escapeURL(value), ]) ); diff --git a/packages/enketo-express/app/lib/utils.js b/packages/enketo-express/app/lib/utils.js index c999c3c50..051abebf2 100644 --- a/packages/enketo-express/app/lib/utils.js +++ b/packages/enketo-express/app/lib/utils.js @@ -5,6 +5,7 @@ const crypto = require('crypto'); const evpBytesToKey = require('evp_bytestokey'); const validUrl = require('valid-url'); +const qs = require('qs'); // var debug = require( 'debug' )( 'utils' ); /** @@ -224,6 +225,30 @@ function areOwnPropertiesEqual(a, b) { return true; } +/** + * Custom URL parser that preserves certain URLs in their original form. + * + * @param {string} str - The string to be parsed + * @return {object} The parsed object + */ +function preserveURLParser(str) { + const parsed = qs.parse(str, { + decoder: (str, defaultDecoder, charset, type) => { + // Preserve URLs that start with 'http' or 'https:' + if ( + type === 'value' && + (str.startsWith('http') || str.startsWith('https')) + ) { + // Return the original string without decoding for URLs + return str; + } + // For non-URL values, use the default decoder + return defaultDecoder(str); + }, + }); + return parsed; +} + module.exports = { getOpenRosaKey, getXformsManifestHash, @@ -235,4 +260,5 @@ module.exports = { areOwnPropertiesEqual, insecureAes192Decrypt, insecureAes192Encrypt, + preserveURLParser, }; diff --git a/packages/enketo-express/config/express.js b/packages/enketo-express/config/express.js index b4b78a89e..2d501662f 100644 --- a/packages/enketo-express/config/express.js +++ b/packages/enketo-express/config/express.js @@ -11,6 +11,7 @@ const I18nextBackend = require('i18next-fs-backend'); const i18nextMiddleware = require('i18next-http-middleware'); const compression = require('compression'); const errorHandler = require('../app/controllers/error-handler'); +const { preserveURLParser } = require('../app/lib/utils'); const controllersPath = path.join(__dirname, '../app/controllers'); const app = express(); @@ -77,8 +78,19 @@ app.use( bodyParser.urlencoded({ limit: config.server['payload limit'], extended: true, + verify: (req, res, buf, encoding) => { + if (buf && buf.length) { + req.rawBody = buf.toString(encoding || 'utf8'); + } + }, }) ); +app.use((req, res, next) => { + if (req.rawBody) { + req.body = preserveURLParser(req.rawBody); + } + next(); +}); app.use(cookieParser(app.get('encryption key'))); app.use( i18nextMiddleware.handle(i18next, { diff --git a/packages/enketo-express/package.json b/packages/enketo-express/package.json index d20bd8125..6b6ffd846 100644 --- a/packages/enketo-express/package.json +++ b/packages/enketo-express/package.json @@ -58,6 +58,7 @@ "pkg-dir": "^5.0.0", "pug": "^3.0.2", "puppeteer": "^13.7.0", + "qs": "^6.13.0", "redis": "^3.1.2", "@cypress/request": "^3.0.1", "serve-favicon": "^2.5.0", diff --git a/yarn.lock b/yarn.lock index 3e0d67ad8..c5651baa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2563,6 +2563,17 @@ call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: get-intrinsic "^1.2.1" set-function-length "^1.1.1" +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -3233,6 +3244,15 @@ define-data-property@^1.0.1, define-data-property@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + define-lazy-prop@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" @@ -3598,6 +3618,18 @@ es-abstract@^1.22.1: unbox-primitive "^1.0.2" which-typed-array "^1.1.13" +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + es-iterator-helpers@^1.0.12: version "1.0.15" resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz#bd81d275ac766431d19305923707c3efd9f1ae40" @@ -4764,6 +4796,17 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -5198,6 +5241,13 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.2.2" +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + has-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" @@ -8235,6 +8285,13 @@ qs@^6.11.0, qs@^6.11.2, qs@^6.4.0: dependencies: side-channel "^1.0.4" +qs@^6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + querystringify@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" @@ -8825,6 +8882,18 @@ set-function-length@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + set-function-name@^2.0.0, set-function-name@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" @@ -8892,6 +8961,16 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + siginfo@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" From bab4d9c7e3d18b0d65b055ae19e0405a97868d2e Mon Sep 17 00:00:00 2001 From: Agus Hilman Date: Fri, 4 Oct 2024 17:06:38 +0700 Subject: [PATCH 3/4] Fixed can paste value into read-only date field --- .../enketo-core/src/widget/date/datepicker-extended.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/enketo-core/src/widget/date/datepicker-extended.js b/packages/enketo-core/src/widget/date/datepicker-extended.js index 6febee311..7e7e9a302 100644 --- a/packages/enketo-core/src/widget/date/datepicker-extended.js +++ b/packages/enketo-core/src/widget/date/datepicker-extended.js @@ -87,7 +87,12 @@ class DatepickerExtended extends Widget { _setChangeHandler($fakeDateI) { const { settings } = this; - $fakeDateI.on('change paste', (e) => { + let changeEvent = 'change'; + if (!$fakeDateI.closest('label').hasClass('readonly')) { + changeEvent += ' paste'; + } + + $fakeDateI.on(changeEvent, (e) => { let convertedValue = ''; let value = e.type === 'paste' From df9296986db36cefa8d6340f8a53a58759c3a28d Mon Sep 17 00:00:00 2001 From: Agus Hilman Date: Fri, 4 Oct 2024 21:54:16 +0700 Subject: [PATCH 4/4] Set autocomplete="off" for RFC fields --- packages/enketo-core/src/js/reasons.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/enketo-core/src/js/reasons.js b/packages/enketo-core/src/js/reasons.js index 5fcc4cc04..26a3a22fd 100644 --- a/packages/enketo-core/src/js/reasons.js +++ b/packages/enketo-core/src/js/reasons.js @@ -15,7 +15,7 @@ export default {
${t('fieldsubmission.reason.heading')}
-
@@ -81,7 +81,7 @@ export default { const fieldFragment = range.createContextualFragment( `
${labelText}${repeatNumberHtml} -
`