Zend_Form Failing validation with Optional File in IE10 - php

TL;DR: Zend_Form_Element_File is not playing nice with IE10
Alright, bear with me while I unravel this tale of one of the worst bugs I've ever encountered. (Only applies to IE10)
I'm using a Zend_Form (Zend Framework 1.12) with a Zend_Form_Element_File:
$file = (new Zend_Form_Element_File('file'))
->setRequired(false);
I'm also using jQuery Form Plugin to use AJAX or an iFrame when appropriate. (Which is a new development, previously I was only using an iframe [and this bug was found in that version] and I since moved the iframe to be XHR2 Feature Detected).
So we have this form that AJAXly submits the file and the other variables to the server, which tries to validate it through Zend_Form. No big deal. Chrome and Firefox send empty files which Zend detects and goes no problem, and IE was sending nothing related to the file, and is now sending an empty parameter named file (NOT an empty file) and Zend_Form is saying that the "file is too big."
The files array is empty, So I implemented the patch suggested on Zend Issue ZF-12189 to get:
$check = $this->_getFiles($files, false, true);
if (empty($check)) {
if ($this->_options['ignoreNoFile']) {
return true;
}
return false;
}
but as $check is not evaluating as empty the problem persists.
Relevant Request Headers:
X-Requested-With: XMLHttpRequest
Accept: text/html, */*; q=0.01
Content-Type: multipart/form-data; boundary=---------------------------7dd299161d06c6
Content-Length: 580
Request Body:
-----------------------------7dd299161d06c6
Content-Disposition: form-data; name="entryId"
9
-----------------------------7dd299161d06c6
Content-Disposition: form-data; name="csrf"
b9774f3998695465d9b3079eb028e342
-----------------------------7dd299161d06c6
Content-Disposition: form-data; name="description"
test
-----------------------------7dd299161d06c6
Content-Disposition: form-data; name="MAX_FILE_SIZE"
2097152
-----------------------------7dd299161d06c6
Content-Disposition: form-data; name="file"
-----------------------------7dd299161d06c6--
Form Messages:
{"file":{"fileUploadErrorIniSize":"File 'file' exceeds the defined ini size"}}
Does anyone know of a solution to this problem?

Here is a workaround you can add to override the Zend_Form's isValid() method. There is a legitimate bug out there that generates the same error when no files are uploaded yet it still attempts to validate anyway. Maybe this will help someone out there.
public function isValid($data) {
$valid = parent::isValid($data);
$errorCount = 0;
foreach($this->getElements() as $elem) {
if ($elem->hasErrors()) {
$errorCount++;
}
// Related issues:
// http://framework.zend.com/issues/browse/ZF-12159
// http://framework.zend.com/issues/browse/ZF-10279
// http://framework.zend.com/issues/browse/ZF-12189
if ($elem instanceof Zend_Form_Element_File && !$elem->isRequired() && !isset($_FILES[$elem->getName()]) && $elem->hasErrors()) {
$elem->clearErrorMessages();
$elem->setTransferAdapter( 'Http' ); // reset any errors that may be on the transfer adapter
$errorCount--;
}
}
if ($this->_errorsForced) {
return false;
}
if ($errorCount==0) {
$this->_errorsExist = false;
}
return $errorCount==0;
}

you may want to see this issue: Form Bug in JS
When we ran into this, we just forced iFrame: true and that did the trick. It made me a little sad, but it works. :)

Related

How to send this PUT-request to prestashop?

I am working with Prestashop web service. I am trying to send a PUT (update) request to the API but with no luck. My request seems to be set up in the 'wrong' way (i.e. not in the way the server expects) Since Prestashop is open-source I took a look at the source code, specifically when it recieves a PUT request it does the following (I don't write php-code):
$input_xml = null;
// if a XML is in PUT or in POST
if (($_SERVER['REQUEST_METHOD'] == 'PUT') || ($_SERVER['REQUEST_METHOD'] == 'POST')) {
$putresource = fopen("php://input", "r");
while ($putData = fread($putresource, 1024)) {
$input_xml .= $putData;
}
fclose($putresource);
}
if (isset($input_xml) && strncmp($input_xml, 'xml=', 4) == 0) {
$input_xml = substr($input_xml, 4);
}
From the code above I understood that my data should look something like this: xml=<data><here></here></data> but I don't know where to put this, should it be in the request-body or embedded in the url? is the "xml=" implicit when you send a request with Content-Type = text/xml? I did try different combinations of the request and still getting the same 404 error. I tried this:
let updateOrderState (orderId:int64) (stateId:int64) (credentials:AuthInfo) =
// url looks like this: http://www.webstoreexample.com/entity/id
let auth = BasicAuth credentials.Key ""
let orderApi = credentials.Api + "/orders/" + orderId.ToString();
let orderAsXml = Http.RequestString(orderApi, httpMethod = "GET", headers = [auth])
let xml = Order.Parse(orderAsXml).XElement // at this point, I have the data
xml.Element(XName.Get("order")).Element(XName.Get("current_state")).SetValue(stateId) // field 'current_state' gets modified
let xmlData = xml.ToString()
// HERE the put request
Http.RequestString(url = credentials.Api + "/orders",
headers = [ auth;
"Content-Type","text/xml" ],
httpMethod= HttpMethod.Put,
body= HttpRequestBody.TextRequest(xmlData))
Variations on the PUT-request didn't work as well, here I changed the request body from TextRequest into FormValues:
Http.RequestString(url = credentials.Api + "/orders",
headers = [ auth;
"Content-Type","text/xml" ],
httpMethod= HttpMethod.Put,
body= HttpRequestBody.FormValues ["xml", xmlData]) // xml=xmlData
Another thing I tried is adding the id to the url (even tho in the docs they say that this is not required):
Http.RequestString(url = credentials.Api + "/order/" + orderId.ToString(), // added the id to the url
headers = [ auth;
"Content-Type","text/xml" ],
httpMethod= HttpMethod.Put,
body= HttpRequestBody.FormValues ["xml", xmlData]) // xml=xmlData
Specifically, I am tring to the update the value of the current_state node of an order. Getting the data and modifying it works as expected but sending the modified data doesn't seem to work and I still recieve the 404: Not found error
Any Help on this would be greatly apprecited!
Okay, I just tested it with library and example that I give in comments, also I repetead same requests using CURL with same positive results, so there is nothing PHP language specific.
I think you need just repeat same Headers/Body in your application.
HTTP REQUEST HEADER
PUT /16011/api/orders/8 HTTP/1.1
Authorization: Basic TlpCUEJKTkhaWFpFMzlCMVBDTkdTM1JQN0s2NTVVQ0Y6
Host: localhost
Accept: */*
Content-Length: 2411
Content-Type: application/x-www-form-urlencoded
XML SENT
<?xml version="1.0" encoding="UTF-8"?>
<prestashop xmlns:xlink="http://www.w3.org/1999/xlink">
<order>
<id>8</id>
<id_address_delivery xlink:href="http://localhost/16011/api/addresses/5">5</id_address_delivery>
<id_address_invoice xlink:href="http://localhost/16011/api/addresses/5">5</id_address_invoice>
<id_cart xlink:href="http://localhost/16011/api/carts/8">8</id_cart>
<id_currency xlink:href="http://localhost/16011/api/currencies/1">1</id_currency>
<id_lang xlink:href="http://localhost/16011/api/languages/1">1</id_lang>
<id_customer xlink:href="http://localhost/16011/api/customers/2">2</id_customer>
<id_carrier xlink:href="http://localhost/16011/api/carriers/3">3</id_carrier>
<current_state xlink:href="http://localhost/16011/api/order_states/2" notFilterable="true">10</current_state>
<module>bankwire</module>
<invoice_number>0</invoice_number>
<invoice_date>0000-00-00 00:00:00</invoice_date>
<delivery_number>0</delivery_number>
<delivery_date>0000-00-00 00:00:00</delivery_date>
<valid>0</valid>
<date_add>2015-09-17 08:29:17</date_add>
<date_upd>2015-10-20 03:45:13</date_upd>
<shipping_number notFilterable="true"></shipping_number>
<id_shop_group>1</id_shop_group>
<id_shop>1</id_shop>
<secure_key>45838497c9182b0d361473894092de02</secure_key>
<payment>Bank wire</payment>
<recyclable>0</recyclable>
<gift>0</gift>
<gift_message></gift_message>
<mobile_theme>0</mobile_theme>
<total_discounts>0.000000</total_discounts>
<total_discounts_tax_incl>0.000000</total_discounts_tax_incl>
<total_discounts_tax_excl>0.000000</total_discounts_tax_excl>
<total_paid>24.450000</total_paid>
<total_paid_tax_incl>24.450000</total_paid_tax_incl>
<total_paid_tax_excl>23.510000</total_paid_tax_excl>
<total_paid_real>0.000000</total_paid_real>
<total_products>16.510000</total_products>
<total_products_wt>17.170000</total_products_wt>
<total_shipping>7.280000</total_shipping>
<total_shipping_tax_incl>7.280000</total_shipping_tax_incl>
<total_shipping_tax_excl>7.000000</total_shipping_tax_excl>
<carrier_tax_rate>4.000</carrier_tax_rate>
<total_wrapping>0.000000</total_wrapping>
<total_wrapping_tax_incl>0.000000</total_wrapping_tax_incl>
<total_wrapping_tax_excl>0.000000</total_wrapping_tax_excl>
<round_mode>2</round_mode>
<conversion_rate>1.000000</conversion_rate>
<reference>ECHCBFWGR</reference>
<associations></associations>
</order>
</prestashop>

