@@ -24,6 +24,7 @@ import (
2424 "github.com/gogf/gf/v2/errors/gcode"
2525 "github.com/gogf/gf/v2/errors/gerror"
2626 "github.com/gogf/gf/v2/internal/httputil"
27+ "github.com/gogf/gf/v2/internal/intlog"
2728 "github.com/gogf/gf/v2/internal/json"
2829 "github.com/gogf/gf/v2/internal/utils"
2930 "github.com/gogf/gf/v2/os/gfile"
@@ -195,29 +196,19 @@ func (c *Client) mergeQueryParams(u string, dataParams string) (string, error) {
195196
196197 // Merge data parameters (for GET requests with default Content-Type)
197198 if dataParams != "" {
198- // Handle noUrlEncode flag: if noUrlEncode is true, parse data params manually
199- // to avoid double encoding/decoding issues
199+ var dataValues url.Values
200200 if c .noUrlEncode {
201- // Split params by & and add them without URL encoding
202- for _ , pair := range strings .Split (dataParams , "&" ) {
203- if pair == "" {
204- continue
205- }
206- parts := strings .SplitN (pair , "=" , 2 )
207- if len (parts ) == 2 {
208- // Data params override URL params
209- queryValues [parts [0 ]] = []string {parts [1 ]}
210- }
211- }
201+ // Parse params without URL decoding to avoid double encoding/decoding issues
202+ dataValues = parseUnEncodedParams (dataParams )
212203 } else {
213- dataValues , err : = url .ParseQuery (dataParams )
204+ dataValues , err = url .ParseQuery (dataParams )
214205 if err != nil {
215206 return "" , gerror .Wrapf (err , `url.ParseQuery failed for data params "%s"` , dataParams )
216207 }
217- for k , v := range dataValues {
218- // Data params override URL params
219- queryValues [ k ] = v
220- }
208+ }
209+ // Data params override URL params
210+ for k , v := range dataValues {
211+ queryValues [ k ] = v
221212 }
222213 }
223214
@@ -265,18 +256,7 @@ func (c *Client) mergeQueryParams(u string, dataParams string) (string, error) {
265256 // Update URL with merged parameters
266257 // Respect noUrlEncode flag
267258 if c .noUrlEncode {
268- // Manually build query string without encoding
269- var queryParts []string
270- for k , values := range queryValues {
271- for _ , v := range values {
272- if v == "" {
273- queryParts = append (queryParts , k )
274- } else {
275- queryParts = append (queryParts , k + "=" + v )
276- }
277- }
278- }
279- parsedURL .RawQuery = strings .Join (queryParts , "&" )
259+ parsedURL .RawQuery = buildUnEncodedQuery (queryValues )
280260 } else {
281261 parsedURL .RawQuery = queryValues .Encode ()
282262 }
@@ -295,22 +275,82 @@ func (c *Client) normalizeURL(u string) string {
295275 return u
296276}
297277
278+ // getMediaType safely parses the Content-Type header and returns the media type.
279+ // If parsing fails, it logs the error and returns the raw header value as fallback.
280+ func (c * Client ) getMediaType (ctx context.Context ) string {
281+ contentType := c .header [httpHeaderContentType ]
282+ if contentType == "" {
283+ return ""
284+ }
285+
286+ mediaType , _ , err := mime .ParseMediaType (contentType )
287+ if err != nil {
288+ // Log the parsing error for debugging purposes
289+ intlog .Errorf (ctx ,
290+ `mime.ParseMediaType failed for Content-Type "%s": %v, using raw value as fallback` ,
291+ contentType , err ,
292+ )
293+ return contentType
294+ }
295+ return mediaType
296+ }
297+
298+ // parseUnEncodedParams parses URL-encoded parameter string without decoding.
299+ // This is used when noUrlEncode flag is true to avoid double encoding/decoding.
300+ // It splits the params by '&' and '=' and returns a url.Values map.
301+ func parseUnEncodedParams (params string ) url.Values {
302+ values := make (url.Values )
303+ if params == "" {
304+ return values
305+ }
306+
307+ for _ , pair := range strings .Split (params , "&" ) {
308+ if pair == "" {
309+ continue
310+ }
311+ parts := strings .SplitN (pair , "=" , 2 )
312+ if len (parts ) == 2 {
313+ values [parts [0 ]] = []string {parts [1 ]}
314+ } else if len (parts ) == 1 {
315+ // Handle key without value (e.g., "?flag")
316+ values [parts [0 ]] = []string {"" }
317+ }
318+ }
319+ return values
320+ }
321+
322+ // buildUnEncodedQuery builds a query string from url.Values without encoding.
323+ // This is used when noUrlEncode flag is true.
324+ func buildUnEncodedQuery (values url.Values ) string {
325+ if len (values ) == 0 {
326+ return ""
327+ }
328+
329+ var queryParts []string
330+ for k , vals := range values {
331+ for _ , v := range vals {
332+ if v == "" {
333+ queryParts = append (queryParts , k )
334+ } else {
335+ queryParts = append (queryParts , k + "=" + v )
336+ }
337+ }
338+ }
339+ return strings .Join (queryParts , "&" )
340+ }
341+
298342// buildRequestParams converts data to request parameters based on Content-Type.
299343// It returns:
300344// - params: serialized parameter string
301345// - allowFileUploading: whether file uploading is allowed for this request
302346// - err: error if serialization fails
303- func (c * Client ) buildRequestParams (data ... any ) (params string , allowFileUploading bool , err error ) {
347+ func (c * Client ) buildRequestParams (ctx context. Context , data ... any ) (params string , allowFileUploading bool , err error ) {
304348 allowFileUploading = true
305349 if len (data ) == 0 {
306350 return "" , allowFileUploading , nil
307351 }
308352
309- mediaType , _ , err := mime .ParseMediaType (c .header [httpHeaderContentType ])
310- if err != nil {
311- // Fallback: use the raw header value if parsing fails.
312- mediaType = c .header [httpHeaderContentType ]
313- }
353+ mediaType := c .getMediaType (ctx )
314354
315355 switch mediaType {
316356 case httpHeaderContentTypeJson :
@@ -495,14 +535,10 @@ func (c *Client) createMultipartRequest(method, u string, params string) (*http.
495535
496536// createGetRequest creates http.Request for GET method.
497537// It merges query parameters and handles different Content-Types.
498- func (c * Client ) createGetRequest (u string , params string ) (* http.Request , error ) {
538+ func (c * Client ) createGetRequest (ctx context. Context , u string , params string ) (* http.Request , error ) {
499539 var bodyBuffer * bytes.Buffer
500540 if params != "" {
501- mediaType , _ , err := mime .ParseMediaType (c .header [httpHeaderContentType ])
502- if err != nil {
503- // Fallback: use the raw header value if parsing fails.
504- mediaType = c .header [httpHeaderContentType ]
505- }
541+ mediaType := c .getMediaType (ctx )
506542 switch mediaType {
507543 case
508544 httpHeaderContentTypeJson ,
@@ -511,6 +547,7 @@ func (c *Client) createGetRequest(u string, params string) (*http.Request, error
511547 default :
512548 // Merge all query parameters before creating http.Request
513549 // This includes: URL params + data params + c.queryParams
550+ var err error
514551 if u , err = c .mergeQueryParams (u , params ); err != nil {
515552 return nil , err
516553 }
@@ -556,14 +593,14 @@ func (c *Client) prepareRequest(ctx context.Context, method, u string, data ...a
556593 u = c .normalizeURL (u )
557594
558595 // 2. Build request parameters
559- params , allowFileUploading , err := c .buildRequestParams (data ... )
596+ params , allowFileUploading , err := c .buildRequestParams (ctx , data ... )
560597 if err != nil {
561598 return nil , err
562599 }
563600
564601 // 3. Create request based on method
565602 if method == http .MethodGet {
566- req , err = c .createGetRequest (u , params )
603+ req , err = c .createGetRequest (ctx , u , params )
567604 } else {
568605 req , err = c .createPostRequest (method , u , params , allowFileUploading )
569606 }
0 commit comments