Skip to content

Commit 122bfd2

Browse files
committedDec 31, 2024
Fix #6606 "Max aggregate file size not enforced when uploading multiple files"
- add new `xxforms-upload-store` event - check again upload size constraints when processing that event - then only dispatch `xxforms-submit-done|error`
1 parent ca89791 commit 122bfd2

File tree

13 files changed

+87
-38
lines changed

13 files changed

+87
-38
lines changed
 

‎form-runner/jvm/src/main/resources/xbl/orbeon/attachment/attachment.xbl

-1
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,6 @@
512512

513513
targetid="fr-attachment"
514514
name="{event('xxf:type')}">
515-
<xf:property name="file" value="event('file')"/>
516515
<xf:property name="filename" value="event('filename')"/>
517516
<xf:property name="content-type" value="event('content-type')"/>
518517
<xf:property name="content-length" value="event('content-length')"/>

‎form-runner/jvm/src/main/resources/xbl/orbeon/image-attachment/image-attachment.xbl

-1
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,6 @@
316316
name="{event('xxf:type')}"
317317
propagate="stop"
318318
defaultAction="cancel">
319-
<xf:property name="file" value="event('file')"/>
320319
<xf:property name="filename" value="event('filename')"/>
321320
<xf:property name="content-type" value="event('content-type')"/>
322321
<xf:property name="content-length" value="event('content-length')"/>

‎form-runner/jvm/src/main/resources/xbl/orbeon/video-attachment/video-attachment.xbl

-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@
7171
name="{event('xxf:type')}"
7272
propagate="stop"
7373
defaultAction="cancel">
74-
<xf:property name="file" value="event('file')"/>
7574
<xf:property name="filename" value="event('filename')"/>
7675
<xf:property name="content-type" value="event('content-type')"/>
7776
<xf:property name="content-length" value="event('content-length')"/>

‎xforms-analysis/shared/src/main/scala/org/orbeon/oxf/xforms/analysis/controls/controls.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ private object UploadControl {
154154
EventNames.XXFormsUploadStart,
155155
EventNames.XXFormsUploadProgress,
156156
EventNames.XXFormsUploadCancel,
157-
EventNames.XXFormsUploadError
157+
EventNames.XXFormsUploadError,
158+
EventNames.XXFormsUploadStore
158159
)
159160

160161
val AllowedExtensionAttributes: Set[QName] =