Fosrestbundle body empty when multipart request

In the code bellow I expect the $request->getContents() to get the body content of the HTTP request. When sending non multipart request this works as expected though when using multipart requests the $body variable remains empty.
public function postDebugAction(Request $request) {
$body = $request->getContent();
if (empty($body)) {
throw new \Exception('Body empty.');
}
return $this->view(array(), 201);
}
After reading this question and answer I added a body listener aswell.
<?php
namespace VSmart\ApiBundle\Listener;
use FOS\RestBundle\EventListener\BodyListener as BaseBodyListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use FOS\RestBundle\Decoder\DecoderProviderInterface;
class BodyListener extends BaseBodyListener {
/**
* #var DecoderProviderInterface
*/
private $decoderProvider;
/**
* #param DecoderProviderInterface $decoderProvider Provider for fetching decoders
*/
public function __construct(DecoderProviderInterface $decoderProvider) {
$this->decoderProvider = $decoderProvider;
}
/**
* {#inheritdoc}
*/
public function onKernelRequest(GetResponseEvent $event) {
$request = $event->getRequest();
if (strpos($request->headers->get('Content-Type'), 'multipart/form-data') !== 0) {
return;
}
$format = 'json';
if (!$this->decoderProvider->supports($format)) {
return;
}
$decoder = $this->decoderProvider->getDecoder($format);
$iterator = $request->request->getIterator();
$request->request->set($iterator->key(), $decoder->decode($iterator->current(), $format));
}
}
According to my PHPUnit test this was working though when using Postman and Advanced Rest Client to simulate the request the body seems to be empty again. I double checked this to run both the simulate requests as PHPUnit with the debugger. Result is that, indeed, the body is empty when simulated via a Rest client and not empty when ran through PHPUnit.
The test case I used:
POST url:
http://localhost/EntisServer/web/app_dev.php/api2/debug
Headers:
Authorization: Bearer ZGYzYjY1YzY4MGY3YWM3OTFhYTI4Njk3ZmI0NmNmOWZmMjg5MDFkYzJmOWZkOWE4ZTkyYTRmMGM4NTE1MWM0Nw
Content-Type: multipart/form-data; boundary=-----XXXXX
Content:
-----XXXXX
Content-Disposition: form-data; name="json"
Content-Type: application/json; charset=utf-8
{
"blabla": 11
}
-----XXXXX
Content-Disposition: form-data; name="q_3101"; filename="image.jpg"
Content-Type: image/jpeg
contents of a file...
-----XXXXX--
UPDATE
I was uncertain whether I stepped through the debugger without using the BodyListener. When I did the result is exactly the same. So, without the BodyListener the PHPUnit case gets the body though the simulated request is still empty.
See php:// wrappers on php.net:
Note: Prior to PHP 5.6, a stream opened with php://input could only be read once; the stream did not support seek operations. However, depending on the SAPI implementation, it may be possible to open another php://input stream and restart reading. This is only possible if the request body data has been saved. Typically, this is the case for POST requests, but not other request methods, such as PUT or PROPFIND.
So update your PHP version or make sure you only read the input once.
You can find your uploaded files in $request->files->all() after fos_rest.decoder_provider decoding.

