Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions relay/channel/api_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ import (
func SetupApiRequestHeader(info *common.RelayInfo, c *gin.Context, req *http.Header) {
if info.RelayMode == constant.RelayModeAudioTranscription || info.RelayMode == constant.RelayModeAudioTranslation {
// multipart/form-data
} else if info.RelayMode == constant.RelayModeImagesEdits {
// multipart/form-data
} else if info.RelayMode == constant.RelayModeRealtime {
// websocket
} else {
Expand Down
22 changes: 2 additions & 20 deletions relay/channel/gemini/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,29 +143,11 @@ func processSizeParameters(size, quality string) ImageConfig {
}

func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) {
if model_setting.IsGeminiModelSupportImagine(info.UpstreamModelName) {
var content any
if base64Data, err := relaycommon.GetImageBase64sFromForm(c); err == nil {
content = []any{
dto.MediaContent{
Type: dto.ContentTypeText,
Text: request.Prompt,
},
dto.MediaContent{
Type: dto.ContentTypeFile,
File: &dto.MessageFile{
FileData: base64Data.String(),
},
},
}
} else {
content = request.Prompt
}

if strings.HasPrefix(info.UpstreamModelName, "gemini-3-pro-image") {
chatRequest := dto.GeneralOpenAIRequest{
Model: request.Model,
Messages: []dto.Message{
{Role: "user", Content: content},
{Role: "user", Content: request.Prompt},
},
N: int(request.N),
}
Expand Down
7 changes: 3 additions & 4 deletions relay/channel/gemini/relay-gemini.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func ThinkingAdaptor(geminiRequest *dto.GeminiChatRequest, info *relaycommon.Rel
}

// Setting safety to the lowest possible values since Gemini is already powerless enough
func CovertOpenAI2Gemini(c *gin.Context, textRequest dto.GeneralOpenAIRequest, info *relaycommon.RelayInfo, base64Data ...*relaycommon.Base64Data) (*dto.GeminiChatRequest, error) {
func CovertOpenAI2Gemini(c *gin.Context, textRequest dto.GeneralOpenAIRequest, info *relaycommon.RelayInfo) (*dto.GeminiChatRequest, error) {

geminiRequest := dto.GeminiChatRequest{
Contents: make([]dto.GeminiChatContent, 0, len(textRequest.Messages)),
Expand Down Expand Up @@ -464,11 +464,10 @@ func CovertOpenAI2Gemini(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
})
}
} else if part.Type == dto.ContentTypeFile {
file := part.GetFile()
if file.FileId != "" {
if part.GetFile().FileId != "" {
return nil, fmt.Errorf("only base64 file is supported in gemini")
}
format, base64String, err := service.DecodeBase64FileData(file.FileData)
format, base64String, err := service.DecodeBase64FileData(part.GetFile().FileData)
Comment on lines +467 to +470

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find GetFile() method definition
rg -n "func.*GetFile\(\)" --type=go -A5 | head -30

Repository: QuantumNous/new-api

Length of output: 414


🏁 Script executed:

#!/bin/bash
# Check GetFile usage patterns and any nil checks
rg -n -B2 -A2 "GetFile\(\)" --type=go | head -60

Repository: QuantumNous/new-api

Length of output: 1250


🏁 Script executed:

#!/bin/bash
# Look for proto definitions related to File or Content
fd -e proto -e pb.go | xargs grep -l "GetFile" 2>/dev/null | head -10

Repository: QuantumNous/new-api

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Get the full GetFile() implementation
sed -n '339,360p' dto/openai_request.go

Repository: QuantumNous/new-api

Length of output: 678


Add nil check before calling GetFile() accessor methods.

The GetFile() method (defined in dto/openai_request.go:339) explicitly returns nil if m.File is nil or fails type assertions. At lines 467 and 470, the code directly calls GetFile().FileId and GetFile().FileData without null safety checks, creating a panic risk. Ensure the result is assigned to a variable and checked for nil before dereferencing, following the pattern used elsewhere (e.g., dto/openai_request.go:163).

🤖 Prompt for AI Agents
In relay/channel/gemini/relay-gemini.go around lines 467 to 470, the code calls
part.GetFile().FileId and part.GetFile().FileData directly which can panic
because GetFile() may return nil; assign the result of part.GetFile() to a local
variable, check it for nil before accessing FileId/FileData, and return an
appropriate error (or handle the nil case) if the file is nil; follow the
existing pattern used elsewhere (e.g., dto/openai_request.go:163) to safe-guard
the dereferences.

if err != nil {
return nil, fmt.Errorf("decode base64 file data failed: %s", err.Error())
}
Expand Down
54 changes: 0 additions & 54 deletions relay/common/relay_utils.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package common

import (
"encoding/base64"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"
Expand Down Expand Up @@ -229,54 +226,3 @@ func ValidateBasicTaskRequest(c *gin.Context, info *RelayInfo, action string) *d
storeTaskRequest(c, info, action, req)
return nil
}
func GetImagesBase64sFromForm(c *gin.Context) ([]*Base64Data, error) {
return GetBase64sFromForm(c, "image")
}
func GetImageBase64sFromForm(c *gin.Context) (*Base64Data, error) {
base64s, err := GetImagesBase64sFromForm(c)
if err != nil {
return nil, err
}
return base64s[0], nil
}

type Base64Data struct {
MimeType string
Data string
}

func (m Base64Data) String() string {
return fmt.Sprintf("data:%s;base64,%s", m.MimeType, m.Data)
}
func GetBase64sFromForm(c *gin.Context, fieldName string) ([]*Base64Data, error) {
mf := c.Request.MultipartForm
if mf == nil {
if _, err := c.MultipartForm(); err != nil {
return nil, fmt.Errorf("failed to parse image edit form request: %w", err)
}
mf = c.Request.MultipartForm
}
imageFiles, exists := mf.File[fieldName]
if !exists || len(imageFiles) == 0 {
return nil, errors.New("field " + fieldName + " is not found or empty")
}
var imageBase64s []*Base64Data
for _, file := range imageFiles {
image, err := file.Open()
if err != nil {
return nil, errors.New("failed to open image file")
}
defer image.Close()
imageData, err := io.ReadAll(image)
if err != nil {
return nil, errors.New("failed to read image file")
}
mimeType := http.DetectContentType(imageData)
base64Data := base64.StdEncoding.EncodeToString(imageData)
imageBase64s = append(imageBase64s, &Base64Data{
MimeType: mimeType,
Data: base64Data,
})
}
return imageBase64s, nil
}
1 change: 0 additions & 1 deletion relay/helper/valid_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ func GetAndValidOpenAIImageRequest(c *gin.Context, relayMode int) (*dto.ImageReq
imageRequest.N = uint(common.String2Int(formData.Get("n")))
imageRequest.Quality = formData.Get("quality")
imageRequest.Size = formData.Get("size")
imageRequest.ResponseFormat = formData.Get("response_format")
if imageValue := formData.Get("image"); imageValue != "" {
imageRequest.Image, _ = json.Marshal(imageValue)
}
Expand Down