@@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.channelFlow
1515import kotlinx.coroutines.launch
1616import kotlinx.coroutines.runBlocking
1717import kotlinx.coroutines.withContext
18+ import kotlinx.coroutines.withTimeout
1819import org.apache.commons.codec.digest.DigestUtils
1920import org.apache.commons.io.FileUtils
2021import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext
@@ -97,7 +98,7 @@ class FeatureDevSessionContext(val project: Project, val maxProjectSizeBytes: Lo
9798 addAll(additionalGitIgnoreRules.map { convertGitIgnorePatternToRegex(it) })
9899 addAll(parseGitIgnore())
99100 }.mapNotNull { pattern ->
100- runCatching { Regex (pattern) }.getOrNull()
101+ runCatching { pattern?. let { Regex (it) } }.getOrNull()
101102 }
102103 } catch (e: Exception ) {
103104 emptyList()
@@ -136,7 +137,13 @@ class FeatureDevSessionContext(val project: Project, val maxProjectSizeBytes: Lo
136137 // entries against them by adding a trailing /.
137138 // TODO: Add unit tests for gitignore matching
138139 val relative = if (path.startsWith(projectRootPath.toString())) Paths .get(path).relativeTo(projectRootPath) else path
139- async { pattern.matches(" $relative /" ) }
140+ async {
141+ try {
142+ withTimeout(REGEX_TIMEOUT_MS ) { pattern.matches(" $relative /" ) }
143+ } catch (e: Exception ) {
144+ false
145+ }
146+ }
140147 }
141148 }
142149
@@ -240,7 +247,7 @@ class FeatureDevSessionContext(val project: Project, val maxProjectSizeBytes: Lo
240247 tempFilePath
241248 }
242249
243- private fun parseGitIgnore (): Set <String > {
250+ private fun parseGitIgnore (): Set <String ? > {
244251 if (! gitIgnoreFile.exists()) {
245252 return emptySet()
246253 }
@@ -252,46 +259,57 @@ class FeatureDevSessionContext(val project: Project, val maxProjectSizeBytes: Lo
252259 }
253260
254261 // gitignore patterns are not regex, method update needed.
255- fun convertGitIgnorePatternToRegex (pattern : String ): String = pattern
256- // Escape special regex characters except * and ?
257- .replace(" ." , " \\ ." )
258- .replace(" +" , " \\ +" )
259- .replace(" (" , " \\ (" )
260- .replace(" )" , " \\ )" )
261- .replace(" [" , " \\ [" )
262- .replace(" ]" , " \\ ]" )
263- .replace(" {" , " \\ {" )
264- .replace(" }" , " \\ }" )
265- .replace(" ," , " \\ ," )
266- .replace(" ^" , " \\ ^" )
267- .replace(" $" , " \\ $" )
268- .replace(" |" , " \\ |" )
269- // Convert gitignore glob patterns to regex
270- .replace(" **" , " .*?" ) // Match any directory depth
271- .replace(" *" , " [^/]*?" ) // Match any character except path separator
272- .replace(" ?" , " [^/]" ) // Match single character except path separator
273- .let { pattern ->
274- when {
275- // If pattern starts with '/', anchor it to the start of the path
276- pattern.startsWith(" /" ) -> " ^${pattern.substring(1 )} "
277- // If pattern doesn't start with '/', it can match anywhere in the path
278- else -> " (?:^|.*/?)$pattern "
279- }
262+ fun convertGitIgnorePatternToRegex (pattern : String ): String? {
263+ // Skip invalid patterns for length check
264+ if (pattern.length > MAX_PATTERN_LENGTH ) {
265+ return null
280266 }
281- .let { pattern ->
282- when {
283- // If pattern ends with '/', it should match directories
284- pattern.endsWith(" /" ) -> " $pattern .*"
285- // Otherwise match exactly or with a trailing slash for directories
286- else -> " $pattern (?:/.*)?$"
267+ return pattern
268+ // Escape special regex characters except * and ?
269+ .replace(" ." , " \\ ." )
270+ .replace(" +" , " \\ +" )
271+ .replace(" (" , " \\ (" )
272+ .replace(" )" , " \\ )" )
273+ .replace(" [" , " \\ [" )
274+ .replace(" ]" , " \\ ]" )
275+ .replace(" {" , " \\ {" )
276+ .replace(" }" , " \\ }" )
277+ .replace(" ," , " \\ ," )
278+ .replace(" ^" , " \\ ^" )
279+ .replace(" $" , " \\ $" )
280+ .replace(" |" , " \\ |" )
281+ // Convert gitignore glob patterns to regex
282+ .replace(" **" , " .*?" ) // Match any directory depth
283+ .replace(" *" , " [^/]*?" ) // Match any character except path separator
284+ .replace(" ?" , " [^/]" ) // Match single character except path separator
285+ .let { pattern ->
286+ when {
287+ // If pattern starts with '/', anchor it to the start of the path
288+ pattern.startsWith(" /" ) -> " ^${pattern.substring(1 )} "
289+ // If pattern doesn't start with '/', it can match anywhere in the path
290+ else -> " (?:^|.*/?)$pattern "
291+ }
287292 }
288- }
293+ .let { pattern ->
294+ when {
295+ // If pattern ends with '/', it should match directories
296+ pattern.endsWith(" /" ) -> " $pattern .*"
297+ // Otherwise match exactly or with a trailing slash for directories
298+ else -> " $pattern (?:/.*)?$"
299+ }
300+ }
301+ }
289302
290303 var selectedSourceFolder: VirtualFile
291304 set(newRoot) {
292305 _selectedSourceFolder = newRoot
293306 }
294307 get() = _selectedSourceFolder
308+
309+ companion object {
310+ private const val MAX_PATTERN_LENGTH = 256 // Maximum allowed pattern length
311+ private const val REGEX_TIMEOUT_MS = 100L // Timeout for regex operations in milliseconds
312+ }
295313}
296314
297315data class ZipCreationResult (val payload : File , val checksum : String , val contentLength : Long )
0 commit comments