@@ -22,6 +22,7 @@ import (
2222 "github.com/containers/buildah"
2323 buildahDefine "github.com/containers/buildah/define"
2424 "github.com/containers/buildah/pkg/parse"
25+ "github.com/containers/podman/v5/internal/localapi"
2526 "github.com/containers/podman/v5/libpod"
2627 "github.com/containers/podman/v5/pkg/api/handlers/utils"
2728 api "github.com/containers/podman/v5/pkg/api/types"
@@ -143,6 +144,29 @@ type BuildContext struct {
143144 IgnoreFile string
144145}
145146
147+ func (b * BuildContext ) validateLocalAPIPaths () error {
148+ if err := localapi .ValidatePathForLocalAPI (b .ContextDirectory ); err != nil {
149+ return err
150+ }
151+
152+ for _ , containerfile := range b .ContainerFiles {
153+ if err := localapi .ValidatePathForLocalAPI (containerfile ); err != nil {
154+ return err
155+ }
156+ }
157+
158+ for _ , ctx := range b .AdditionalBuildContexts {
159+ if ctx .IsURL || ctx .IsImage {
160+ continue
161+ }
162+ if err := localapi .ValidatePathForLocalAPI (ctx .Value ); err != nil {
163+ return err
164+ }
165+ }
166+
167+ return nil
168+ }
169+
146170// genSpaceErr wraps filesystem errors to provide more context for disk space issues.
147171func genSpaceErr (err error ) error {
148172 if errors .Is (err , syscall .ENOSPC ) {
@@ -196,7 +220,7 @@ func validateContentType(r *http.Request) (bool, error) {
196220 logrus .Infof ("Received %s" , hdr [0 ])
197221 multipart = true
198222 default :
199- if utils .IsLibpodRequest (r ) {
223+ if utils .IsLibpodRequest (r ) && ! utils . IsLibpodLocalRequest ( r ) {
200224 return false , utils .GetBadRequestError ("Content-Type" , hdr [0 ],
201225 fmt .Errorf ("Content-Type: %s is not supported. Should be \" application/x-tar\" " , hdr [0 ]))
202226 }
@@ -264,10 +288,14 @@ func processBuildContext(query url.Values, r *http.Request, buildContext *BuildC
264288 }
265289
266290 for _ , containerfile := range m {
267- // Add path to containerfile iff it is not URL
291+ // Add path to containerfile if it is not URL
268292 if ! strings .HasPrefix (containerfile , "http://" ) && ! strings .HasPrefix (containerfile , "https://" ) {
269- containerfile = filepath .Join (buildContext .ContextDirectory ,
270- filepath .Clean (filepath .FromSlash (containerfile )))
293+ if filepath .IsAbs (containerfile ) {
294+ containerfile = filepath .Clean (filepath .FromSlash (containerfile ))
295+ } else {
296+ containerfile = filepath .Join (buildContext .ContextDirectory ,
297+ filepath .Clean (filepath .FromSlash (containerfile )))
298+ }
271299 }
272300 buildContext .ContainerFiles = append (buildContext .ContainerFiles , containerfile )
273301 }
@@ -587,7 +615,7 @@ func createBuildOptions(query *BuildQuery, buildCtx *BuildContext, queryValues u
587615 // Credential value(s) not returned as their value is not human readable
588616 return nil , nil , utils .GetGenericBadRequestError (err )
589617 }
590- // this smells
618+
591619 cleanup := func () {
592620 auth .RemoveAuthfile (authfile )
593621 }
@@ -866,7 +894,128 @@ func executeBuild(runtime *libpod.Runtime, w http.ResponseWriter, r *http.Reques
866894 }
867895}
868896
897+ // handleLocalBuildContexts processes build contexts for local API builds and validates local paths.
898+ //
899+ // This function handles the main build context and any additional build contexts specified in the request:
900+ // - Validates that the main context directory (localcontextdir) exists and is accessible for local API usage
901+ // - Processes additional build contexts which can be:
902+ // - URLs (url:) - downloads content to temporary directories under anchorDir
903+ // - Container images (image:) - records image references for later resolution during build
904+ // - Local paths (localpath:) - validates and cleans local filesystem paths
905+ //
906+ // Returns a BuildContext struct with the main context directory and a map of additional build contexts,
907+ // or an error if validation fails or required parameters are missing.
908+ func handleLocalBuildContexts (query url.Values , anchorDir string ) (* BuildContext , error ) {
909+ localContextDir := query .Get ("localcontextdir" )
910+ if localContextDir == "" {
911+ return nil , utils .GetBadRequestError ("localcontextdir" , localContextDir , fmt .Errorf ("localcontextdir cannot be empty" ))
912+ }
913+ localContextDir = filepath .Clean (localContextDir )
914+ if err := localapi .ValidatePathForLocalAPI (localContextDir ); err != nil {
915+ if errors .Is (err , os .ErrNotExist ) {
916+ return nil , utils .GetFileNotFoundError (err )
917+ }
918+ return nil , utils .GetGenericBadRequestError (err )
919+ }
920+
921+ out := & BuildContext {
922+ ContextDirectory : localContextDir ,
923+ AdditionalBuildContexts : make (map [string ]* buildahDefine.AdditionalBuildContext ),
924+ }
925+
926+ for _ , url := range query ["additionalbuildcontexts" ] {
927+ name , value , found := strings .Cut (url , "=" )
928+ if ! found {
929+ return nil , utils .GetInternalServerError (fmt .Errorf ("additionalbuildcontexts must be in name=value format: %q" , url ))
930+ }
931+
932+ logrus .Debugf ("Processing additional build context: name=%q, value=%q" , name , value )
933+
934+ switch {
935+ case strings .HasPrefix (value , "url:" ):
936+ value = strings .TrimPrefix (value , "url:" )
937+ tempDir , subdir , err := buildahDefine .TempDirForURL (anchorDir , "buildah" , value )
938+ if err != nil {
939+ return nil , utils .GetInternalServerError (genSpaceErr (err ))
940+ }
941+
942+ contextPath := filepath .Join (tempDir , subdir )
943+ out .AdditionalBuildContexts [name ] = & buildahDefine.AdditionalBuildContext {
944+ IsURL : true ,
945+ IsImage : false ,
946+ Value : contextPath ,
947+ DownloadedCache : contextPath ,
948+ }
949+ case strings .HasPrefix (value , "image:" ):
950+ value = strings .TrimPrefix (value , "image:" )
951+ out .AdditionalBuildContexts [name ] = & buildahDefine.AdditionalBuildContext {
952+ IsURL : false ,
953+ IsImage : true ,
954+ Value : value ,
955+ }
956+ case strings .HasPrefix (value , "localpath:" ):
957+ value = strings .TrimPrefix (value , "localpath:" )
958+ out .AdditionalBuildContexts [name ] = & buildahDefine.AdditionalBuildContext {
959+ IsURL : false ,
960+ IsImage : false ,
961+ Value : filepath .Clean (value ),
962+ }
963+ }
964+ }
965+ return out , nil
966+ }
967+
968+ // getLocalBuildContext processes build contexts from Local API HTTP request to a BuildContext struct.
969+ func getLocalBuildContext (r * http.Request , query url.Values , anchorDir string , _ bool ) (* BuildContext , error ) {
970+ // Handle build contexts
971+ buildContext , err := handleLocalBuildContexts (query , anchorDir )
972+ if err != nil {
973+ return nil , err
974+ }
975+
976+ // Process build context and container files
977+ buildContext , err = processBuildContext (query , r , buildContext , anchorDir )
978+ if err != nil {
979+ return nil , err
980+ }
981+
982+ if err := buildContext .validateLocalAPIPaths (); err != nil {
983+ if errors .Is (err , os .ErrNotExist ) {
984+ return nil , utils .GetFileNotFoundError (err )
985+ }
986+ return nil , utils .GetGenericBadRequestError (err )
987+ }
988+
989+ // Process dockerignore
990+ _ , ignoreFile , err := util .ParseDockerignore (buildContext .ContainerFiles , buildContext .ContextDirectory )
991+ if err != nil {
992+ return nil , utils .GetInternalServerError (fmt .Errorf ("processing ignore file: %w" , err ))
993+ }
994+ buildContext .IgnoreFile = ignoreFile
995+
996+ return buildContext , nil
997+ }
998+
999+ // LocalBuildImage handles HTTP requests for building container images using the Local API.
1000+ //
1001+ // Uses localcontextdir and additionalbuildcontexts query parameters to specify build contexts
1002+ // from the server's local filesystem. All paths must be absolute and exist on the server.
1003+ // Processes build parameters, executes the build using buildah, and streams output to the client.
1004+ func LocalBuildImage (w http.ResponseWriter , r * http.Request ) {
1005+ buildImage (w , r , getLocalBuildContext )
1006+ }
1007+
1008+ // BuildImage handles HTTP requests for building container images using the Docker-compatible API.
1009+ //
1010+ // Extracts build contexts from the request body (tar/multipart), processes build parameters,
1011+ // executes the build using buildah, and streams output back to the client.
8691012func BuildImage (w http.ResponseWriter , r * http.Request ) {
1013+ buildImage (w , r , getBuildContext )
1014+ }
1015+
1016+ type getBuildContextFunc func (r * http.Request , query url.Values , anchorDir string , multipart bool ) (* BuildContext , error )
1017+
1018+ func buildImage (w http.ResponseWriter , r * http.Request , getBuildContextFunc getBuildContextFunc ) {
8701019 // Create temporary directory for build context
8711020 anchorDir , err := os .MkdirTemp (parse .GetTempDir (), "libpod_builder" )
8721021 if err != nil {
@@ -897,7 +1046,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
8971046 }
8981047 queryValues := r .URL .Query ()
8991048
900- buildContext , err := getBuildContext (r , queryValues , anchorDir , multipart )
1049+ buildContext , err := getBuildContextFunc (r , queryValues , anchorDir , multipart )
9011050 if err != nil {
9021051 utils .ProcessBuildError (w , err )
9031052 return
0 commit comments