@ -140,6 +140,10 @@ func (lc *LocalClient) doLocalRequestNiceError(req *http.Request) (*http.Respons
all , _ := io . ReadAll ( res . Body )
all , _ := io . ReadAll ( res . Body )
return nil , & AccessDeniedError { errors . New ( errorMessageFromBody ( all ) ) }
return nil , & AccessDeniedError { errors . New ( errorMessageFromBody ( all ) ) }
}
}
if res . StatusCode == http . StatusPreconditionFailed {
all , _ := io . ReadAll ( res . Body )
return nil , & PreconditionsFailedError { errors . New ( errorMessageFromBody ( all ) ) }
}
return res , nil
return res , nil
}
}
if ue , ok := err . ( * url . Error ) ; ok {
if ue , ok := err . ( * url . Error ) ; ok {
@ -170,6 +174,24 @@ func IsAccessDeniedError(err error) bool {
return errors . As ( err , & ae )
return errors . As ( err , & ae )
}
}
// PreconditionsFailedError is returned when the server responds
// with an HTTP 412 status code.
type PreconditionsFailedError struct {
err error
}
func ( e * PreconditionsFailedError ) Error ( ) string {
return fmt . Sprintf ( "Preconditions failed: %v" , e . err )
}
func ( e * PreconditionsFailedError ) Unwrap ( ) error { return e . err }
// IsPreconditionsFailedError reports whether err is or wraps an PreconditionsFailedError.
func IsPreconditionsFailedError ( err error ) bool {
var ae * PreconditionsFailedError
return errors . As ( err , & ae )
}
// bestError returns either err, or if body contains a valid JSON
// bestError returns either err, or if body contains a valid JSON
// object of type errorJSON, its non-empty error body.
// object of type errorJSON, its non-empty error body.
func bestError ( err error , body [ ] byte ) error {
func bestError ( err error , body [ ] byte ) error {
@ -198,27 +220,42 @@ func SetVersionMismatchHandler(f func(clientVer, serverVer string)) {
}
}
func ( lc * LocalClient ) send ( ctx context . Context , method , path string , wantStatus int , body io . Reader ) ( [ ] byte , error ) {
func ( lc * LocalClient ) send ( ctx context . Context , method , path string , wantStatus int , body io . Reader ) ( [ ] byte , error ) {
slurp , _ , err := lc . sendWithHeaders ( ctx , method , path , wantStatus , body , nil )
return slurp , err
}
func ( lc * LocalClient ) sendWithHeaders (
ctx context . Context ,
method ,
path string ,
wantStatus int ,
body io . Reader ,
h http . Header ,
) ( [ ] byte , http . Header , error ) {
if jr , ok := body . ( jsonReader ) ; ok && jr . err != nil {
if jr , ok := body . ( jsonReader ) ; ok && jr . err != nil {
return nil , jr . err // fail early if there was a JSON marshaling error
return nil , nil , jr . err // fail early if there was a JSON marshaling error
}
}
req , err := http . NewRequestWithContext ( ctx , method , "http://" + apitype . LocalAPIHost + path , body )
req , err := http . NewRequestWithContext ( ctx , method , "http://" + apitype . LocalAPIHost + path , body )
if err != nil {
if err != nil {
return nil , err
return nil , nil , err
}
if h != nil {
req . Header = h
}
}
res , err := lc . doLocalRequestNiceError ( req )
res , err := lc . doLocalRequestNiceError ( req )
if err != nil {
if err != nil {
return nil , err
return nil , nil , err
}
}
defer res . Body . Close ( )
defer res . Body . Close ( )
slurp , err := io . ReadAll ( res . Body )
slurp , err := io . ReadAll ( res . Body )
if err != nil {
if err != nil {
return nil , err
return nil , nil , err
}
}
if res . StatusCode != wantStatus {
if res . StatusCode != wantStatus {
err = fmt . Errorf ( "%v: %s" , res . Status , bytes . TrimSpace ( slurp ) )
err = fmt . Errorf ( "%v: %s" , res . Status , bytes . TrimSpace ( slurp ) )
return nil , bestError ( err , slurp )
return nil , nil , bestError ( err , slurp )
}
}
return slurp , nil
return slurp , res . Header , nil
}
}
func ( lc * LocalClient ) get200 ( ctx context . Context , path string ) ( [ ] byte , error ) {
func ( lc * LocalClient ) get200 ( ctx context . Context , path string ) ( [ ] byte , error ) {
@ -1093,7 +1130,11 @@ func (lc *LocalClient) NetworkLockSubmitRecoveryAUM(ctx context.Context, aum tka
// SetServeConfig sets or replaces the serving settings.
// SetServeConfig sets or replaces the serving settings.
// If config is nil, settings are cleared and serving is disabled.
// If config is nil, settings are cleared and serving is disabled.
func ( lc * LocalClient ) SetServeConfig ( ctx context . Context , config * ipn . ServeConfig ) error {
func ( lc * LocalClient ) SetServeConfig ( ctx context . Context , config * ipn . ServeConfig ) error {
_ , err := lc . send ( ctx , "POST" , "/localapi/v0/serve-config" , 200 , jsonBody ( config ) )
h := make ( http . Header )
if config != nil {
h . Set ( "If-Match" , config . ETag )
}
_ , _ , err := lc . sendWithHeaders ( ctx , "POST" , "/localapi/v0/serve-config" , 200 , jsonBody ( config ) , h )
if err != nil {
if err != nil {
return fmt . Errorf ( "sending serve config: %w" , err )
return fmt . Errorf ( "sending serve config: %w" , err )
}
}
@ -1112,11 +1153,19 @@ func (lc *LocalClient) NetworkLockDisable(ctx context.Context, secret []byte) er
//
//
// If the serve config is empty, it returns (nil, nil).
// If the serve config is empty, it returns (nil, nil).
func ( lc * LocalClient ) GetServeConfig ( ctx context . Context ) ( * ipn . ServeConfig , error ) {
func ( lc * LocalClient ) GetServeConfig ( ctx context . Context ) ( * ipn . ServeConfig , error ) {
body , err := lc . send ( ctx , "GET" , "/localapi/v0/serve-config" , 200 , nil )
body , h, err := lc . send WithHeaders ( ctx , "GET" , "/localapi/v0/serve-config" , 200 , nil , nil )
if err != nil {
if err != nil {
return nil , fmt . Errorf ( "getting serve config: %w" , err )
return nil , fmt . Errorf ( "getting serve config: %w" , err )
}
}
return getServeConfigFromJSON ( body )
sc , err := getServeConfigFromJSON ( body )
if err != nil {
return nil , err
}
if sc == nil {
sc = new ( ipn . ServeConfig )
}
sc . ETag = h . Get ( "Etag" )
return sc , nil
}
}
func getServeConfigFromJSON ( body [ ] byte ) ( sc * ipn . ServeConfig , err error ) {
func getServeConfigFromJSON ( body [ ] byte ) ( sc * ipn . ServeConfig , err error ) {