Skip to content

Commit 91c0189

Browse files
authored
fix(internal/librarian): handle slashes in library IDs (#2463)
This ensures that we don't create nested output directories during the generate command. Fixes #2305
1 parent 71db17e commit 91c0189

File tree

2 files changed

+50
-2
lines changed

2 files changed

+50
-2
lines changed

internal/librarian/generate.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
175176
func (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+
}

internal/librarian/generate_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,3 +1217,34 @@ func TestGetExistingSrc(t *testing.T) {
12171217
})
12181218
}
12191219
}
1220+
1221+
func TestGetSafeDirectoryName(t *testing.T) {
1222+
for _, test := range []struct {
1223+
name string
1224+
id string
1225+
want string
1226+
}{
1227+
{
1228+
name: "simple",
1229+
id: "pubsub",
1230+
want: "pubsub",
1231+
},
1232+
{
1233+
name: "nested",
1234+
id: "pubsub/v2",
1235+
want: "pubsub-slash-v2",
1236+
},
1237+
{
1238+
name: "deeply nested",
1239+
id: "compute/metadata/v2",
1240+
want: "compute-slash-metadata-slash-v2",
1241+
},
1242+
} {
1243+
t.Run(test.name, func(t *testing.T) {
1244+
got := getSafeDirectoryName(test.id)
1245+
if test.want != got {
1246+
t.Errorf("getSafeDirectoryName() = %q; want %q", got, test.want)
1247+
}
1248+
})
1249+
}
1250+
}

0 commit comments

Comments
 (0)