Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Treat HTTP headers as case insensitive #4116

Merged
merged 2 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions modules/nf-commons/src/main/nextflow/util/InsensitiveMap.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2013-2023, Seqera Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package nextflow.util

import groovy.transform.CompileStatic

/**
* A {@link Map} that handles keys in a case insensitive manner
*
* @author Paolo Di Tommaso <[email protected]>
*/
@CompileStatic
class InsensitiveMap<K,V> implements Map<K,V> {

@Delegate
private Map<K,V> target

private InsensitiveMap(Map<K,V> map) {
this.target = map
}

@Override
boolean containsKey(Object key) {
target.any( it -> key?.toString()?.toLowerCase() == it.key?.toString()?.toLowerCase())
}

@Override
V get(Object key) {
target.find(it -> key?.toString()?.toLowerCase() == it.key?.toString()?.toLowerCase())?.value
}

static <K,V> Map<K,V> of(Map<K,V> target) {
new InsensitiveMap(target)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2013-2023, Seqera Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package nextflow.util

import spock.lang.Specification

/**
*
* @author Paolo Di Tommaso <[email protected]>
*/
class InsensitiveMapTest extends Specification {

def 'should get value by case insensitive keys' () {
given:
def map = InsensitiveMap.of([alpha: 1, BETA: 2])

expect:
map.alpha == 1
map.ALPHA == 1
map.Alpha == 1
and:
map.get('alpha') == 1
map.get('ALPHA') == 1
map.get('Alpha') == 1
and:
map.beta == 2
map.BETA == 2
map.Beta == 2
and:
map.get('beta') == 2
map.get('BETA') == 2
map.get('Beta') == 2
and:
map.foo == null
and:
map.containsKey('alpha')
map.containsKey('ALPHA')
and:
map.containsKey('beta')
map.containsKey('BETA')
and:
!map.containsKey('foo')
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import nextflow.SysEnv
import nextflow.extension.FilesEx
import nextflow.util.InsensitiveMap
import sun.net.www.protocol.ftp.FtpURLConnection

import static XFileSystemConfig.*
Expand Down Expand Up @@ -191,7 +192,7 @@ abstract class XFileSystemProvider extends FileSystemProvider {
XAuthRegistry.instance.authorize(conn)
}
if ( conn instanceof HttpURLConnection && conn.getResponseCode() in [307, 308] && attempt < MAX_REDIRECT_HOPS ) {
def header = conn.getHeaderFields()
final header = InsensitiveMap.of(conn.getHeaderFields())
String location = header.get("Location")?.get(0)
URL newPath = new URI(location).toURL()
log.debug "Remote redirect URL: $newPath"
Expand Down Expand Up @@ -454,15 +455,16 @@ abstract class XFileSystemProvider extends FileSystemProvider {
return new XFileAttributes(null,-1)
}
if ( conn instanceof HttpURLConnection && conn.getResponseCode() in [200, 301, 302, 307, 308]) {
def header = conn.getHeaderFields()
final header = conn.getHeaderFields()
return readHttpAttributes(header)
}
return null
}

protected XFileAttributes readHttpAttributes(Map<String,List<String>> header) {
def lastMod = header.get("Last-Modified")?.get(0)
long contentLen = header.get("Content-Length")?.get(0)?.toLong() ?: -1
final header0 = InsensitiveMap.<String,List<String>>of(header)
def lastMod = header0.get("Last-Modified")?.get(0)
long contentLen = header0.get("Content-Length")?.get(0)?.toLong() ?: -1
def dateFormat = new SimpleDateFormat('E, dd MMM yyyy HH:mm:ss Z', Locale.ENGLISH) // <-- make sure date parse is not language dependent (for the week day)
def modTime = lastMod ? FileTime.from(dateFormat.parse(lastMod).time, TimeUnit.MILLISECONDS) : (FileTime)null
new XFileAttributes(modTime, contentLen)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class XFileSystemProviderTest extends Specification {
def "should read file attributes from map"() {
given:
def fs = new HttpFileSystemProvider()
def attrMap = ['Last-Modified': ['Fri, 04 Nov 2016 21:50:34 GMT'], 'Content-Length': ['21729']]
def attrMap = ['last-modified': ['Fri, 04 Nov 2016 21:50:34 GMT'], 'content-length': ['21729']]

when:
def attrs = fs.readHttpAttributes(attrMap)
Expand All @@ -85,7 +85,7 @@ class XFileSystemProviderTest extends Specification {
def GERMAN = new Locale.Builder().setLanguage("de").setRegion("DE").build()
Locale.setDefault(Locale.Category.FORMAT, GERMAN)
def fs = new HttpFileSystemProvider()
def attrMap = ['Last-Modified': ['Fri, 04 Nov 2016 21:50:34 GMT'], 'Content-Length': ['21729']]
def attrMap = ['last-modified': ['Fri, 04 Nov 2016 21:50:34 GMT'], 'content-length': ['21729']]

when:
def attrs = fs.readHttpAttributes(attrMap)
Expand Down