FosRestbundle force content-type for error messages

I'm looking for a simple, stupid solution to force the content-type to application/json for all symfony2 http error messages in fosrestbundle (like MethodNotAllowedHttpException etc.).
Example request headers:
Content-Type: application/x-www-form-urlencoded
Accept: */*
Current response headers (MethodNotAllowedHttpException):
Content-Type: text/html; charset=UTF-8
you can throw the error if the value in the header fails your logic test. then in your catch statement, return a json response. something like this (untested code)
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
// .....
public function myAction(Request $request){
try{
// ...
$cType = $request->headers->get('Content-Type');
// logic for testing if the content type is allowed
if(!in_array($cType,$allowedArray)){
throw new MethodNotAllowedHttpException();
}
// .....
}catch(\Exception $e){
if(get_class($e) == "MethodNotAllowedHttpException"){
$data = array("success"=>false,"message"=>"Method not allowed")
return new JsonResponse($data);
}
}
}
This way you can handle different exceptions in different ways. I'm not sure if it was the content type that you want to use to determine if you throw the exception or not, but you can get any header info by using $request->headers->get()

Access-Control-Allow-Origin is not showing up in response headers from codeigniter

My Codeigniter file says
$CI->output->set_header("Access-Control-Allow-Origin: *");
$CI->output->set_header("Access-Control-Expose-Headers: Access-Control-Allow-Origin");
$CI->output->set_status_header(200);
$CI->output->set_content_type('application/json');
echo json_encode(array("city" => "dhaka"));
but the http response that i get are:
Request URL:http://localhost/index.php/location/city
Request Method:POST
Status Code:200 OK
Connection:Keep-Alive
Content-Length:16
Content-Type:text/html
Date:Sun, 22 Jul 2012 10:27:32 GMT
Keep-Alive:timeout=5, max=100
Server:Apache/2.2.21 (Unix) mod_ssl/2.2.21 OpenSSL/0.9.8r DAV/2 PHP/5.3.6
X-Powered-By:PHP/5.3.6
The header Access-Control-Allow-Origin is missing in the response even after including Access-Control-Expose-Headers: Access-Control-Allow-Origin. My source of information about this header is from Mozilla Developer Website
It turns out, it worked for me only when i set the headers via the PHP syntax header() instead of the codeigniter syntax $CI->output->set_header(). That's sad.
Thanks to the first comment by #Yan at the Question of this topic
If you look closely you can also notice the content-type being different: it's text/html, whereas you are requesting application/json. This happens because while you are preparing the headers correctly, you never actually output them. As far as I know you can do this in at least 2 ways:
Use the output library's set_output function to output everything at once.
$json = json_encode(array("city" => "dhaka"));
$this->output->set_header("Access-Control-Allow-Origin: *");
$this->output->set_header("Access-Control-Expose-Headers: Access-Control-Allow-Origin");
$this->output->set_status_header(200);
$this->output->set_content_type('application/json');
$this->output->set_output($json);
Call the output-library's _display() function, to first output the correct headers and then append your json object with echo.
$this->output->set_header("Access-Control-Allow-Origin: *");
$this->output->set_header("Access-Control-Expose-Headers: Access-Control-Allow-Origin");
$this->output->set_status_header(200);
$this->output->set_content_type('application/json');
$this->output->_display();
echo json_encode(array("city" => "dhaka"));
This function sends the finalized output data to the browser along with any server headers and profile data. (From CI/system/core/Output.php line 316)
after some digging around, i found that $CI->output->set_header() does work - when there isn't an error or exception.
When there is an error or exception that CI can catch, the output & view classes are bypassed completely and the appropriate error pages are rendered with include(VIEWPATH.'errors/'.$template.'.php') and headers sent with set_status_header($status_code) (located at <CI System Dir>/core/Common.php)
see <CI System Dir>/core/Exceptions.php
here's a sample:
/**
* General Error Page
*
* Takes an error message as input (either as a string or an array)
* and displays it using the specified template.
*
* #param string $heading Page heading
* #param string|string[] $message Error message
* #param string $template Template name
* #param int $status_code (default: 500)
*
* #return string Error page output
*/
public function show_error($heading, $message, $template = 'error_general', $status_code = 500)
{
set_status_header($status_code);
$message = '<p>'.implode('</p><p>', is_array($message) ? $message : array($message)).'</p>';
if (ob_get_level() > $this->ob_level + 1)
{
ob_end_flush();
}
ob_start();
include(VIEWPATH.'errors/'.$template.'.php');
$buffer = ob_get_contents();
ob_end_clean();
return $buffer;
}
it's annoying in that it makes DRY less straight forward. to work around it, i suggest you create a helper function, for example (untested):
function my_generate_headers($headers=array(),$useOutputClass=true)
{
if(is_array($headers) && count($headers)<1) return false;
foreach($headers AS $eHeader)
{
($useOutputClass) ?
get_instance()->output->set_header('X-Powered-By: C-C-C-Cocaine') :
#header('X-Powered-By: Errors',true);
}
return true;
}
use that function in your different error pages at <CI Views>/errors/error_*.php as well as in your controllers.
What worked for me is:
$this->output
->set_header('Access-Control-Allow-Origin: http://localhost:4567')
->set_header('Content-type: application/json')
->set_status_header(200)
->set_output( json_encode($to_encode) )
->_display();

