Skip to content

Commit c6338e1

Browse files
committed
Merge remote-tracking branch 'origin/develop' into fb_mcp_mvp
2 parents 00ab5af + 84db9dc commit c6338e1

55 files changed

Lines changed: 2418 additions & 2472 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api/src/org/labkey/api/action/AbstractFileUploadAction.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package org.labkey.api.action;
1717

18-
import jakarta.servlet.http.HttpServletRequest;
1918
import jakarta.servlet.http.HttpServletResponse;
2019
import org.jetbrains.annotations.NotNull;
2120
import org.labkey.api.util.ExceptionUtil;
@@ -42,7 +41,6 @@
4241
import java.io.Writer;
4342
import java.nio.charset.StandardCharsets;
4443
import java.util.HashMap;
45-
import java.util.Iterator;
4644
import java.util.Map;
4745

4846
/**
@@ -192,19 +190,16 @@ private void export(FORM form, HttpServletResponse response) throws Exception
192190
return;
193191
}
194192

195-
HttpServletRequest basicRequest = getViewContext().getRequest();
196-
197193
// Parameter name (String) -> File on disk/original file name Pair
198194
Map<String, Pair<FileLike, String>> savedFiles = new HashMap<>();
199195

200-
if (basicRequest instanceof MultipartHttpServletRequest request)
196+
if (getViewContext().getRequest() instanceof MultipartHttpServletRequest)
201197
{
202-
203-
Iterator<String> nameIterator = request.getFileNames();
204-
while (nameIterator.hasNext())
198+
Map<String, MultipartFile> fileMap = getFileMap();
199+
for (var e : fileMap.entrySet())
205200
{
206-
String formElementName = nameIterator.next();
207-
MultipartFile file = request.getFile(formElementName);
201+
var formElementName = e.getKey();
202+
var file = e.getValue();
208203
String filename = file.getOriginalFilename();
209204

210205
try (InputStream input = file.getInputStream())

api/src/org/labkey/api/action/BaseViewAction.java

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import java.util.Collection;
7777
import java.util.Collections;
7878
import java.util.HashMap;
79+
import java.util.LinkedHashMap;
7980
import java.util.List;
8081
import java.util.Map;
8182
import java.util.function.Predicate;
@@ -185,51 +186,26 @@ public static PropertyValues getPropertyValuesForFormBinding(PropertyValues pvs,
185186
return ret;
186187
}
187188

188-
static final String FORM_DATE_ENCODED_PARAM = "formDataEncoded";
189189

190-
/**
191-
* When a double quote is encountered in a multipart/form-data context, it is encoded as %22 using URL-encoding by browsers.
192-
* This process replaces the double quote with its hexadecimal equivalent in a URL-safe format, preventing it from being misinterpreted as the end of a value or a boundary.
193-
* The consequence of such encoding is we can't distinguish '"' from the actual '%22' in parameter name.
194-
* As a workaround, a client-side util `encodeFormDataQuote` is used to convert %22 to %2522 and " to %22 explicitly, while passing in an additional param formDataEncoded=true.
195-
* This class converts those encoded param names back to its decoded form during PropertyValues binding.
196-
* See Issue 52827, 52925 and 52119 for more information.
197-
*/
190+
/// Some characters can be mishandled by the browser in multipart/formdata requests (e.g. doublequote and backslask).
191+
/// We support an encoding from fields to avoid these characters, see {@link PageFlowUtil#encodeFormName} and {@link PageFlowUtil#decodeFormName}.
198192
static public class ViewActionParameterPropertyValues extends ServletRequestParameterPropertyValues
199193
{
200-
201194
public ViewActionParameterPropertyValues(ServletRequest request) {
202195
this(request, null, null);
203196
}
204197

205198
public ViewActionParameterPropertyValues(ServletRequest request, @Nullable String prefix, @Nullable String prefixSeparator)
206199
{
207200
super(request, prefix, prefixSeparator);
208-
if (isFormDataEncoded())
209-
{
210-
for (int i = 0; i < getPropertyValues().length; i++)
211-
{
212-
PropertyValue formDataPropValue = getPropertyValues()[i];
213-
String propValueName = formDataPropValue.getName();
214-
String decoded = PageFlowUtil.decodeQuoteEncodedFormDataKey(propValueName);
215-
if (!propValueName.equals(decoded))
216-
setPropertyValueAt(new PropertyValue(decoded, formDataPropValue.getValue()), i);
217-
}
218-
}
219-
}
220-
221-
private boolean isFormDataEncoded()
222-
{
223-
PropertyValue formDataPropValue = getPropertyValue(FORM_DATE_ENCODED_PARAM);
224-
if (formDataPropValue != null)
201+
for (int i = 0; i < getPropertyValues().length; i++)
225202
{
226-
Object v = formDataPropValue.getValue();
227-
String formDataPropValueStr = v == null ? null : String.valueOf(v);
228-
if (StringUtils.isNotBlank(formDataPropValueStr))
229-
return (Boolean) ConvertUtils.convert(formDataPropValueStr, Boolean.class);
203+
PropertyValue formDataPropValue = getPropertyValues()[i];
204+
String propValueName = formDataPropValue.getName();
205+
String decoded = PageFlowUtil.decodeFormName(propValueName);
206+
if (!propValueName.equals(decoded))
207+
setPropertyValueAt(new PropertyValue(decoded, formDataPropValue.getValue()), i);
230208
}
231-
232-
return false;
233209
}
234210
}
235211