‎xforms-client-server/src/main/scala/org/orbeon/xforms/EventNames.scala

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ object EventNames {
2222
val XXFormsUploadCancel = Prefix + "cancel"
2323
val XXFormsUploadDone = Prefix + "done"
2424
val XXFormsUploadError = Prefix + "error"
25+
val XXFormsUploadStore = Prefix + "store"
2526

2627
val XXFormsAllEventsRequired = "xxforms-all-events-required"
2728
val XXFormsSessionHeartbeat = "xxforms-session-heartbeat"

‎xforms-runtime/jvm/src/main/scala/org/orbeon/oxf/xforms/upload/UploaderServer.scala

+6-10
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ object UploaderServer {
8686
uuid = uuid,
8787
timeout = XFormsGlobalProperties.uploadXFormsAccessTimeout)
8888
{ doc =>
89-
doc.getUploadConstraintsForControl(controlEffectiveId).map(_ -> doc.getRequestUri)
90-
} .flatten
89+
doc.getUploadConstraintsForControl(controlEffectiveId) -> doc.getRequestUri
90+
}
9191
}
9292
),
9393
maxSize = MaximumSize.UnlimitedSize, // because we use our own limiter
@@ -211,7 +211,10 @@ object UploaderServer {
211211
// Browsers do set the outer `Content-Length` though. Again we assume that the overhead of the
212212
// entire request vs. the part is small so it's ok, for progress purposes, to use the outer size.
213213
untrustedExpectedSizeOpt foreach { untrustedExpectedSize =>
214-
checkSizeLimitExceeded(maxSize = maxUploadSizeForControl, currentSize = untrustedExpectedSize)
214+
UploadCheckerLogic.checkSizeLimitExceeded(
215+
maxSize = maxUploadSizeForControl,
216+
currentSize = untrustedExpectedSize
217+
).foreach(Multipart.throwSizeLimitExceeded(_, untrustedExpectedSize))
215218
}
216219

217220
// Otherwise update the outer limiter to support enough additional bytes
@@ -325,13 +328,6 @@ object UploaderServer {
325328
def getProgressSessionKey(uuid: String, fieldName: String): String =
326329
UploadProgressSessionKey + uuid + "." + fieldName
327330

328-
def checkSizeLimitExceeded(maxSize: MaximumSize, currentSize: Long): Unit = maxSize match {
329-
case UnlimitedSize =>
330-
case LimitedSize(maxSize) =>
331-
if (currentSize > maxSize)
332-
Multipart.throwSizeLimitExceeded(maxSize, currentSize)
333-
}
334-
335331
def convertFileItemHeaders(headers: FileItemHeaders): List[(String, List[String])] =
336332
for (name <- headers.getHeaderNames.asScala.toList)
337333
yield name -> headers.getHeaders(name).asScala.toList

‎xforms-runtime/shared/src/main/scala/org/orbeon/oxf/xforms/XFormsContainingDocumentSupport.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ trait ContainingDocumentUpload {
302302
def uploadMaxSizeAggregatePerFormProperty: MaximumSize = staticState.uploadMaxSizeAggregatePerForm
303303
}
304304

305-
def getUploadConstraintsForControl(controlEffectiveId: String): Try[(MaximumSize, AllowedMediatypes)] = {
305+
def getUploadConstraintsForControl(controlEffectiveId: String): (MaximumSize, AllowedMediatypes) = {
306306

307307
val allowedMediatypesMaybeRange = {
308308

@@ -319,7 +319,7 @@ trait ContainingDocumentUpload {
319319
AllowedMediatypes.AllowedAnyMediatype
320320
}
321321

322-
Success(UploadChecker.uploadMaxSizeForControl(controlEffectiveId) -> allowedMediatypesMaybeRange)
322+
UploadChecker.uploadMaxSizeForControl(controlEffectiveId) -> allowedMediatypesMaybeRange
323323
}
324324
}
325325

‎xforms-runtime/shared/src/main/scala/org/orbeon/oxf/xforms/control/controls/XFormsUploadControl.scala

+48-14
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import org.orbeon.oxf.xforms.event.EventCollector.ErrorEventCollector
2626
import org.orbeon.oxf.xforms.event.XFormsEvent.*
2727
import org.orbeon.oxf.xforms.event.events.*
2828
import org.orbeon.oxf.xforms.event.{Dispatch, XFormsEvent}
29+
import org.orbeon.oxf.xforms.upload.UploadCheckerLogic
2930
import org.orbeon.oxf.xforms.xbl.XBLContainer
3031
import org.orbeon.oxf.xml.XMLConstants.*
3132
import org.orbeon.oxf.xml.XMLReceiverAdapter
@@ -86,23 +87,56 @@ class XFormsUploadControl(container: XBLContainer, parent: XFormsControl, elemen
8687
// Upload canceled by the user
8788
containingDocument.endUpload(getUploadUniqueId)
8889
XFormsCrossPlatformSupport.removeUploadProgress(XFormsCrossPlatformSupport.externalContext.getRequest, this)
89-
case doneEvent: XXFormsUploadDoneEvent =>
90-
// Upload done: process upload to this control
91-
// Notify that the upload has ended
92-
containingDocument.endUpload(getUploadUniqueId)
93-
XFormsCrossPlatformSupport.removeUploadProgress(XFormsCrossPlatformSupport.externalContext.getRequest, this)
94-
handleUploadedFile(
95-
doneEvent.file,
96-
Option(doneEvent.filename).map(PathUtils.filenameFromPath), // in case the filename contains a path
97-
Option(doneEvent.contentType),
98-
Option(doneEvent.contentLength),
99-
collector
100-
)
101-
visitWithAncestors()
90+
case _: XXFormsUploadDoneEvent =>
91+
// 2024-11-04: Now just a notification event
92+
// https://github.com/orbeon/orbeon-forms/issues/6606
10293
case _: XXFormsUploadErrorEvent =>
103-
// Upload error: sent by the client in case of error
94+
// Upload error: can be sent by the client in case of error, or as consequence of `xxforms-upload-store`
10495
containingDocument.endUpload(getUploadUniqueId)
10596
XFormsCrossPlatformSupport.removeUploadProgress(XFormsCrossPlatformSupport.externalContext.getRequest, this)
97+
case storeEvent: XXFormsUploadStoreEvent =>
98+
// https://github.com/orbeon/orbeon-forms/issues/6606
99+
val size = storeEvent.contentLength.toLong
100+
UploadCheckerLogic.checkSizeLimitExceeded(
101+
maxSize = containingDocument.getUploadConstraintsForControl(effectiveId)._1,
102+
currentSize = size
103+
) match {
104+
case Some(maxSize) =>
105+
XFormsCrossPlatformSupport.removeUploadProgress(XFormsCrossPlatformSupport.externalContext.getRequest, this)
106+
Dispatch.dispatchEvent(
107+
new XXFormsUploadErrorEvent(
108+
this,
109+
Map(
110+
"error-type" -> Some("size-error"),
111+
"permitted" -> Some(maxSize),
112+
"actual" -> Some(size)
113+
)
114+
),
115+
collector
116+
)
117+
case None =>
118+
containingDocument.endUpload(getUploadUniqueId)
119+
XFormsCrossPlatformSupport.removeUploadProgress(XFormsCrossPlatformSupport.externalContext.getRequest, this)
120+
handleUploadedFile(
121+
storeEvent.file,
122+
Option(storeEvent.filename).map(PathUtils.filenameFromPath), // in case the filename contains a path
123+
Option(storeEvent.contentType),
124+
Option(storeEvent.contentLength),
125+
collector
126+
)
127+
visitWithAncestors()
128+
Dispatch.dispatchEvent(
129+
new XXFormsUploadDoneEvent(
130+
this,
131+
Map(
132+
"filename" -> Option(storeEvent.filename),
133+
"content-type" -> Option(storeEvent.contentType),
134+
"content-length" -> Option(storeEvent.contentLength)
135+
)
136+
),
137+
collector
138+
)
139+
}
106140
case _ =>
107141
}
108142
}

‎xforms-runtime/shared/src/main/scala/org/orbeon/oxf/xforms/event/XFormsEventFactory.scala

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ object XFormsEventFactory {
5353
XXFormsUploadCancel -> (new XXFormsUploadCancelEvent(_, _)),
5454
XXFormsUploadDone -> (new XXFormsUploadDoneEvent(_, _)),
5555
XXFormsUploadError -> (new XXFormsUploadErrorEvent(_, _)),
56+
XXFormsUploadStore -> (new XXFormsUploadStoreEvent(_, _)),
5657
XFORMS_MODEL_CONSTRUCT_DONE -> (new XFormsModelConstructDoneEvent(_, _)),
5758
XFORMS_MODEL_CONSTRUCT -> (new XFormsModelConstructEvent(_, _)),
5859
XXFORMS_INSTANCES_READY -> (new XXFormsInstancesReadyEvent(_, _)),

‎xforms-runtime/shared/src/main/scala/org/orbeon/oxf/xforms/event/events/events.scala

+10-2
Original file line numberDiff line numberDiff line change
@@ -163,16 +163,24 @@ class XXFormsUploadCancelEvent(target: XFormsEventTarget, properties: PropertyGe
163163
class XXFormsUploadDoneEvent(target: XFormsEventTarget, properties: PropertyGetter)
164164
extends XFormsEvent(EventNames.XXFormsUploadDone, target, properties, bubbles = true, cancelable = true) {
165165

166+
def filename = property[String]("filename").get
167+
def contentType = property[String](Headers.ContentTypeLower).get
168+
def contentLength = property[String](Headers.ContentLengthLower).get // comes as String from the client
169+
}
170+
171+
class XXFormsUploadStoreEvent(target: XFormsEventTarget, properties: PropertyGetter)
172+
extends XFormsEvent(EventNames.XXFormsUploadStore, target, properties, bubbles = true, cancelable = true) {
173+
166174
// These properties come from the client
167175
def file = property[String]("file").get
168176
def filename = property[String]("filename").get
169177
def contentType = property[String](Headers.ContentTypeLower).get
170178
def contentLength = property[String](Headers.ContentLengthLower).get // comes as String from the client
171179
}
172180

173-
object XXFormsUploadDoneEvent {
181+
object XXFormsUploadStoreEvent {
174182
val StandardProperties = Map(
175-
EventNames.XXFormsUploadDone -> List("file", "filename", Headers.ContentTypeLower, Headers.ContentLengthLower)
183+
EventNames.XXFormsUploadStore -> List("file", "filename", Headers.ContentTypeLower, Headers.ContentLengthLower)
176184
)
177185
}
178186

‎xforms-runtime/shared/src/main/scala/org/orbeon/oxf/xforms/upload/UploadCheckerLogic.scala

+10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.orbeon.oxf.xforms.upload
22

33
import org.orbeon.datatypes.MaximumSize
4+
import org.orbeon.datatypes.MaximumSize.LimitedSize
45
import org.orbeon.oxf.xforms.function.xxforms.ValidationFunctionNames
56

67

@@ -15,6 +16,7 @@ trait UploadCheckerLogic {
1516
def uploadMaxSizeAggregatePerFormProperty : MaximumSize
1617

1718
def uploadMaxSizeForControl(controlEffectiveId: String): MaximumSize = {
19+
1820
val maximumSizePerFile =
1921
attachmentMaxSizeValidationMipFor(controlEffectiveId, ValidationFunctionNames.UploadMaxSizePerFile)
2022
.flatMap(MaximumSize.unapply)
@@ -40,4 +42,12 @@ trait UploadCheckerLogic {
4042
// Do not evaluate aggregate sizes unless needed
4143
MaximumSize.min(maximumSizePerFile #:: maximumSizeAggregatePerControl #:: maximumSizeAggregatePerForm #:: LazyList.empty)
4244
}
45+
}
46+
47+
object UploadCheckerLogic {
48+
def checkSizeLimitExceeded(maxSize: MaximumSize, currentSize: Long): Option[Long] =
49+
maxSize match {
50+
case LimitedSize(maxSize) if currentSize > maxSize => Some(maxSize)
51+
case _ => None
52+
}
4353
}

‎xforms/jvm/src/main/scala/org/orbeon/oxf/xforms/processor/XFormsUploadRoute.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import org.orbeon.oxf.xforms.XFormsGlobalProperties
2929
import org.orbeon.oxf.xforms.upload.UploaderServer
3030
import org.orbeon.oxf.xml.EncodeDecode
3131
import org.orbeon.scaxon.NodeConversions
32+
import org.orbeon.xforms.EventNames
3233

3334

3435
object XFormsUploadRoute extends XmlNativeRoute {
@@ -119,7 +120,7 @@ object XFormsUploadRoute extends XmlNativeRoute {
119120
mediatypeOpt = Mediatypes.fromHeadersOrFilename(n => headers.get(n).flatten, filename)
120121
} yield
121122
<xxf:event
122-
name="xxforms-upload-done"
123+
name={EventNames.XXFormsUploadStore}
123124
source-control-id={fieldName}
124125
file={sessionUrl.toString}
125126
filename={filename.getOrElse("")}
@@ -139,7 +140,7 @@ object XFormsUploadRoute extends XmlNativeRoute {
139140
case FileScanException(fieldName, fileScanResult) =>
140141
outputResponse(
141142
<xxf:events xmlns:xxf="http://orbeon.org/oxf/xml/xforms">
142-
<xxf:event name="xxforms-upload-error" source-control-id={fieldName}>
143+
<xxf:event name={EventNames.XXFormsUploadError} source-control-id={fieldName}>
143144
<xxf:property name="error-type">file-scan-error</xxf:property>
144145
<xxf:property name="message">{fileScanResult.message.getOrElse("")}</xxf:property>
145146
</xxf:event>

‎xforms/jvm/src/main/scala/org/orbeon/oxf/xforms/route/XFormsServerRoute.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import org.orbeon.oxf.servlet.OrbeonXFormsFilterImpl
1212
import org.orbeon.oxf.util.Logging.debug
1313
import org.orbeon.oxf.util.StringUtils.*
1414
import org.orbeon.oxf.xforms.event.XFormsServer
15-
import org.orbeon.oxf.xforms.event.events.{KeyboardEvent, XXFormsDndEvent, XXFormsLoadEvent, XXFormsUploadDoneEvent}
15+
import org.orbeon.oxf.xforms.event.events.*
1616
import org.orbeon.oxf.xforms.processor.PipelineResponse
1717
import org.orbeon.oxf.xforms.state.RequestParameters
1818
import org.orbeon.oxf.xforms.{Loggers, XFormsContainingDocument}
@@ -144,9 +144,9 @@ object XFormsServerRoute extends XmlNativeRoute {
144144

145145
// Only a few events specify custom properties that can be set by the client
146146
val AllStandardProperties =
147-
XXFormsDndEvent.StandardProperties ++
148-
KeyboardEvent.StandardProperties ++
149-
XXFormsUploadDoneEvent.StandardProperties ++
147+
XXFormsDndEvent.StandardProperties ++
148+
KeyboardEvent.StandardProperties ++
149+
XXFormsUploadStoreEvent.StandardProperties ++
150150
XXFormsLoadEvent.StandardProperties
151151

152152
def extractWireEvent(eventElem: Element): WireAjaxEvent = {

0 commit comments

Comments
 (0)
Please sign in to comment.