I am currently struggling with an error that appears in my Chrome developer tools console
GET http://localhost:8080/movie_db/search/movie?movieTitle=Machete 415 (Unsupported Media Type)
when I try to connect to my REST service with angular JS. Since I had this issue before when I tried to access the REST service via a Simple REST plugin in Chrome, I assume that most likely a missing Content-Type header parameter is the reason for that (should be Content-Type: 'application/json').
Angular JS website says, that one could set header parameters as follows: $httpProvider.defaults.headers.get['My-Header']='value'. Regretfully this didn't have any impact and the problem still exists.
Thus I'd like to know how I can manipulate the header parameters with angular JS that are sent over to my REST service.
Right at the moment, my code looks like this:
function SearchMovieController($scope, $resource) {
$scope.SearchMovie = $resource('http://localhost\\:8080/movie_db/search/:action',
{action: 'movie', movieTitle: 'Machete'},
{get: {method: 'JSONP', headers: [{'Content-Type': 'application/json'}, {'Accept' : 'application/json'}]}});
$scope.SearchMovie.get()
}
Server side looks as follows (I use Spring MVC):
CONTROLLER:
#Controller
#RequestMapping( "/search/movie" )
public class SearchController { // TODO rename?
private final TheMovieDBService theMovieDBService;
#Autowired
public SearchController( TheMovieDBService theMovieDBService ) {
this.theMovieDBService = theMovieDBService;
}
#ResponseBody
#RequestMapping( method = RequestMethod.GET, produces = "application/json", consumes = "application/json" )
public Map<String, Object> search( #RequestParam String movieTitle ) {
Map<String, Object> response = new HashMap<String, Object>();
try {
TheMovieDBSearchResult searchResult = theMovieDBService.searchMovie( movieTitle );
response.put( "result", searchResult );
response.put( "success", "true" );
} catch ( EncoderException e ) {
response.put( "success", "false" );
}
return response;
}
}
web.xml
<servlet>
<servlet-name>json</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.u6f6o.apps.movie_db.config.web.ServletConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>json</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
I had to step through all of Angular's $httpBackend stuff just recently, and JSONP uses a different method to load data than the other requests.
EDIT: The code in question
So if you specify jsonp has your method it is using the following method to load your data. This will, of course, completely ignore any headers you set yourself.
in $httpBackend:
function jsonpReq(url, done) {
// we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.:
// - fetches local scripts via XHR and evals them
// - adds and immediately removes script elements from the document
var script = rawDocument.createElement('script'),
doneWrapper = function() {
rawDocument.body.removeChild(script);
if (done) done();
};
script.type = 'text/javascript';
script.src = url;
if (msie) {
script.onreadystatechange = function() {
if (/loaded|complete/.test(script.readyState)) doneWrapper();
};
} else {
script.onload = script.onerror = doneWrapper;
}
rawDocument.body.appendChild(script);
}
Related
The application is configured to use HTTPS. We want to be able to make calls from the client to a printer on their local network that exposes a simple api that uses HTTP. So from our javascript code we do a POST with a "text/plain" payload to send commands to the printer. When we send this request we get the following error.
jquery-3.3.1.min.js:2 Mixed Content: The page at 'https://...' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://.../pstprnt'. This request has been blocked; the content must be served over HTTPS.
Is there a way to configure CORS in such a way that only this traffic from and to a printer can be done using HTTP while the rest of the application uses HTTPS, without specifying the target IN startup.cs ? ( this is because the printers should be able to be expanded at runtime, so basically just 'allow all orgins', so that its not restricted to the ones specified in Startup.cs)
I have tried multiple guides online, but I'm guessing there is something wrong with our Startup.cs file structure.
The request to the printer looks like this:
$.ajax({
type: "POST",
url: "http://<printer-ip>/pstprnt",
data: 'some ZPL',
contentType: 'text/plain'
}).done((res) => {
console.log("second success");
}).fail((e) => {
alert(e);
})
Here is a snippet our Startup file.
CONFIGURE SERVICES
public void ConfigureServices(IServiceCollection services)
{
// Add Cors
services.AddCors();
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
/* (Verification/password reset) email sender */
//services.AddTransient<IEmailSender, EmailSender>();
//services.Configure<AuthMessageSenderOptions>(Configuration);
Task.Run(() => {
var options = new DbContextOptionsBuilder<ApplicationDbContext>().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).Options;
using (var dbContext = new ApplicationDbContext(options)) {
var model = dbContext.AankoopProduct;
}
});
services.AddLocalization();
/*
I commented this out because I am using UseEndpoints, Am I doing this correctly?
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
*/
services.AddIdentity<Gebruiker, IdentityRole>(options =>
{
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.SignIn.RequireConfirmedEmail = true;
}).AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
// Password settings.
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
});
services.AddControllersWithViews().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
// .cshtml views & .razor components
services.AddRazorPages();
//SignalR for Websockets
services.AddSignalR();
// reload views after changing JS
#if DEBUG
var mvcBuilder = services.AddControllersWithViews();
mvcBuilder.AddRazorRuntimeCompilation();
#endif
services.ConfigureApplicationCookie(opts => opts.LoginPath = "/Account/Login");
/* Breadcrumbs */
services.AddBreadcrumbs(GetType().Assembly, options =>
{
options.TagName = "nav";
options.TagClasses = "";
options.OlClasses = "breadcrumb breadcrumb--transparent m-0";
options.LiClasses = "breadcrumb-item";
options.ActiveLiClasses = "breadcrumb-item active";
//options.SeparatorElement = "<li class=\"separator\">/</li>";
});
/* Repositories */
services.RegisterRepositories();
services.AddSession();
}
CONFIGURE
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IVerkoopProductXMLRepository rep)
{
//app.ApplicationServices.GetService<IInkomendeBestellingTrackerSingleton>();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
#region Auth
var supportedCultures = new[]
{
new CultureInfo("nl-BE")
};
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("nl-BE"),
// Formatting numbers, dates, etc.
SupportedCultures = supportedCultures,
// UI strings that we have localized.
SupportedUICultures = supportedCultures
});
var cultureInfo = new CultureInfo("nl-BE");
cultureInfo.NumberFormat.CurrencySymbol = "€";
cultureInfo.NumberFormat.NumberDecimalSeparator = ".";
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("nl-BE");
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("nl-BE");
// To configure external authentication,
// see: http://go.microsoft.com/fwlink/?LinkID=532715
#endregion
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseStatusCodePages();
app.UseRouting();
app.UseSession();
// Enable Cors
app.UseCors();
/*
I commented this out because I am using UseEndpoints() , Am I doing this correctly?
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=UserSelection}/{id?}");
});
*/
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Account}/{action=Login}/{id?}");
});
}
This doesn't relate to your ASP.NET CORS configuration, because you're making a request directly from the client (the browser) to the printer; CORS would come into play if you were making cross-domain requests to the ASP.NET API.
What you could do is make the request to the printer from the server, instead, assuming your network topology permits it. Make an AJAX request from your JS to a new endpoint on the server, which then makes a plain HTTP request to the printer.
I am sending Json data but when I verify my method in my controller it arrives as null
html:
<div class="hijo">
#Html.LabelFor(m => m.Proyecto, new { #class = "", style = "color:#040404" })
#Html.DropDownListFor(Model => Model.ProyectoID, Model.Proyecto, "Seleccionar")
#Html.ValidationMessageFor(Model => Model.Proyecto, null, new { #class = "label label-danger", id = "Proyecto" })
</div>
<div class="hijo">
<input type="button" id="adicionar" value="Agregar" class="myButton">
</div>
JS:
$('#adicionar').click(function () {
debugger;
var data= {
Proyecto: $("#ProyectoID option:selected").text()
};
$.ajax({
url: "../ManejoDatos",
type: "POST",
dataType: "json",
data: JSON.stringify(data),
success: function (mydata) {
$("#UpdateDiv").html(mydata);
history.pushState('', 'New URL: ' + href, href); // This Code lets you to change url howyouwant
}
});
}
My Controller
public JsonResult ManejoDatos(string Proyecto)
{
........
...
return Json(Proyecto);
}
Console
I don't know if it's something very simple to fix but I don't see the error
regards
I found your JavaScript code is absolutely fine. The weird things is controller with your format ghostly doesn't work But I got success below way:
[System.Web.Http.HttpPost]
public HttpResponseMessage ManejoDatos(string Proyecto)
{
var data = " my test data ";
return Request.CreateResponse(HttpStatusCode.OK, data);
}
I will investigate that obviously and update the answer again .
Hope above idea help you to resolve your problem. Let me know if you encounter any more problem.
Simple types like string,int,float etc are to be fetched from query string in .Net framework where as complex types always fetched from body.
Solution-1
In this case .Net Framework is expecting string Proyecto from query parameters and you can call your API like this without making any change to your server side code
Note:Also you don't need to define key of the parameter (in http request) which is being defined as [From Body]. As total of one simple type can be passed using [FromBody] attribute which you can see below like this =ProyectoValue
POST ../ManejoDatos HTTP/1.1
Host: localhost:59353
Content-Type: application/x-www-form-urlencoded
=ProyectoValue
Solution-2
Or you can call your API by putting all your params in complex model and pass params through body instead of query parameters like this
Complex Type Model
public class MyModel
{
public string Proyecto{ get; set; }
}
API Code
public JsonResult ManejoDatos(MyModel myModel)
{
//Access using mymodel.Proyecto
........
...
return Json(mymodel.Proyecto);
}
And now you can consume your API by passing paramters through body like this
POST ../ManejoDatos HTTP/1.1
Host: localhost:59353
Content-Type: application/x-www-form-urlencoded
proyecto=myproyecto
I'm trying to send a comment to a REST api. That rest API has the CORS already set for my app address..
BackEnd
#RestController
#CrossOrigin(origins = "http://localhost:8000", allowedHeaders = "*", methods = {RequestMethod.GET,RequestMethod.POST,RequestMethod.DELETE,RequestMethod.PUT})
#RequestMapping("/api")
public class CommentController {
#Autowired
private CommentRepository commentRepository;
// Get All Comments From a certain workitemId
#GetMapping("/comments/{workitemId}")
public List<Comment> getTicketHistory(#PathVariable Long workitemId) {
return commentRepository.getCommentsByWorkitemId(workitemId);
}
// Create a comment related with a given Workitem
#PostMapping("/comment")
public boolean createComment(#RequestBody Comment comment) {
commentRepository.save(comment);
return true;
}
}
But I get
Failed to load http://localhost:8999/api/comment: Response to
preflight request doesn't pass access control check: The value of the
'Access-Control-Allow-Credentials' header in the response is '' which
must be 'true' when the request's credentials mode is 'include'.
Origin 'http://localhost:8000' is therefore not allowed access. The
credentials mode of requests initiated by the XMLHttpRequest is
controlled by the withCredentials attribute.
My Code:
$http.post(baseUrl + "/comment", vm.comment).then(
function(response) {
// success callback
console.log("Comment Submitted!");
},
function(response) {
// failure call back
console.log("Error while submitting the comment");
});
Try adding allowCredentials = "true" in #CrossOrigin annotation as,
#CrossOrigin(
allowCredentials = "true",
origins = "http://localhost:8000",
allowedHeaders = "*",
methods = {RequestMethod.GET,RequestMethod.POST,RequestMethod.DELETE,RequestMethod.PUT}
)
This might work.
You forgot to add:
allowCredentials=true
It shoul be:
#CrossOrigin(origins = "http://localhost:8000", allowCredentials = "true", allowedHeaders = "*", methods = {RequestMethod.GET,RequestMethod.POST,RequestMethod.DELETE,RequestMethod.PUT})
I have a layout page that has a form with AntiForgeryToken
using (Html.BeginForm(action, "Account", new { ReturnUrl = returnUrl }, FormMethod.Post, new { Id = "xcrf-form" }))
This generates a hidden field
<input name="__RequestVerificationToken" type="hidden" value="p43bTJU6xjctQ-ETI7T0e_0lJX4UsbTz_IUjQjWddsu29Nx_UE5rcdOONiDhFcdjan88ngBe5_ZQbHTBieB2vVXgNJGNmfQpOm5ATPbifYE1">
In my angular view (that is loaded in a div in the layout page, I do this
<form class="form" role="form" ng-submit="postReview()">
And my code for postReview() is as follows
$scope.postReview = function () {
var token = $('[name=__RequestVerificationToken]').val();
var config = {
headers: {
"Content-Type": "multipart/form-data",
// the following when uncommented does not work either
//'RequestVerificationToken' : token
//"X-XSRF-TOKEN" : token
}
}
// tried the following, since my other MVC controllers (non-angular) send the token as part of form data, this did not work though
$scope.reviewModel.__RequestVerificationToken = token;
// the following was mentioned in some link I found, this does not work either
$http.defaults.headers.common['__RequestVerificationToken'] = token;
$http.post('/Review/Create', $scope.reviewModel, config)
.then(function (result) {
// Success
alert(result.data);
}, function (error) {
// Failure
alert("Failed");
});
}
My MVC Create method is as follows
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public ActionResult Create([Bind(Include = "Id,CommentText,Vote")] ReviewModel reviewModel)
{
if (User.Identity.IsAuthenticated == false)
{
// I am doing this instead of [Authorize] because I dont want 302, which browser handles and I cant do client re-direction
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
}
// just for experimenting I have not yet added it to db, and simply returning
return new JsonResult {Data = reviewModel, JsonRequestBehavior = JsonRequestBehavior.AllowGet};
}
So no matter where I put the token, no matter what I use for 'Content-Type' (I tried application-json and www-form-urlencoded) I always get the error "The required anti-forgery form field "__RequestVerificationToken" is not present."
I even tried naming __RequestVerificationToken and RequestVerificationToken
Why does my server not find the damn token?
I also looked at couple of links that ask you to implement your own AntiForgeryToeknVerifyAttrbute and verify the token that is sent as cookieToken:formToken, I have not tried that but why I am not able to get it working whereas this works for the MVC controllers (non-angular posts)
Yes. By default, MVC Framework will check for Request.Form["__RequestVerificationToken"].
Checking the MVC source code
public AntiForgeryToken GetFormToken(HttpContextBase httpContext)
{
string value = httpContext.Request.Form[_config.FormFieldName];
if (String.IsNullOrEmpty(value))
{
// did not exist
return null;
}
return _serializer.Deserialize(value);
}
You need to create your own filter to check it from Request.Header
Code Snippet from Phil Haack's Article - MVC 3
private class JsonAntiForgeryHttpContextWrapper : HttpContextWrapper {
readonly HttpRequestBase _request;
public JsonAntiForgeryHttpContextWrapper(HttpContext httpContext)
: base(httpContext) {
_request = new JsonAntiForgeryHttpRequestWrapper(httpContext.Request);
}
public override HttpRequestBase Request {
get {
return _request;
}
}
}
private class JsonAntiForgeryHttpRequestWrapper : HttpRequestWrapper {
readonly NameValueCollection _form;
public JsonAntiForgeryHttpRequestWrapper(HttpRequest request)
: base(request) {
_form = new NameValueCollection(request.Form);
if (request.Headers["__RequestVerificationToken"] != null) {
_form["__RequestVerificationToken"]
= request.Headers["__RequestVerificationToken"];
}
}
public override NameValueCollection Form {
get {
return _form;
}
}
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,
AllowMultiple = false, Inherited = true)]
public class ValidateJsonAntiForgeryTokenAttribute :
FilterAttribute, IAuthorizationFilter {
public void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
var httpContext = new JsonAntiForgeryHttpContextWrapper(HttpContext.Current);
AntiForgery.Validate(httpContext, Salt ?? string.Empty);
}
public string Salt {
get;
set;
}
// The private context classes go here
}
Check out here for MVC 4 implementation, to avoid salt issue
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,
AllowMultiple = false, Inherited = true)]
public sealed class ValidateJsonAntiForgeryTokenAttribute
: FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
var httpContext = filterContext.HttpContext;
var cookie = httpContext.Request.Cookies[AntiForgeryConfig.CookieName];
AntiForgery.Validate(cookie != null ? cookie.Value : null,
httpContext.Request.Headers["__RequestVerificationToken"]);
}
}
I had the same problem. Turned out that I don't need to set antiforgery token anywhere explicitly in my angular js code. The MVC controller expects this token value to be delivered from 1. the form field, 2. cookie. The filter equates and is happy when they match.
When we submit the form, hidden field for the anti forgery token automatically supplies its value. Cookie is automatically set by the browser. So as I said, we don't need to do anything explicitly.
The problem really is request's content-type. By default it goes as as application/json and therefore the a.f. token value (or rather any form data) is not received.
Following worked for me:
// create the controller
var RegisterController = function ($scope, $http) {
$scope.onSubmit = function (e) {
// suppress default form submission
e.preventDefault();
var form = $("#registerform");
if (form.valid()) {
var url = form.attr('action');
var data = form.serialize();
var config = {
headers: {
'Content-type':'application/x-www-form-urlencoded',
}
};
$http.post(url, data, config).success(function (data) {
alert(data);
}).error(function(reason) {
alert(reason);
});
}
};
};
As Murali suggested I guess I need to put the toekn in the form itself, so I tried putting the token as part of form data and I needed to encode the form data as explained in https://stackoverflow.com/a/14868725/2475810
This approach does not require any additional code on server side, also we do not need to create and join cookie and form token. Just by form-encoding the data and including token as one of the fields as explained in the answer above we can get it rolling.
You should perform the HTTP request in this way:
$http({
url: '/Review/Create',
data: "__RequestVerificationToken=" + token + "¶m1=1¶m2=2",
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
}).success(function(result) {
alert(result.data);
}).error(function(error) {
alert("Failed");
});
I have a method which save an image file in the database as a BLOB file. The method works fine, but when I get the callback in ExtJS filefield component, it always goes through failure function and I don't know what I have to respond to go through success function, this is my code:
Server method:
#POST
#Path("/upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces({ MediaType.APPLICATION_JSON })
public ServiceResponse uploadFile(#QueryParam("id") Long iconId, FormDataMultiPart form) {
CatIcon icon;
if (iconId != null) {
icon = catIconBean.getOne(iconId);
} else {
icon = new CatIcon();
}
byte[] image = form.getField("iconBmp").getValueAs(byte[].class);
if (image.length != 0) {
MultivaluedMap<String, String> headers = form.getField("iconBmp").getHeaders();
String type = headers.getFirst("Content-type");
List<String> list = Arrays.asList("image/gif", "image/png", "image/jpg", "image/jpeg",
"image/x-icon", "image/bmp");
if (list.contains(type)) {
icon.setIconBmp(image);
icon.setType(type);
}
}
icon.setDescription(form.getField("description").getValue());
icon.setFileName(form.getField("fileName").getValue());
icon = catIconBean.saveIcon(icon);
ServiceResponse sr = new ServiceResponse();
sr.httpResponse = true;
return sr;
}
What I have to return in the code above?
Client:
uploadIcon : function(item, e, eOpts) {
var me = this;
var form = this.getDetail().getForm();
var valid = form.isValid();
if (!valid) {
return false;
}
var values = form.getValues();
if(values) {
form.submit({
url : myApplication.defaultHost() + 'icon/upload?id=' + values.id,
waitMsg : 'Uploading...',
success : function(form, action) {
me.onCompleteSaveOrDelete();
},
failure : function(form, action) {
me.onCompleteSaveOrDelete();
}
});
}
},
I write the same function, me.onCompleteSaveOrDelete(), in both callback functions to make it be called, that's the method which I want to be called in success.
Greetings.
UPDATE:
I did almost the same Alexander.Berg answered. The only difference was that I write #Produces({ MediaType.APPLICATION_JSON }) instead of #Produces({ MediaType.TEXT_HTML }), because I need Json Response. But when I debug in chrome and check the response, I get this:
In failure:
failure : function(form, action) {
me.onCompleteSaveOrDelete();
}
In action param, within responseText:
"{"data":"{\"success\":true}","httpResponse":true,"totalCount":0}"
But It's still going through failure...I think I'm very close, any help??
Greetings.
The fileupload in Extjs is more tricky, because it is using iframe and submit, not a real ajax request for uploading files.
Try this on Server method:
#POST
#Path("/upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.TEXT_HTML)
public String uploadFile(#QueryParam("id") Long iconId, FormDataMultiPart form) {
(...)
JSONObject json = new JSONObject();
json.put("success", true);
json.put("msg", "Success");
return json.toString();
}
this is because the upload accepts Content-Type text/html,
see Example at http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.form.field.File -> Example Usage -> Live Preview
Use Firefox browser with Firebug plugin and on Net tab the following URL -> http://docs.sencha.com/extjs/4.2.2/photo-upload.php
Response Headersview source
(...)
Content-Type text/html
(...)
Request Headersview source
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
(...)