From 853ab093d5cca44e9678bc4166a7f7b07fc77a33 Mon Sep 17 00:00:00 2001 From: Andreas Granig Date: Fri, 25 Jul 2014 23:31:08 +0200 Subject: [PATCH] MT#8299 API: Use multipart upload for device model --- .../Panel/Controller/API/PbxDeviceModels.pm | 26 ++++++++++++++---- lib/NGCP/Panel/Role/API.pm | 23 ++++++++++++---- lib/NGCP/Panel/Role/API/PbxDeviceModels.pm | 1 + sandbox/spa504g-back.jpg | Bin 0 -> 22862 bytes 4 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 sandbox/spa504g-back.jpg diff --git a/lib/NGCP/Panel/Controller/API/PbxDeviceModels.pm b/lib/NGCP/Panel/Controller/API/PbxDeviceModels.pm index f7b1b6e1d7..e67ec51fa2 100644 --- a/lib/NGCP/Panel/Controller/API/PbxDeviceModels.pm +++ b/lib/NGCP/Panel/Controller/API/PbxDeviceModels.pm @@ -14,6 +14,8 @@ require Catalyst::ActionRole::CheckTrailingSlash; require Catalyst::ActionRole::HTTPMethods; require Catalyst::ActionRole::RequireSSL; +# curl -v -X POST --user $USER --insecure -F front_image=@sandbox/spa504g-front.jpg -F mac_image=@sandbox/spa504g-back.jpg -F json='{"reseller_id":1, "vendor":"Cisco", "model":"SPA999", "linerange":[{"name": "Phone Keys", "num_lines":4, "can_private":true, "can_shared":true, "can_blf":true}]}' https://localhost:4443/api/pbxdevicemodels/ + class_has 'api_description' => ( is => 'ro', isa => 'Str', @@ -162,11 +164,14 @@ sub POST :Allow { my $guard = $c->model('DB')->txn_scope_guard; { - my $resource = $self->get_valid_post_data( - c => $c, - media_type => 'application/json', - ); - last unless $resource; + last unless $self->forbid_link_header($c); + last unless $self->valid_media_type($c, 'multipart/form-data'); + last unless $self->require_wellformed_json($c, 'application/json', $c->req->param('json')); + my $resource = JSON::from_json($c->req->param('json')); + $resource->{front_image} = $self->get_upload($c, 'front_image'); + last unless $resource->{front_image}; + # optional, don't set error + $resource->{mac_image} = $c->req->upload('mac_image'); my $form = $self->get_form($c); last unless $self->validate_form( @@ -206,6 +211,17 @@ sub POST :Allow { last; } + my $ft = File::Type->new(); + if($resource->{front_image}) { + my $front_image = delete $resource->{front_image}; + $resource->{front_image} = $front_image->slurp; + $resource->{front_image_type} = $ft->mime_type($resource->{front_image}); + } + if($resource->{mac_image}) { + my $front_image = delete $resource->{mac_image}; + $resource->{mac_image} = $front_image->slurp; + $resource->{mac_image_type} = $ft->mime_type($resource->{mac_image}); + } try { $item = $c->model('DB')->resultset('autoprov_devices')->create($resource); diff --git a/lib/NGCP/Panel/Role/API.pm b/lib/NGCP/Panel/Role/API.pm index 198017a95d..d12496dca8 100644 --- a/lib/NGCP/Panel/Role/API.pm +++ b/lib/NGCP/Panel/Role/API.pm @@ -198,27 +198,39 @@ sub forbid_link_header { sub valid_media_type { my ($self, $c, $media_type) = @_; + my $ctype = $c->request->header('Content-Type'); + $ctype =~ s/;\s+boundary.+$//; my $type; if(ref $media_type eq "ARRAY") { $type = join ' or ', @{ $media_type }; - return 1 if $c->request->header('Content-Type') && - $c->request->header('Content-Type') ~~ $media_type; + return 1 if $ctype && $ctype ~~ $media_type; } else { $type = $media_type; - return 1 if($c->request->header('Content-Type') && - index($c->request->header('Content-Type'), $media_type) == 0); + return 1 if($ctype && index($ctype, $media_type) == 0); } - $self->error($c, HTTP_UNSUPPORTED_MEDIA_TYPE, "Unsupported media type, accepting $type only."); + $self->error($c, HTTP_UNSUPPORTED_MEDIA_TYPE, "Unsupported media type '" . ($ctype // 'undefined') . "', accepting $type only."); return; } sub require_body { my ($self, $c) = @_; return 1 if length $c->stash->{body}; + + + $self->error($c, HTTP_BAD_REQUEST, "This request is missing a message body."); return; } +# returns Catalyst::Request::Upload +sub get_upload { + my ($self, $c, $field) = @_; + my $upload = $c->req->upload($field); + return $upload if $upload; + $self->error($c, HTTP_BAD_REQUEST, "This request is missing the upload part '$field' in body."); + return; +} + sub require_precondition { my ($self, $c, $header_name) = @_; return 1 if $c->request->header($header_name); @@ -252,6 +264,7 @@ sub require_wellformed_json { NGCP::Panel::Utils::ValidateJSON->new($patch); $ret = 1; } catch($e) { + chomp $e; $self->error($c, HTTP_BAD_REQUEST, "The entity is not a well-formed '$media_type' document. $e"); } return $ret; diff --git a/lib/NGCP/Panel/Role/API/PbxDeviceModels.pm b/lib/NGCP/Panel/Role/API/PbxDeviceModels.pm index c9330e9b4a..ff9afe07cc 100644 --- a/lib/NGCP/Panel/Role/API/PbxDeviceModels.pm +++ b/lib/NGCP/Panel/Role/API/PbxDeviceModels.pm @@ -12,6 +12,7 @@ use Data::HAL qw(); use Data::HAL::Link qw(); use HTTP::Status qw(:constants); use JSON qw(); +use File::Type; use NGCP::Panel::Form::Device::ModelAPI; sub get_form { diff --git a/sandbox/spa504g-back.jpg b/sandbox/spa504g-back.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dfa82d01afe6b06304e755078df1c1fad932170e GIT binary patch literal 22862 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 literal 0 HcmV?d00001