@@ -725,9 +701,7 @@ public <T> T convertIfNecessary(Object value, Class<T> requiredType, MethodParam
725701
*/
726702
protected Map<String, MultipartFile> getFileMap()
727703
{
728-
if (getViewContext().getRequest() instanceof MultipartHttpServletRequest)
729-
return ((MultipartHttpServletRequest)getViewContext().getRequest()).getFileMap();
730-
return Collections.emptyMap();
704+
return PageFlowUtil.getFileMap(getViewContext().getRequest());
731705
}
732706

733707
protected List<AttachmentFile> getAttachmentFileList()

api/src/org/labkey/api/assay/AssayFileWriter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.labkey.api.query.AbstractQueryUpdateService;
3232
import org.labkey.api.util.FileUtil;
3333
import org.labkey.api.util.NetworkDrive;
34+
import org.labkey.api.util.PageFlowUtil;
3435
import org.labkey.api.view.ViewContext;
3536
import org.labkey.vfs.FileLike;
3637
import org.springframework.web.multipart.MultipartFile;
@@ -233,7 +234,7 @@ public Map<String, FileLike> savePostedFiles(ContextType context, @NotNull Set<S
233234
Set<String> originalFileNames = new HashSet<>();
234235
if (context.getRequest() instanceof MultipartHttpServletRequest multipartRequest)
235236
{
236-
Iterator<Map.Entry<String, List<MultipartFile>>> iter = multipartRequest.getMultiFileMap().entrySet().iterator();
237+
Iterator<Map.Entry<String, List<MultipartFile>>> iter = PageFlowUtil.getMultiFileMap(context.getRequest()).entrySet().iterator();
237238
Deque<FileLike> overflowFiles = new ArrayDeque<>(); // using a deque for easy removal of single elements
238239
Set<String> unusedParameterNames = new HashSet<>(parameterNames);
239240
while (iter.hasNext())

api/src/org/labkey/api/assay/actions/AssayRunUploadForm.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.apache.logging.log4j.Logger;
2424
import org.jetbrains.annotations.NotNull;
2525
import org.jetbrains.annotations.Nullable;
26+
import org.labkey.api.action.BaseViewAction;
2627
import org.labkey.api.assay.AbstractAssayProvider;
2728
import org.labkey.api.assay.AssayDataCollector;
2829
import org.labkey.api.assay.AssayFileWriter;
@@ -61,6 +62,7 @@
6162
import org.labkey.api.study.assay.ParticipantVisitResolverType;
6263
import org.labkey.api.util.FileUtil;
6364
import org.labkey.api.util.GUID;
65+
import org.labkey.api.util.PageFlowUtil;
6466
import org.labkey.api.view.ActionURL;
6567
import org.labkey.api.view.NotFoundException;
6668
import org.labkey.api.view.UnauthorizedException;
@@ -360,10 +362,11 @@ public Map<DomainProperty, FileLike> getAdditionalPostedFiles(List<? extends Dom
360362
File assayDirectory = getAssayDirectory(getContainer(), null);
361363

362364
// Hidden values in form containing previously uploaded files if the previous upload resulted in error
365+
var fileMap = PageFlowUtil.getFileMap(request);
363366
for (String fileParam : filePdNames)
364367
{
365368
DomainProperty domainProperty = fileParameters.get(fileParam);
366-
MultipartFile multiFile = request.getFileMap().get(fileParam);
369+
MultipartFile multiFile = fileMap.get(fileParam);
367370

368371
// If the file is removed from form after error, override hidden file name with an empty file
369372
if (null != multiFile && multiFile.getOriginalFilename().isEmpty())

api/src/org/labkey/api/data/TableViewForm.java

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -553,24 +553,21 @@ public CaseInsensitiveHashMap<Object> getTypedColumns(boolean includeUntyped)
553553

554554
for (ColumnInfo column : getTable().getColumns())
555555
{
556+
var fieldName = getFormFieldName(column);
557+
556558
if (hasTypedValue(column))
557559
{
558560
values.put(column.getName(), getTypedValue(column));
559561
}
560-
else if (includeUntyped && _stringValues.containsKey(getFormFieldName(column)))
562+
else if (includeUntyped && _stringValues.containsKey(fieldName))
561563
{
562-
values.put(column.getName(), _stringValues.get(getFormFieldName(column)));
564+
values.put(column.getName(), _stringValues.get(fieldName));
563565
}
564566
else if (getRequest() instanceof MultipartHttpServletRequest request)
565567
{
566-
String fieldName = getMultiPartFormFieldName(column);
567-
Object typedValue = _getTypedValues().get(fieldName);
568-
569-
if (typedValue != null)
570-
values.put(column.getName(), typedValue);
571-
else if (File.class.equals(column.getJavaClass()))
568+
if (File.class.equals(column.getJavaClass()))
572569
{
573-
MultipartFile file = request.getFile(fieldName);
570+
MultipartFile file = PageFlowUtil.getFileMap(request).get(fieldName);
574571
if (file != null)
575572
{
576573
// Check if the file was removed
@@ -587,10 +584,11 @@ else if (File.class.equals(column.getJavaClass()))
587584
ColumnInfo mvColumn = getTable().getColumn(column.getMvColumnName());
588585
if (null != mvColumn)
589586
{
587+
var mvFieldName = getFormFieldName(mvColumn);
590588
if (hasTypedValue(mvColumn))
591589
values.put(mvColumn.getName(), getTypedValue(mvColumn));
592-
else if (includeUntyped && _stringValues.containsKey(getFormFieldName(mvColumn)))
593-
values.put(mvColumn.getName(), _stringValues.get(getFormFieldName(mvColumn)));
590+
else if (includeUntyped && _stringValues.containsKey(mvFieldName))
591+
values.put(mvColumn.getName(), _stringValues.get(mvFieldName));
594592
}
595593
}
596594
}
@@ -739,11 +737,6 @@ public String getFormFieldName(@NotNull ColumnInfo column)
739737
return column.getName();
740738
}
741739

742-
public String getMultiPartFormFieldName(@NotNull ColumnInfo column)
743-
{
744-
return getFormFieldName(column);
745-
}
746-
747740
@Nullable
748741
public ColumnInfo getColumnByFormFieldName(@NotNull String name)
749742
{

api/src/org/labkey/api/dataiterator/DataIteratorUtil.java

Lines changed: 2 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -141,57 +141,14 @@ public static Map<String,ColumnInfo> createTableMap(TableInfo target, boolean us
141141

142142
// rank of a match of import column NAME matching various properties of target column
143143
// MatchType.low is used for matches based on something other than name
144-
public enum MatchType
144+
private enum MatchType
145145
{
146146
propertyuri,
147147
name,
148148
alias,
149149
jdbcname,
150150
tsvColumn,
151-
multiPartFormData()
152-
{
153-
@Override
154-
public String getMatchedName(@Nullable String name)
155-
{
156-
if (name == null)
157-
return null;
158-
// " is encoded as %22 when content-type is "multipart/form-data" (but is not otherwise encoded so decode() does not work)
159-
return name.replaceAll("\"", "%22");
160-
}
161-
162-
@Override
163-
public boolean updateRowMap(@NotNull ColumnInfo col, Map<String, Object> rowMap)
164-
{
165-
if (col.getName().contains("\"") && File.class.equals(col.getJavaClass()))
166-
{
167-
// Issue 52827: File/attachment fields with special characters
168-
String quoteEncodedName = DataIteratorUtil.MatchType.multiPartFormData.getMatchedName(col.getName());
169-
if (rowMap.containsKey(quoteEncodedName))
170-
{
171-
rowMap.put(col.getName(), rowMap.get(quoteEncodedName));
172-
rowMap.remove(quoteEncodedName);
173-
return true;
174-
}
175-
}
176-
return false;
177-
}
178-
},
179-
low;
180-
181-
public String getMatchedName(@Nullable String name)
182-
{
183-
return name;
184-
}
185-
186-
/**
187-
* Update rowMap content based on passed in col.
188-
* For example, the original rowMap may contain encoded field name. This util substitute the key in rowMap to reflect the actual col name
189-
* @return If rowMap has been updated
190-
*/
191-
public boolean updateRowMap(@NotNull ColumnInfo col, Map<String, Object> rowMap)
192-
{
193-
return false;
194-
}
151+
low
195152
}
196153

197154

@@ -209,9 +166,6 @@ protected static Map<String,Pair<ColumnInfo,MatchType>> _createTableMap(TableInf
209166

210167
Map<String, Pair<ColumnInfo,MatchType>> targetAliasesMap = new CaseInsensitiveHashMap<>(cols.size()*4);
211168

212-
for (ColumnInfo col : cols)
213-
targetAliasesMap.put(MatchType.multiPartFormData.getMatchedName(col.getName()), new Pair<>(col, MatchType.multiPartFormData));
214-
215169
// should this be under the useImportAliases flag???
216170
for (ColumnInfo col : cols)
217171
{

api/src/org/labkey/api/dataiterator/StandardDataIteratorBuilder.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import org.labkey.api.data.TableInfo;
2626
import org.labkey.api.data.validator.ColumnValidator;
2727
import org.labkey.api.data.validator.ColumnValidators;
28-
import org.labkey.api.data.validator.RequiredValidator;
2928
import org.labkey.api.exp.PropertyDescriptor;
3029
import org.labkey.api.exp.PropertyType;
3130
import org.labkey.api.exp.api.ExperimentService;

api/src/org/labkey/api/defaults/SetDefaultValuesAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ public boolean handlePost(FormType domainIdForm, BindException errors) throws Ex
448448
*/
449449
protected String encodePropertyValues(FormType domainIdForm, String propName) throws IOException
450450
{
451-
return domainIdForm.getRequest().getParameter(propName);
451+
return (String)getProperty(propName);
452452
}
453453

454454
@Override

api/src/org/labkey/api/exp/property/DomainUtil.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1587,8 +1587,6 @@ public static ValidationException validateProperties(@Nullable Domain domain, @N
15871587
else
15881588
{
15891589
altNameMap.put(name, name);
1590-
altNameMap.put(DataIteratorUtil.MatchType.multiPartFormData.getMatchedName(name), name);
1591-
altNameMap.put(name.replaceAll("%22", "\""), name);
15921590
}
15931591
}
15941592

api/src/org/labkey/api/jsp/JspBase.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,11 @@ public static HtmlString h(URLHelper url)
211211
return h(url == null ? null : url.toString());
212212
}
213213

214+
public static HtmlString hname(String name)
215+
{
216+
return HtmlString.of(PageFlowUtil.encodeFormName(name));
217+
}
218+
214219
// Note: If you have a stream, use LabKeyCollectors.toJsonArray()
215220
public static JSONArray toJsonArray(Collection<?> c)
216221
{

0 commit comments

Comments
 (0)