@@ -21,6 +21,7 @@ import (
2121 "log/slog"
2222 "os"
2323 "path/filepath"
24+ "strings"
2425
2526 "github.com/googleapis/librarian/internal/config"
2627 "github.com/googleapis/librarian/internal/docker"
@@ -173,9 +174,10 @@ func (r *generateRunner) run(ctx context.Context) error {
173174//
174175// Returns the last generated commit before the generation and error, if any.
175176func (r * generateRunner ) generateSingleLibrary (ctx context.Context , libraryID , outputDir string ) (string , error ) {
177+ safeLibraryDirectory := getSafeDirectoryName (libraryID )
176178 if r .needsConfigure () {
177179 slog .Info ("library not configured, start initial configuration" , "library" , r .library )
178- configureOutputDir := filepath .Join (outputDir , libraryID , "configure" )
180+ configureOutputDir := filepath .Join (outputDir , safeLibraryDirectory , "configure" )
179181 if err := os .MkdirAll (configureOutputDir , 0755 ); err != nil {
180182 return "" , err
181183 }
@@ -201,7 +203,7 @@ func (r *generateRunner) generateSingleLibrary(ctx context.Context, libraryID, o
201203 // For each library, create a separate output directory. This avoids
202204 // libraries interfering with each other, and makes it easier to see what
203205 // was generated for each library when debugging.
204- libraryOutputDir := filepath .Join (outputDir , libraryID )
206+ libraryOutputDir := filepath .Join (outputDir , safeLibraryDirectory )
205207 if err := os .MkdirAll (libraryOutputDir , 0755 ); err != nil {
206208 return "" , err
207209 }
@@ -449,3 +451,18 @@ func setAllAPIStatus(state *config.LibrarianState, status string) {
449451 }
450452 }
451453}
454+
455+ // getSafeDirectoryName returns a directory name which doesn't contain slashes
456+ // based on a library ID. This avoids cases where a library ID contains
457+ // slashes but we want generateSingleLibrary to create a directory which
458+ // is not a subdirectory of some other directory. For example, if there
459+ // are library IDs of "pubsub" and "pubsub/v2" we don't want to create
460+ // "output/pubsub/v2" and then "output/pubsub" later. This function does
461+ // not protect against malicious library IDs, e.g. ".", ".." or deliberate
462+ // collisions (e.g. "pubsub/v2" and "pubsub-slash-v2").
463+ //
464+ // The exact implementation may change over time - nothing should rely on this.
465+ // The current implementation simply replaces any slashes with "-slash-".
466+ func getSafeDirectoryName (libraryID string ) string {
467+ return strings .ReplaceAll (libraryID , "/" , "-slash-" )
468+ }
0 commit comments