Form uploading files to different server without following

How can I send a file in django to a different server without user being redirected to the server ? So all goes to rewriting this simple php function in django :
$filename = 'C:/tmp/myphoto.jpg';
$handler = 'http://www.example.com/upload.php';
$field = 'image';
$res = send_file($filename, $handler, $field);
if ($res) {
echo 'done.';
} else {
echo 'something went wrong.';
}
Function on the second server is just simple php func that reads files from $_FILES:
<?php
move_uploaded_file(
$_FILES['image']['tmp_name'],
'/var/www/image/uploaded-file.jpg'
);
echo 'file saved.';
?>
I've already tried django-filetransfers, and it works but I somehow cannot make it stay on the page from which I am uploading file. I have edited the upload_handler view and files are sent properly but after that I'm redirected to my second server :
def upload_handler(request):
if request.method == 'POST':
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return HttpResponseRedirect("/upload")
upload_url, upload_data = prepare_upload(request, "address of my server/")
form = UploadForm()
return direct_to_template(request, '/upload.html',
{'form': form, 'upload_url': upload_url, 'upload_data': upload_data,
'uploads': UploadModel.objects.all()})
And here's my approach. I'm using functions from httplib and also multipart_encode function from python-poster that creates me file headers :
def file_upload(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
f = request.FILES['file']
logging.debug(f)
status = send_file(request.FILES['file'])
c = RequestContext(request, {
"status" : status,
})
template = "management/status.html"
result = render_to_string(template, c)
return HttpResponse(result)
else:
form = UploadFileForm()
return render_to_response('management/file_upload.html', {'form': form})
def send_file(file):
datagen, headers = multipart_encode({"myfile": file})
conn = httplib.HTTPConnection(HOST)
conn.request('POST', '/rte/', file, headers)
res = conn.getresponse()
if res.status != 200:
logging.debug("error \n")
logging.debug(file)
logging.debug("\n")
logging.debug(headers)
return res.status
HTML:
<form action="{{ views.file_upload }}" method="POST" enctype="multipart/form-data">
{{ form.as_p }}
<input type="submit" value="Upload" />
</form>
As a result I get 'Error 500' and in debug :
2010-10-20 18:12:55,819 DEBUG thumb.php4.jpg
2010-10-20 18:14:55,968 DEBUG error
2010-10-20 18:14:55,968 DEBUG thumb.php4.jpg
2010-10-20 18:14:55,969 DEBUG
2010-10-20 18:14:55,969 DEBUG {'Content-Length': 15019, 'Content-Type': 'multipart/form-data; boundary=02cafbc1d080471284be55dc1095b399'}
My functions are based on python/django docs and few solutions I've found on the internet. Functionality looks the same but somehow it doesn't work. Should I take different approach ? In php I do not need to define headers etc.
Well, first of all I'd like to ask why are you doing this? Is it because of the storage or load or perhaps failover? In case it's storage, why are you sending the file to the second server via HTTP? I guess a simple SSH file transfer would be just fine (scp). In case it's failover you'll be better off with rsync.
If using your own second server is not mandatory you might as well go with a CDN service.

Categories